/
canvas.tsx
120 lines (103 loc) · 3.29 KB
/
canvas.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { Canvas as SkiaCanvas } from '@shopify/react-native-skia';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Gesture,
GestureDetector,
PanGesture,
} from 'react-native-gesture-handler';
import Animated, { useSharedValue } from 'react-native-reanimated';
import {
TouchHandlerContext,
type TouchableHandlerContextType,
} from './context';
import type { CanvasProps } from '@shopify/react-native-skia';
type TouchableCanvasProps = CanvasProps & {
panGesture?: PanGesture;
timeoutBeforeCollectingRefs?: number; // default 100
};
const AnimatedSkiaCanvas = Animated.createAnimatedComponent(SkiaCanvas);
const Canvas: React.FC<TouchableCanvasProps> = ({
children,
panGesture = Gesture.Pan(),
timeoutBeforeCollectingRefs = 100,
...props
}) => {
// Instead of value, provide a subscribe method and reload the refs
const touchableRefs: TouchableHandlerContextType = useMemo(() => {
return { value: {} };
}, []);
const activeKey = useSharedValue<string[]>([]);
// This must be improved, it's a hack to wait for the refs to be loaded
const [loadedRefs, prepareLoadedRefs] = useState<
TouchableHandlerContextType['value']
>({});
const ref = useRef<NodeJS.Timeout>();
useEffect(() => {
ref.current = setTimeout(() => {
prepareLoadedRefs(touchableRefs.value);
}, timeoutBeforeCollectingRefs);
return () => {
clearTimeout(ref.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timeoutBeforeCollectingRefs]);
const mainGesture = panGesture
.onBegin((event) => {
const keys = Object.keys(loadedRefs);
for (let i = 0; i < keys.length; i++) {
const key = keys[i] as string;
const touchableItem = loadedRefs[key];
const isPointInPath = touchableItem?.isPointInPath(event);
if (isPointInPath && touchableItem?.onStart) {
activeKey.value.push(`${key}__${event.handlerTag}`);
touchableItem.onStart?.(event);
}
}
})
.onUpdate((event) => {
const activatedKey = activeKey.value.find((key) =>
key.includes(event.handlerTag.toString())
);
if (!activatedKey) {
return;
}
const indexedKey = activatedKey.split('__')?.[0];
if (!indexedKey) {
return;
}
const touchableItem = loadedRefs[indexedKey];
return touchableItem?.onActive?.(event);
})
.onFinalize((event) => {
const activatedKey = activeKey.value.find((key) =>
key.includes(event.handlerTag.toString())
);
if (!activatedKey) {
return;
}
const indexedKey = activatedKey.split('__')?.[0];
if (!indexedKey) {
return;
}
const touchableItem = loadedRefs[indexedKey];
activeKey.value = activeKey.value.filter(
(key) => !key.includes(event.handlerTag.toString())
);
return touchableItem?.onEnd?.(event as any);
});
useEffect(() => {
return () => {
touchableRefs.value = {};
};
}, [touchableRefs]);
return (
<GestureDetector gesture={mainGesture}>
<AnimatedSkiaCanvas {...props}>
<TouchHandlerContext.Provider value={touchableRefs}>
{children}
</TouchHandlerContext.Provider>
</AnimatedSkiaCanvas>
</GestureDetector>
);
};
export { Canvas };