From 71506cefbfb7aa883c5c43c707d177ab0cda531d Mon Sep 17 00:00:00 2001 From: Vincent Riemer Date: Mon, 10 Apr 2023 13:08:03 -0700 Subject: [PATCH] Add basic emitting of `click` event (#36616) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36616 Changelog: [iOS][Internal] - Add basic emitting of click event This diff adds the basic implementation of click event emitting, particularlly focused on ensuring clicks are only fired if the pointer interaction ends within the same tree "branch" of elements as which it started. Reviewed By: yungsters Differential Revision: D44148427 fbshipit-source-id: 9e91c32fe0e1ca8a5c50e72cd819625294e070f4 --- .../React/Fabric/RCTSurfacePointerHandler.mm | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm b/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm index edff8b261bf..2e7338b3aeb 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm @@ -24,6 +24,26 @@ typedef NS_ENUM(NSInteger, RCTPointerEventType) { RCTPointerEventTypeCancel, }; +static BOOL AllTouchesAreCancelledOrEnded(NSSet *touches) +{ + for (UITouch *touch in touches) { + if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved || touch.phase == UITouchPhaseStationary) { + return NO; + } + } + return YES; +} + +static BOOL AnyTouchesChanged(NSSet *touches) +{ + for (UITouch *touch in touches) { + if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) { + return YES; + } + } + return NO; +} + struct ActivePointer { /* * Pointer ID @@ -349,7 +369,9 @@ static void UpdateActivePointerWithUITouch( UIEvent *uiEvent, UIView *rootComponentView) { - activePointer.componentView = FindClosestFabricManagedTouchableView(uiTouch.view); + CGPoint location = [uiTouch locationInView:rootComponentView]; + UIView *hitTestedView = [rootComponentView hitTest:location withEvent:nil]; + activePointer.componentView = FindClosestFabricManagedTouchableView(hitTestedView); activePointer.clientPoint = [uiTouch locationInView:rootComponentView]; activePointer.screenPoint = [rootComponentView convertPoint:activePointer.clientPoint @@ -396,6 +418,22 @@ static BOOL IsAnyViewInPathListeningToEvent(NSOrderedSet * return NO; } +/** + * Given an ActivePointer determine if it is still within the same event target tree as + * the one which initiated the pointer gesture. + */ +static BOOL IsPointerWithinInitialTree(ActivePointer activePointer) +{ + NSOrderedSet *initialViewSet = + GetTouchableViewsInPathToRoot(activePointer.initialComponentView); + for (RCTReactTaggedView *canidateTaggedView in initialViewSet) { + if (canidateTaggedView.tag == activePointer.componentView.tag) { + return YES; + } + } + return NO; +} + /** * Surprisingly, `__unsafe_unretained id` pointers are not regular pointers * and `std::hash<>` cannot hash them. @@ -526,6 +564,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act activePointer.shouldLeaveWhenReleased = YES; } + activePointer.initialComponentView = FindClosestFabricManagedTouchableView(touch.view); + UpdateActivePointerWithUITouch(activePointer, touch, event, _rootComponentView); _activePointers.emplace(touch, activePointer); @@ -618,6 +658,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act } case RCTPointerEventTypeEnd: { eventEmitter->onPointerUp(pointerEvent); + + if (pointerEvent.isPrimary && pointerEvent.button == 0 && IsPointerWithinInitialTree(activePointer)) { + eventEmitter->onClick(pointerEvent); + } + if (activePointer.shouldLeaveWhenReleased) { [self handleIncomingPointerEvent:pointerEvent onView:nil]; } @@ -641,6 +686,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act [self _registerTouches:touches withEvent:event]; [self _dispatchActivePointers:[self _activePointersFromTouches:touches] eventType:RCTPointerEventTypeStart]; + + if (self.state == UIGestureRecognizerStatePossible) { + self.state = UIGestureRecognizerStateBegan; + } else if (self.state == UIGestureRecognizerStateBegan) { + self.state = UIGestureRecognizerStateChanged; + } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event @@ -649,6 +700,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act [self _updateTouches:touches withEvent:event]; [self _dispatchActivePointers:[self _activePointersFromTouches:touches] eventType:RCTPointerEventTypeMove]; + + self.state = UIGestureRecognizerStateChanged; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event @@ -658,6 +711,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act [self _updateTouches:touches withEvent:event]; [self _dispatchActivePointers:[self _activePointersFromTouches:touches] eventType:RCTPointerEventTypeEnd]; [self _unregisterTouches:touches]; + + if (AllTouchesAreCancelledOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateEnded; + } else if (AnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event @@ -667,6 +726,12 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act [self _updateTouches:touches withEvent:event]; [self _dispatchActivePointers:[self _activePointersFromTouches:touches] eventType:RCTPointerEventTypeCancel]; [self _unregisterTouches:touches]; + + if (AllTouchesAreCancelledOrEnded(event.allTouches)) { + self.state = UIGestureRecognizerStateCancelled; + } else if (AnyTouchesChanged(event.allTouches)) { + self.state = UIGestureRecognizerStateChanged; + } } - (void)reset