Add basic onPointerEnter/Leave event emitting to iOS

Summary: Changelog: [Internal] Add basic onPointerEnter/Leave event emitting to iOS

Reviewed By: lunaleaps

Differential Revision: D35414116

fbshipit-source-id: dd62cf7736c466e328b9ebbf51bf010610f4bd92
This commit is contained in:
Vincent Riemer
2022-04-11 15:49:30 -07:00
committed by Facebook GitHub Bot
parent c032401b67
commit c5cb707ba8
5 changed files with 137 additions and 0 deletions
@@ -389,6 +389,20 @@ const PlatformBaseViewConfig: PartialViewConfigWithoutName =
bubbled: 'onPointerUp',
},
},
topPointerEnter2: {
phasedRegistrationNames: {
captured: 'onPointerEnter2Capture',
bubbled: 'onPointerEnter2',
skipBubbling: true,
},
},
topPointerLeave2: {
phasedRegistrationNames: {
captured: 'onPointerLeave2Capture',
bubbled: 'onPointerLeave2',
skipBubbling: true,
},
},
},
directEventTypes: {
topAccessibilityAction: {
+103
View File
@@ -128,6 +128,34 @@ static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponen
return activeTouch;
}
static UIView *FindClosestFabricManagedTouchableView(UIView *componentView)
{
while (componentView) {
if ([componentView respondsToSelector:@selector(touchEventEmitterAtPoint:)]) {
return componentView;
}
componentView = componentView.superview;
}
return nil;
}
static NSOrderedSet *GetTouchableViewsInPathToRoot(UIView *componentView)
{
NSMutableOrderedSet *results = [NSMutableOrderedSet orderedSet];
do {
if ([componentView respondsToSelector:@selector(touchEventEmitterAtPoint:)]) {
[results addObject:componentView];
}
componentView = componentView.superview;
} while (componentView);
return results;
}
static SharedTouchEventEmitter GetTouchEmitterFromView(UIView *componentView, CGPoint point)
{
return [(id<RCTTouchableComponentViewProtocol>)componentView touchEventEmitterAtPoint:point];
}
static const char *PointerTypeCStringFromUITouchType(UITouchType type)
{
switch (type) {
@@ -157,6 +185,22 @@ static PointerEvent CreatePointerEventFromActiveTouch(ActiveTouch activeTouch)
return event;
}
static PointerEvent
CreatePointerEventFromIncompleteHoverData(UIView *view, CGPoint clientLocation, NSTimeInterval timestamp)
{
PointerEvent event = {};
// "touch" events produced from a mouse cursor on iOS always have the ID 0 so
// we can just assume that here since these sort of hover events only ever come
// from the mouse
event.pointerId = 0;
event.pressure = 0.0;
event.pointerType = "mouse";
event.clientPoint = RCTPointFromCGPoint(clientLocation);
event.target = (Tag)view.tag;
event.timestamp = timestamp;
return event;
}
static BOOL AllTouchesAreCancelledOrEnded(NSSet<UITouch *> *touches)
{
for (UITouch *touch in touches) {
@@ -203,6 +247,9 @@ struct PointerHasher {
*/
__weak UIView *_rootComponentView;
IdentifierPool<11> _identifierPool;
UIHoverGestureRecognizer *_hoverRecognizer API_AVAILABLE(ios(13.0));
NSOrderedSet *_currentlyHoveredViews;
}
- (instancetype)init
@@ -217,6 +264,9 @@ struct PointerHasher {
self.delaysTouchesEnded = NO;
self.delegate = self;
_hoverRecognizer = nil;
_currentlyHoveredViews = [NSOrderedSet orderedSet];
}
return self;
@@ -230,6 +280,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
[view addGestureRecognizer:self];
_rootComponentView = view;
if (RCTGetDispatchW3CPointerEvents()) {
if (@available(iOS 13.0, *)) {
_hoverRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hovering:)];
[view addGestureRecognizer:_hoverRecognizer];
}
}
}
- (void)detachFromView:(UIView *)view
@@ -239,6 +296,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
[view removeGestureRecognizer:self];
_rootComponentView = nil;
if (_hoverRecognizer != nil) {
[view removeGestureRecognizer:_hoverRecognizer];
_hoverRecognizer = nil;
}
}
- (void)_registerTouches:(NSSet<UITouch *> *)touches
@@ -483,4 +545,45 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act
[self setEnabled:YES];
}
- (void)hovering:(UIHoverGestureRecognizer *)recognizer API_AVAILABLE(ios(13.0))
{
UIView *listenerView = recognizer.view;
CGPoint clientLocation = [recognizer locationInView:listenerView];
UIView *targetView = [listenerView hitTest:clientLocation withEvent:nil];
targetView = FindClosestFabricManagedTouchableView(targetView);
NSOrderedSet *eventPathViews = GetTouchableViewsInPathToRoot(targetView);
NSTimeInterval timestamp = CACurrentMediaTime();
// Entering
// root -> child
for (UIView *componentView in [eventPathViews reverseObjectEnumerator]) {
if (![_currentlyHoveredViews containsObject:componentView]) {
SharedTouchEventEmitter eventEmitter =
GetTouchEmitterFromView(componentView, [recognizer locationInView:componentView]);
if (eventEmitter != nil) {
PointerEvent event = CreatePointerEventFromIncompleteHoverData(componentView, clientLocation, timestamp);
eventEmitter->onPointerEnter2(event);
}
}
}
// Leaving
// child -> root
for (UIView *componentView in _currentlyHoveredViews) {
if (![eventPathViews containsObject:componentView]) {
SharedTouchEventEmitter eventEmitter =
GetTouchEmitterFromView(componentView, [recognizer locationInView:componentView]);
if (eventEmitter != nil) {
PointerEvent event = CreatePointerEventFromIncompleteHoverData(componentView, clientLocation, timestamp);
eventEmitter->onPointerLeave2(event);
}
}
}
_currentlyHoveredViews = eventPathViews;
}
@end
+2
View File
@@ -450,5 +450,7 @@ RCT_EXPORT_VIEW_PROPERTY(onPointerCancel, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPointerDown, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPointerMove2, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPointerUp, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPointerEnter2, RCTCapturingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPointerLeave2, RCTCapturingEventBlock)
@end
@@ -161,5 +161,21 @@ void TouchEventEmitter::onPointerUp(const PointerEvent &event) const {
RawEvent::Category::ContinuousEnd);
}
void TouchEventEmitter::onPointerEnter2(const PointerEvent &event) const {
dispatchPointerEvent(
"pointerEnter2",
event,
EventPriority::AsynchronousBatched,
RawEvent::Category::ContinuousStart);
}
void TouchEventEmitter::onPointerLeave2(const PointerEvent &event) const {
dispatchPointerEvent(
"pointerLeave2",
event,
EventPriority::AsynchronousBatched,
RawEvent::Category::ContinuousEnd);
}
} // namespace react
} // namespace facebook
@@ -34,6 +34,8 @@ class TouchEventEmitter : public EventEmitter {
void onPointerDown(PointerEvent const &event) const;
void onPointerMove2(PointerEvent const &event) const;
void onPointerUp(PointerEvent const &event) const;
void onPointerEnter2(PointerEvent const &event) const;
void onPointerLeave2(PointerEvent const &event) const;
private:
void dispatchTouchEvent(