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
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
flattenAndFilterEvent,
isEventForHandlerWithTag,
maybeExtractNativeEvent,
runCallback,
touchEventTypeToCallbackType,
} from '../utils';
import { tagMessage } from '../../../utils';
import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper';
import {
ChangeCalculatorType,
GestureCallbacks,
GestureHandlerEventWithHandlerData,
GestureStateChangeEventWithHandlerData,
GestureUpdateEventWithHandlerData,
} from '../../types';
import { CALLBACK_TYPE } from '../../../handlers/gestures/gesture';
import { State } from '../../../State';
import { TouchEventType } from '../../../TouchEventType';
import { GestureTouchEvent } from '../../../handlers/gestureHandlerCommon';

function handleStateChangeEvent<THandlerData>(
eventWithData: GestureStateChangeEventWithHandlerData<THandlerData>,
callbacks: GestureCallbacks<THandlerData>,
context: ReanimatedContext<THandlerData>
) {
'worklet';
const { oldState, state } = eventWithData;
const event = flattenAndFilterEvent(eventWithData);

if (oldState === State.UNDETERMINED && state === State.BEGAN) {
runCallback(CALLBACK_TYPE.BEGAN, callbacks, event);
} else if (
(oldState === State.BEGAN || oldState === State.UNDETERMINED) &&
state === State.ACTIVE
) {
runCallback(CALLBACK_TYPE.START, callbacks, event);
} else if (oldState !== state && state === State.END) {
if (oldState === State.ACTIVE) {
runCallback(CALLBACK_TYPE.END, callbacks, event, true);
}
runCallback(CALLBACK_TYPE.FINALIZE, callbacks, event, true);

if (context) {
context.lastUpdateEvent = undefined;
}
} else if (
(state === State.FAILED || state === State.CANCELLED) &&
state !== oldState
) {
if (oldState === State.ACTIVE) {
runCallback(CALLBACK_TYPE.END, callbacks, event, false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably not in this PR, but we should rename START, END callbacks

}
runCallback(CALLBACK_TYPE.FINALIZE, callbacks, event, false);

if (context) {
context.lastUpdateEvent = undefined;
}
}
}

export function handleUpdateEvent<THandlerData>(
eventWithData: GestureUpdateEventWithHandlerData<THandlerData>,
handlers: GestureCallbacks<THandlerData>,
changeEventCalculator: ChangeCalculatorType<THandlerData> | undefined,
context: ReanimatedContext<THandlerData>
) {
'worklet';
const eventWithChanges = changeEventCalculator
? changeEventCalculator(
eventWithData,
context ? context.lastUpdateEvent : undefined
)
: eventWithData;

const event = flattenAndFilterEvent(eventWithChanges);

// This should never happen, but since we don't want to call hooks conditionally, we have to mark
// context as possibly undefined to make TypeScript happy.
if (!context) {
throw new Error(tagMessage('Event handler context is not defined'));
}

runCallback(CALLBACK_TYPE.UPDATE, handlers, event);

context.lastUpdateEvent = eventWithData;
}

export function handleTouchEvent<THandlerData>(
event: GestureTouchEvent,
handlers: GestureCallbacks<THandlerData>
) {
'worklet';

if (event.eventType !== TouchEventType.UNDETERMINED) {
runCallback(touchEventTypeToCallbackType(event.eventType), handlers, event);
}
}

export function eventHandler<THandlerData>(
handlerTag: number,
sourceEvent: GestureHandlerEventWithHandlerData<THandlerData>,
handlers: GestureCallbacks<THandlerData>,
changeEventCalculator: ChangeCalculatorType<THandlerData> | undefined,
jsContext: ReanimatedContext<THandlerData>,
dispatchesAnimatedEvents: boolean
) {
'worklet';
const eventWithData = maybeExtractNativeEvent(sourceEvent);

if (!isEventForHandlerWithTag(handlerTag, eventWithData)) {
return;
}

if ('oldState' in eventWithData && eventWithData.oldState !== undefined) {
handleStateChangeEvent(eventWithData, handlers, jsContext);
} else if ('allTouches' in eventWithData) {
handleTouchEvent(eventWithData, handlers);
} else if (!dispatchesAnimatedEvents) {
handleUpdateEvent(
eventWithData,
handlers,
changeEventCalculator,
jsContext
);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ReanimatedContext } from '../../../handlers/gestures/reanimatedWrapper';
import {
BaseGestureConfig,
GestureCallbacks,
GestureHandlerEventWithHandlerData,
} from '../../types';
import { useMemo } from 'react';
import { eventHandler } from './eventHandler';

export function useGestureEventHandler<THandlerData, TConfig>(
handlerTag: number,
handlers: GestureCallbacks<THandlerData>,
config: BaseGestureConfig<THandlerData, TConfig>
) {
const jsContext: ReanimatedContext<THandlerData> = useMemo(() => {
return {
lastUpdateEvent: undefined,
};
}, []);

return useMemo(() => {
return (event: GestureHandlerEventWithHandlerData<THandlerData>) => {
eventHandler(
handlerTag,
event,
handlers,
config.changeEventCalculator,
jsContext,
!!config.dispatchesAnimatedEvents
);
};
}, [
handlerTag,
handlers,
config.changeEventCalculator,
config.dispatchesAnimatedEvents,
jsContext,
]);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import {
Reanimated,
ReanimatedHandler,
Expand All @@ -7,53 +8,47 @@ import {
GestureCallbacks,
UnpackedGestureHandlerEventWithHandlerData,
} from '../../types';
import { getStateChangeHandler } from './stateChangeHandler';
import { getTouchEventHandler } from './touchEventHandler';
import { getUpdateHandler } from './updateHandler';
import { eventHandler } from './eventHandler';

const workletNOOP = () => {
'worklet';
// no-op
};

export function useReanimatedEventHandler<THandlerData>(
handlerTag: number,
handlers: GestureCallbacks<THandlerData>,
reanimatedHandler: ReanimatedHandler<THandlerData> | undefined,
changeEventCalculator: ChangeCalculatorType<THandlerData> | undefined
) {
// We don't want to call hooks conditionally, `useEvent` will be always called.
// The only difference is whether we will send events to Reanimated or not.
// The problem here is that if someone passes `Animated.event` as `onUpdate` prop,
// it won't be workletized and therefore `useHandler` will throw. In that case we override it to empty `worklet`.
if (!Reanimated?.isWorkletFunction(handlers.onUpdate)) {
handlers.onUpdate = () => {
'worklet';
// no-op
};
}

const stateChangeCallback = getStateChangeHandler(
handlerTag,
handlers,
reanimatedHandler?.context
);

const updateCallback = getUpdateHandler(
handlerTag,
handlers,
reanimatedHandler?.context,
changeEventCalculator
);
const workletizedHandlers = useMemo(() => {
// We don't want to call hooks conditionally, `useEvent` will be always called.
// The only difference is whether we will send events to Reanimated or not.
// The problem here is that if someone passes `Animated.event` as `onUpdate` prop,
// it won't be workletized and therefore `useHandler` will throw. In that case we override it to empty `worklet`.
if (!Reanimated?.isWorkletFunction(handlers.onUpdate)) {
return {
...handlers,
onUpdate: workletNOOP,
};
}

const touchCallback = getTouchEventHandler(handlerTag, handlers);
return handlers;
}, [handlers]);

const callback = (
event: UnpackedGestureHandlerEventWithHandlerData<THandlerData>
) => {
'worklet';
if ('oldState' in event && event.oldState !== undefined) {
stateChangeCallback(event);
} else if ('allTouches' in event) {
touchCallback(event);
} else {
updateCallback(event);
}
eventHandler(
handlerTag,
event,
workletizedHandlers,
changeEventCalculator,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
reanimatedHandler?.context!,
false
);
};

const reanimatedEvent = Reanimated?.useEvent(
Expand Down
23 changes: 4 additions & 19 deletions packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,11 @@ export function useGesture<THandlerData, TConfig>(

// TODO: Call only necessary hooks depending on which callbacks are defined (?)
const {
onGestureHandlerStateChange,
onGestureHandlerEvent,
onGestureHandlerTouchEvent,
onReanimatedEvent,
onGestureHandlerAnimatedEvent,
} = useGestureCallbacks(tag, config);

// This should never happen, but since we don't want to call hooks conditionally,
// we have to mark these as possibly undefined to make TypeScript happy.
if (
!onGestureHandlerStateChange ||
// If onUpdate is an AnimatedEvent, `onGestureHandlerEvent` will be undefined and vice versa.
(!onGestureHandlerEvent && !onGestureHandlerAnimatedEvent) ||
!onGestureHandlerTouchEvent
) {
throw new Error(tagMessage('Failed to create event handlers.'));
}

if (config.shouldUseReanimatedDetector && !onReanimatedEvent) {
throw new Error(tagMessage('Failed to create reanimated event handlers.'));
}
Expand Down Expand Up @@ -95,14 +82,14 @@ export function useGesture<THandlerData, TConfig>(
}, [tag, config, type]);

return useMemo(
() => ({
(): SingleGesture<THandlerData, TConfig> => ({
tag,
type,
config,
detectorCallbacks: {
onGestureHandlerStateChange,
onGestureHandlerEvent,
onGestureHandlerTouchEvent,
onGestureHandlerStateChange: onGestureHandlerEvent,
onGestureHandlerEvent: onGestureHandlerEvent,
onGestureHandlerTouchEvent: onGestureHandlerEvent,
onGestureHandlerAnimatedEvent,
// On web, we're triggering Reanimated callbacks ourselves, based on the type.
// To handle this properly, we need to provide all three callbacks, so we set
Expand All @@ -128,9 +115,7 @@ export function useGesture<THandlerData, TConfig>(
tag,
type,
config,
onGestureHandlerStateChange,
onGestureHandlerEvent,
onGestureHandlerTouchEvent,
onReanimatedEvent,
onGestureHandlerAnimatedEvent,
gestureRelations,
Expand Down
Loading
Loading