Skip to content

Commit 7172bbf

Browse files
committed
V3 example
1 parent 31ba4b8 commit 7172bbf

2 files changed

Lines changed: 329 additions & 0 deletions

File tree

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
import React from 'react';
2+
import { StyleSheet, View, Image } from 'react-native';
3+
import Animated, {
4+
useAnimatedStyle,
5+
useSharedValue,
6+
} from 'react-native-reanimated';
7+
import {
8+
GestureDetector,
9+
useRotationGesture,
10+
usePinchGesture,
11+
usePanGesture,
12+
useTapGesture,
13+
useSimultaneousGestures,
14+
} from 'react-native-gesture-handler';
15+
import { useState } from 'react';
16+
17+
// @ts-ignore it's an image
18+
import SIGNET from '../../../ListWithHeader/signet.png';
19+
20+
function identity4() {
21+
'worklet';
22+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
23+
}
24+
25+
function multiply4(a: number[], b: number[]) {
26+
'worklet';
27+
return [
28+
a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12],
29+
a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13],
30+
a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14],
31+
a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15],
32+
a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12],
33+
a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13],
34+
a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14],
35+
a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15],
36+
a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12],
37+
a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13],
38+
a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14],
39+
a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15],
40+
a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12],
41+
a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13],
42+
a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14],
43+
a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15],
44+
];
45+
}
46+
47+
function scale4(sx: number, sy: number, sz: number) {
48+
'worklet';
49+
return [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1];
50+
}
51+
52+
function translate4(tx: number, ty: number, tz: number) {
53+
'worklet';
54+
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1];
55+
}
56+
57+
function rotate4(rad: number, x: number, y: number, z: number) {
58+
'worklet';
59+
const len = Math.hypot(x, y, z);
60+
const c = Math.cos(rad);
61+
const s = Math.sin(rad);
62+
const t = 1 - c;
63+
x /= len;
64+
y /= len;
65+
z /= len;
66+
return [
67+
t * x * x + c,
68+
t * x * y - s * z,
69+
t * x * z + s * y,
70+
0,
71+
t * x * y + s * z,
72+
t * y * y + c,
73+
t * y * z - s * x,
74+
0,
75+
t * x * z - s * y,
76+
t * y * z + s * x,
77+
t * z * z + c,
78+
0,
79+
0,
80+
0,
81+
0,
82+
1,
83+
];
84+
}
85+
86+
function invert2(m: number[]) {
87+
'worklet';
88+
const a = m[0];
89+
const b = m[1];
90+
const c = m[2];
91+
const d = m[3];
92+
const det = a * d - b * c;
93+
94+
return [d / det, -b / det, -c / det, a / det];
95+
}
96+
97+
function toTransformedCoords(
98+
point: { x: number; y: number },
99+
matrix: number[]
100+
) {
101+
'worklet';
102+
const m2 = [matrix[0], matrix[1], matrix[4], matrix[5]];
103+
const inv = invert2(m2);
104+
const x = point.x;
105+
const y = point.y;
106+
const newX = inv[0] * x + inv[2] * y;
107+
const newY = inv[1] * x + inv[3] * y;
108+
109+
return { x: newX, y: newY };
110+
}
111+
112+
function createMatrix(
113+
translation: { x: number; y: number },
114+
scale: number,
115+
rotation: number,
116+
origin: { x: number; y: number }
117+
) {
118+
'worklet';
119+
let matrix = identity4();
120+
121+
if (scale !== 1) {
122+
matrix = multiply4(matrix, translate4(origin.x, origin.y, 0));
123+
matrix = multiply4(matrix, scale4(scale, scale, 1));
124+
matrix = multiply4(matrix, translate4(-origin.x, -origin.y, 0));
125+
}
126+
if (rotation !== 0) {
127+
matrix = multiply4(matrix, translate4(origin.x, origin.y, 0));
128+
matrix = multiply4(matrix, rotate4(-rotation, 0, 0, 1));
129+
matrix = multiply4(matrix, translate4(-origin.x, -origin.y, 0));
130+
}
131+
132+
if (translation.x !== 0 || translation.y !== 0) {
133+
matrix = multiply4(matrix, translate4(translation.x, translation.y, 0));
134+
}
135+
136+
return matrix;
137+
}
138+
139+
function applyTransformations(
140+
translation: { x: number; y: number },
141+
scale: number,
142+
rotation: number,
143+
origin: { x: number; y: number },
144+
matrix: number[]
145+
) {
146+
'worklet';
147+
const translationInViewCoords = toTransformedCoords(translation, matrix);
148+
const transform = createMatrix(
149+
translationInViewCoords,
150+
scale,
151+
rotation,
152+
origin
153+
);
154+
return multiply4(transform, matrix);
155+
}
156+
157+
function Photo() {
158+
const [size, setSize] = useState({ width: 0, height: 0 });
159+
const translation = useSharedValue({ x: 0, y: 0 });
160+
const origin = useSharedValue({ x: 0, y: 0 });
161+
const scale = useSharedValue(1);
162+
const rotation = useSharedValue(0);
163+
const isRotating = useSharedValue(false);
164+
const isScaling = useSharedValue(false);
165+
166+
const transform = useSharedValue(identity4());
167+
168+
const style = useAnimatedStyle(() => {
169+
const matrix = applyTransformations(
170+
translation.value,
171+
scale.value,
172+
rotation.value,
173+
origin.value,
174+
transform.value
175+
);
176+
177+
return {
178+
transform: [
179+
{ translateX: matrix[12] },
180+
{ translateY: matrix[13] },
181+
{ scale: Math.hypot(matrix[0], matrix[1]) },
182+
{ rotateZ: `${Math.atan2(matrix[1], matrix[0])}rad` },
183+
],
184+
};
185+
});
186+
187+
const rotationGesture = useRotationGesture({
188+
onActivate: (e) => {
189+
if (!isRotating.value && !isScaling.value) {
190+
origin.value = {
191+
x: -(e.anchorX - size.width / 2),
192+
y: -(e.anchorY - size.height / 2),
193+
};
194+
}
195+
isRotating.value = true;
196+
},
197+
onUpdate: (e) => {
198+
rotation.value += e.rotationChange;
199+
},
200+
onDeactivate: () => {
201+
transform.value = applyTransformations(
202+
translation.value,
203+
scale.value,
204+
rotation.value,
205+
origin.value,
206+
transform.value
207+
);
208+
209+
rotation.value = 0;
210+
translation.value = { x: 0, y: 0 };
211+
scale.value = 1;
212+
isRotating.value = false;
213+
},
214+
});
215+
216+
const scaleGesture = usePinchGesture({
217+
onActivate: (e) => {
218+
if (!isRotating.value && !isScaling.value) {
219+
origin.value = {
220+
x: -(e.focalX - size.width / 2),
221+
y: -(e.focalY - size.height / 2),
222+
};
223+
}
224+
isScaling.value = true;
225+
},
226+
onUpdate: (e) => {
227+
scale.value *= e.scaleChange;
228+
},
229+
onDeactivate: () => {
230+
transform.value = applyTransformations(
231+
translation.value,
232+
scale.value,
233+
rotation.value,
234+
origin.value,
235+
transform.value
236+
);
237+
rotation.value = 0;
238+
translation.value = { x: 0, y: 0 };
239+
scale.value = 1;
240+
isScaling.value = false;
241+
},
242+
});
243+
244+
const panGesture = usePanGesture({
245+
averageTouches: true,
246+
onUpdate: (e) => {
247+
translation.value = {
248+
x: translation.value.x + e.changeX,
249+
y: translation.value.y + e.changeY,
250+
};
251+
},
252+
onDeactivate: () => {
253+
transform.value = applyTransformations(
254+
translation.value,
255+
scale.value,
256+
rotation.value,
257+
origin.value,
258+
transform.value
259+
);
260+
261+
rotation.value = 0;
262+
translation.value = { x: 0, y: 0 };
263+
scale.value = 1;
264+
},
265+
});
266+
267+
const doubleTapGesture = useTapGesture({
268+
numberOfTaps: 2,
269+
onDeactivate: () => {
270+
scale.value *= 1.25;
271+
},
272+
});
273+
274+
const gesture = useSimultaneousGestures(
275+
rotationGesture,
276+
scaleGesture,
277+
panGesture,
278+
doubleTapGesture
279+
);
280+
281+
return (
282+
<GestureDetector gesture={gesture}>
283+
<Animated.View
284+
onLayout={({ nativeEvent }) => {
285+
setSize({
286+
width: nativeEvent.layout.width,
287+
height: nativeEvent.layout.height,
288+
});
289+
}}
290+
style={[styles.container, style]}>
291+
<Image source={SIGNET} style={styles.image} resizeMode="contain" />
292+
</Animated.View>
293+
</GestureDetector>
294+
);
295+
}
296+
297+
export default function Example() {
298+
return (
299+
<View style={styles.home}>
300+
<Photo />
301+
</View>
302+
);
303+
}
304+
305+
const styles = StyleSheet.create({
306+
home: {
307+
flex: 1,
308+
justifyContent: 'center',
309+
alignItems: 'center',
310+
},
311+
container: {
312+
width: 240,
313+
height: 240,
314+
backgroundColor: '#eef0ff',
315+
padding: 16,
316+
elevation: 8,
317+
borderRadius: 48,
318+
shadowColor: '#000',
319+
shadowOffset: { width: 0, height: 2 },
320+
shadowOpacity: 0.3,
321+
shadowRadius: 4,
322+
},
323+
image: {
324+
width: 208,
325+
height: 208,
326+
},
327+
});

apps/common-app/src/new_api/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import TimerExample from './showcase/timer';
1010
import CameraExample from './complicated/camera';
1111
import ChatHeadsExample from './complicated/chat_heads';
1212
import LockExample from './complicated/lock';
13+
import TransformationsExample from './complicated/transformations';
1314
import VelocityExample from './complicated/velocity_test';
1415

1516
import ContextMenuExample from './hover_mouse/context_menu';
@@ -99,6 +100,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [
99100
{ name: 'Velocity Test', component: VelocityExample },
100101
{ name: 'Chat Heads', component: ChatHeadsExample },
101102
{ name: 'Camera', component: CameraExample },
103+
{ name: 'Transformations', component: TransformationsExample },
102104
],
103105
},
104106

0 commit comments

Comments
 (0)