Skip to content

Commit

Permalink
Use *shadow* view traversal for handling nested mouse events
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Gleitman committed Jun 26, 2024
1 parent f8f9ab0 commit 5a7a39e
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
16 changes: 13 additions & 3 deletions packages/react-native/Libraries/Text/Text/RCTTextView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#endif // [macOS]

#import <React/RCTAssert.h> // [macOS]
#import <React/RCTUIManager.h> // [macOS]
#import <React/RCTUtils.h>
#import <React/UIView+React.h>
#import <React/RCTFocusChangeEvent.h> // [macOS]
Expand Down Expand Up @@ -506,13 +507,20 @@ - (void)updateHoveredSubviewWithEvent:(NSEvent *)event

// self will always be an ancestor of any views we pass in here, so it serves as a good default option.
// Also, if we do set from/to nil, we have to call the relevant events on the entire subtree.
RCTPlatformView *commonAncestor = [(_currentHoveredSubview ?: self) ancestorSharedWithView:(hoveredView ?: self)] ?: self;
RCTUIManager *uiManager = [[_eventDispatcher bridge] uiManager];
RCTShadowView *oldShadowView = [uiManager shadowViewForReactTag:[(_currentHoveredSubview ?: self) reactTag]];
RCTShadowView *newShadowView = [uiManager shadowViewForReactTag:[(hoveredView ?: self) reactTag]];

for (RCTPlatformView *exitedView = _currentHoveredSubview; exitedView != commonAncestor && exitedView != nil; exitedView = [exitedView superview]) {
// Find the common ancestor between the two shadow views
RCTShadowView *commonAncestor = [oldShadowView ancestorSharedWithShadowView:newShadowView];

for (RCTShadowView *exitedShadowView = oldShadowView; exitedShadowView != commonAncestor && exitedShadowView != nil; exitedShadowView = [exitedShadowView reactSuperview]) {
RCTPlatformView *exitedView = [uiManager viewForReactTag:[exitedShadowView reactTag]];
if (![exitedView isKindOfClass:[RCTUIView class]]) {
RCTLogError(@"Unexpected view of type %@ found in hierarchy, must be RCTUIView or subclass", [exitedView class]);
continue;
}

RCTUIView *exitedReactView = (RCTUIView *)exitedView;
[self sendMouseEventWithBlock:[exitedReactView onMouseLeave]
locationInfo:[self locationInfoFromEvent:event]
Expand All @@ -522,11 +530,13 @@ - (void)updateHoveredSubviewWithEvent:(NSEvent *)event

// We cache these so we can call them from outermost to innermost
NSMutableArray<RCTUIView *> *enteredViewHierarchy = [NSMutableArray new];
for (RCTPlatformView *enteredView = hoveredView; enteredView != commonAncestor && enteredView != nil; enteredView = [enteredView superview]) {
for (RCTShadowView *enteredShadowView = newShadowView; enteredShadowView != commonAncestor && enteredShadowView != nil; enteredShadowView = [enteredShadowView reactSuperview]) {
RCTPlatformView *enteredView = [uiManager viewForReactTag:[enteredShadowView reactTag]];
if (![enteredView isKindOfClass:[RCTUIView class]]) {
RCTLogError(@"Unexpected view of type %@ found in hierarchy, must be RCTUIView or subclass", [enteredView class]);
continue;
}

[enteredViewHierarchy addObject:(RCTUIView *)enteredView];
}
for (NSInteger i = [enteredViewHierarchy count] - 1; i >= 0; i--) {
Expand Down
7 changes: 7 additions & 0 deletions packages/react-native/React/Views/RCTShadowView.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, RCTPlatformView *> *vie
*/
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor;

// [macOS
/**
* Returns the closest ancestor shared by this shadow view and another specified shadow view.
*/
- (RCTShadowView *)ancestorSharedWithShadowView:(RCTShadowView *)shadowView;
// macOS]

/**
* Checks if the current shadow view is a descendant of the provided `ancestor`
*/
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native/React/Views/RCTShadowView.m
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,25 @@ - (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
return (CGRect){offset, self.layoutMetrics.frame.size};
}

// [macOS
- (RCTShadowView *)ancestorSharedWithShadowView:(RCTShadowView *)shadowView
{
// TODO: This can be optimized by climbing up both hierarchies at the same time
NSMutableSet<RCTShadowView *> *selfSuperviews = [NSMutableSet set];
for (RCTShadowView *view = self; view != nil; view = [view reactSuperview]) {
[selfSuperviews addObject:view];
}

for (RCTShadowView *candidateView = shadowView; candidateView != nil; candidateView = [candidateView reactSuperview]) {
if ([selfSuperviews containsObject:candidateView]) {
return candidateView;
}
}

return nil;
}
// macOS]

- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor
{
RCTShadowView *shadowView = self;
Expand Down

0 comments on commit 5a7a39e

Please sign in to comment.