Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New events for RCTUIView #2136

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions packages/react-native/React/Base/RCTUIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ NS_ASSUME_NONNULL_END

#import <AppKit/AppKit.h>

#import <React/RCTComponent.h>

NS_ASSUME_NONNULL_BEGIN

//
Expand Down Expand Up @@ -403,6 +405,15 @@ CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *path);

- (void)setNeedsDisplay;

// Methods related to mouse events
- (NSDictionary*)locationInfoFromDraggingLocation:(NSPoint)locationInWindow;
- (NSDictionary*)locationInfoFromEvent:(NSEvent*)event;

- (void)sendMouseEventWithBlock:(RCTDirectEventBlock)block
locationInfo:(NSDictionary*)locationInfo
modifierFlags:(NSEventModifierFlags)modifierFlags
additionalData:(NSDictionary*)additionalData;

// FUTURE: When Xcode 14 is no longer supported (CI is building with Xcode 15), we can remove this override since it's now declared on NSView
@property BOOL clipsToBounds;
@property (nonatomic, copy) NSColor *backgroundColor;
Expand All @@ -426,6 +437,24 @@ CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *path);
*/
@property (nonatomic, assign) BOOL enableFocusRing;

// Mouse events
@property (nonatomic, copy) RCTDirectEventBlock onMouseEnter;
@property (nonatomic, copy) RCTDirectEventBlock onMouseLeave;
@property (nonatomic, copy) RCTDirectEventBlock onDragEnter;
@property (nonatomic, copy) RCTDirectEventBlock onDragLeave;
@property (nonatomic, copy) RCTDirectEventBlock onDrop;

// Focus events
@property (nonatomic, copy) RCTBubblingEventBlock onBlur;
@property (nonatomic, copy) RCTBubblingEventBlock onFocus;

@property (nonatomic, copy) RCTBubblingEventBlock onResponderGrant;
@property (nonatomic, copy) RCTBubblingEventBlock onResponderMove;
@property (nonatomic, copy) RCTBubblingEventBlock onResponderRelease;
@property (nonatomic, copy) RCTBubblingEventBlock onResponderTerminate;
@property (nonatomic, copy) RCTBubblingEventBlock onResponderTerminationRequest;
@property (nonatomic, copy) RCTBubblingEventBlock onStartShouldSetResponder;
Comment on lines +451 to +456
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can actually remove these, I think these are actually mostly handled in JS, if nothing is calling this in native code across iOS/macOS


@end

// UIScrollView
Expand Down
135 changes: 135 additions & 0 deletions packages/react-native/React/Base/macOS/RCTUIKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import <React/RCTUIKit.h>

#import <React/RCTAssert.h>
#import <UIView+React.h>

#import <objc/runtime.h>

Expand Down Expand Up @@ -214,6 +215,7 @@ @implementation RCTUIView
@private
NSColor *_backgroundColor;
BOOL _clipsToBounds;
BOOL _hasMouseOver;
BOOL _userInteractionEnabled;
BOOL _mouseDownCanMoveWindow;
}
Expand Down Expand Up @@ -287,9 +289,142 @@ - (BOOL)isFirstResponder {

- (void)viewDidMoveToWindow
{
// Subscribe to view bounds changed notification so that the view can be notified when a
// scroll event occurs either due to trackpad/gesture based scrolling or a scrollwheel event
// both of which would not cause the mouseExited to be invoked.

if ([self window] == nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSViewBoundsDidChangeNotification
object:nil];
}
else if ([[self enclosingScrollView] contentView] != nil) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(viewBoundsChanged:)
name:NSViewBoundsDidChangeNotification
object:[[self enclosingScrollView] contentView]];
}

[self reactViewDidMoveToWindow]; // [macOS] Github#1412

[self didMoveToWindow];
}

- (void)viewBoundsChanged:(NSNotification*)__unused inNotif
{
// When an enclosing scrollview is scrolled using the scrollWheel or trackpad,
// the mouseExited: event does not get called on the view where mouseEntered: was previously called.
// This creates an unnatural pairing of mouse enter and exit events and can cause problems.
// We therefore explicitly check for this here and handle them by calling the appropriate callbacks.

if (!_hasMouseOver && self.onMouseEnter)
{
NSPoint locationInWindow = [[self window] mouseLocationOutsideOfEventStream];
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

if (NSPointInRect(locationInView, [self bounds]))
{
_hasMouseOver = YES;

[self sendMouseEventWithBlock:self.onMouseEnter
locationInfo:[self locationInfoFromDraggingLocation:locationInWindow]
modifierFlags:0
additionalData:nil];
}
}
else if (_hasMouseOver && self.onMouseLeave)
{
NSPoint locationInWindow = [[self window] mouseLocationOutsideOfEventStream];
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

if (!NSPointInRect(locationInView, [self bounds]))
{
_hasMouseOver = NO;

[self sendMouseEventWithBlock:self.onMouseLeave
locationInfo:[self locationInfoFromDraggingLocation:locationInWindow]
modifierFlags:0
additionalData:nil];
}
}
}

- (NSDictionary*)locationInfoFromDraggingLocation:(NSPoint)locationInWindow
{
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

return @{@"screenX": @(locationInWindow.x),
@"screenY": @(locationInWindow.y),
@"clientX": @(locationInView.x),
@"clientY": @(locationInView.y)
};
}

- (NSDictionary*)locationInfoFromEvent:(NSEvent*)event
{
NSPoint locationInWindow = event.locationInWindow;
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

return @{@"screenX": @(locationInWindow.x),
@"screenY": @(locationInWindow.y),
@"clientX": @(locationInView.x),
@"clientY": @(locationInView.y)
};
}

- (void)mouseEntered:(NSEvent *)event
{
_hasMouseOver = YES;
[self sendMouseEventWithBlock:self.onMouseEnter
locationInfo:[self locationInfoFromEvent:event]
modifierFlags:event.modifierFlags
additionalData:nil];
}

- (void)mouseExited:(NSEvent *)event
{
_hasMouseOver = NO;
[self sendMouseEventWithBlock:self.onMouseLeave
locationInfo:[self locationInfoFromEvent:event]
modifierFlags:event.modifierFlags
additionalData:nil];
}

- (void)sendMouseEventWithBlock:(RCTDirectEventBlock)block
locationInfo:(NSDictionary*)locationInfo
modifierFlags:(NSEventModifierFlags)modifierFlags
additionalData:(NSDictionary*)additionalData
{
if (block == nil) {
return;
}

NSMutableDictionary *body = [NSMutableDictionary new];

if (modifierFlags & NSEventModifierFlagShift) {
body[@"shiftKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagControl) {
body[@"ctrlKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagOption) {
body[@"altKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagCommand) {
body[@"metaKey"] = @YES;
}

if (locationInfo) {
[body addEntriesFromDictionary:locationInfo];
}

if (additionalData) {
[body addEntriesFromDictionary:additionalData];
}

block(body);
}

- (BOOL)mouseDownCanMoveWindow{
return _mouseDownCanMoveWindow;
}
Expand Down
6 changes: 0 additions & 6 deletions packages/react-native/React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,6 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
// that we can set through JS and the getter for `allowsVibrancy` can read in RCTView.
@property (nonatomic, assign) BOOL allowsVibrancyInternal;

@property (nonatomic, copy) RCTDirectEventBlock onMouseEnter;
@property (nonatomic, copy) RCTDirectEventBlock onMouseLeave;
@property (nonatomic, copy) RCTDirectEventBlock onDragEnter;
@property (nonatomic, copy) RCTDirectEventBlock onDragLeave;
@property (nonatomic, copy) RCTDirectEventBlock onDrop;

// Keyboarding events
// NOTE does not properly work with single line text inputs (most key downs). This is because those are
// presumably handled by the window's field editor. To make it work, we'd need to look into providing
Expand Down
137 changes: 0 additions & 137 deletions packages/react-native/React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ @implementation RCTView {
id<RCTEventDispatcherProtocol> _eventDispatcher; // [macOS]
#if TARGET_OS_OSX // [macOS
NSTrackingArea *_trackingArea;
BOOL _hasMouseOver;
BOOL _mouseDownCanMoveWindow;
#endif // macOS]
NSMutableDictionary<NSString *, NSDictionary *> *accessibilityActionsNameMap;
Expand Down Expand Up @@ -778,67 +777,7 @@ -(void)didUpdateShadow
[self setShadow:shadow];
}

- (void)viewDidMoveToWindow
{
// Subscribe to view bounds changed notification so that the view can be notified when a
// scroll event occurs either due to trackpad/gesture based scrolling or a scrollwheel event
// both of which would not cause the mouseExited to be invoked.

if ([self window] == nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSViewBoundsDidChangeNotification
object:nil];
}
else if ([[self enclosingScrollView] contentView] != nil) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(viewBoundsChanged:)
name:NSViewBoundsDidChangeNotification
object:[[self enclosingScrollView] contentView]];
}

[self reactViewDidMoveToWindow]; // [macOS] Github#1412

[super viewDidMoveToWindow];
}

- (void)viewBoundsChanged:(NSNotification*)__unused inNotif
{
// When an enclosing scrollview is scrolled using the scrollWheel or trackpad,
// the mouseExited: event does not get called on the view where mouseEntered: was previously called.
// This creates an unnatural pairing of mouse enter and exit events and can cause problems.
// We therefore explicitly check for this here and handle them by calling the appropriate callbacks.

if (!_hasMouseOver && self.onMouseEnter)
{
NSPoint locationInWindow = [[self window] mouseLocationOutsideOfEventStream];
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

if (NSPointInRect(locationInView, [self bounds]))
{
_hasMouseOver = YES;

[self sendMouseEventWithBlock:self.onMouseEnter
locationInfo:[self locationInfoFromDraggingLocation:locationInWindow]
modifierFlags:0
additionalData:nil];
}
}
else if (_hasMouseOver && self.onMouseLeave)
{
NSPoint locationInWindow = [[self window] mouseLocationOutsideOfEventStream];
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

if (!NSPointInRect(locationInView, [self bounds]))
{
_hasMouseOver = NO;

[self sendMouseEventWithBlock:self.onMouseLeave
locationInfo:[self locationInfoFromDraggingLocation:locationInWindow]
modifierFlags:0
additionalData:nil];
}
}
}
#endif // macOS]

#pragma mark - Statics for dealing with layoutGuides
Expand Down Expand Up @@ -1549,24 +1488,6 @@ - (void)updateTrackingAreas
[super updateTrackingAreas];
}

- (void)mouseEntered:(NSEvent *)event
{
_hasMouseOver = YES;
[self sendMouseEventWithBlock:self.onMouseEnter
locationInfo:[self locationInfoFromEvent:event]
modifierFlags:event.modifierFlags
additionalData:nil];
}

- (void)mouseExited:(NSEvent *)event
{
_hasMouseOver = NO;
[self sendMouseEventWithBlock:self.onMouseLeave
locationInfo:[self locationInfoFromEvent:event]
modifierFlags:event.modifierFlags
additionalData:nil];
}

- (BOOL)mouseDownCanMoveWindow{
return _mouseDownCanMoveWindow;
}
Expand All @@ -1579,53 +1500,6 @@ - (BOOL)allowsVibrancy {
return _allowsVibrancyInternal;
}

- (NSDictionary*)locationInfoFromEvent:(NSEvent*)event
{
NSPoint locationInWindow = event.locationInWindow;
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

return @{@"screenX": @(locationInWindow.x),
@"screenY": @(locationInWindow.y),
@"clientX": @(locationInView.x),
@"clientY": @(locationInView.y)
};
}

- (void)sendMouseEventWithBlock:(RCTDirectEventBlock)block
locationInfo:(NSDictionary*)locationInfo
modifierFlags:(NSEventModifierFlags)modifierFlags
additionalData:(NSDictionary*)additionalData
{
if (block == nil) {
return;
}

NSMutableDictionary *body = [NSMutableDictionary new];

if (modifierFlags & NSEventModifierFlagShift) {
body[@"shiftKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagControl) {
body[@"ctrlKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagOption) {
body[@"altKey"] = @YES;
}
if (modifierFlags & NSEventModifierFlagCommand) {
body[@"metaKey"] = @YES;
}

if (locationInfo) {
[body addEntriesFromDictionary:locationInfo];
}

if (additionalData) {
[body addEntriesFromDictionary:additionalData];
}

block(body);
}

- (NSDictionary*)dataTransferInfoFromPasteboard:(NSPasteboard*)pasteboard
{
NSArray *fileNames = [pasteboard propertyListForType:NSFilenamesPboardType] ?: @[];
Expand Down Expand Up @@ -1704,17 +1578,6 @@ - (NSDictionary*)dataTransferInfoFromPasteboard:(NSPasteboard*)pasteboard
@"types": types}};
}

- (NSDictionary*)locationInfoFromDraggingLocation:(NSPoint)locationInWindow
{
NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil];

return @{@"screenX": @(locationInWindow.x),
@"screenY": @(locationInWindow.y),
@"clientX": @(locationInView.x),
@"clientY": @(locationInView.y)
};
}

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard = sender.draggingPasteboard;
Expand Down
Loading