@motion-core/motion-gpu 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/dist/core/pointer.d.ts +96 -0
- package/dist/core/pointer.d.ts.map +1 -0
- package/dist/core/pointer.js +71 -0
- package/dist/core/pointer.js.map +1 -0
- package/dist/motion-gpu.css +295 -0
- package/dist/react/advanced.js +2 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/use-pointer.d.ts +94 -0
- package/dist/react/use-pointer.d.ts.map +1 -0
- package/dist/react/use-pointer.js +285 -0
- package/dist/react/use-pointer.js.map +1 -0
- package/dist/svelte/advanced.js +2 -1
- package/dist/svelte/index.d.ts +2 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +2 -1
- package/dist/svelte/use-pointer.d.ts +94 -0
- package/dist/svelte/use-pointer.d.ts.map +1 -0
- package/dist/svelte/use-pointer.js +292 -0
- package/dist/svelte/use-pointer.js.map +1 -0
- package/dist/vue/FragCanvas.js +8 -0
- package/dist/vue/FragCanvas.js.map +1 -0
- package/dist/vue/FragCanvas.vue.d.ts +49 -0
- package/dist/vue/FragCanvas.vue.d.ts.map +1 -0
- package/dist/vue/FragCanvas.vue_vue_type_script_setup_true_lang.js +228 -0
- package/dist/vue/FragCanvas.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/vue/MotionGPUErrorOverlay.js +8 -0
- package/dist/vue/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/vue/MotionGPUErrorOverlay.vue.d.ts +8 -0
- package/dist/vue/MotionGPUErrorOverlay.vue.d.ts.map +1 -0
- package/dist/vue/MotionGPUErrorOverlay.vue_vue_type_script_setup_true_lang.js +166 -0
- package/dist/vue/MotionGPUErrorOverlay.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/vue/Portal.js +7 -0
- package/dist/vue/Portal.js.map +1 -0
- package/dist/vue/Portal.vue.d.ts +18 -0
- package/dist/vue/Portal.vue.d.ts.map +1 -0
- package/dist/vue/Portal.vue_vue_type_script_setup_true_lang.js +29 -0
- package/dist/vue/Portal.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/vue/advanced.d.ts +12 -0
- package/dist/vue/advanced.d.ts.map +1 -0
- package/dist/vue/advanced.js +15 -0
- package/dist/vue/frame-context.d.ts +22 -0
- package/dist/vue/frame-context.d.ts.map +1 -0
- package/dist/vue/frame-context.js +38 -0
- package/dist/vue/frame-context.js.map +1 -0
- package/dist/vue/index.d.ts +21 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +14 -0
- package/dist/vue/motiongpu-context.d.ts +81 -0
- package/dist/vue/motiongpu-context.d.ts.map +1 -0
- package/dist/vue/motiongpu-context.js +29 -0
- package/dist/vue/motiongpu-context.js.map +1 -0
- package/dist/vue/shims-vue.d.js +0 -0
- package/dist/vue/use-motiongpu-user-context.d.ts +44 -0
- package/dist/vue/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/vue/use-motiongpu-user-context.js +76 -0
- package/dist/vue/use-motiongpu-user-context.js.map +1 -0
- package/dist/vue/use-pointer.d.ts +94 -0
- package/dist/vue/use-pointer.d.ts.map +1 -0
- package/dist/vue/use-pointer.js +298 -0
- package/dist/vue/use-pointer.js.map +1 -0
- package/dist/vue/use-texture.d.ts +45 -0
- package/dist/vue/use-texture.d.ts.map +1 -0
- package/dist/vue/use-texture.js +135 -0
- package/dist/vue/use-texture.js.map +1 -0
- package/package.json +25 -7
- package/src/lib/core/pointer.ts +177 -0
- package/src/lib/react/index.ts +10 -0
- package/src/lib/react/use-pointer.ts +515 -0
- package/src/lib/svelte/index.ts +10 -0
- package/src/lib/svelte/use-pointer.ts +507 -0
- package/src/lib/vue/FragCanvas.vue +294 -0
- package/src/lib/vue/MotionGPUErrorOverlay.vue +518 -0
- package/src/lib/vue/Portal.vue +46 -0
- package/src/lib/vue/advanced.ts +32 -0
- package/src/lib/vue/frame-context.ts +96 -0
- package/src/lib/vue/index.ts +78 -0
- package/src/lib/vue/motiongpu-context.ts +97 -0
- package/src/lib/vue/shims-vue.d.ts +6 -0
- package/src/lib/vue/use-motiongpu-user-context.ts +145 -0
- package/src/lib/vue/use-pointer.ts +514 -0
- package/src/lib/vue/use-texture.ts +232 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { onMount } from 'svelte';
|
|
2
|
+
import {
|
|
3
|
+
createCurrentWritable as currentWritable,
|
|
4
|
+
type CurrentReadable
|
|
5
|
+
} from '../core/current-value.js';
|
|
6
|
+
import {
|
|
7
|
+
createInitialPointerState,
|
|
8
|
+
getPointerCoordinates,
|
|
9
|
+
getPointerNowSeconds,
|
|
10
|
+
normalizePointerKind,
|
|
11
|
+
resolvePointerFrameRequestMode,
|
|
12
|
+
type PointerClick,
|
|
13
|
+
type PointerFrameRequestMode,
|
|
14
|
+
type PointerState,
|
|
15
|
+
type PointerVec2
|
|
16
|
+
} from '../core/pointer.js';
|
|
17
|
+
import { useMotionGPU } from './motiongpu-context.js';
|
|
18
|
+
|
|
19
|
+
export type {
|
|
20
|
+
PointerClick,
|
|
21
|
+
PointerFrameRequestMode,
|
|
22
|
+
PointerKind,
|
|
23
|
+
PointerPoint,
|
|
24
|
+
PointerState
|
|
25
|
+
} from '../core/pointer.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for pointer input handling in `usePointer`.
|
|
29
|
+
*/
|
|
30
|
+
export interface UsePointerOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Enables pointer listeners.
|
|
33
|
+
*
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Frame wake-up strategy for pointer-driven state changes.
|
|
39
|
+
*
|
|
40
|
+
* @default 'auto'
|
|
41
|
+
*/
|
|
42
|
+
requestFrame?: PointerFrameRequestMode;
|
|
43
|
+
/**
|
|
44
|
+
* Requests pointer capture on pointer down.
|
|
45
|
+
*
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
capturePointer?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Tracks pointer move/up outside canvas while pointer is pressed.
|
|
51
|
+
*
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
trackWhilePressedOutsideCanvas?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Enables click/tap synthesis on pointer up.
|
|
57
|
+
*
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
clickEnabled?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Maximum press duration to consider pointer up a click (milliseconds).
|
|
63
|
+
*
|
|
64
|
+
* @default 350
|
|
65
|
+
*/
|
|
66
|
+
clickMaxDurationMs?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Maximum pointer travel from down to up to consider pointer up a click (pixels).
|
|
69
|
+
*
|
|
70
|
+
* @default 8
|
|
71
|
+
*/
|
|
72
|
+
clickMaxMovePx?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Allowed pointer buttons for click synthesis.
|
|
75
|
+
*
|
|
76
|
+
* @default [0]
|
|
77
|
+
*/
|
|
78
|
+
clickButtons?: number[];
|
|
79
|
+
/**
|
|
80
|
+
* Called after pointer move state update.
|
|
81
|
+
*/
|
|
82
|
+
onMove?: (state: PointerState, event: PointerEvent) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Called after pointer down state update.
|
|
85
|
+
*/
|
|
86
|
+
onDown?: (state: PointerState, event: PointerEvent) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Called after pointer up/cancel state update.
|
|
89
|
+
*/
|
|
90
|
+
onUp?: (state: PointerState, event: PointerEvent) => void;
|
|
91
|
+
/**
|
|
92
|
+
* Called when click/tap is synthesized.
|
|
93
|
+
*/
|
|
94
|
+
onClick?: (click: PointerClick, state: PointerState, event: PointerEvent) => void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reactive state returned by `usePointer`.
|
|
99
|
+
*/
|
|
100
|
+
export interface UsePointerResult {
|
|
101
|
+
/**
|
|
102
|
+
* Current pointer state.
|
|
103
|
+
*/
|
|
104
|
+
state: CurrentReadable<PointerState>;
|
|
105
|
+
/**
|
|
106
|
+
* Last synthesized click/tap event.
|
|
107
|
+
*/
|
|
108
|
+
lastClick: CurrentReadable<PointerClick | null>;
|
|
109
|
+
/**
|
|
110
|
+
* Clears last click snapshot.
|
|
111
|
+
*/
|
|
112
|
+
resetClick: () => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface PointerDownSnapshot {
|
|
116
|
+
button: number;
|
|
117
|
+
inside: boolean;
|
|
118
|
+
pointerId: number;
|
|
119
|
+
pointerType: 'mouse' | 'pen' | 'touch';
|
|
120
|
+
px: PointerVec2;
|
|
121
|
+
timeMs: number;
|
|
122
|
+
uv: PointerVec2;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Normalizes click button configuration with a primary-button fallback.
|
|
127
|
+
*/
|
|
128
|
+
function normalizeClickButtons(buttons: number[] | undefined): Set<number> {
|
|
129
|
+
const source = buttons && buttons.length > 0 ? buttons : [0];
|
|
130
|
+
return new Set(source);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolves a valid click duration threshold in milliseconds.
|
|
135
|
+
*/
|
|
136
|
+
function resolveClickMaxDurationMs(value: number | undefined): number {
|
|
137
|
+
if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {
|
|
138
|
+
return 350;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolves a valid click travel threshold in pixels.
|
|
146
|
+
*/
|
|
147
|
+
function resolveClickMaxMovePx(value: number | undefined): number {
|
|
148
|
+
if (typeof value !== 'number' || Number.isNaN(value) || value < 0) {
|
|
149
|
+
return 8;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Tracks normalized pointer coordinates and click/tap snapshots for the active `FragCanvas`.
|
|
157
|
+
*/
|
|
158
|
+
export function usePointer(options: UsePointerOptions = {}): UsePointerResult {
|
|
159
|
+
const motiongpu = useMotionGPU();
|
|
160
|
+
const pointerState = currentWritable<PointerState>(createInitialPointerState());
|
|
161
|
+
const lastClick = currentWritable<PointerClick | null>(null);
|
|
162
|
+
const enabled = options.enabled ?? true;
|
|
163
|
+
const requestFrameMode = options.requestFrame ?? 'auto';
|
|
164
|
+
const capturePointer = options.capturePointer ?? true;
|
|
165
|
+
const trackOutside = options.trackWhilePressedOutsideCanvas ?? true;
|
|
166
|
+
const clickEnabled = options.clickEnabled ?? true;
|
|
167
|
+
const clickMaxDurationMs = resolveClickMaxDurationMs(options.clickMaxDurationMs);
|
|
168
|
+
const clickMaxMovePx = resolveClickMaxMovePx(options.clickMaxMovePx);
|
|
169
|
+
const clickButtons = normalizeClickButtons(options.clickButtons);
|
|
170
|
+
let activePointerId: number | null = null;
|
|
171
|
+
let downSnapshot: PointerDownSnapshot | null = null;
|
|
172
|
+
let clickCounter = 0;
|
|
173
|
+
let previousPx: PointerVec2 | null = null;
|
|
174
|
+
let previousUv: PointerVec2 | null = null;
|
|
175
|
+
let previousTimeSeconds = 0;
|
|
176
|
+
|
|
177
|
+
const requestFrame = (): void => {
|
|
178
|
+
const mode = resolvePointerFrameRequestMode(requestFrameMode, motiongpu.renderMode.current);
|
|
179
|
+
if (mode === 'invalidate') {
|
|
180
|
+
motiongpu.invalidate();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (mode === 'advance') {
|
|
184
|
+
motiongpu.advance();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Commits a full pointer state snapshot with computed delta and velocity vectors.
|
|
190
|
+
*/
|
|
191
|
+
const updatePointerState = (input: {
|
|
192
|
+
button: number | null;
|
|
193
|
+
buttons: number;
|
|
194
|
+
downPx: PointerVec2 | null;
|
|
195
|
+
downUv: PointerVec2 | null;
|
|
196
|
+
dragging: boolean;
|
|
197
|
+
inside: boolean;
|
|
198
|
+
pointerId: number | null;
|
|
199
|
+
pointerType: 'mouse' | 'pen' | 'touch' | null;
|
|
200
|
+
pressed: boolean;
|
|
201
|
+
point: {
|
|
202
|
+
ndc: PointerVec2;
|
|
203
|
+
px: PointerVec2;
|
|
204
|
+
uv: PointerVec2;
|
|
205
|
+
};
|
|
206
|
+
resetDelta?: boolean;
|
|
207
|
+
}): PointerState => {
|
|
208
|
+
const nowSeconds = getPointerNowSeconds();
|
|
209
|
+
const dt = previousTimeSeconds > 0 ? Math.max(nowSeconds - previousTimeSeconds, 1e-6) : 0;
|
|
210
|
+
const deltaPx: PointerVec2 =
|
|
211
|
+
input.resetDelta || !previousPx
|
|
212
|
+
? [0, 0]
|
|
213
|
+
: [input.point.px[0] - previousPx[0], input.point.px[1] - previousPx[1]];
|
|
214
|
+
const deltaUv: PointerVec2 =
|
|
215
|
+
input.resetDelta || !previousUv
|
|
216
|
+
? [0, 0]
|
|
217
|
+
: [input.point.uv[0] - previousUv[0], input.point.uv[1] - previousUv[1]];
|
|
218
|
+
const velocityPx: PointerVec2 = dt > 0 ? [deltaPx[0] / dt, deltaPx[1] / dt] : [0, 0];
|
|
219
|
+
const velocityUv: PointerVec2 = dt > 0 ? [deltaUv[0] / dt, deltaUv[1] / dt] : [0, 0];
|
|
220
|
+
const nextState: PointerState = {
|
|
221
|
+
px: input.point.px,
|
|
222
|
+
uv: input.point.uv,
|
|
223
|
+
ndc: input.point.ndc,
|
|
224
|
+
inside: input.inside,
|
|
225
|
+
pressed: input.pressed,
|
|
226
|
+
dragging: input.dragging,
|
|
227
|
+
pointerType: input.pointerType,
|
|
228
|
+
pointerId: input.pointerId,
|
|
229
|
+
button: input.button,
|
|
230
|
+
buttons: input.buttons,
|
|
231
|
+
time: nowSeconds,
|
|
232
|
+
downPx: input.downPx,
|
|
233
|
+
downUv: input.downUv,
|
|
234
|
+
deltaPx,
|
|
235
|
+
deltaUv,
|
|
236
|
+
velocityPx,
|
|
237
|
+
velocityUv
|
|
238
|
+
};
|
|
239
|
+
pointerState.set(nextState);
|
|
240
|
+
previousPx = input.point.px;
|
|
241
|
+
previousUv = input.point.uv;
|
|
242
|
+
previousTimeSeconds = nowSeconds;
|
|
243
|
+
requestFrame();
|
|
244
|
+
return nextState;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Updates only the `inside` flag while keeping the latest pointer coordinates.
|
|
249
|
+
*/
|
|
250
|
+
const updateInsideState = (inside: boolean): PointerState => {
|
|
251
|
+
const current = pointerState.current;
|
|
252
|
+
const nextState: PointerState = {
|
|
253
|
+
...current,
|
|
254
|
+
inside,
|
|
255
|
+
time: getPointerNowSeconds(),
|
|
256
|
+
deltaPx: [0, 0],
|
|
257
|
+
deltaUv: [0, 0],
|
|
258
|
+
velocityPx: [0, 0],
|
|
259
|
+
velocityUv: [0, 0]
|
|
260
|
+
};
|
|
261
|
+
pointerState.set(nextState);
|
|
262
|
+
requestFrame();
|
|
263
|
+
return nextState;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Checks whether an event belongs to the active tracked pointer.
|
|
268
|
+
*/
|
|
269
|
+
const isTrackedPointer = (event: PointerEvent): boolean =>
|
|
270
|
+
activePointerId === null || event.pointerId === activePointerId;
|
|
271
|
+
|
|
272
|
+
onMount(() => {
|
|
273
|
+
if (!enabled) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const canvas = motiongpu.canvas;
|
|
278
|
+
if (!canvas) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const handlePointerDown = (event: PointerEvent): void => {
|
|
283
|
+
const point = getPointerCoordinates(
|
|
284
|
+
event.clientX,
|
|
285
|
+
event.clientY,
|
|
286
|
+
canvas.getBoundingClientRect()
|
|
287
|
+
);
|
|
288
|
+
const pointerType = normalizePointerKind(event.pointerType);
|
|
289
|
+
activePointerId = event.pointerId;
|
|
290
|
+
downSnapshot = {
|
|
291
|
+
pointerId: event.pointerId,
|
|
292
|
+
pointerType,
|
|
293
|
+
button: event.button,
|
|
294
|
+
timeMs: getPointerNowSeconds() * 1000,
|
|
295
|
+
px: point.px,
|
|
296
|
+
uv: point.uv,
|
|
297
|
+
inside: point.inside
|
|
298
|
+
};
|
|
299
|
+
if (capturePointer) {
|
|
300
|
+
try {
|
|
301
|
+
canvas.setPointerCapture(event.pointerId);
|
|
302
|
+
} catch {
|
|
303
|
+
// Browser rejected capture (e.g. unsupported pointer state).
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const nextState = updatePointerState({
|
|
307
|
+
point,
|
|
308
|
+
inside: point.inside,
|
|
309
|
+
pressed: true,
|
|
310
|
+
dragging: false,
|
|
311
|
+
pointerType,
|
|
312
|
+
pointerId: event.pointerId,
|
|
313
|
+
button: event.button,
|
|
314
|
+
buttons: event.buttons,
|
|
315
|
+
downPx: point.px,
|
|
316
|
+
downUv: point.uv,
|
|
317
|
+
resetDelta: true
|
|
318
|
+
});
|
|
319
|
+
options.onDown?.(nextState, event);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handleMove = (event: PointerEvent): void => {
|
|
323
|
+
if (!isTrackedPointer(event)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const point = getPointerCoordinates(
|
|
328
|
+
event.clientX,
|
|
329
|
+
event.clientY,
|
|
330
|
+
canvas.getBoundingClientRect()
|
|
331
|
+
);
|
|
332
|
+
const pressed = activePointerId !== null && event.pointerId === activePointerId;
|
|
333
|
+
const downPx = pressed ? (downSnapshot?.px ?? point.px) : null;
|
|
334
|
+
const downUv = pressed ? (downSnapshot?.uv ?? point.uv) : null;
|
|
335
|
+
let dragging = false;
|
|
336
|
+
if (pressed && downPx) {
|
|
337
|
+
const dx = point.px[0] - downPx[0];
|
|
338
|
+
const dy = point.px[1] - downPx[1];
|
|
339
|
+
dragging = Math.hypot(dx, dy) > 0;
|
|
340
|
+
}
|
|
341
|
+
const nextState = updatePointerState({
|
|
342
|
+
point,
|
|
343
|
+
inside: point.inside,
|
|
344
|
+
pressed,
|
|
345
|
+
dragging,
|
|
346
|
+
pointerType: normalizePointerKind(event.pointerType),
|
|
347
|
+
pointerId: event.pointerId,
|
|
348
|
+
button: pressed ? (downSnapshot?.button ?? event.button) : null,
|
|
349
|
+
buttons: event.buttons,
|
|
350
|
+
downPx,
|
|
351
|
+
downUv
|
|
352
|
+
});
|
|
353
|
+
options.onMove?.(nextState, event);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const handleWindowMove = (event: PointerEvent): void => {
|
|
357
|
+
if (!trackOutside || activePointerId === null || event.pointerId !== activePointerId) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const point = getPointerCoordinates(
|
|
362
|
+
event.clientX,
|
|
363
|
+
event.clientY,
|
|
364
|
+
canvas.getBoundingClientRect()
|
|
365
|
+
);
|
|
366
|
+
if (point.inside) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const downPx = downSnapshot?.px ?? point.px;
|
|
370
|
+
const downUv = downSnapshot?.uv ?? point.uv;
|
|
371
|
+
const dx = point.px[0] - downPx[0];
|
|
372
|
+
const dy = point.px[1] - downPx[1];
|
|
373
|
+
const nextState = updatePointerState({
|
|
374
|
+
point,
|
|
375
|
+
inside: false,
|
|
376
|
+
pressed: true,
|
|
377
|
+
dragging: Math.hypot(dx, dy) > 0,
|
|
378
|
+
pointerType: downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType),
|
|
379
|
+
pointerId: event.pointerId,
|
|
380
|
+
button: downSnapshot?.button ?? event.button,
|
|
381
|
+
buttons: event.buttons,
|
|
382
|
+
downPx,
|
|
383
|
+
downUv
|
|
384
|
+
});
|
|
385
|
+
options.onMove?.(nextState, event);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const releasePointer = (event: PointerEvent, emitClick: boolean): void => {
|
|
389
|
+
if (activePointerId === null || event.pointerId !== activePointerId) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const point = getPointerCoordinates(
|
|
394
|
+
event.clientX,
|
|
395
|
+
event.clientY,
|
|
396
|
+
canvas.getBoundingClientRect()
|
|
397
|
+
);
|
|
398
|
+
const pointerType = downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType);
|
|
399
|
+
const previous = downSnapshot;
|
|
400
|
+
const nextState = updatePointerState({
|
|
401
|
+
point,
|
|
402
|
+
inside: point.inside,
|
|
403
|
+
pressed: false,
|
|
404
|
+
dragging: false,
|
|
405
|
+
pointerType,
|
|
406
|
+
pointerId: null,
|
|
407
|
+
button: null,
|
|
408
|
+
buttons: event.buttons,
|
|
409
|
+
downPx: null,
|
|
410
|
+
downUv: null
|
|
411
|
+
});
|
|
412
|
+
options.onUp?.(nextState, event);
|
|
413
|
+
|
|
414
|
+
if (capturePointer && canvas.hasPointerCapture(event.pointerId)) {
|
|
415
|
+
try {
|
|
416
|
+
canvas.releasePointerCapture(event.pointerId);
|
|
417
|
+
} catch {
|
|
418
|
+
// Browser rejected release for this pointer id.
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (emitClick && clickEnabled && previous && clickButtons.has(previous.button)) {
|
|
423
|
+
const durationMs = getPointerNowSeconds() * 1000 - previous.timeMs;
|
|
424
|
+
const dx = point.px[0] - previous.px[0];
|
|
425
|
+
const dy = point.px[1] - previous.px[1];
|
|
426
|
+
const moveDistance = Math.hypot(dx, dy);
|
|
427
|
+
if (
|
|
428
|
+
previous.inside &&
|
|
429
|
+
point.inside &&
|
|
430
|
+
durationMs <= clickMaxDurationMs &&
|
|
431
|
+
moveDistance <= clickMaxMovePx
|
|
432
|
+
) {
|
|
433
|
+
clickCounter += 1;
|
|
434
|
+
const click: PointerClick = {
|
|
435
|
+
id: clickCounter,
|
|
436
|
+
time: getPointerNowSeconds(),
|
|
437
|
+
pointerType,
|
|
438
|
+
pointerId: event.pointerId,
|
|
439
|
+
button: previous.button,
|
|
440
|
+
modifiers: {
|
|
441
|
+
alt: event.altKey,
|
|
442
|
+
ctrl: event.ctrlKey,
|
|
443
|
+
shift: event.shiftKey,
|
|
444
|
+
meta: event.metaKey
|
|
445
|
+
},
|
|
446
|
+
px: point.px,
|
|
447
|
+
uv: point.uv,
|
|
448
|
+
ndc: point.ndc
|
|
449
|
+
};
|
|
450
|
+
lastClick.set(click);
|
|
451
|
+
options.onClick?.(click, nextState, event);
|
|
452
|
+
requestFrame();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
activePointerId = null;
|
|
457
|
+
downSnapshot = null;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const handlePointerUp = (event: PointerEvent): void => {
|
|
461
|
+
releasePointer(event, true);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const handlePointerCancel = (event: PointerEvent): void => {
|
|
465
|
+
releasePointer(event, false);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const handlePointerLeave = (): void => {
|
|
469
|
+
if (activePointerId !== null) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
updateInsideState(false);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
canvas.addEventListener('pointerdown', handlePointerDown);
|
|
476
|
+
canvas.addEventListener('pointermove', handleMove);
|
|
477
|
+
canvas.addEventListener('pointerup', handlePointerUp);
|
|
478
|
+
canvas.addEventListener('pointercancel', handlePointerCancel);
|
|
479
|
+
canvas.addEventListener('pointerleave', handlePointerLeave);
|
|
480
|
+
if (trackOutside) {
|
|
481
|
+
window.addEventListener('pointermove', handleWindowMove);
|
|
482
|
+
window.addEventListener('pointerup', handlePointerUp);
|
|
483
|
+
window.addEventListener('pointercancel', handlePointerCancel);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return () => {
|
|
487
|
+
canvas.removeEventListener('pointerdown', handlePointerDown);
|
|
488
|
+
canvas.removeEventListener('pointermove', handleMove);
|
|
489
|
+
canvas.removeEventListener('pointerup', handlePointerUp);
|
|
490
|
+
canvas.removeEventListener('pointercancel', handlePointerCancel);
|
|
491
|
+
canvas.removeEventListener('pointerleave', handlePointerLeave);
|
|
492
|
+
if (trackOutside) {
|
|
493
|
+
window.removeEventListener('pointermove', handleWindowMove);
|
|
494
|
+
window.removeEventListener('pointerup', handlePointerUp);
|
|
495
|
+
window.removeEventListener('pointercancel', handlePointerCancel);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
state: pointerState,
|
|
502
|
+
lastClick,
|
|
503
|
+
resetClick: () => {
|
|
504
|
+
lastClick.set(null);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|