mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook GitHub Bot
parent
c032401b67
commit
c5cb707ba8
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user