Skip to content

Commit 95d6123

Browse files
committed
Pointer events (microsoft#14713)
* PointerEvent fixes * Change files * format
1 parent 8cd904e commit 95d6123

File tree

6 files changed

+146
-113
lines changed

6 files changed

+146
-113
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "PointerEvent fixes",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp

Lines changed: 79 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#include <IReactContext.h>
1010
#include <React.h>
1111
#include <Views/DevMenu.h>
12-
#include <Views/ShadowNodeBase.h>
1312
#include <windows.h>
1413
#include <windowsx.h>
1514
#include <winrt/Windows.UI.Core.h>
@@ -637,17 +636,6 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
637636

638637
auto eventPathViews = GetTouchableViewsInPathToRoot(targetView);
639638

640-
// Over
641-
if (targetView != nullptr && previousTargetTag != targetView.Tag()) {
642-
bool shouldEmitOverEvent =
643-
IsAnyViewInPathListeningToEvent(eventPathViews, facebook::react::ViewEvents::Offset::PointerOver);
644-
const auto eventEmitter = winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(targetView)
645-
->eventEmitterAtPoint(event.offsetPoint);
646-
if (shouldEmitOverEvent && eventEmitter != nullptr) {
647-
eventEmitter->onPointerOver(event);
648-
}
649-
}
650-
651639
// Entering
652640

653641
// We only want to emit events to JS if there is a view that is currently listening to said event
@@ -664,7 +652,6 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
664652
auto componentView = *itComponentView;
665653
bool shouldEmitEvent = componentView != nullptr &&
666654
(hasParentEnterListener ||
667-
IsViewListeningToEvent(componentView, facebook::react::ViewEvents::Offset::PointerEnter) ||
668655
IsViewListeningToEvent(componentView, facebook::react::WindowsViewEvents::Offset::MouseEnter));
669656

670657
if (std::find(currentlyHoveredViews.begin(), currentlyHoveredViews.end(), componentView) ==
@@ -674,16 +661,12 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
674661
m_context, componentView.Tag(), pointerPoint, keyModifiers);
675662
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(componentView)
676663
->OnPointerEntered(args);
677-
678664
if (shouldEmitEvent) {
679665
const auto eventEmitter =
680666
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(componentView)
681667
->eventEmitter();
682-
if (eventEmitter) {
683-
eventEmitter->onPointerEnter(event);
684-
if (IsMousePointerEvent(event)) {
685-
eventEmitter->onMouseEnter(event);
686-
}
668+
if (eventEmitter && IsMousePointerEvent(event)) {
669+
eventEmitter->onMouseEnter(event);
687670
}
688671
}
689672
}
@@ -696,41 +679,25 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
696679
// Call the underlaying pointer handler
697680
handler(eventPathViews);
698681

699-
// Out
700-
if (previousTargetTag != -1 && previousTargetTag != (targetView ? targetView.Tag() : -1)) {
701-
bool shouldEmitOutEvent =
702-
IsAnyViewInPathListeningToEvent(currentlyHoveredViews, facebook::react::ViewEvents::Offset::PointerOut);
703-
const auto eventEmitter =
704-
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(prevTargetView)->eventEmitter();
705-
if (shouldEmitOutEvent && eventEmitter != nullptr) {
706-
eventEmitter->onPointerOut(event);
707-
}
708-
}
709-
710682
// Leaving
711683

712684
// pointerleave events need to be emitted from the deepest target to the root but
713685
// we also need to efficiently keep track of if a view has a parent which is listening to the leave events,
714686
// so we first iterate from the root to the target, collecting the views which need events fired for, of which
715687
// we reverse iterate (now from target to root), actually emitting the events.
716-
std::vector<winrt::Microsoft::ReactNative::ComponentView>
717-
viewsToEmitJSLeaveEventsTo; // NSMutableOrderedSet<UIView *> *viewsToEmitLeaveEventsTo =
718-
// [NSMutableOrderedSet orderedSet];
688+
std::vector<winrt::Microsoft::ReactNative::ComponentView> viewsToEmitJSLeaveEventsTo;
719689

720690
std::vector<winrt::Microsoft::ReactNative::ComponentView> viewsToEmitLeaveEventsTo;
721691

722692
winrt::Microsoft::ReactNative::ComponentView viewToEmitNativeExitedEvent{nullptr};
723693

724694
bool hasParentLeaveListener = false;
725695
for (auto itComponentView = currentlyHoveredViews.rbegin(); itComponentView != currentlyHoveredViews.rend();
726-
itComponentView++) { // for (RCTReactTaggedView *taggedView in [currentlyHoveredViews
727-
// reverseObjectEnumerator])
728-
// {
696+
itComponentView++) {
729697
auto componentView = *itComponentView;
730698

731699
bool shouldEmitJSEvent = componentView != nullptr &&
732700
(hasParentLeaveListener ||
733-
IsViewListeningToEvent(componentView, facebook::react::ViewEvents::Offset::PointerLeave) ||
734701
IsViewListeningToEvent(componentView, facebook::react::WindowsViewEvents::Offset::MouseLeave));
735702

736703
if (std::find(eventPathViews.begin(), eventPathViews.end(), componentView) == eventPathViews.end()) {
@@ -755,17 +722,13 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
755722
}
756723

757724
for (auto itComponentView = viewsToEmitJSLeaveEventsTo.rbegin(); itComponentView != viewsToEmitJSLeaveEventsTo.rend();
758-
itComponentView++) { // for (UIView *componentView in [viewsToEmitJSLeaveEventsTo
759-
// reverseObjectEnumerator]) {
725+
itComponentView++) {
760726
auto componentView = *itComponentView;
761727

762728
const auto eventEmitter =
763729
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(componentView)->eventEmitter();
764-
if (eventEmitter) {
765-
eventEmitter->onPointerLeave(event);
766-
if (IsMousePointerEvent(event)) {
767-
eventEmitter->onMouseLeave(event);
768-
}
730+
if (eventEmitter && IsMousePointerEvent(event)) {
731+
eventEmitter->onMouseLeave(event);
769732
}
770733
}
771734

@@ -815,41 +778,39 @@ void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR
815778
case facebook::react::Cursor::Pointer:
816779
type = winrt::Windows::UI::Core::CoreCursorType::Hand;
817780
break;
818-
/* -- Additional cursors not added in core until later version
819-
case facebook::react::Cursor::Help:
820-
type = winrt::Windows::UI::Core::CoreCursorType::Help;
821-
break;
822-
case facebook::react::Cursor::NotAllowed:
823-
type = winrt::Windows::UI::Core::CoreCursorType::UniversalNo;
824-
break;
825-
case facebook::react::Cursor::Wait:
826-
type = winrt::Windows::UI::Core::CoreCursorType::Wait;
827-
break;
828-
case facebook::react::Cursor::Move:
829-
type = winrt::Windows::UI::Core::CoreCursorType::SizeAll;
830-
break;
831-
case facebook::react::Cursor::NESWResize:
832-
type = winrt::Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest;
833-
break;
834-
case facebook::react::Cursor::NSResize:
835-
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthSouth;
836-
break;
837-
case facebook::react::Cursor::NWSEResize:
838-
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast;
839-
break;
840-
case facebook::react::Cursor::EWResize:
841-
type = winrt::Windows::UI::Core::CoreCursorType::SizeWestEast;
842-
break;
843-
case facebook::react::Cursor::Text:
844-
type = winrt::Windows::UI::Core::CoreCursorType::IBeam;
845-
break;
846-
case facebook::react::Cursor::Progress:
847-
type = winrt::Windows::UI::Core::CoreCursorType::Wait; // IDC_APPSTARTING not mapped to CoreCursor?
848-
break;
849-
case facebook::react::Cursor::Crosshair:
850-
type = winrt::Windows::UI::Core::CoreCursorType::Cross;
851-
break;
852-
*/
781+
case facebook::react::Cursor::Help:
782+
type = winrt::Windows::UI::Core::CoreCursorType::Help;
783+
break;
784+
case facebook::react::Cursor::NotAllowed:
785+
type = winrt::Windows::UI::Core::CoreCursorType::UniversalNo;
786+
break;
787+
case facebook::react::Cursor::Wait:
788+
type = winrt::Windows::UI::Core::CoreCursorType::Wait;
789+
break;
790+
case facebook::react::Cursor::Move:
791+
type = winrt::Windows::UI::Core::CoreCursorType::SizeAll;
792+
break;
793+
case facebook::react::Cursor::NESWResize:
794+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest;
795+
break;
796+
case facebook::react::Cursor::NSResize:
797+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthSouth;
798+
break;
799+
case facebook::react::Cursor::NWSEResize:
800+
type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast;
801+
break;
802+
case facebook::react::Cursor::EWResize:
803+
type = winrt::Windows::UI::Core::CoreCursorType::SizeWestEast;
804+
break;
805+
case facebook::react::Cursor::Text:
806+
type = winrt::Windows::UI::Core::CoreCursorType::IBeam;
807+
break;
808+
case facebook::react::Cursor::Progress:
809+
type = winrt::Windows::UI::Core::CoreCursorType::Wait; // IDC_APPSTARTING not mapped to CoreCursor?
810+
break;
811+
case facebook::react::Cursor::Crosshair:
812+
type = winrt::Windows::UI::Core::CoreCursorType::Cross;
813+
break;
853814
default:
854815
break;
855816
}
@@ -880,7 +841,6 @@ void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR
880841
case facebook::react::Cursor::Pointer:
881842
idc = IDC_HAND;
882843
break;
883-
/* -- Additional cursors not added in core until later version
884844
case facebook::react::Cursor::Help:
885845
idc = IDC_HELP;
886846
break;
@@ -914,7 +874,6 @@ void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR
914874
case facebook::react::Cursor::Crosshair:
915875
idc = IDC_CROSS;
916876
break;
917-
*/
918877
default:
919878
break;
920879
}
@@ -1064,21 +1023,48 @@ void CompositionEventHandler::onPointerMoved(
10641023

10651024
facebook::react::PointerEvent pointerEvent = CreatePointerEventFromIncompleteHoverData(ptScaled, ptLocal);
10661025

1067-
auto handler = [&targetView,
1068-
&pointerEvent](std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
1026+
// check if this pointer corresponds to active touch that has a responder
1027+
auto activeTouch = m_activeTouches.find(pointerId);
1028+
bool isActiveTouch = activeTouch != m_activeTouches.end() && activeTouch->second.eventEmitter != nullptr;
1029+
1030+
auto handler = [&, targetView, pointerEvent, isActiveTouch](
1031+
std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
10691032
const auto eventEmitter = targetView
10701033
? winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(targetView)
10711034
->eventEmitterAtPoint(pointerEvent.offsetPoint)
1072-
: nullptr;
1073-
bool hasMoveEventListeners =
1074-
IsAnyViewInPathListeningToEvent(eventPathViews, facebook::react::ViewEvents::Offset::PointerMove) ||
1075-
IsAnyViewInPathListeningToEvent(eventPathViews, facebook::react::ViewEvents::Offset::PointerMoveCapture);
1076-
if (eventEmitter != nullptr && hasMoveEventListeners) {
1035+
: RootComponentView().eventEmitterAtPoint(pointerEvent.offsetPoint);
1036+
1037+
if (eventEmitter != nullptr) {
10771038
eventEmitter->onPointerMove(pointerEvent);
1039+
} else {
1040+
ClearAllHoveredForPointer(pointerEvent);
10781041
}
10791042
};
10801043

10811044
HandleIncomingPointerEvent(pointerEvent, targetView, pointerPoint, keyModifiers, handler);
1045+
1046+
if (isActiveTouch) {
1047+
// For active touches with responders, also dispatch through touch event system
1048+
UpdateActiveTouch(activeTouch->second, ptScaled, ptLocal);
1049+
DispatchTouchEvent(TouchEventType::Move, pointerId, pointerPoint, keyModifiers);
1050+
}
1051+
}
1052+
}
1053+
1054+
void CompositionEventHandler::ClearAllHoveredForPointer(const facebook::react::PointerEvent &pointerEvent) noexcept {
1055+
// special case if we have no target
1056+
// PointerEventsProcessor requires move events to keep track of the hovered components in core.
1057+
// It also treats a onPointerLeave event as a special case that removes the hover state of all currently hovered
1058+
// events. If we get null for the targetView, that means that the mouse is no over any components, so we have no
1059+
// element to send the move event to. However we need to send something so that any previously hovered elements
1060+
// are no longer hovered.
1061+
auto children = RootComponentView().Children();
1062+
if (auto size = children.Size()) {
1063+
auto firstChild = children.GetAt(0);
1064+
if (auto childEventEmitter =
1065+
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(firstChild)->eventEmitter()) {
1066+
childEventEmitter->onPointerLeave(pointerEvent);
1067+
}
10821068
}
10831069
}
10841070

@@ -1104,7 +1090,9 @@ void CompositionEventHandler::onPointerExited(
11041090

11051091
facebook::react::PointerEvent pointerEvent = CreatePointerEventFromIncompleteHoverData(ptScaled, ptLocal);
11061092

1107-
auto handler = [](std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {};
1093+
auto handler = [&](std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
1094+
ClearAllHoveredForPointer(pointerEvent);
1095+
};
11081096

11091097
HandleIncomingPointerEvent(pointerEvent, nullptr, pointerPoint, keyModifiers, handler);
11101098
}
@@ -1392,12 +1380,7 @@ void CompositionEventHandler::DispatchTouchEvent(
13921380
activeTouch.eventEmitter->onPointerDown(pointerEvent);
13931381
break;
13941382
case TouchEventType::Move: {
1395-
bool hasMoveEventListeners =
1396-
IsAnyViewInPathListeningToEvent(eventPathViews, facebook::react::ViewEvents::Offset::PointerMove) ||
1397-
IsAnyViewInPathListeningToEvent(eventPathViews, facebook::react::ViewEvents::Offset::PointerMoveCapture);
1398-
if (hasMoveEventListeners) {
1399-
activeTouch.eventEmitter->onPointerMove(pointerEvent);
1400-
}
1383+
activeTouch.eventEmitter->onPointerMove(pointerEvent);
14011384
break;
14021385
}
14031386
case TouchEventType::End:
@@ -1452,4 +1435,4 @@ void CompositionEventHandler::DispatchTouchEvent(
14521435
}
14531436
}
14541437

1455-
} // namespace Microsoft::ReactNative
1438+
} // namespace Microsoft::ReactNative

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include <winrt/Windows.Devices.Input.h>
1515
#include <optional>
1616
#include <set>
17-
#include "Utils/BatchingEventEmitter.h"
1817

1918
namespace winrt {
2019
using namespace Windows::UI;
@@ -99,6 +98,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
9998
const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
10099
winrt::Windows::System::VirtualKeyModifiers keyModifiers,
101100
std::function<void(std::vector<winrt::Microsoft::ReactNative::ComponentView> &)> handler);
101+
void ClearAllHoveredForPointer(const facebook::react::PointerEvent &pointerEvent) noexcept;
102102

103103
struct ActiveTouch {
104104
facebook::react::Touch touch;

vnext/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewEventEmitter.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,30 @@ void HostPlatformViewEventEmitter::onBlur() const {
3838

3939
#pragma mark - Mouse Events
4040

41-
void HostPlatformViewEventEmitter::onMouseEnter(PointerEvent const &pointerEvent) const {
42-
dispatchEvent(
43-
"mouseEnter",
44-
std::make_shared<PointerEvent>(pointerEvent),
45-
EventPriority::AsynchronousBatched,
46-
RawEvent::Category::ContinuousStart);
41+
void HostPlatformViewEventEmitter::onMouseEnter(MouseEvent const &pointerEvent) const {
42+
dispatchEvent("mouseEnter", std::make_shared<MouseEvent>(pointerEvent), RawEvent::Category::ContinuousStart);
4743
}
4844

49-
void HostPlatformViewEventEmitter::onMouseLeave(PointerEvent const &pointerEvent) const {
50-
dispatchEvent(
51-
"mouseLeave",
52-
std::make_shared<PointerEvent>(pointerEvent),
53-
EventPriority::AsynchronousBatched,
54-
RawEvent::Category::ContinuousStart);
45+
void HostPlatformViewEventEmitter::onMouseLeave(MouseEvent const &pointerEvent) const {
46+
dispatchEvent("mouseLeave", std::make_shared<MouseEvent>(pointerEvent), RawEvent::Category::ContinuousStart);
5547
}
5648

57-
} // namespace facebook::react
49+
#pragma mark - Touch Events
50+
51+
void HostPlatformViewEventEmitter::onPressIn(GestureResponderEvent event) const {
52+
dispatchEvent("pressIn", [event](jsi::Runtime &runtime) {
53+
auto payload = jsi::Object(runtime);
54+
auto nativeEvent = jsi::Object(runtime);
55+
nativeEvent.setProperty(runtime, "target", static_cast<double>(event.target));
56+
nativeEvent.setProperty(runtime, "pageX", event.pagePoint.x);
57+
nativeEvent.setProperty(runtime, "pageY", event.pagePoint.y);
58+
nativeEvent.setProperty(runtime, "locationX", event.offsetPoint.x);
59+
nativeEvent.setProperty(runtime, "locationY", event.offsetPoint.y);
60+
nativeEvent.setProperty(runtime, "timestamp", event.timestamp);
61+
nativeEvent.setProperty(runtime, "identifier", event.identifier);
62+
payload.setProperty(runtime, "nativeEvent", nativeEvent);
63+
return payload;
64+
});
65+
}
66+
67+
} // namespace facebook::react

0 commit comments

Comments
 (0)