diff --git a/change/react-native-windows-83b6df1e-ce5d-4de3-9cf1-2a5b8f9b74f5.json b/change/react-native-windows-83b6df1e-ce5d-4de3-9cf1-2a5b8f9b74f5.json new file mode 100644 index 00000000000..02f4ec9856e --- /dev/null +++ b/change/react-native-windows-83b6df1e-ce5d-4de3-9cf1-2a5b8f9b74f5.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fixed: Touch screen events not properly captured for pressable and text inputs", + "packageName": "react-native-windows", + "email": "gordomacmaster@gmail.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp index 01eaf359155..898d1c6a1ac 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp @@ -358,6 +358,7 @@ CompositionEventHandler::~CompositionEventHandler() { pointerSource.PointerMoved(m_pointerMovedToken); pointerSource.PointerCaptureLost(m_pointerCaptureLostToken); pointerSource.PointerWheelChanged(m_pointerWheelChangedToken); + pointerSource.PointerExited(m_pointerExitedToken); auto keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(island); keyboardSource.KeyDown(m_keyDownToken); keyboardSource.KeyUp(m_keyUpToken); @@ -1180,14 +1181,15 @@ void CompositionEventHandler::onPointerPressed( PointerId pointerId = pointerPoint.PointerId(); - auto staleTouch = std::find_if(m_activeTouches.begin(), m_activeTouches.end(), [pointerId](const auto &pair) { - return pair.second.touch.identifier == pointerId; - }); + auto staleTouch = m_activeTouches.find(pointerId); if (staleTouch != m_activeTouches.end()) { - // A pointer with this ID already exists - Should we fire a button cancel or something? - // assert(false); - return; + // A previous pointer with this ID was never properly released (e.g., app lost focus, + // pointer left window). Cancel the stale touch and clean it up so the new press can proceed. + if (staleTouch->second.eventEmitter) { + DispatchSynthesizedTouchCancelForActiveTouch(staleTouch->second, pointerPoint, keyModifiers); + } + m_activeTouches.erase(staleTouch); } const auto eventType = TouchEventType::Start; @@ -1246,6 +1248,12 @@ void CompositionEventHandler::onPointerPressed( targetComponentView = targetComponentView.Parent(); } + // Don't register the touch if no eventEmitter was found — inserting a null-emitter entry + // into m_activeTouches would block future presses with the same pointer ID. + if (!activeTouch.eventEmitter) { + return; + } + UpdateActiveTouch(activeTouch, ptScaled, ptLocal); // activeTouch.touch.isPrimary = true; @@ -1269,9 +1277,7 @@ void CompositionEventHandler::onPointerReleased( winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept { int pointerId = pointerPoint.PointerId(); - auto activeTouch = std::find_if(m_activeTouches.begin(), m_activeTouches.end(), [pointerId](const auto &pair) { - return pair.second.touch.identifier == pointerId; - }); + auto activeTouch = m_activeTouches.find(pointerId); if (activeTouch == m_activeTouches.end()) { return; @@ -1283,8 +1289,13 @@ void CompositionEventHandler::onPointerReleased( facebook::react::Point ptLocal, ptScaled; getTargetPointerArgs(fabricuiManager, pointerPoint, tag, ptScaled, ptLocal); - if (tag == -1) + if (tag == -1) { + if (activeTouch->second.eventEmitter) { + DispatchSynthesizedTouchCancelForActiveTouch(activeTouch->second, pointerPoint, keyModifiers); + } + m_activeTouches.erase(pointerId); return; + } auto targetComponentView = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(tag).view; auto args = winrt::make( @@ -1456,6 +1467,47 @@ facebook::react::PointerEvent CompositionEventHandler::CreatePointerEventFromAct return event; } +void CompositionEventHandler::DispatchSynthesizedTouchCancelForActiveTouch( + const ActiveTouch &cancelledTouch, + const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint, + winrt::Windows::System::VirtualKeyModifiers keyModifiers) { + if (!cancelledTouch.eventEmitter) { + return; + } + + facebook::react::PointerEvent pointerEvent = + CreatePointerEventFromActiveTouch(cancelledTouch, TouchEventType::Cancel); + winrt::Microsoft::ReactNative::ComponentView targetView{nullptr}; + facebook::react::SharedTouchEventEmitter emitter = cancelledTouch.eventEmitter; + auto pointerHandler = [emitter, pointerEvent](std::vector &) { + emitter->onPointerCancel(pointerEvent); + }; + HandleIncomingPointerEvent(pointerEvent, targetView, pointerPoint, keyModifiers, pointerHandler); + + facebook::react::TouchEvent touchEvent; + touchEvent.changedTouches.insert(cancelledTouch.touch); + + for (const auto &pair : m_activeTouches) { + if (!pair.second.eventEmitter || pair.second.eventEmitter != cancelledTouch.eventEmitter) { + continue; + } + + if (touchEvent.changedTouches.find(pair.second.touch) != touchEvent.changedTouches.end()) { + continue; + } + + touchEvent.touches.insert(pair.second.touch); + } + + for (const auto &pair : m_activeTouches) { + if (pair.second.eventEmitter == cancelledTouch.eventEmitter) { + touchEvent.targetTouches.insert(pair.second.touch); + } + } + + cancelledTouch.eventEmitter->onTouchCancel(touchEvent); +} + // If we have events that include multiple pointer updates, we should change arg from pointerId to vector void CompositionEventHandler::DispatchTouchEvent( TouchEventType eventType, diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h index 82e80b3dde8..03fa6e2b9de 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h @@ -149,6 +149,11 @@ class CompositionEventHandler : public std::enable_shared_from_thisTxSendMessage(msg, static_cast(wParam), static_cast(lParam), &lresult); args.Handled(hr != S_FALSE); - } - m_textServices->OnTxSetCursor( - DVASPECT_CONTENT, -1, nullptr, nullptr, nullptr, nullptr, nullptr, ptContainer.x, ptContainer.y); + m_textServices->OnTxSetCursor( + DVASPECT_CONTENT, -1, nullptr, nullptr, nullptr, nullptr, nullptr, ptContainer.x, ptContainer.y); + } } void WindowsTextInputComponentView::OnPointerWheelChanged( @@ -933,7 +937,7 @@ bool WindowsTextInputComponentView::ShouldSubmit( bool ctrlDown = (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) & winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down; - bool altDown = (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Control) & + bool altDown = (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::Menu) & winrt::Microsoft::UI::Input::VirtualKeyStates::Down) == winrt::Microsoft::UI::Input::VirtualKeyStates::Down; bool metaDown = (args.KeyboardSource().GetKeyState(winrt::Windows::System::VirtualKey::LeftWindows) & @@ -944,7 +948,7 @@ bool WindowsTextInputComponentView::ShouldSubmit( winrt::Microsoft::UI::Input::VirtualKeyStates::Down; return (submitKeyEvent.shiftKey && shiftDown) || (submitKeyEvent.ctrlKey && ctrlDown) || (submitKeyEvent.altKey && altDown) || (submitKeyEvent.metaKey && metaDown) || - (!submitKeyEvent.shiftKey && !submitKeyEvent.altKey && !submitKeyEvent.metaKey && !submitKeyEvent.altKey && + (!submitKeyEvent.shiftKey && !submitKeyEvent.ctrlKey && !submitKeyEvent.altKey && !submitKeyEvent.metaKey && !shiftDown && !ctrlDown && !altDown && !metaDown); } else { shouldSubmit = false;