@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,292 @@
|
|
|
1
|
+
import { createCurrentWritable } from "../core/current-value.js";
|
|
2
|
+
import { createInitialPointerState, getPointerCoordinates, getPointerNowSeconds, normalizePointerKind, resolvePointerFrameRequestMode } from "../core/pointer.js";
|
|
3
|
+
import { useMotionGPU } from "./motiongpu-context.js";
|
|
4
|
+
import { onMount } from "svelte";
|
|
5
|
+
//#region src/lib/svelte/use-pointer.ts
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes click button configuration with a primary-button fallback.
|
|
8
|
+
*/
|
|
9
|
+
function normalizeClickButtons(buttons) {
|
|
10
|
+
const source = buttons && buttons.length > 0 ? buttons : [0];
|
|
11
|
+
return new Set(source);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a valid click duration threshold in milliseconds.
|
|
15
|
+
*/
|
|
16
|
+
function resolveClickMaxDurationMs(value) {
|
|
17
|
+
if (typeof value !== "number" || Number.isNaN(value) || value <= 0) return 350;
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a valid click travel threshold in pixels.
|
|
22
|
+
*/
|
|
23
|
+
function resolveClickMaxMovePx(value) {
|
|
24
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) return 8;
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Tracks normalized pointer coordinates and click/tap snapshots for the active `FragCanvas`.
|
|
29
|
+
*/
|
|
30
|
+
function usePointer(options = {}) {
|
|
31
|
+
const motiongpu = useMotionGPU();
|
|
32
|
+
const pointerState = createCurrentWritable(createInitialPointerState());
|
|
33
|
+
const lastClick = createCurrentWritable(null);
|
|
34
|
+
const enabled = options.enabled ?? true;
|
|
35
|
+
const requestFrameMode = options.requestFrame ?? "auto";
|
|
36
|
+
const capturePointer = options.capturePointer ?? true;
|
|
37
|
+
const trackOutside = options.trackWhilePressedOutsideCanvas ?? true;
|
|
38
|
+
const clickEnabled = options.clickEnabled ?? true;
|
|
39
|
+
const clickMaxDurationMs = resolveClickMaxDurationMs(options.clickMaxDurationMs);
|
|
40
|
+
const clickMaxMovePx = resolveClickMaxMovePx(options.clickMaxMovePx);
|
|
41
|
+
const clickButtons = normalizeClickButtons(options.clickButtons);
|
|
42
|
+
let activePointerId = null;
|
|
43
|
+
let downSnapshot = null;
|
|
44
|
+
let clickCounter = 0;
|
|
45
|
+
let previousPx = null;
|
|
46
|
+
let previousUv = null;
|
|
47
|
+
let previousTimeSeconds = 0;
|
|
48
|
+
const requestFrame = () => {
|
|
49
|
+
const mode = resolvePointerFrameRequestMode(requestFrameMode, motiongpu.renderMode.current);
|
|
50
|
+
if (mode === "invalidate") {
|
|
51
|
+
motiongpu.invalidate();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (mode === "advance") motiongpu.advance();
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Commits a full pointer state snapshot with computed delta and velocity vectors.
|
|
58
|
+
*/
|
|
59
|
+
const updatePointerState = (input) => {
|
|
60
|
+
const nowSeconds = getPointerNowSeconds();
|
|
61
|
+
const dt = previousTimeSeconds > 0 ? Math.max(nowSeconds - previousTimeSeconds, 1e-6) : 0;
|
|
62
|
+
const deltaPx = input.resetDelta || !previousPx ? [0, 0] : [input.point.px[0] - previousPx[0], input.point.px[1] - previousPx[1]];
|
|
63
|
+
const deltaUv = input.resetDelta || !previousUv ? [0, 0] : [input.point.uv[0] - previousUv[0], input.point.uv[1] - previousUv[1]];
|
|
64
|
+
const velocityPx = dt > 0 ? [deltaPx[0] / dt, deltaPx[1] / dt] : [0, 0];
|
|
65
|
+
const velocityUv = dt > 0 ? [deltaUv[0] / dt, deltaUv[1] / dt] : [0, 0];
|
|
66
|
+
const nextState = {
|
|
67
|
+
px: input.point.px,
|
|
68
|
+
uv: input.point.uv,
|
|
69
|
+
ndc: input.point.ndc,
|
|
70
|
+
inside: input.inside,
|
|
71
|
+
pressed: input.pressed,
|
|
72
|
+
dragging: input.dragging,
|
|
73
|
+
pointerType: input.pointerType,
|
|
74
|
+
pointerId: input.pointerId,
|
|
75
|
+
button: input.button,
|
|
76
|
+
buttons: input.buttons,
|
|
77
|
+
time: nowSeconds,
|
|
78
|
+
downPx: input.downPx,
|
|
79
|
+
downUv: input.downUv,
|
|
80
|
+
deltaPx,
|
|
81
|
+
deltaUv,
|
|
82
|
+
velocityPx,
|
|
83
|
+
velocityUv
|
|
84
|
+
};
|
|
85
|
+
pointerState.set(nextState);
|
|
86
|
+
previousPx = input.point.px;
|
|
87
|
+
previousUv = input.point.uv;
|
|
88
|
+
previousTimeSeconds = nowSeconds;
|
|
89
|
+
requestFrame();
|
|
90
|
+
return nextState;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Updates only the `inside` flag while keeping the latest pointer coordinates.
|
|
94
|
+
*/
|
|
95
|
+
const updateInsideState = (inside) => {
|
|
96
|
+
const nextState = {
|
|
97
|
+
...pointerState.current,
|
|
98
|
+
inside,
|
|
99
|
+
time: getPointerNowSeconds(),
|
|
100
|
+
deltaPx: [0, 0],
|
|
101
|
+
deltaUv: [0, 0],
|
|
102
|
+
velocityPx: [0, 0],
|
|
103
|
+
velocityUv: [0, 0]
|
|
104
|
+
};
|
|
105
|
+
pointerState.set(nextState);
|
|
106
|
+
requestFrame();
|
|
107
|
+
return nextState;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Checks whether an event belongs to the active tracked pointer.
|
|
111
|
+
*/
|
|
112
|
+
const isTrackedPointer = (event) => activePointerId === null || event.pointerId === activePointerId;
|
|
113
|
+
onMount(() => {
|
|
114
|
+
if (!enabled) return;
|
|
115
|
+
const canvas = motiongpu.canvas;
|
|
116
|
+
if (!canvas) return;
|
|
117
|
+
const handlePointerDown = (event) => {
|
|
118
|
+
const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
|
|
119
|
+
const pointerType = normalizePointerKind(event.pointerType);
|
|
120
|
+
activePointerId = event.pointerId;
|
|
121
|
+
downSnapshot = {
|
|
122
|
+
pointerId: event.pointerId,
|
|
123
|
+
pointerType,
|
|
124
|
+
button: event.button,
|
|
125
|
+
timeMs: getPointerNowSeconds() * 1e3,
|
|
126
|
+
px: point.px,
|
|
127
|
+
uv: point.uv,
|
|
128
|
+
inside: point.inside
|
|
129
|
+
};
|
|
130
|
+
if (capturePointer) try {
|
|
131
|
+
canvas.setPointerCapture(event.pointerId);
|
|
132
|
+
} catch {}
|
|
133
|
+
const nextState = updatePointerState({
|
|
134
|
+
point,
|
|
135
|
+
inside: point.inside,
|
|
136
|
+
pressed: true,
|
|
137
|
+
dragging: false,
|
|
138
|
+
pointerType,
|
|
139
|
+
pointerId: event.pointerId,
|
|
140
|
+
button: event.button,
|
|
141
|
+
buttons: event.buttons,
|
|
142
|
+
downPx: point.px,
|
|
143
|
+
downUv: point.uv,
|
|
144
|
+
resetDelta: true
|
|
145
|
+
});
|
|
146
|
+
options.onDown?.(nextState, event);
|
|
147
|
+
};
|
|
148
|
+
const handleMove = (event) => {
|
|
149
|
+
if (!isTrackedPointer(event)) return;
|
|
150
|
+
const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
|
|
151
|
+
const pressed = activePointerId !== null && event.pointerId === activePointerId;
|
|
152
|
+
const downPx = pressed ? downSnapshot?.px ?? point.px : null;
|
|
153
|
+
const downUv = pressed ? downSnapshot?.uv ?? point.uv : null;
|
|
154
|
+
let dragging = false;
|
|
155
|
+
if (pressed && downPx) {
|
|
156
|
+
const dx = point.px[0] - downPx[0];
|
|
157
|
+
const dy = point.px[1] - downPx[1];
|
|
158
|
+
dragging = Math.hypot(dx, dy) > 0;
|
|
159
|
+
}
|
|
160
|
+
const nextState = updatePointerState({
|
|
161
|
+
point,
|
|
162
|
+
inside: point.inside,
|
|
163
|
+
pressed,
|
|
164
|
+
dragging,
|
|
165
|
+
pointerType: normalizePointerKind(event.pointerType),
|
|
166
|
+
pointerId: event.pointerId,
|
|
167
|
+
button: pressed ? downSnapshot?.button ?? event.button : null,
|
|
168
|
+
buttons: event.buttons,
|
|
169
|
+
downPx,
|
|
170
|
+
downUv
|
|
171
|
+
});
|
|
172
|
+
options.onMove?.(nextState, event);
|
|
173
|
+
};
|
|
174
|
+
const handleWindowMove = (event) => {
|
|
175
|
+
if (!trackOutside || activePointerId === null || event.pointerId !== activePointerId) return;
|
|
176
|
+
const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
|
|
177
|
+
if (point.inside) return;
|
|
178
|
+
const downPx = downSnapshot?.px ?? point.px;
|
|
179
|
+
const downUv = downSnapshot?.uv ?? point.uv;
|
|
180
|
+
const dx = point.px[0] - downPx[0];
|
|
181
|
+
const dy = point.px[1] - downPx[1];
|
|
182
|
+
const nextState = updatePointerState({
|
|
183
|
+
point,
|
|
184
|
+
inside: false,
|
|
185
|
+
pressed: true,
|
|
186
|
+
dragging: Math.hypot(dx, dy) > 0,
|
|
187
|
+
pointerType: downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType),
|
|
188
|
+
pointerId: event.pointerId,
|
|
189
|
+
button: downSnapshot?.button ?? event.button,
|
|
190
|
+
buttons: event.buttons,
|
|
191
|
+
downPx,
|
|
192
|
+
downUv
|
|
193
|
+
});
|
|
194
|
+
options.onMove?.(nextState, event);
|
|
195
|
+
};
|
|
196
|
+
const releasePointer = (event, emitClick) => {
|
|
197
|
+
if (activePointerId === null || event.pointerId !== activePointerId) return;
|
|
198
|
+
const point = getPointerCoordinates(event.clientX, event.clientY, canvas.getBoundingClientRect());
|
|
199
|
+
const pointerType = downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType);
|
|
200
|
+
const previous = downSnapshot;
|
|
201
|
+
const nextState = updatePointerState({
|
|
202
|
+
point,
|
|
203
|
+
inside: point.inside,
|
|
204
|
+
pressed: false,
|
|
205
|
+
dragging: false,
|
|
206
|
+
pointerType,
|
|
207
|
+
pointerId: null,
|
|
208
|
+
button: null,
|
|
209
|
+
buttons: event.buttons,
|
|
210
|
+
downPx: null,
|
|
211
|
+
downUv: null
|
|
212
|
+
});
|
|
213
|
+
options.onUp?.(nextState, event);
|
|
214
|
+
if (capturePointer && canvas.hasPointerCapture(event.pointerId)) try {
|
|
215
|
+
canvas.releasePointerCapture(event.pointerId);
|
|
216
|
+
} catch {}
|
|
217
|
+
if (emitClick && clickEnabled && previous && clickButtons.has(previous.button)) {
|
|
218
|
+
const durationMs = getPointerNowSeconds() * 1e3 - previous.timeMs;
|
|
219
|
+
const dx = point.px[0] - previous.px[0];
|
|
220
|
+
const dy = point.px[1] - previous.px[1];
|
|
221
|
+
const moveDistance = Math.hypot(dx, dy);
|
|
222
|
+
if (previous.inside && point.inside && durationMs <= clickMaxDurationMs && moveDistance <= clickMaxMovePx) {
|
|
223
|
+
clickCounter += 1;
|
|
224
|
+
const click = {
|
|
225
|
+
id: clickCounter,
|
|
226
|
+
time: getPointerNowSeconds(),
|
|
227
|
+
pointerType,
|
|
228
|
+
pointerId: event.pointerId,
|
|
229
|
+
button: previous.button,
|
|
230
|
+
modifiers: {
|
|
231
|
+
alt: event.altKey,
|
|
232
|
+
ctrl: event.ctrlKey,
|
|
233
|
+
shift: event.shiftKey,
|
|
234
|
+
meta: event.metaKey
|
|
235
|
+
},
|
|
236
|
+
px: point.px,
|
|
237
|
+
uv: point.uv,
|
|
238
|
+
ndc: point.ndc
|
|
239
|
+
};
|
|
240
|
+
lastClick.set(click);
|
|
241
|
+
options.onClick?.(click, nextState, event);
|
|
242
|
+
requestFrame();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
activePointerId = null;
|
|
246
|
+
downSnapshot = null;
|
|
247
|
+
};
|
|
248
|
+
const handlePointerUp = (event) => {
|
|
249
|
+
releasePointer(event, true);
|
|
250
|
+
};
|
|
251
|
+
const handlePointerCancel = (event) => {
|
|
252
|
+
releasePointer(event, false);
|
|
253
|
+
};
|
|
254
|
+
const handlePointerLeave = () => {
|
|
255
|
+
if (activePointerId !== null) return;
|
|
256
|
+
updateInsideState(false);
|
|
257
|
+
};
|
|
258
|
+
canvas.addEventListener("pointerdown", handlePointerDown);
|
|
259
|
+
canvas.addEventListener("pointermove", handleMove);
|
|
260
|
+
canvas.addEventListener("pointerup", handlePointerUp);
|
|
261
|
+
canvas.addEventListener("pointercancel", handlePointerCancel);
|
|
262
|
+
canvas.addEventListener("pointerleave", handlePointerLeave);
|
|
263
|
+
if (trackOutside) {
|
|
264
|
+
window.addEventListener("pointermove", handleWindowMove);
|
|
265
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
266
|
+
window.addEventListener("pointercancel", handlePointerCancel);
|
|
267
|
+
}
|
|
268
|
+
return () => {
|
|
269
|
+
canvas.removeEventListener("pointerdown", handlePointerDown);
|
|
270
|
+
canvas.removeEventListener("pointermove", handleMove);
|
|
271
|
+
canvas.removeEventListener("pointerup", handlePointerUp);
|
|
272
|
+
canvas.removeEventListener("pointercancel", handlePointerCancel);
|
|
273
|
+
canvas.removeEventListener("pointerleave", handlePointerLeave);
|
|
274
|
+
if (trackOutside) {
|
|
275
|
+
window.removeEventListener("pointermove", handleWindowMove);
|
|
276
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
277
|
+
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
state: pointerState,
|
|
283
|
+
lastClick,
|
|
284
|
+
resetClick: () => {
|
|
285
|
+
lastClick.set(null);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
export { usePointer };
|
|
291
|
+
|
|
292
|
+
//# sourceMappingURL=use-pointer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-pointer.js","names":[],"sources":["../../src/lib/svelte/use-pointer.ts"],"sourcesContent":["import { onMount } from 'svelte';\nimport {\n\tcreateCurrentWritable as currentWritable,\n\ttype CurrentReadable\n} from '../core/current-value.js';\nimport {\n\tcreateInitialPointerState,\n\tgetPointerCoordinates,\n\tgetPointerNowSeconds,\n\tnormalizePointerKind,\n\tresolvePointerFrameRequestMode,\n\ttype PointerClick,\n\ttype PointerFrameRequestMode,\n\ttype PointerState,\n\ttype PointerVec2\n} from '../core/pointer.js';\nimport { useMotionGPU } from './motiongpu-context.js';\n\nexport type {\n\tPointerClick,\n\tPointerFrameRequestMode,\n\tPointerKind,\n\tPointerPoint,\n\tPointerState\n} from '../core/pointer.js';\n\n/**\n * Configuration for pointer input handling in `usePointer`.\n */\nexport interface UsePointerOptions {\n\t/**\n\t * Enables pointer listeners.\n\t *\n\t * @default true\n\t */\n\tenabled?: boolean;\n\t/**\n\t * Frame wake-up strategy for pointer-driven state changes.\n\t *\n\t * @default 'auto'\n\t */\n\trequestFrame?: PointerFrameRequestMode;\n\t/**\n\t * Requests pointer capture on pointer down.\n\t *\n\t * @default true\n\t */\n\tcapturePointer?: boolean;\n\t/**\n\t * Tracks pointer move/up outside canvas while pointer is pressed.\n\t *\n\t * @default true\n\t */\n\ttrackWhilePressedOutsideCanvas?: boolean;\n\t/**\n\t * Enables click/tap synthesis on pointer up.\n\t *\n\t * @default true\n\t */\n\tclickEnabled?: boolean;\n\t/**\n\t * Maximum press duration to consider pointer up a click (milliseconds).\n\t *\n\t * @default 350\n\t */\n\tclickMaxDurationMs?: number;\n\t/**\n\t * Maximum pointer travel from down to up to consider pointer up a click (pixels).\n\t *\n\t * @default 8\n\t */\n\tclickMaxMovePx?: number;\n\t/**\n\t * Allowed pointer buttons for click synthesis.\n\t *\n\t * @default [0]\n\t */\n\tclickButtons?: number[];\n\t/**\n\t * Called after pointer move state update.\n\t */\n\tonMove?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called after pointer down state update.\n\t */\n\tonDown?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called after pointer up/cancel state update.\n\t */\n\tonUp?: (state: PointerState, event: PointerEvent) => void;\n\t/**\n\t * Called when click/tap is synthesized.\n\t */\n\tonClick?: (click: PointerClick, state: PointerState, event: PointerEvent) => void;\n}\n\n/**\n * Reactive state returned by `usePointer`.\n */\nexport interface UsePointerResult {\n\t/**\n\t * Current pointer state.\n\t */\n\tstate: CurrentReadable<PointerState>;\n\t/**\n\t * Last synthesized click/tap event.\n\t */\n\tlastClick: CurrentReadable<PointerClick | null>;\n\t/**\n\t * Clears last click snapshot.\n\t */\n\tresetClick: () => void;\n}\n\ninterface PointerDownSnapshot {\n\tbutton: number;\n\tinside: boolean;\n\tpointerId: number;\n\tpointerType: 'mouse' | 'pen' | 'touch';\n\tpx: PointerVec2;\n\ttimeMs: number;\n\tuv: PointerVec2;\n}\n\n/**\n * Normalizes click button configuration with a primary-button fallback.\n */\nfunction normalizeClickButtons(buttons: number[] | undefined): Set<number> {\n\tconst source = buttons && buttons.length > 0 ? buttons : [0];\n\treturn new Set(source);\n}\n\n/**\n * Resolves a valid click duration threshold in milliseconds.\n */\nfunction resolveClickMaxDurationMs(value: number | undefined): number {\n\tif (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {\n\t\treturn 350;\n\t}\n\n\treturn value;\n}\n\n/**\n * Resolves a valid click travel threshold in pixels.\n */\nfunction resolveClickMaxMovePx(value: number | undefined): number {\n\tif (typeof value !== 'number' || Number.isNaN(value) || value < 0) {\n\t\treturn 8;\n\t}\n\n\treturn value;\n}\n\n/**\n * Tracks normalized pointer coordinates and click/tap snapshots for the active `FragCanvas`.\n */\nexport function usePointer(options: UsePointerOptions = {}): UsePointerResult {\n\tconst motiongpu = useMotionGPU();\n\tconst pointerState = currentWritable<PointerState>(createInitialPointerState());\n\tconst lastClick = currentWritable<PointerClick | null>(null);\n\tconst enabled = options.enabled ?? true;\n\tconst requestFrameMode = options.requestFrame ?? 'auto';\n\tconst capturePointer = options.capturePointer ?? true;\n\tconst trackOutside = options.trackWhilePressedOutsideCanvas ?? true;\n\tconst clickEnabled = options.clickEnabled ?? true;\n\tconst clickMaxDurationMs = resolveClickMaxDurationMs(options.clickMaxDurationMs);\n\tconst clickMaxMovePx = resolveClickMaxMovePx(options.clickMaxMovePx);\n\tconst clickButtons = normalizeClickButtons(options.clickButtons);\n\tlet activePointerId: number | null = null;\n\tlet downSnapshot: PointerDownSnapshot | null = null;\n\tlet clickCounter = 0;\n\tlet previousPx: PointerVec2 | null = null;\n\tlet previousUv: PointerVec2 | null = null;\n\tlet previousTimeSeconds = 0;\n\n\tconst requestFrame = (): void => {\n\t\tconst mode = resolvePointerFrameRequestMode(requestFrameMode, motiongpu.renderMode.current);\n\t\tif (mode === 'invalidate') {\n\t\t\tmotiongpu.invalidate();\n\t\t\treturn;\n\t\t}\n\t\tif (mode === 'advance') {\n\t\t\tmotiongpu.advance();\n\t\t}\n\t};\n\n\t/**\n\t * Commits a full pointer state snapshot with computed delta and velocity vectors.\n\t */\n\tconst updatePointerState = (input: {\n\t\tbutton: number | null;\n\t\tbuttons: number;\n\t\tdownPx: PointerVec2 | null;\n\t\tdownUv: PointerVec2 | null;\n\t\tdragging: boolean;\n\t\tinside: boolean;\n\t\tpointerId: number | null;\n\t\tpointerType: 'mouse' | 'pen' | 'touch' | null;\n\t\tpressed: boolean;\n\t\tpoint: {\n\t\t\tndc: PointerVec2;\n\t\t\tpx: PointerVec2;\n\t\t\tuv: PointerVec2;\n\t\t};\n\t\tresetDelta?: boolean;\n\t}): PointerState => {\n\t\tconst nowSeconds = getPointerNowSeconds();\n\t\tconst dt = previousTimeSeconds > 0 ? Math.max(nowSeconds - previousTimeSeconds, 1e-6) : 0;\n\t\tconst deltaPx: PointerVec2 =\n\t\t\tinput.resetDelta || !previousPx\n\t\t\t\t? [0, 0]\n\t\t\t\t: [input.point.px[0] - previousPx[0], input.point.px[1] - previousPx[1]];\n\t\tconst deltaUv: PointerVec2 =\n\t\t\tinput.resetDelta || !previousUv\n\t\t\t\t? [0, 0]\n\t\t\t\t: [input.point.uv[0] - previousUv[0], input.point.uv[1] - previousUv[1]];\n\t\tconst velocityPx: PointerVec2 = dt > 0 ? [deltaPx[0] / dt, deltaPx[1] / dt] : [0, 0];\n\t\tconst velocityUv: PointerVec2 = dt > 0 ? [deltaUv[0] / dt, deltaUv[1] / dt] : [0, 0];\n\t\tconst nextState: PointerState = {\n\t\t\tpx: input.point.px,\n\t\t\tuv: input.point.uv,\n\t\t\tndc: input.point.ndc,\n\t\t\tinside: input.inside,\n\t\t\tpressed: input.pressed,\n\t\t\tdragging: input.dragging,\n\t\t\tpointerType: input.pointerType,\n\t\t\tpointerId: input.pointerId,\n\t\t\tbutton: input.button,\n\t\t\tbuttons: input.buttons,\n\t\t\ttime: nowSeconds,\n\t\t\tdownPx: input.downPx,\n\t\t\tdownUv: input.downUv,\n\t\t\tdeltaPx,\n\t\t\tdeltaUv,\n\t\t\tvelocityPx,\n\t\t\tvelocityUv\n\t\t};\n\t\tpointerState.set(nextState);\n\t\tpreviousPx = input.point.px;\n\t\tpreviousUv = input.point.uv;\n\t\tpreviousTimeSeconds = nowSeconds;\n\t\trequestFrame();\n\t\treturn nextState;\n\t};\n\n\t/**\n\t * Updates only the `inside` flag while keeping the latest pointer coordinates.\n\t */\n\tconst updateInsideState = (inside: boolean): PointerState => {\n\t\tconst current = pointerState.current;\n\t\tconst nextState: PointerState = {\n\t\t\t...current,\n\t\t\tinside,\n\t\t\ttime: getPointerNowSeconds(),\n\t\t\tdeltaPx: [0, 0],\n\t\t\tdeltaUv: [0, 0],\n\t\t\tvelocityPx: [0, 0],\n\t\t\tvelocityUv: [0, 0]\n\t\t};\n\t\tpointerState.set(nextState);\n\t\trequestFrame();\n\t\treturn nextState;\n\t};\n\n\t/**\n\t * Checks whether an event belongs to the active tracked pointer.\n\t */\n\tconst isTrackedPointer = (event: PointerEvent): boolean =>\n\t\tactivePointerId === null || event.pointerId === activePointerId;\n\n\tonMount(() => {\n\t\tif (!enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst canvas = motiongpu.canvas;\n\t\tif (!canvas) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst handlePointerDown = (event: PointerEvent): void => {\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pointerType = normalizePointerKind(event.pointerType);\n\t\t\tactivePointerId = event.pointerId;\n\t\t\tdownSnapshot = {\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tpointerType,\n\t\t\t\tbutton: event.button,\n\t\t\t\ttimeMs: getPointerNowSeconds() * 1000,\n\t\t\t\tpx: point.px,\n\t\t\t\tuv: point.uv,\n\t\t\t\tinside: point.inside\n\t\t\t};\n\t\t\tif (capturePointer) {\n\t\t\t\ttry {\n\t\t\t\t\tcanvas.setPointerCapture(event.pointerId);\n\t\t\t\t} catch {\n\t\t\t\t\t// Browser rejected capture (e.g. unsupported pointer state).\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed: true,\n\t\t\t\tdragging: false,\n\t\t\t\tpointerType,\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: event.button,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx: point.px,\n\t\t\t\tdownUv: point.uv,\n\t\t\t\tresetDelta: true\n\t\t\t});\n\t\t\toptions.onDown?.(nextState, event);\n\t\t};\n\n\t\tconst handleMove = (event: PointerEvent): void => {\n\t\t\tif (!isTrackedPointer(event)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pressed = activePointerId !== null && event.pointerId === activePointerId;\n\t\t\tconst downPx = pressed ? (downSnapshot?.px ?? point.px) : null;\n\t\t\tconst downUv = pressed ? (downSnapshot?.uv ?? point.uv) : null;\n\t\t\tlet dragging = false;\n\t\t\tif (pressed && downPx) {\n\t\t\t\tconst dx = point.px[0] - downPx[0];\n\t\t\t\tconst dy = point.px[1] - downPx[1];\n\t\t\t\tdragging = Math.hypot(dx, dy) > 0;\n\t\t\t}\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed,\n\t\t\t\tdragging,\n\t\t\t\tpointerType: normalizePointerKind(event.pointerType),\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: pressed ? (downSnapshot?.button ?? event.button) : null,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx,\n\t\t\t\tdownUv\n\t\t\t});\n\t\t\toptions.onMove?.(nextState, event);\n\t\t};\n\n\t\tconst handleWindowMove = (event: PointerEvent): void => {\n\t\t\tif (!trackOutside || activePointerId === null || event.pointerId !== activePointerId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tif (point.inside) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst downPx = downSnapshot?.px ?? point.px;\n\t\t\tconst downUv = downSnapshot?.uv ?? point.uv;\n\t\t\tconst dx = point.px[0] - downPx[0];\n\t\t\tconst dy = point.px[1] - downPx[1];\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: false,\n\t\t\t\tpressed: true,\n\t\t\t\tdragging: Math.hypot(dx, dy) > 0,\n\t\t\t\tpointerType: downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType),\n\t\t\t\tpointerId: event.pointerId,\n\t\t\t\tbutton: downSnapshot?.button ?? event.button,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx,\n\t\t\t\tdownUv\n\t\t\t});\n\t\t\toptions.onMove?.(nextState, event);\n\t\t};\n\n\t\tconst releasePointer = (event: PointerEvent, emitClick: boolean): void => {\n\t\t\tif (activePointerId === null || event.pointerId !== activePointerId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst point = getPointerCoordinates(\n\t\t\t\tevent.clientX,\n\t\t\t\tevent.clientY,\n\t\t\t\tcanvas.getBoundingClientRect()\n\t\t\t);\n\t\t\tconst pointerType = downSnapshot?.pointerType ?? normalizePointerKind(event.pointerType);\n\t\t\tconst previous = downSnapshot;\n\t\t\tconst nextState = updatePointerState({\n\t\t\t\tpoint,\n\t\t\t\tinside: point.inside,\n\t\t\t\tpressed: false,\n\t\t\t\tdragging: false,\n\t\t\t\tpointerType,\n\t\t\t\tpointerId: null,\n\t\t\t\tbutton: null,\n\t\t\t\tbuttons: event.buttons,\n\t\t\t\tdownPx: null,\n\t\t\t\tdownUv: null\n\t\t\t});\n\t\t\toptions.onUp?.(nextState, event);\n\n\t\t\tif (capturePointer && canvas.hasPointerCapture(event.pointerId)) {\n\t\t\t\ttry {\n\t\t\t\t\tcanvas.releasePointerCapture(event.pointerId);\n\t\t\t\t} catch {\n\t\t\t\t\t// Browser rejected release for this pointer id.\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (emitClick && clickEnabled && previous && clickButtons.has(previous.button)) {\n\t\t\t\tconst durationMs = getPointerNowSeconds() * 1000 - previous.timeMs;\n\t\t\t\tconst dx = point.px[0] - previous.px[0];\n\t\t\t\tconst dy = point.px[1] - previous.px[1];\n\t\t\t\tconst moveDistance = Math.hypot(dx, dy);\n\t\t\t\tif (\n\t\t\t\t\tprevious.inside &&\n\t\t\t\t\tpoint.inside &&\n\t\t\t\t\tdurationMs <= clickMaxDurationMs &&\n\t\t\t\t\tmoveDistance <= clickMaxMovePx\n\t\t\t\t) {\n\t\t\t\t\tclickCounter += 1;\n\t\t\t\t\tconst click: PointerClick = {\n\t\t\t\t\t\tid: clickCounter,\n\t\t\t\t\t\ttime: getPointerNowSeconds(),\n\t\t\t\t\t\tpointerType,\n\t\t\t\t\t\tpointerId: event.pointerId,\n\t\t\t\t\t\tbutton: previous.button,\n\t\t\t\t\t\tmodifiers: {\n\t\t\t\t\t\t\talt: event.altKey,\n\t\t\t\t\t\t\tctrl: event.ctrlKey,\n\t\t\t\t\t\t\tshift: event.shiftKey,\n\t\t\t\t\t\t\tmeta: event.metaKey\n\t\t\t\t\t\t},\n\t\t\t\t\t\tpx: point.px,\n\t\t\t\t\t\tuv: point.uv,\n\t\t\t\t\t\tndc: point.ndc\n\t\t\t\t\t};\n\t\t\t\t\tlastClick.set(click);\n\t\t\t\t\toptions.onClick?.(click, nextState, event);\n\t\t\t\t\trequestFrame();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tactivePointerId = null;\n\t\t\tdownSnapshot = null;\n\t\t};\n\n\t\tconst handlePointerUp = (event: PointerEvent): void => {\n\t\t\treleasePointer(event, true);\n\t\t};\n\n\t\tconst handlePointerCancel = (event: PointerEvent): void => {\n\t\t\treleasePointer(event, false);\n\t\t};\n\n\t\tconst handlePointerLeave = (): void => {\n\t\t\tif (activePointerId !== null) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tupdateInsideState(false);\n\t\t};\n\n\t\tcanvas.addEventListener('pointerdown', handlePointerDown);\n\t\tcanvas.addEventListener('pointermove', handleMove);\n\t\tcanvas.addEventListener('pointerup', handlePointerUp);\n\t\tcanvas.addEventListener('pointercancel', handlePointerCancel);\n\t\tcanvas.addEventListener('pointerleave', handlePointerLeave);\n\t\tif (trackOutside) {\n\t\t\twindow.addEventListener('pointermove', handleWindowMove);\n\t\t\twindow.addEventListener('pointerup', handlePointerUp);\n\t\t\twindow.addEventListener('pointercancel', handlePointerCancel);\n\t\t}\n\n\t\treturn () => {\n\t\t\tcanvas.removeEventListener('pointerdown', handlePointerDown);\n\t\t\tcanvas.removeEventListener('pointermove', handleMove);\n\t\t\tcanvas.removeEventListener('pointerup', handlePointerUp);\n\t\t\tcanvas.removeEventListener('pointercancel', handlePointerCancel);\n\t\t\tcanvas.removeEventListener('pointerleave', handlePointerLeave);\n\t\t\tif (trackOutside) {\n\t\t\t\twindow.removeEventListener('pointermove', handleWindowMove);\n\t\t\t\twindow.removeEventListener('pointerup', handlePointerUp);\n\t\t\t\twindow.removeEventListener('pointercancel', handlePointerCancel);\n\t\t\t}\n\t\t};\n\t});\n\n\treturn {\n\t\tstate: pointerState,\n\t\tlastClick,\n\t\tresetClick: () => {\n\t\t\tlastClick.set(null);\n\t\t}\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import FragCanvas_vue_vue_type_script_setup_true_lang_default from "./FragCanvas.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
/* empty css */
|
|
3
|
+
//#region src/lib/vue/FragCanvas.vue
|
|
4
|
+
var FragCanvas_default = FragCanvas_vue_vue_type_script_setup_true_lang_default;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { FragCanvas_default as default };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=FragCanvas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FragCanvas.js","names":[],"sources":["../../src/lib/vue/FragCanvas.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { MotionGPUErrorReport } from '../core/error-report.js';\nimport type { FragMaterial } from '../core/material.js';\nimport type {\n\tAnyPass,\n\tOutputColorSpace,\n\tRenderMode,\n\tRenderTargetDefinitionMap\n} from '../core/types.js';\n\nexport interface FragCanvasProps {\n\tmaterial: FragMaterial;\n\trenderTargets?: RenderTargetDefinitionMap;\n\tpasses?: AnyPass[];\n\tclearColor?: [number, number, number, number];\n\toutputColorSpace?: OutputColorSpace;\n\trenderMode?: RenderMode;\n\tautoRender?: boolean;\n\tmaxDelta?: number;\n\tadapterOptions?: GPURequestAdapterOptions;\n\tdeviceDescriptor?: GPUDeviceDescriptor;\n\tdpr?: number;\n\tshowErrorOverlay?: boolean;\n\tonError?: (report: MotionGPUErrorReport) => void;\n\terrorHistoryLimit?: number;\n\tonErrorHistory?: (history: MotionGPUErrorReport[]) => void;\n\tcanvasClass?: string;\n\tcanvasStyle?: string | Record<string, string | number>;\n}\n\nconst initialDpr = typeof window === 'undefined' ? 1 : (window.devicePixelRatio ?? 1);\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue';\nimport { createCurrentWritable as currentWritable } from '../core/current-value.js';\nimport { toMotionGPUErrorReport } from '../core/error-report.js';\nimport { createFrameRegistry } from '../core/frame-registry.js';\nimport { createMotionGPURuntimeLoop } from '../core/runtime-loop.js';\nimport { provideFrameRegistry } from './frame-context.js';\nimport { provideMotionGPUContext } from './motiongpu-context.js';\nimport MotionGPUErrorOverlay from './MotionGPUErrorOverlay.vue';\n\nconst props = withDefaults(defineProps<FragCanvasProps>(), {\n\trenderTargets: () => ({}),\n\tpasses: () => [],\n\tclearColor: () => [0, 0, 0, 1] as [number, number, number, number],\n\toutputColorSpace: 'srgb',\n\trenderMode: 'always',\n\tautoRender: true,\n\tmaxDelta: 0.1,\n\tdpr: () => initialDpr,\n\tshowErrorOverlay: true,\n\terrorHistoryLimit: 0,\n\tcanvasClass: ''\n});\n\nconst wrapperStyle = Object.freeze({\n\tposition: 'relative',\n\twidth: '100%',\n\theight: '100%',\n\tminWidth: '0',\n\tminHeight: '0',\n\toverflow: 'hidden'\n});\n\nconst baseCanvasStyle = Object.freeze({\n\tposition: 'absolute',\n\tinset: '0',\n\tdisplay: 'block',\n\twidth: '100%',\n\theight: '100%'\n});\n\nconst resolvedCanvasStyle = computed(() => [baseCanvasStyle, props.canvasStyle]);\n\ndefineSlots<{\n\tdefault(): unknown;\n\terrorRenderer(props: { report: MotionGPUErrorReport }): unknown;\n}>();\n\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasEl');\nconst errorReport = shallowRef<MotionGPUErrorReport | null>(null);\nconst errorHistory = shallowRef<MotionGPUErrorReport[]>([]);\n\nconst registry = createFrameRegistry({ maxDelta: 0.1 });\nprovideFrameRegistry(registry);\n\nlet requestFrameSignal: (() => void) | null = null;\nlet runtimeLoopHandle: ReturnType<typeof createMotionGPURuntimeLoop> | null = null;\nconst requestFrame = (): void => {\n\trequestFrameSignal?.();\n};\nconst invalidateFrame = (): void => {\n\tregistry.invalidate();\n\trequestFrame();\n};\nconst advanceFrame = (): void => {\n\tregistry.advance();\n\trequestFrame();\n};\n\nconst size = currentWritable({ width: 0, height: 0 });\nconst dprState = currentWritable<number>(initialDpr, requestFrame);\nconst maxDeltaState = currentWritable<number>(0.1, (value) => {\n\tregistry.setMaxDelta(value);\n\trequestFrame();\n});\nconst renderModeState = currentWritable<RenderMode>('always', (value) => {\n\tregistry.setRenderMode(value);\n\trequestFrame();\n});\nconst autoRenderState = currentWritable<boolean>(true, (value) => {\n\tregistry.setAutoRender(value);\n\trequestFrame();\n});\nconst userState = currentWritable<Record<string | symbol, unknown>>({});\n\nprovideMotionGPUContext({\n\tget canvas() {\n\t\treturn canvasRef.value ?? undefined;\n\t},\n\tsize,\n\tdpr: dprState,\n\tmaxDelta: maxDeltaState,\n\trenderMode: renderModeState,\n\tautoRender: autoRenderState,\n\tuser: userState,\n\tinvalidate: invalidateFrame,\n\tadvance: advanceFrame,\n\tscheduler: {\n\t\tcreateStage: registry.createStage,\n\t\tgetStage: registry.getStage,\n\t\tsetDiagnosticsEnabled: registry.setDiagnosticsEnabled,\n\t\tgetDiagnosticsEnabled: registry.getDiagnosticsEnabled,\n\t\tgetLastRunTimings: registry.getLastRunTimings,\n\t\tgetSchedule: registry.getSchedule,\n\t\tsetProfilingEnabled: registry.setProfilingEnabled,\n\t\tsetProfilingWindow: registry.setProfilingWindow,\n\t\tresetProfiling: registry.resetProfiling,\n\t\tgetProfilingEnabled: registry.getProfilingEnabled,\n\t\tgetProfilingWindow: registry.getProfilingWindow,\n\t\tgetProfilingSnapshot: registry.getProfilingSnapshot\n\t}\n});\n\n/**\n * Normalizes the user-supplied error history limit to a non-negative integer.\n */\nfunction getNormalizedErrorHistoryLimit(value: number): number {\n\tif (!Number.isFinite(value) || value <= 0) {\n\t\treturn 0;\n\t}\n\n\treturn Math.floor(value);\n}\n\nwatch(\n\t() => props.renderMode,\n\t(value) => {\n\t\trenderModeState.set(value);\n\t}\n);\n\nwatch(\n\t() => props.autoRender,\n\t(value) => {\n\t\tautoRenderState.set(value);\n\t}\n);\n\nwatch(\n\t() => props.maxDelta,\n\t(value) => {\n\t\tmaxDeltaState.set(value);\n\t}\n);\n\nwatch(\n\t() => props.dpr,\n\t(value) => {\n\t\tdprState.set(value);\n\t}\n);\n\nwatch([() => errorHistory.value, () => props.errorHistoryLimit], ([history, rawLimit]) => {\n\tconst limit = getNormalizedErrorHistoryLimit(rawLimit);\n\tif (limit <= 0) {\n\t\tif (history.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\terrorHistory.value = [];\n\t\tprops.onErrorHistory?.([]);\n\t\treturn;\n\t}\n\n\tif (history.length <= limit) {\n\t\treturn;\n\t}\n\n\tconst trimmed = history.slice(history.length - limit);\n\terrorHistory.value = trimmed;\n\tprops.onErrorHistory?.(trimmed);\n});\n\nonMounted(() => {\n\trenderModeState.set(props.renderMode);\n\tautoRenderState.set(props.autoRender);\n\tmaxDeltaState.set(props.maxDelta);\n\tdprState.set(props.dpr);\n\trequestFrame();\n\n\tconst canvas = canvasRef.value;\n\tif (!canvas) {\n\t\tconst report = toMotionGPUErrorReport(\n\t\t\tnew Error('Canvas element is not available'),\n\t\t\t'initialization'\n\t\t);\n\t\terrorReport.value = report;\n\t\tconst historyLimit = getNormalizedErrorHistoryLimit(props.errorHistoryLimit);\n\t\tif (historyLimit > 0) {\n\t\t\tconst nextHistory = [report].slice(-historyLimit);\n\t\t\terrorHistory.value = nextHistory;\n\t\t\tprops.onErrorHistory?.(nextHistory);\n\t\t}\n\t\tprops.onError?.(report);\n\t\treturn;\n\t}\n\n\tconst runtimeLoop = createMotionGPURuntimeLoop({\n\t\tcanvas,\n\t\tregistry,\n\t\tsize,\n\t\tdpr: dprState,\n\t\tmaxDelta: maxDeltaState,\n\t\tgetMaterial: () => props.material,\n\t\tgetRenderTargets: () => props.renderTargets,\n\t\tgetPasses: () => props.passes,\n\t\tgetClearColor: () => props.clearColor,\n\t\tgetOutputColorSpace: () => props.outputColorSpace,\n\t\tgetAdapterOptions: () => props.adapterOptions,\n\t\tgetDeviceDescriptor: () => props.deviceDescriptor,\n\t\tgetOnError: () => props.onError,\n\t\tgetErrorHistoryLimit: () => props.errorHistoryLimit,\n\t\tgetOnErrorHistory: () => props.onErrorHistory,\n\t\treportErrorHistory: (history) => {\n\t\t\terrorHistory.value = history;\n\t\t},\n\t\treportError: (report) => {\n\t\t\terrorReport.value = report;\n\t\t}\n\t});\n\truntimeLoopHandle = runtimeLoop;\n\trequestFrameSignal = runtimeLoop.requestFrame;\n});\n\nonBeforeUnmount(() => {\n\trequestFrameSignal = null;\n\truntimeLoopHandle?.destroy();\n\truntimeLoopHandle = null;\n\tregistry.clear();\n});\n</script>\n\n<template>\n\t<div class=\"motiongpu-canvas-wrap\" :style=\"wrapperStyle\">\n\t\t<canvas ref=\"canvasEl\" :class=\"canvasClass\" :style=\"resolvedCanvasStyle\"></canvas>\n\t\t<template v-if=\"showErrorOverlay && errorReport\">\n\t\t\t<slot name=\"errorRenderer\" :report=\"errorReport\">\n\t\t\t\t<MotionGPUErrorOverlay :report=\"errorReport\" />\n\t\t\t</slot>\n\t\t</template>\n\t\t<slot />\n\t</div>\n</template>\n\n<style>\n.motiongpu-canvas-wrap {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-width: 0;\n\tmin-height: 0;\n\toverflow: hidden;\n}\n\n.motiongpu-canvas-wrap > canvas {\n\tposition: absolute;\n\tinset: 0;\n\tdisplay: block;\n\twidth: 100%;\n\theight: 100%;\n}\n</style>\n"],"mappings":""}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/// <reference types="@webgpu/types" />
|
|
2
|
+
import type { MotionGPUErrorReport } from '../core/error-report.js';
|
|
3
|
+
import type { FragMaterial } from '../core/material.js';
|
|
4
|
+
import type { AnyPass, OutputColorSpace, RenderMode, RenderTargetDefinitionMap } from '../core/types.js';
|
|
5
|
+
export interface FragCanvasProps {
|
|
6
|
+
material: FragMaterial;
|
|
7
|
+
renderTargets?: RenderTargetDefinitionMap;
|
|
8
|
+
passes?: AnyPass[];
|
|
9
|
+
clearColor?: [number, number, number, number];
|
|
10
|
+
outputColorSpace?: OutputColorSpace;
|
|
11
|
+
renderMode?: RenderMode;
|
|
12
|
+
autoRender?: boolean;
|
|
13
|
+
maxDelta?: number;
|
|
14
|
+
adapterOptions?: GPURequestAdapterOptions;
|
|
15
|
+
deviceDescriptor?: GPUDeviceDescriptor;
|
|
16
|
+
dpr?: number;
|
|
17
|
+
showErrorOverlay?: boolean;
|
|
18
|
+
onError?: (report: MotionGPUErrorReport) => void;
|
|
19
|
+
errorHistoryLimit?: number;
|
|
20
|
+
onErrorHistory?: (history: MotionGPUErrorReport[]) => void;
|
|
21
|
+
canvasClass?: string;
|
|
22
|
+
canvasStyle?: string | Record<string, string | number>;
|
|
23
|
+
}
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
26
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<FragCanvasProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FragCanvasProps> & Readonly<{}>, {
|
|
27
|
+
clearColor: [number, number, number, number];
|
|
28
|
+
renderMode: RenderMode;
|
|
29
|
+
renderTargets: RenderTargetDefinitionMap;
|
|
30
|
+
passes: AnyPass[];
|
|
31
|
+
outputColorSpace: OutputColorSpace;
|
|
32
|
+
autoRender: boolean;
|
|
33
|
+
maxDelta: number;
|
|
34
|
+
dpr: number;
|
|
35
|
+
showErrorOverlay: boolean;
|
|
36
|
+
errorHistoryLimit: number;
|
|
37
|
+
canvasClass: string;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
39
|
+
default(): unknown;
|
|
40
|
+
errorRenderer(props: {
|
|
41
|
+
report: MotionGPUErrorReport;
|
|
42
|
+
}): unknown;
|
|
43
|
+
}>;
|
|
44
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
45
|
+
new (): {
|
|
46
|
+
$slots: S;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=FragCanvas.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FragCanvas.vue.d.ts","sourceRoot":"","sources":["../../src/lib/vue/FragCanvas.vue"],"names":[],"mappings":";AAmTA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACX,OAAO,EACP,gBAAgB,EAChB,UAAU,EACV,yBAAyB,EACzB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,CAAC,EAAE,yBAAyB,CAAC;IAC1C,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CACvD;wBAGoB,OAAO,YAAY;AAAxC,wBAAyC;AACzC,QAAA,MAAM,YAAY;gBAlBJ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAEhC,UAAU;mBAJP,yBAAyB;YAChC,OAAO,EAAE;sBAEC,gBAAgB;gBAEtB,OAAO;cACT,MAAM;SAGX,MAAM;sBACO,OAAO;uBAEN,MAAM;iBAEZ,MAAM;;eA0CT,OAAO;yBACG;QAAE,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,OAAO;EA8P5D,CAAC;AAWL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { toMotionGPUErrorReport } from "../core/error-report.js";
|
|
2
|
+
import { createCurrentWritable } from "../core/current-value.js";
|
|
3
|
+
import { createFrameRegistry } from "../core/frame-registry.js";
|
|
4
|
+
import { createMotionGPURuntimeLoop } from "../core/runtime-loop.js";
|
|
5
|
+
import { provideFrameRegistry } from "./frame-context.js";
|
|
6
|
+
import { provideMotionGPUContext } from "./motiongpu-context.js";
|
|
7
|
+
import MotionGPUErrorOverlay_default from "./MotionGPUErrorOverlay.js";
|
|
8
|
+
import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, renderSlot, shallowRef, unref, useTemplateRef, watch } from "vue";
|
|
9
|
+
//#region src/lib/vue/FragCanvas.vue?vue&type=script&setup=true&lang.ts
|
|
10
|
+
var initialDpr = typeof window === "undefined" ? 1 : window.devicePixelRatio ?? 1;
|
|
11
|
+
var FragCanvas_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
12
|
+
__name: "FragCanvas",
|
|
13
|
+
props: {
|
|
14
|
+
material: {},
|
|
15
|
+
renderTargets: { default: () => ({}) },
|
|
16
|
+
passes: { default: () => [] },
|
|
17
|
+
clearColor: { default: () => [
|
|
18
|
+
0,
|
|
19
|
+
0,
|
|
20
|
+
0,
|
|
21
|
+
1
|
|
22
|
+
] },
|
|
23
|
+
outputColorSpace: { default: "srgb" },
|
|
24
|
+
renderMode: { default: "always" },
|
|
25
|
+
autoRender: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true
|
|
28
|
+
},
|
|
29
|
+
maxDelta: { default: .1 },
|
|
30
|
+
adapterOptions: {},
|
|
31
|
+
deviceDescriptor: {},
|
|
32
|
+
dpr: { default: () => initialDpr },
|
|
33
|
+
showErrorOverlay: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: true
|
|
36
|
+
},
|
|
37
|
+
onError: {},
|
|
38
|
+
errorHistoryLimit: { default: 0 },
|
|
39
|
+
onErrorHistory: {},
|
|
40
|
+
canvasClass: { default: "" },
|
|
41
|
+
canvasStyle: {}
|
|
42
|
+
},
|
|
43
|
+
setup(__props) {
|
|
44
|
+
const props = __props;
|
|
45
|
+
const wrapperStyle = Object.freeze({
|
|
46
|
+
position: "relative",
|
|
47
|
+
width: "100%",
|
|
48
|
+
height: "100%",
|
|
49
|
+
minWidth: "0",
|
|
50
|
+
minHeight: "0",
|
|
51
|
+
overflow: "hidden"
|
|
52
|
+
});
|
|
53
|
+
const baseCanvasStyle = Object.freeze({
|
|
54
|
+
position: "absolute",
|
|
55
|
+
inset: "0",
|
|
56
|
+
display: "block",
|
|
57
|
+
width: "100%",
|
|
58
|
+
height: "100%"
|
|
59
|
+
});
|
|
60
|
+
const resolvedCanvasStyle = computed(() => [baseCanvasStyle, props.canvasStyle]);
|
|
61
|
+
const canvasRef = useTemplateRef("canvasEl");
|
|
62
|
+
const errorReport = shallowRef(null);
|
|
63
|
+
const errorHistory = shallowRef([]);
|
|
64
|
+
const registry = createFrameRegistry({ maxDelta: .1 });
|
|
65
|
+
provideFrameRegistry(registry);
|
|
66
|
+
let requestFrameSignal = null;
|
|
67
|
+
let runtimeLoopHandle = null;
|
|
68
|
+
const requestFrame = () => {
|
|
69
|
+
requestFrameSignal?.();
|
|
70
|
+
};
|
|
71
|
+
const invalidateFrame = () => {
|
|
72
|
+
registry.invalidate();
|
|
73
|
+
requestFrame();
|
|
74
|
+
};
|
|
75
|
+
const advanceFrame = () => {
|
|
76
|
+
registry.advance();
|
|
77
|
+
requestFrame();
|
|
78
|
+
};
|
|
79
|
+
const size = createCurrentWritable({
|
|
80
|
+
width: 0,
|
|
81
|
+
height: 0
|
|
82
|
+
});
|
|
83
|
+
const dprState = createCurrentWritable(initialDpr, requestFrame);
|
|
84
|
+
const maxDeltaState = createCurrentWritable(.1, (value) => {
|
|
85
|
+
registry.setMaxDelta(value);
|
|
86
|
+
requestFrame();
|
|
87
|
+
});
|
|
88
|
+
const renderModeState = createCurrentWritable("always", (value) => {
|
|
89
|
+
registry.setRenderMode(value);
|
|
90
|
+
requestFrame();
|
|
91
|
+
});
|
|
92
|
+
const autoRenderState = createCurrentWritable(true, (value) => {
|
|
93
|
+
registry.setAutoRender(value);
|
|
94
|
+
requestFrame();
|
|
95
|
+
});
|
|
96
|
+
provideMotionGPUContext({
|
|
97
|
+
get canvas() {
|
|
98
|
+
return canvasRef.value ?? void 0;
|
|
99
|
+
},
|
|
100
|
+
size,
|
|
101
|
+
dpr: dprState,
|
|
102
|
+
maxDelta: maxDeltaState,
|
|
103
|
+
renderMode: renderModeState,
|
|
104
|
+
autoRender: autoRenderState,
|
|
105
|
+
user: createCurrentWritable({}),
|
|
106
|
+
invalidate: invalidateFrame,
|
|
107
|
+
advance: advanceFrame,
|
|
108
|
+
scheduler: {
|
|
109
|
+
createStage: registry.createStage,
|
|
110
|
+
getStage: registry.getStage,
|
|
111
|
+
setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
|
|
112
|
+
getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
|
|
113
|
+
getLastRunTimings: registry.getLastRunTimings,
|
|
114
|
+
getSchedule: registry.getSchedule,
|
|
115
|
+
setProfilingEnabled: registry.setProfilingEnabled,
|
|
116
|
+
setProfilingWindow: registry.setProfilingWindow,
|
|
117
|
+
resetProfiling: registry.resetProfiling,
|
|
118
|
+
getProfilingEnabled: registry.getProfilingEnabled,
|
|
119
|
+
getProfilingWindow: registry.getProfilingWindow,
|
|
120
|
+
getProfilingSnapshot: registry.getProfilingSnapshot
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
/**
|
|
124
|
+
* Normalizes the user-supplied error history limit to a non-negative integer.
|
|
125
|
+
*/
|
|
126
|
+
function getNormalizedErrorHistoryLimit(value) {
|
|
127
|
+
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
128
|
+
return Math.floor(value);
|
|
129
|
+
}
|
|
130
|
+
watch(() => props.renderMode, (value) => {
|
|
131
|
+
renderModeState.set(value);
|
|
132
|
+
});
|
|
133
|
+
watch(() => props.autoRender, (value) => {
|
|
134
|
+
autoRenderState.set(value);
|
|
135
|
+
});
|
|
136
|
+
watch(() => props.maxDelta, (value) => {
|
|
137
|
+
maxDeltaState.set(value);
|
|
138
|
+
});
|
|
139
|
+
watch(() => props.dpr, (value) => {
|
|
140
|
+
dprState.set(value);
|
|
141
|
+
});
|
|
142
|
+
watch([() => errorHistory.value, () => props.errorHistoryLimit], ([history, rawLimit]) => {
|
|
143
|
+
const limit = getNormalizedErrorHistoryLimit(rawLimit);
|
|
144
|
+
if (limit <= 0) {
|
|
145
|
+
if (history.length === 0) return;
|
|
146
|
+
errorHistory.value = [];
|
|
147
|
+
props.onErrorHistory?.([]);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (history.length <= limit) return;
|
|
151
|
+
const trimmed = history.slice(history.length - limit);
|
|
152
|
+
errorHistory.value = trimmed;
|
|
153
|
+
props.onErrorHistory?.(trimmed);
|
|
154
|
+
});
|
|
155
|
+
onMounted(() => {
|
|
156
|
+
renderModeState.set(props.renderMode);
|
|
157
|
+
autoRenderState.set(props.autoRender);
|
|
158
|
+
maxDeltaState.set(props.maxDelta);
|
|
159
|
+
dprState.set(props.dpr);
|
|
160
|
+
requestFrame();
|
|
161
|
+
const canvas = canvasRef.value;
|
|
162
|
+
if (!canvas) {
|
|
163
|
+
const report = toMotionGPUErrorReport(/* @__PURE__ */ new Error("Canvas element is not available"), "initialization");
|
|
164
|
+
errorReport.value = report;
|
|
165
|
+
const historyLimit = getNormalizedErrorHistoryLimit(props.errorHistoryLimit);
|
|
166
|
+
if (historyLimit > 0) {
|
|
167
|
+
const nextHistory = [report].slice(-historyLimit);
|
|
168
|
+
errorHistory.value = nextHistory;
|
|
169
|
+
props.onErrorHistory?.(nextHistory);
|
|
170
|
+
}
|
|
171
|
+
props.onError?.(report);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const runtimeLoop = createMotionGPURuntimeLoop({
|
|
175
|
+
canvas,
|
|
176
|
+
registry,
|
|
177
|
+
size,
|
|
178
|
+
dpr: dprState,
|
|
179
|
+
maxDelta: maxDeltaState,
|
|
180
|
+
getMaterial: () => props.material,
|
|
181
|
+
getRenderTargets: () => props.renderTargets,
|
|
182
|
+
getPasses: () => props.passes,
|
|
183
|
+
getClearColor: () => props.clearColor,
|
|
184
|
+
getOutputColorSpace: () => props.outputColorSpace,
|
|
185
|
+
getAdapterOptions: () => props.adapterOptions,
|
|
186
|
+
getDeviceDescriptor: () => props.deviceDescriptor,
|
|
187
|
+
getOnError: () => props.onError,
|
|
188
|
+
getErrorHistoryLimit: () => props.errorHistoryLimit,
|
|
189
|
+
getOnErrorHistory: () => props.onErrorHistory,
|
|
190
|
+
reportErrorHistory: (history) => {
|
|
191
|
+
errorHistory.value = history;
|
|
192
|
+
},
|
|
193
|
+
reportError: (report) => {
|
|
194
|
+
errorReport.value = report;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
runtimeLoopHandle = runtimeLoop;
|
|
198
|
+
requestFrameSignal = runtimeLoop.requestFrame;
|
|
199
|
+
});
|
|
200
|
+
onBeforeUnmount(() => {
|
|
201
|
+
requestFrameSignal = null;
|
|
202
|
+
runtimeLoopHandle?.destroy();
|
|
203
|
+
runtimeLoopHandle = null;
|
|
204
|
+
registry.clear();
|
|
205
|
+
});
|
|
206
|
+
return (_ctx, _cache) => {
|
|
207
|
+
return openBlock(), createElementBlock("div", {
|
|
208
|
+
class: "motiongpu-canvas-wrap",
|
|
209
|
+
style: normalizeStyle(unref(wrapperStyle))
|
|
210
|
+
}, [
|
|
211
|
+
createElementVNode("canvas", {
|
|
212
|
+
ref: "canvasEl",
|
|
213
|
+
class: normalizeClass(__props.canvasClass),
|
|
214
|
+
style: normalizeStyle(resolvedCanvasStyle.value)
|
|
215
|
+
}, null, 6),
|
|
216
|
+
__props.showErrorOverlay && errorReport.value ? renderSlot(_ctx.$slots, "errorRenderer", {
|
|
217
|
+
key: 0,
|
|
218
|
+
report: errorReport.value
|
|
219
|
+
}, () => [createVNode(MotionGPUErrorOverlay_default, { report: errorReport.value }, null, 8, ["report"])]) : createCommentVNode("", true),
|
|
220
|
+
renderSlot(_ctx.$slots, "default")
|
|
221
|
+
], 4);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
//#endregion
|
|
226
|
+
export { FragCanvas_vue_vue_type_script_setup_true_lang_default as default };
|
|
227
|
+
|
|
228
|
+
//# sourceMappingURL=FragCanvas.vue_vue_type_script_setup_true_lang.js.map
|