Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/docs/components/gestureview.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ onPanVertical: (gestureState: PanGestureState) => void = undefined;
onPanHorizontal: (gestureState: PanGestureState) => void = undefined;
onTap: (gestureState: TapGestureState) => void = undefined;
onDoubleTap: (gestureState: TapGestureState) => void = undefined;
onContextMenu: (gestureState: TapGestureState) => void = undefined;

// We can set vertical or horizontal as preferred
preferredPan: PreferredPanGesture = undefined; // Horizontal or vertical
Expand Down
55 changes: 51 additions & 4 deletions samples/RXPTest/src/Tests/GestureViewTest.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Tests the functionality of a GestureView component
* Tests the functionality of a GestureView component
* through user interaction.
*/

Expand Down Expand Up @@ -38,10 +38,12 @@ const _styles = {
};

const _colors = ['red', 'green', 'blue'];
const _shades = ['#000', '#333', '#666', '#999', '#CCC', '#FFF'];

interface GestureViewState {
test1ColorIndex?: number;
test2ColorIndex?: number;
test4ColorIndex?: number;
}

class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
Expand All @@ -51,7 +53,7 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
private _test1AnimatedStyle = RX.Styles.createAnimatedViewStyle({
transform: [{
scale: this._test1ScaleValue
}, {
}, {
rotate: this._test1RotateValue.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg']
Expand Down Expand Up @@ -79,12 +81,23 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
}]
});

private _test4HorizontalOffset = new RX.Animated.Value(0);
private _test4VerticalOffset = new RX.Animated.Value(0);
private _test4AnimatedStyle = RX.Styles.createAnimatedViewStyle({
transform: [{
translateX: this._test4HorizontalOffset
}, {
translateY: this._test4VerticalOffset
}]
});

constructor(props: RX.CommonProps) {
super(props);

this.state = {
test1ColorIndex: 0,
test2ColorIndex: 1
test2ColorIndex: 1,
test4ColorIndex: 2
};
}

Expand All @@ -98,6 +111,10 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
backgroundColor: _colors[this.state.test2ColorIndex]
}, false);

let test4ColorStyle = RX.Styles.createViewStyle({
backgroundColor: _shades[this.state.test4ColorIndex]
}, false);

return (
<RX.View style={ _styles.container}>
<RX.View style={ _styles.explainTextContainer } key={ 'explanation1' }>
Expand Down Expand Up @@ -154,6 +171,23 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
style={ [_styles.smallBox, this._test3AnimatedStyle] }
/>
</RX.GestureView>

<RX.View style={ _styles.explainTextContainer } key={ 'explanation4' }>
<RX.Text style={ _styles.explainText }>
{ 'Desktop platforms: Left click will make the box lighter. ' +
'Right click (context menu) will make the box darker.' }
</RX.Text>
</RX.View>
<RX.GestureView
style={ _styles.gestureView }
onTap={ state => this._onTapTest4(state) }
onContextMenu={ e => this._onContextMenu4(e) }
mouseOverCursor={ RX.Types.GestureMouseCursor.Pointer }
>
<RX.Animated.View
style={ [_styles.smallBox, test4ColorStyle, this._test4AnimatedStyle] }
/>
</RX.GestureView>
</RX.View>
);
}
Expand Down Expand Up @@ -222,6 +256,19 @@ class GestureViewView extends RX.Component<RX.CommonProps, GestureViewState> {
this._test3VerticalOffset.setValue(state.pageY - state.initialPageY);
}
}

private _onTapTest4(gestureState: RX.Types.TapGestureState) {
// Change the color.
this.setState({
test4ColorIndex: Math.min(this.state.test4ColorIndex + 1, _shades.length - 1)
});
}

private _onContextMenu4(gestureState: RX.Types.TapGestureState) {
this.setState({
test4ColorIndex: Math.max(this.state.test4ColorIndex - 1, 0)
});
}
}

class GestureViewTest implements Test {
Expand All @@ -230,7 +277,7 @@ class GestureViewTest implements Test {
getPath(): string {
return 'Components/GestureView';
}

getTestType(): TestType {
return TestType.Interactive;
}
Expand Down
1 change: 1 addition & 0 deletions src/common/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ export interface GestureViewProps extends CommonStyledProps<ViewStyleRuleSet>, C
onPanHorizontal?: (gestureState: PanGestureState) => void;
onTap?: (gestureState: TapGestureState) => void;
onDoubleTap?: (gestureState: TapGestureState) => void;
onContextMenu?: (gestureState: TapGestureState) => void;

// We can set vertical or horizontal as preferred
preferredPan?: PreferredPanGesture;
Expand Down
29 changes: 27 additions & 2 deletions src/native-common/GestureView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import React = require('react');
import RN = require('react-native');

import AccessibilityUtil from './AccessibilityUtil';
import EventHelpers from './utils/EventHelpers';

import Types = require('../common/Types');
import UserInterface from './UserInterface';
Expand Down Expand Up @@ -63,7 +64,7 @@ export abstract class GestureView extends React.Component<Types.GestureViewProps

this._lastGestureStartEvent = event;
// If we're trying to detect a tap, set this as the responder immediately.
if (this.props.onTap || this.props.onDoubleTap) {
if (this.props.onTap || this.props.onDoubleTap || this.props.onContextMenu) {
return true;
}
return false;
Expand Down Expand Up @@ -501,7 +502,21 @@ export abstract class GestureView extends React.Component<Types.GestureViewProps
}

private _sendTapEvent(e: Types.TouchEvent) {
if (this.props.onTap) {
const button = EventHelpers.toMouseButton(e);
if (button === 2) {
// Always handle secondary button, even if context menu is not set - it shouldn't trigger onTap.
if (this.props.onContextMenu) {
const tapEvent: Types.TapGestureState = {
pageX: e.pageX!!!,
pageY: e.pageY!!!,
clientX: e.locationX!!!,
clientY: e.locationY!!!,
timeStamp: e.timeStamp
};

this.props.onContextMenu(tapEvent);
}
} else if (this.props.onTap) {
const tapEvent: Types.TapGestureState = {
pageX: e.pageX!!!,
pageY: e.pageY!!!,
Expand All @@ -515,6 +530,16 @@ export abstract class GestureView extends React.Component<Types.GestureViewProps
}

private _sendDoubleTapEvent(e: Types.TouchEvent) {
// If user did a double click with different mouse buttons, eg. left (50ms) right
// both clicks need to be registered as separate events.
const lastButton = EventHelpers.toMouseButton(this._lastTapEvent!!!);
const button = EventHelpers.toMouseButton(e);
if (lastButton !== button || button === 2) {
this._sendTapEvent(this._lastTapEvent!!!);
this._sendTapEvent(e);
return;
}

if (this.props.onDoubleTap) {
const tapEvent: Types.TapGestureState = {
pageX: e.pageX!!!,
Expand Down
25 changes: 16 additions & 9 deletions src/native-common/utils/EventHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class EventHelpers {
case 'ArrowDown':
keyCode = 20;
break;

case 'Number0':
keyCode = 48;
break;
Expand Down Expand Up @@ -190,7 +190,7 @@ export class EventHelpers {
// reuses events, so we're not allowed to modify the original.
// Instead, we'll clone it.
let mouseEvent = _.clone(e as Types.MouseEvent);

const nativeEvent = e.nativeEvent;

// We keep pageX/Y and clientX/Y coordinates in sync, similar to the React web behavior
Expand All @@ -203,13 +203,7 @@ export class EventHelpers {
mouseEvent.clientY = mouseEvent.pageY = nativeEvent.pageY;
}

if (!!nativeEvent.IsRightButton) {
mouseEvent.button = 2;
} else if (!!nativeEvent.IsMiddleButton) {
mouseEvent.button = 1;
} else {
mouseEvent.button = 0;
}
mouseEvent.button = this.toMouseButton(e.nativeEvent as Types.TouchEvent);

if (nativeEvent.shiftKey) {
mouseEvent.shiftKey = nativeEvent.shiftKey;
Expand Down Expand Up @@ -239,6 +233,19 @@ export class EventHelpers {
return mouseEvent;
}

toMouseButton(e: Types.TouchEvent): number {
const nativeEvent = e as any;
if (nativeEvent.button !== undefined) {
return nativeEvent.button;
} else if (nativeEvent.isRightButton || nativeEvent.IsRightButton) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in previous PR, I tested current implementations and it seems that UpperCase IsRightButton is not produced by any native code, but I'd rather keep backwards compatibility.

return 2;
} else if (nativeEvent.isMiddleButton || nativeEvent.IsMiddleButton) {
return 1;
}

return 0;
}

isRightMouseButton(e: Types.SyntheticEvent): boolean {
return !!e.nativeEvent.isRightButton;
}
Expand Down
22 changes: 22 additions & 0 deletions src/web/GestureView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class GestureView extends React.Component<Types.GestureViewProps, Types.S
role={ ariaRole }
aria-label={ this.props.accessibilityLabel }
aria-hidden={ isAriaHidden }
onContextMenu={ this.props.onContextMenu ? this._sendContextMenuEvent : undefined }
>
{ this.props.children }
</div>
Expand Down Expand Up @@ -182,6 +183,27 @@ export class GestureView extends React.Component<Types.GestureViewProps, Types.S
}
}

private _sendContextMenuEvent = (e: React.MouseEvent<any>) => {
if (this.props.onContextMenu) {
e.preventDefault();
e.stopPropagation();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved these a bit earlier to keep this behavior on par with the rest on reactxp - events are being cancelled before their synthentic handlers. At the same time if onContextMenu is not defined, I don't think we should prevent the event from bubbling down.


const clientRect = this._getGestureViewClientRect();

if (clientRect) {
const tapEvent: Types.TapGestureState = {
pageX: e.pageX,
pageY: e.pageY,
clientX: e.clientX - clientRect.left,
clientY: e.clientY - clientRect.top,
timeStamp: e.timeStamp
};

this.props.onContextMenu(tapEvent);
}
}
}

private _detectGestureType = (gestureState: Types.PanGestureState) => {
// we need to lock gesture type until it's completed
if (this._gestureTypeLocked) {
Expand Down