Skip to content

Improved input handling #228

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 4 additions & 2 deletions Libraries/Components/Touchable/TouchableOpacity.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,10 @@ var TouchableOpacity = createReactClass({
onResponderMove={this.touchableHandleResponderMove}
onResponderRelease={this.touchableHandleResponderRelease}
onResponderTerminate={this.touchableHandleResponderTerminate}
onMouseEnter={this.props.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
onMouseMove={this.props.onMouseMove}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
onContextMenu={this.props.onContextMenu}
onContextMenuItemClick={this.props.onContextMenuItemClick}
contextMenu={this.props.contextMenu}>
{this.props.children}
Expand Down
12 changes: 8 additions & 4 deletions Libraries/Components/Touchable/TouchableWithoutFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ const TouchableWithoutFeedback = createReactClass({
* views.
*/
hitSlop: EdgeInsetsPropType,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseOver: PropTypes.func,
onMouseOut: PropTypes.func,
onContextMenu: PropTypes.func,
onContextMenuItemClick: PropTypes.func,
contextMenu: PropTypes.array,
},
Expand Down Expand Up @@ -213,8 +215,10 @@ const TouchableWithoutFeedback = createReactClass({
onResponderMove: this.touchableHandleResponderMove,
onResponderRelease: this.touchableHandleResponderRelease,
onResponderTerminate: this.touchableHandleResponderTerminate,
onMouseEnter: this.props.onMouseEnter,
onMouseLeave: this.props.onMouseLeave,
onMouseMove: this.props.onMouseMove,
onMouseOver: this.props.onMouseOver,
onMouseOut: this.props.onMouseOut,
onContextMenu: this.props.onContextMenu,
onContextMenuItemClick: this.props.onContextMenuItemClick,
contextMenu: this.props.contextMenu,
style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ exports[`TouchableHighlight renders correctly 1`] = `
isTVSelectable={true}
nativeID={undefined}
onLayout={undefined}
onMouseEnter={undefined}
onMouseLeave={undefined}
onMouseOver={undefined}
onMouseOut={undefined}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
Expand Down
6 changes: 4 additions & 2 deletions Libraries/Components/View/ReactNativeViewAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ ReactNativeViewAttributes.UIView = {
renderToHardwareTextureAndroid: true,
shouldRasterizeIOS: true,
onLayout: true,
onMouseEnter: true,
onMouseLeave: true,
onMouseMove: true,
onMouseOver: true,
onMouseOut: true,
onContextMenu: true,
onAccessibilityTap: true,
onMagicTap: true,
collapsable: true,
Expand Down
6 changes: 4 additions & 2 deletions Libraries/Components/View/ViewPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,13 @@ module.exports = {
* Desktop specific events
* @platform macos
*/
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseOver: PropTypes.func,
onMouseOut: PropTypes.func,
onDragEnter: PropTypes.func,
onDragLeave: PropTypes.func,
onDrop: PropTypes.func,
onContextMenu: PropTypes.func,
onContextMenuItemClick: PropTypes.func,
/**
* Mapped to toolTip property of NSView. Used to show extra information when
Expand Down
60 changes: 24 additions & 36 deletions RNTester/RNTester/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,41 @@
#import <React/RCTLinkingManager.h>
#import <React/RCTRootView.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTWindow.h>

@interface AppDelegate() <RCTBridgeDelegate, NSSearchFieldDelegate>

@end


@implementation AppDelegate

-(id)init
{
if(self = [super init]) {

// -- Init Window
NSRect contentSize = NSMakeRect(200, 500, 1000, 500);

self.window = [[NSWindow alloc] initWithContentRect:contentSize
styleMask:NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask
backing:NSBackingStoreBuffered
defer:NO];
NSWindowController *windowController = [[NSWindowController alloc] initWithWindow:self.window];

[[self window] setTitle:@"RNTester"];
[[self window] setTitleVisibility:NSWindowTitleHidden];
[windowController showWindow:self.window];

[windowController setShouldCascadeWindows:NO];
[windowController setWindowFrameAutosaveName:@"RNTester"];
[self setDefaultURL];

// -- Init Toolbar
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar setDelegate:self];
[toolbar setSizeMode:NSToolbarSizeModeRegular];

[self.window setToolbar:toolbar];

// -- Init Menu
[self setUpMainMenu];
}
return self;
NSToolbar *_toolbar;
}

- (void)applicationDidFinishLaunching:(NSNotification * __unused)aNotification
{
[self setDefaultURL];

_bridge = [[RCTBridge alloc] initWithDelegate:self
launchOptions:@{@"argv": [self argv]}];

RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge
moduleName:@"RNTesterApp"
initialProperties:nil];
_window = [[RCTWindow alloc] initWithBridge:_bridge
contentRect:NSMakeRect(200, 500, 1000, 500)
styleMask:(NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSClosableWindowMask)
defer:NO];

_window.title = @"RNTester";
_window.titleVisibility = NSWindowTitleHidden;

[self setUpToolbar];
[self setUpMainMenu];

_window.contentView = [[RCTRootView alloc] initWithBridge:_bridge
moduleName:@"RNTesterApp"
initialProperties:nil];

[self.window setContentView:rootView];
[_window makeKeyAndOrderFront:nil];
}

- (void)setDefaultURL
Expand Down Expand Up @@ -105,6 +85,14 @@ - (void)loadSourceForBridge:(RCTBridge *)bridge
onComplete:loadCallback];
}

- (void)setUpToolbar
{
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
toolbar.delegate = self;
toolbar.sizeMode = NSToolbarSizeModeRegular;
_window.toolbar = toolbar;
}

- (NSArray *)toolbarAllowedItemIdentifiers:(__unused NSToolbar *)toolbar
{
return @[NSToolbarFlexibleSpaceItemIdentifier, @"searchBar", NSToolbarFlexibleSpaceItemIdentifier, @"resetButton"];
Expand Down
4 changes: 2 additions & 2 deletions RNTester/js/DragnDropExample.macos.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class DragExample extends React.Component {
this.state.mouseOver ? 'orange' : 'white',
padding: 40, alignItems: 'center'}}
draggedTypes={['NSFilenamesPboardType']}
onMouseEnter={() => this.setState({mouseOver: true})}
onMouseLeave={() => this.setState({mouseOver: false})}
onMouseOver={() => this.setState({mouseOver: true})}
onMouseOut={() => this.setState({mouseOver: false})}
onDragEnter={() => this.setState({dragOver: true})}
onDragLeave={() => this.setState({dragOver: false})}
onDrop={(e) => this.setState({files: e.nativeEvent.files, dragOver: false})}>
Expand Down
23 changes: 23 additions & 0 deletions React/Base/RCTMouseEvent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <Foundation/Foundation.h>

#import <React/RCTEventDispatcher.h>

@interface RCTMouseEvent : NSObject <RCTEvent>

- (instancetype)initWithEventName:(NSString *)eventName
target:(NSNumber *)target
userInfo:(NSDictionary *)userInfo
coalescingKey:(uint16_t)coalescingKey NS_DESIGNATED_INITIALIZER;

@property (readonly) NSTimeInterval timestamp;

@end
78 changes: 78 additions & 0 deletions React/Base/RCTMouseEvent.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTMouseEvent.h"

#import "RCTAssert.h"

@implementation RCTMouseEvent
{
NSDictionary *_userInfo;
uint16_t _coalescingKey;
}

@synthesize eventName = _eventName;
@synthesize viewTag = _viewTag;

- (instancetype)initWithEventName:(NSString *)eventName
target:(NSNumber *)target
userInfo:(NSDictionary *)userInfo
coalescingKey:(uint16_t)coalescingKey
{
if (self = [super init]) {
_viewTag = target;
_userInfo = [NSDictionary dictionaryWithDictionary:userInfo];
_eventName = eventName;
_coalescingKey = coalescingKey;
}
return self;
}

RCT_NOT_IMPLEMENTED(- (instancetype)init)

#pragma mark - RCTEvent

- (BOOL)canCoalesce
{
return [_eventName isEqual:@"mouseMove"];
}

// We coalesce only move events, while holding some assumptions that seem reasonable but there are no explicit guarantees about them.
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
{
RCTAssert([newEvent isKindOfClass:[RCTMouseEvent class]], @"Mouse event cannot be coalesced with any other type of event, such as provided %@", newEvent);
return ((RCTMouseEvent *)newEvent).timestamp > self.timestamp ? newEvent : self;
}

+ (NSString *)moduleDotMethod
{
return @"RCTEventEmitter.receiveEvent";
}

- (NSArray *)arguments
{
return @[_viewTag, RCTNormalizeInputEventName(_eventName), _userInfo];
}

- (uint16_t)coalescingKey
{
return _coalescingKey;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name = %@; coalescing key = %hu>", [self class], self, _eventName, _coalescingKey];
}

- (NSTimeInterval)timestamp
{
return [_userInfo[@"timestamp"] doubleValue];
}

@end
34 changes: 24 additions & 10 deletions React/Base/RCTRootContentView.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "RCTRootViewInternal.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTWindow.h"
#import "NSView+React.h"

@implementation RCTRootContentView
Expand All @@ -28,8 +29,6 @@ - (instancetype)initWithFrame:(CGRect)frame
_bridge = bridge;
self.reactTag = reactTag;
_sizeFlexibility = sizeFlexibility;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_touchHandler attachToView:self];
[_bridge.uiManager registerRootView:self];
}
return self;
Expand Down Expand Up @@ -85,15 +84,13 @@ - (void)updateAvailableSize
[_bridge.uiManager setAvailableSize:self.availableSize forRootView:self];
}

- (NSView *)hitTest:(CGPoint)point withEvent:(NSEvent *)event
- (NSView *)hitTest:(CGPoint)point
{
// The root content view itself should never receive touches
// NSView *hitView = [super hitTest:point withEvent:event];
// if (_passThroughTouches && hitView == self) {
// return nil;
// }
// return hitView;
return nil;
// Flip the coordinate system to top-left origin.
NSPoint convertedPoint = [self convertPoint:point fromView:nil];

NSView *hitView = [super hitTest:convertedPoint];
return _passThroughTouches && hitView == self ? nil : hitView;
}

- (void)invalidate
Expand All @@ -108,4 +105,21 @@ - (void)invalidate
//}
}

- (void)viewDidMoveToWindow
{
if (self.window == nil) {
return;
}
// RCTWindow handles all touches within
if ([self.window isKindOfClass:RCTWindow.class] == NO) {
if (_touchHandler == nil) {
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[_touchHandler attachToView:self];
}
} else if (_touchHandler) {
[_touchHandler detachFromView:self];
_touchHandler = nil;
}
}

@end
13 changes: 6 additions & 7 deletions React/Base/RCTRootView.m
Original file line number Diff line number Diff line change
Expand Up @@ -333,15 +333,14 @@ - (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
_contentView.sizeFlexibility = _sizeFlexibility;
}

- (NSView *)hitTest:(CGPoint)point withEvent:(NSEvent *)event
- (NSView *)hitTest:(CGPoint)point
{
// The root view itself should never receive touches
// NSView *hitView = [super hitTest:point withEvent:event];
// if (self.passThroughTouches && hitView == self) {
// return nil;
// }
// return hitView;
return nil;
NSView *hitView = [super hitTest:point];
if (self.passThroughTouches && hitView == self) {
return nil;
}
return hitView;
}

- (void)setAppProperties:(NSDictionary *)appProperties
Expand Down
8 changes: 4 additions & 4 deletions React/Base/RCTTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ @implementation RCTTouchHandler
CFTimeInterval _mostRecentEnqueueJS;

/*
* Storing tag to dispatch mouseEnter and mouseLeave events
* Storing tag to dispatch mouseOver and mouseOut events
*/
NSNumber *_currentMouseOverTag;
}
Expand Down Expand Up @@ -336,13 +336,13 @@ - (void)mouseMoved:(NSEvent *)event
}
if (_currentMouseOverTag != reactTag && _currentMouseOverTag.intValue > 0) {
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:@[_currentMouseOverTag, @"topMouseLeave"]];
args:@[_currentMouseOverTag, @"topMouseOut"]];
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:@[reactTag, @"topMouseEnter"]];
args:@[reactTag, @"topMouseOver"]];
_currentMouseOverTag = reactTag;
} else if (_currentMouseOverTag == 0) {
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:@[reactTag, @"topMouseEnter"]];
args:@[reactTag, @"topMouseOver"]];
_currentMouseOverTag = reactTag;
}
}
Expand Down
Loading