Skip to content

Implement gesture relations #3664

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

Draft
wants to merge 13 commits into
base: @mbert/shared-values
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public NativeRNGestureHandlerModuleSpec(ReactApplicationContext reactContext) {
@DoNotStrip
public abstract void updateGestureHandlerConfig(double handlerTag, ReadableMap newConfig);

@ReactMethod
@DoNotStrip
public abstract void configureRelations(double handlerTag, ReadableMap relations);

@ReactMethod
@DoNotStrip
public abstract void dropGestureHandler(double handlerTag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class RNGestureHandlerEvent private constructor() : Event<RNGestureHandlerEvent>
EVENT_NAME
}

override fun canCoalesce() = true
// Unfortunately getCoalescingKey is not considered when sending event to C++, therefore we have to disable coalescing in v3
override fun canCoalesce() = actionType != GestureHandler.ACTION_TYPE_NATIVE_DETECTOR

override fun getCoalescingKey() = coalescingKey

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
val handler = registry.getHandler(handlerTag) ?: return
val factory = RNGestureHandlerFactoryUtil.findFactoryForHandler<GestureHandler>(handler) ?: return

interactionManager.dropRelationsForHandlerWithTag(handlerTag)
interactionManager.configureInteractions(handler, config)
factory.setConfig(handler, config)
}

Expand All @@ -110,6 +108,15 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
factory.updateConfig(handler, config)
}

@ReactMethod
override fun configureRelations(handlerTagDouble: Double, relations: ReadableMap) {
val handlerTag = handlerTagDouble.toInt()
val handler = registry.getHandler(handlerTag) ?: return

interactionManager.dropRelationsForHandlerWithTag(handlerTag)
interactionManager.configureInteractions(handler, relations)
}

@ReactMethod
override fun dropGestureHandler(handlerTagDouble: Double) {
val handlerTag = handlerTagDouble.toInt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
- (void)resetConfig NS_REQUIRES_SUPER;
- (void)setConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER;
- (void)updateConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER;
- (void)updateRelations:(nonnull NSDictionary *)relations;
- (void)handleGesture:(nonnull id)recognizer;
- (void)handleGesture:(nonnull id)recognizer inState:(RNGestureHandlerState)state;
- (BOOL)containsPointInView;
Expand Down
11 changes: 7 additions & 4 deletions packages/react-native-gesture-handler/apple/RNGestureHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,6 @@ - (void)setConfig:(NSDictionary *)config

- (void)updateConfig:(NSDictionary *)config
{
_handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]];
_simultaneousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]];
_handlersThatShouldWait = [RCTConvert NSNumberArray:config[@"blocksHandlers"]];

id prop = config[@"enabled"];
if (prop != nil) {
self.enabled = [RCTConvert BOOL:prop];
Expand Down Expand Up @@ -188,6 +184,13 @@ - (void)updateConfig:(NSDictionary *)config
}
}

- (void)updateRelations:(NSDictionary *)relations
{
_handlersToWaitFor = [RCTConvert NSNumberArray:relations[@"waitFor"]];
_simultaneousHandlers = [RCTConvert NSNumberArray:relations[@"simultaneousHandlers"]];
_handlersThatShouldWait = [RCTConvert NSNumberArray:relations[@"blocksHandlers"]];
}

- (void)setEnabled:(BOOL)enabled
{
_enabled = enabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

- (void)updateGestureHandlerConfig:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config;

- (void)updateGestureHandlerRelations:(nonnull NSNumber *)handlerTag relations:(nonnull NSDictionary *)relations;

- (void)dropGestureHandler:(nonnull NSNumber *)handlerTag;

- (void)dropAllGestureHandlers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ - (void)updateGestureHandlerConfig:(NSNumber *)handlerTag config:(NSDictionary *
[handler updateConfig:config];
}

- (void)updateGestureHandlerRelations:(NSNumber *)handlerTag relations:(NSDictionary *)relations
{
RNGestureHandler *handler = [_registry handlerWithTag:handlerTag];
[handler updateRelations:relations];
}

- (void)dropGestureHandler:(NSNumber *)handlerTag
{
[_registry dropHandlerWithTag:handlerTag];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ - (void)updateGestureHandlerConfig:(double)handlerTag newConfig:(NSDictionary *)
[manager updateGestureHandlerConfig:[NSNumber numberWithDouble:handlerTag] config:config];
}

- (void)configureRelations:(double)handlerTag relations:(NSDictionary *)relations
{
RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId];
[manager updateGestureHandlerRelations:[NSNumber numberWithDouble:handlerTag] relations:relations];
}

- (void)dropGestureHandler:(double)handlerTag
{
[self addOperationBlock:^(RNGestureHandlerManager *manager) {
Expand Down
9 changes: 7 additions & 2 deletions packages/react-native-gesture-handler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,14 @@ export type {
} from './components/DrawerLayout';
export { default as DrawerLayout } from './components/DrawerLayout';

export type { NativeDetectorProps } from './v3/NativeDetector';
export { NativeDetector } from './v3/NativeDetector';
export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector';
export { NativeDetector } from './v3/NativeDetector/NativeDetector';

export * from './v3/hooks/useGesture';
export * from './v3/hooks/relations/useSimultaneous';
export * from './v3/hooks/relations/useExclusive';
export * from './v3/hooks/relations/useRace';

export { HandlerType } from './v3/types';

initialize();
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface Spec extends TurboModule {
setGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void;
// eslint-disable-next-line @typescript-eslint/ban-types
updateGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void;
// eslint-disable-next-line @typescript-eslint/ban-types
configureRelations: (handlerTag: Double, relations: Object) => void;
dropGestureHandler: (handlerTag: Double) => void;
flushOperations: () => void;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RNGestureHandlerDetectorNativeComponent from '../../specs/RNGestureHandlerDetectorNativeComponent';
const HostGestureDetector = RNGestureHandlerDetectorNativeComponent;
export default HostGestureDetector;
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Ref, useEffect, useRef } from 'react';
import RNGestureHandlerModule from '../RNGestureHandlerModule.web';
import { ActionType } from '../ActionType';
import { PropsRef } from '../web/interfaces';
import RNGestureHandlerModule from '../../RNGestureHandlerModule.web';
import { ActionType } from '../../ActionType';
import { PropsRef } from '../../web/interfaces';
import { View } from 'react-native';
import { tagMessage } from '../utils';
import { tagMessage } from '../../utils';

export interface GestureHandlerDetectorProps extends PropsRef {
handlerTags: number[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from 'react';
import { NativeGesture } from './hooks/useGesture';
import { Reanimated } from '../handlers/gestures/reanimatedWrapper';
import { NativeGesture, ComposedGesture, ComposedGestureType } from '../types';
import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';

import { Animated, StyleSheet } from 'react-native';
import HostGestureDetector from './HostGestureDetector';
import { tagMessage } from '../utils';
import { tagMessage } from '../../utils';
import { isComposedGesture } from '../hooks/utils';
import { dfs } from './utils';

export interface NativeDetectorProps {
children?: React.ReactNode;
gesture: NativeGesture;
gesture: NativeGesture | ComposedGesture;
}

const AnimatedNativeDetector =
Expand All @@ -34,20 +36,32 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) {
);
}

if (
isComposedGesture(gesture) &&
gesture.type === ComposedGestureType.Simultaneous
) {
dfs(gesture, new Set(gesture.tags));
} else {
dfs(gesture);
}

return (
<NativeDetectorComponent
// @ts-ignore TODO: Fix types
onGestureHandlerStateChange={
gesture.gestureEvents.onGestureHandlerStateChange
}
// @ts-ignore TODO: Fix types
onGestureHandlerEvent={gesture.gestureEvents.onGestureHandlerEvent}
onGestureHandlerAnimatedEvent={
gesture.gestureEvents.onGestureHandlerAnimatedEvent
}
// @ts-ignore TODO: Fix types
onGestureHandlerTouchEvent={
gesture.gestureEvents.onGestureHandlerTouchEvent
}
moduleId={globalThis._RNGH_MODULE_ID}
handlerTags={[gesture.tag]}
handlerTags={isComposedGesture(gesture) ? gesture.tags : [gesture.tag]}
style={styles.detector}>
{children}
</NativeDetectorComponent>
Expand Down
115 changes: 115 additions & 0 deletions packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// This piece of magic traverses the gesture tree and populates `waitFor` and `simultaneousHandlers`
// arrays for each gesture. It traverses the tree recursively using DFS.
// `waitFor` and `simultaneousHandlers` are global data structures that will be populated into each gesture.
// For `waitFor` we need array as order of the gestures matters.
// For `simultaneousHandlers` we use Set as the order doesn't matter.

import RNGestureHandlerModule from '../../RNGestureHandlerModule';
import { isComposedGesture } from '../hooks/utils';
import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types';

// The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes.
export const dfs = (
node: NativeGesture | ComposedGesture,
simultaneousHandlers: Set<number> = new Set(),
waitFor: number[] = []
) => {
// If we are in the leaf node, we want to fill gesture relations arrays with current
// waitFor and simultaneousHandlers. We also want to configure relations on the native side.
// TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture`
if (!isComposedGesture(node)) {
node.simultaneousHandlers.push(...simultaneousHandlers);
node.waitFor.push(...waitFor);

RNGestureHandlerModule.configureRelations(node.tag, {
waitFor,
simultaneousHandlers: Array.from(simultaneousHandlers),
blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture`
});

return;
}

// If we are in the composed gesture, we want to traverse its children.
node.gestures.forEach((child) => {
// If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`.
if (isComposedGesture(child)) {
// We have to update `simultaneousHandlers` before traversing the child.

// If we go from a non-simultaneous gesture to a simultaneous gesture,
// we add the tags of the simultaneous gesture to the `simultaneousHandlers`.
// This way when we traverse the child, we already have the tags of the simultaneous gestures
if (
node.type !== ComposedGestureType.Simultaneous &&
child.type === ComposedGestureType.Simultaneous
) {
child.tags.forEach((tag) => simultaneousHandlers.add(tag));
}

// If we go from a simultaneous gesture to a non-simultaneous gesture,
// we remove the tags of the child gestures from the `simultaneousHandlers`,
// as those are not simultaneous with each other.
if (
node.type === ComposedGestureType.Simultaneous &&
child.type !== ComposedGestureType.Simultaneous
) {
child.tags.forEach((tag) => simultaneousHandlers.delete(tag));
}

// We will keep the current length of `waitFor` to reset it to previous state
// after traversing the child.
const length = waitFor.length;

// We traverse the child, passing the current `waitFor` and `simultaneousHandlers`.
dfs(child, simultaneousHandlers, waitFor);

// After traversing the child, we need to update `waitFor` and `simultaneousHandlers`

// If we go back from a simultaneous gesture to a non-simultaneous gesture,
// we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` -
// those gestures are not simultaneous with each other anymore.
if (
child.type === ComposedGestureType.Simultaneous &&
node.type !== ComposedGestureType.Simultaneous
) {
node.tags.forEach((tag) => simultaneousHandlers.delete(tag));
}

// If we go back from a non-simultaneous gesture to a simultaneous gesture,
// we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`,
// as those gestures are simultaneous with other children of the current node.
if (
child.type !== ComposedGestureType.Simultaneous &&
node.type === ComposedGestureType.Simultaneous
) {
node.tags.forEach((tag) => simultaneousHandlers.add(tag));
}

// If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array.
// This will allow us to pass exclusive gesture tags to the right subtree of the current node.
if (node.type === ComposedGestureType.Exclusive) {
child.tags.forEach((tag) => waitFor.push(tag));
}

// If we go back from an exclusive gesture to a non-exclusive gesture, we want to reset the `waitFor` array
// to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to
// add tags to the `waitFor` array, we can override `length` property to reset it to the previous state.
if (
child.type === ComposedGestureType.Exclusive &&
node.type !== ComposedGestureType.Exclusive
) {
waitFor.length = length;
}
}
// This means that child is a leaf node.
else {
// In the leaf node, we only care about filling `waitFor` array. First we traverse the child...
dfs(child, simultaneousHandlers, waitFor);

// ..and when we go back we add the tag of the child to the `waitFor` array.
if (node.type === ComposedGestureType.Exclusive) {
waitFor.push(child.tag);
}
}
});
};
Loading
Loading