Skip to content

Commit

Permalink
Add mouseButton prop (#2676)
Browse files Browse the repository at this point in the history
## Description

After giving it some thought, we decided to add new modifier to our handlers - `mouseButton`. This way users will be able to choose mouse buttons that handler will respond to.

For now this prop is available on all handlers, but it is a topic for a short discussion.

I think it is also worth to mention, that right now all three mouse buttons work on web, so this prop will allow us to 'disable' all buttons except those provided as arguments.

This PR also adds two small examples (one for buttons and one for context menu) that can be used to test given prop on other platforms (when this functionality will be added)

## Test plan

Tested on example app.
  • Loading branch information
m-bert committed Jan 16, 2024
1 parent 876e9ce commit a30f42f
Show file tree
Hide file tree
Showing 19 changed files with 393 additions and 20 deletions.
4 changes: 4 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { ComboWithGHScroll } from './release_tests/combo';
import { TouchablesIndex, TouchableExample } from './release_tests/touchables';
import Rows from './release_tests/rows';
import Fling from './release_tests/fling';
import MouseButtons from './release_tests/mouseButtons';
import ContextMenu from './release_tests/contextMenu';
import NestedTouchables from './release_tests/nestedTouchables';
import NestedButtons from './release_tests/nestedButtons';
import NestedGestureHandlerRootViewWithModal from './release_tests/nestedGHRootViewWithModal';
Expand Down Expand Up @@ -116,6 +118,8 @@ const EXAMPLES: ExamplesSection[] = [
{ name: 'Fling', component: Fling },
{ name: 'Combo', component: ComboWithGHScroll },
{ name: 'Touchables', component: TouchablesIndex as React.ComponentType },
{ name: 'MouseButtons', component: MouseButtons },
{ name: 'ContextMenu (web only)', component: ContextMenu },
{ name: 'Rounded buttons', component: RoundedButtons },
],
},
Expand Down
61 changes: 61 additions & 0 deletions example/src/release_tests/contextMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import {
Gesture,
GestureDetector,
MouseButton,
} from 'react-native-gesture-handler';

export default function ContextMenuExample() {
const p1 = Gesture.Pan().mouseButton(MouseButton.RIGHT);
const p2 = Gesture.Pan();
const p3 = Gesture.Pan();

return (
<View style={styles.container}>
<GestureDetector gesture={p1}>
<View style={[styles.box, styles.grandParent]}>
<GestureDetector gesture={p2} enableContextMenu={true}>
<View style={[styles.box, styles.parent]}>
<GestureDetector gesture={p3} enableContextMenu={false}>
<View style={[styles.box, styles.child]} />
</GestureDetector>
</View>
</GestureDetector>
</View>
</GestureDetector>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'space-around',
alignItems: 'center',
},

grandParent: {
width: 300,
height: 300,
backgroundColor: 'lightblue',
},

parent: {
width: 200,
height: 200,
backgroundColor: 'lightgreen',
},

child: {
width: 100,
height: 100,
backgroundColor: 'crimson',
},

box: {
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
},
});
181 changes: 181 additions & 0 deletions example/src/release_tests/mouseButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React from 'react';
import {
Gesture,
GestureDetector,
GestureType,
MouseButton,
Directions,
ScrollView,
} from 'react-native-gesture-handler';
import { StyleSheet, View, Text } from 'react-native';

const COLORS = ['darkmagenta', 'darkgreen', 'darkblue', 'crimson', 'pink'];

type TestProps = {
name: string;
gestureHandlers: GestureType[];
};

function Test({ name, gestureHandlers }: TestProps) {
return (
<View style={styles.center}>
<Text>{name}</Text>
<View
style={[
{ margin: 10, width: '100%', flexDirection: 'row' },
styles.center,
]}>
{gestureHandlers.map((handler, index) => {
return (
<GestureDetector gesture={handler} key={index}>
<View style={[styles.box, { backgroundColor: COLORS[index] }]} />
</GestureDetector>
);
})}
</View>
</View>
);
}

function TapTests() {
const leftTap = Gesture.Tap()
.mouseButton(MouseButton.LEFT)
.onEnd(() => console.log('Tap with left'));

const middleTap = Gesture.Tap()
.mouseButton(MouseButton.MIDDLE)
.onEnd(() => console.log('Tap with middle'));

const rightTap = Gesture.Tap()
.mouseButton(MouseButton.RIGHT)
.onEnd(() => console.log('Tap with right'));

const leftRightTap = Gesture.Tap()
.mouseButton(MouseButton.LEFT | MouseButton.RIGHT)
.onEnd(() => console.log('Tap with left | right'));

const allTap = Gesture.Tap()
.mouseButton(MouseButton.ALL)
.onEnd(() => console.log('Tap with any button'));

const gestureHandlers = [leftTap, middleTap, rightTap, leftRightTap, allTap];

return <Test name={'Tap'} gestureHandlers={gestureHandlers} />;
}

function PanTests() {
const leftPan = Gesture.Pan()
.mouseButton(MouseButton.LEFT)
.onChange(() => console.log('Panning with left'));

const middlePan = Gesture.Pan()
.mouseButton(MouseButton.MIDDLE)
.onChange(() => console.log('Panning with middle'));

const rightPan = Gesture.Pan()
.mouseButton(MouseButton.RIGHT)
.onChange(() => console.log('Panning with right'));

const leftRightPan = Gesture.Pan()
.mouseButton(MouseButton.LEFT | MouseButton.RIGHT)
.onChange(() => console.log('Panning with left | right'));

const allPan = Gesture.Pan()
.mouseButton(MouseButton.ALL)
.onChange(() => console.log('Panning with any button'));

const gestureHandlers = [leftPan, middlePan, rightPan, leftRightPan, allPan];

return <Test name={'Pan'} gestureHandlers={gestureHandlers} />;
}

function LongPressTests() {
const leftLongPress = Gesture.LongPress()
.mouseButton(MouseButton.LEFT)
.onStart(() => console.log('LongPress with left'));

const middleLongPress = Gesture.LongPress()
.mouseButton(MouseButton.MIDDLE)
.onStart(() => console.log('LongPress with middle'));

const rightLongPress = Gesture.LongPress()
.mouseButton(MouseButton.RIGHT)
.onStart(() => console.log('LongPress with right'));

const leftRightLongPress = Gesture.LongPress()
.mouseButton(MouseButton.LEFT | MouseButton.RIGHT)
.onStart(() => console.log('LongPress with left | right'));

const allLongPress = Gesture.LongPress()
.mouseButton(MouseButton.ALL)
.onStart(() => console.log('LongPress with any button'));

const gestureHandlers = [
leftLongPress,
middleLongPress,
rightLongPress,
leftRightLongPress,
allLongPress,
];

return <Test name={'LongPress'} gestureHandlers={gestureHandlers} />;
}

function FlingTests() {
const leftFling = Gesture.Fling()
.direction(Directions.LEFT | Directions.RIGHT)
.mouseButton(MouseButton.LEFT)
.onStart(() => console.log('Fling with left'));

const middleFling = Gesture.Fling()
.direction(Directions.LEFT | Directions.RIGHT)
.mouseButton(MouseButton.MIDDLE)
.onStart(() => console.log('Fling with middle'));

const rightFling = Gesture.Fling()
.direction(Directions.LEFT | Directions.RIGHT)
.mouseButton(MouseButton.RIGHT)
.onStart(() => console.log('Fling with right'));

const leftRightFling = Gesture.Fling()
.direction(Directions.LEFT | Directions.RIGHT)
.mouseButton(MouseButton.LEFT | MouseButton.RIGHT)
.onStart(() => console.log('Fling with left | right'));

const allFling = Gesture.Fling()
.direction(Directions.LEFT | Directions.RIGHT)
.mouseButton(MouseButton.ALL)
.onStart(() => console.log('Fling with any button'));

const gestureHandlers = [
leftFling,
middleFling,
rightFling,
leftRightFling,
allFling,
];

return <Test name={'Fling'} gestureHandlers={gestureHandlers} />;
}

export default function Buttons() {
return (
<ScrollView style={{ flex: 1 }}>
<TapTests />
<PanTests />
<LongPressTests />
<FlingTests />
</ScrollView>
);
}

const styles = StyleSheet.create({
center: {
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
width: 75,
height: 75,
},
});
15 changes: 15 additions & 0 deletions src/components/DrawerLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
TapGestureHandlerEventPayload,
} from '../handlers/TapGestureHandler';
import { State } from '../State';
import { MouseButton } from '../web/interfaces';

const DRAG_TOSS = 0.05;

Expand Down Expand Up @@ -173,6 +174,18 @@ export interface DrawerLayoutProps {
* Values: see CSS cursor values
*/
activeCursor?: ActiveCursor;

/**
* @default 'MouseButton.LEFT'
* Allows to choose which mouse button should underlying pan handler react to.
*/
mouseButton?: MouseButton;

/**
* @default 'false if MouseButton.RIGHT is specified'
* Allows to enable/disable context menu.
*/
enableContextMenu?: boolean;
}

export type DrawerLayoutState = {
Expand Down Expand Up @@ -700,6 +713,8 @@ export default class DrawerLayout extends Component<
// @ts-ignore could be fixed in handler types
userSelect={this.props.userSelect}
activeCursor={this.props.activeCursor}
mouseButton={this.props.mouseButton}
enableContextMenu={this.props.enableContextMenu}
ref={this.setPanGestureRef}
hitSlop={hitSlop}
activeOffsetX={gestureOrientation * minSwipeDistance!}
Expand Down
5 changes: 5 additions & 0 deletions src/handlers/gestureHandlerCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { handlerIDToTag } from './handlersRegistry';
import { toArray } from '../utils';
import RNGestureHandlerModule from '../RNGestureHandlerModule';
import { ghQueueMicrotask } from '../ghQueueMicrotask';
import { MouseButton } from '../web/interfaces';

const commonProps = [
'id',
Expand All @@ -21,6 +22,8 @@ const commonProps = [
'cancelsTouchesInView',
'userSelect',
'activeCursor',
'mouseButton',
'enableContextMenu',
] as const;

const componentInteractionProps = [
Expand Down Expand Up @@ -149,6 +152,8 @@ export type CommonGestureConfig = {
hitSlop?: HitSlop;
userSelect?: UserSelect;
activeCursor?: ActiveCursor;
mouseButton?: MouseButton;
enableContextMenu?: boolean;
};

// Events payloads are types instead of interfaces due to TS limitation.
Expand Down
16 changes: 15 additions & 1 deletion src/handlers/gestures/GestureDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -605,10 +605,20 @@ const applyUserSelectProp = (
}
};

const applyEnableContextMenuProp = (
enableContextMenu: boolean,
gesture: ComposedGesture | GestureType
): void => {
for (const g of gesture.toGestureArray()) {
g.config.enableContextMenu = enableContextMenu;
}
};

interface GestureDetectorProps {
gesture: ComposedGesture | GestureType;
userSelect?: UserSelect;
children?: React.ReactNode;
userSelect?: UserSelect;
enableContextMenu?: boolean;
}
interface GestureDetectorState {
firstRender: boolean;
Expand All @@ -630,6 +640,10 @@ export const GestureDetector = (props: GestureDetectorProps) => {
applyUserSelectProp(props.userSelect, gestureConfig);
}

if (props.enableContextMenu !== undefined) {
applyEnableContextMenuProp(props.enableContextMenu, gestureConfig);
}

const gesture = gestureConfig.toGestureArray();
const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated);

Expand Down
6 changes: 6 additions & 0 deletions src/handlers/gestures/gesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { RotationGestureHandlerEventPayload } from '../RotationGestureHandler';
import { TapGestureHandlerEventPayload } from '../TapGestureHandler';
import { NativeViewGestureHandlerPayload } from '../NativeViewGestureHandler';
import { isRemoteDebuggingEnabled } from '../../utils';
import { MouseButton } from '../../web/interfaces';

export type GestureType =
| BaseGesture<Record<string, unknown>>
Expand Down Expand Up @@ -257,6 +258,11 @@ export abstract class BaseGesture<
return this;
}

mouseButton(mouseButton: MouseButton) {
this.config.mouseButton = mouseButton;
return this;
}

runOnJS(runOnJS: boolean) {
this.config.runOnJS = runOnJS;
return this;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { initialize } from './init';

export { Directions } from './Directions';
export { State } from './State';
export { MouseButton } from './web/interfaces';
export { default as gestureHandlerRootHOC } from './components/gestureHandlerRootHOC';
export { default as GestureHandlerRootView } from './components/GestureHandlerRootView';
export type {
Expand Down
4 changes: 4 additions & 0 deletions src/web/handlers/FlingGestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ export default class FlingGestureHandler extends GestureHandler {
}

protected onPointerDown(event: AdaptedEvent): void {
if (!this.isButtonInConfig(event.button)) {
return;
}

this.tracker.addToTracker(event);
this.keyPointer = event.pointerId;

Expand Down
Loading

0 comments on commit a30f42f

Please sign in to comment.