@weasel-js/animator 1.10.0 → 1.11.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 +3 -2
- package/dist/chunk-H7WENFPC.js +1625 -0
- package/dist/chunk-H7WENFPC.js.map +1 -0
- package/dist/index.d.ts +10 -941
- package/dist/index.js +12 -3271
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +8 -0
- package/dist/react.js +2111 -0
- package/dist/react.js.map +1 -0
- package/dist/types-QKD4dhHF.d.ts +970 -0
- package/package.json +13 -6
package/dist/react.js
ADDED
|
@@ -0,0 +1,2111 @@
|
|
|
1
|
+
import { ColorOverrideRegistry, createTargetBook, createTimeline, rowTableOf, stateOf, axesFor, takeUp, travel, shareBehind, goBehind, times, runsOn, seat, bumpOn, plus, axesOf, norm, absorb, minus, axisIndexes, comeOut, commitOffset, bump, isTarget, createHoldStore, committedInto, setBeside, shortWay } from './chunk-H7WENFPC.js';
|
|
2
|
+
import { useRef, useCallback, useMemo, useInsertionEffect, useEffect } from 'react';
|
|
3
|
+
import { useLatest, useVisibleRaf, isDev } from '@weasel-js/react';
|
|
4
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
5
|
+
import { resolveEasing, easeOut, SPRING_PRESETS } from '@weasel-js/geom';
|
|
6
|
+
import { patch, tween, glide, spring, mix, kit, vec, sum } from '@msb235/blits';
|
|
7
|
+
|
|
8
|
+
var AnimatorEventHub = class {
|
|
9
|
+
events = createEmitter();
|
|
10
|
+
get watched() {
|
|
11
|
+
return this.events.size > 0;
|
|
12
|
+
}
|
|
13
|
+
add(listener) {
|
|
14
|
+
return this.events.subscribe(listener);
|
|
15
|
+
}
|
|
16
|
+
emit(event) {
|
|
17
|
+
this.events.emit(event);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/loop.ts
|
|
22
|
+
function createLoop(createSupervisor, factory, opts = {}) {
|
|
23
|
+
const max = opts.count ?? Infinity;
|
|
24
|
+
let canceled = false;
|
|
25
|
+
let current = null;
|
|
26
|
+
const supervisor = createSupervisor({ kind: "loop", cancelKey: opts.cancelKey, label: opts.label });
|
|
27
|
+
supervisor.setOnCancel(() => {
|
|
28
|
+
canceled = true;
|
|
29
|
+
current?.cancel();
|
|
30
|
+
current = null;
|
|
31
|
+
});
|
|
32
|
+
const applyRate = (paused, timeScale) => {
|
|
33
|
+
if (!current) return;
|
|
34
|
+
if (paused) current.pause();
|
|
35
|
+
else current.resume();
|
|
36
|
+
current.setTimeScale(timeScale);
|
|
37
|
+
};
|
|
38
|
+
supervisor.setOnRate(applyRate);
|
|
39
|
+
const fromEnd = supervisor.atEnd && max < Infinity;
|
|
40
|
+
let pass = fromEnd ? max : -1;
|
|
41
|
+
let first = true;
|
|
42
|
+
const next = () => {
|
|
43
|
+
if (canceled) return;
|
|
44
|
+
const back = first ? fromEnd : supervisor.rate() < 0;
|
|
45
|
+
first = false;
|
|
46
|
+
const i = back ? pass - 1 : pass + 1;
|
|
47
|
+
if (i < 0 || i >= max) {
|
|
48
|
+
current = null;
|
|
49
|
+
canceled = true;
|
|
50
|
+
supervisor.finish();
|
|
51
|
+
opts.onDone?.();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
pass = i;
|
|
55
|
+
current = supervisor.starting(back, () => factory(i, next));
|
|
56
|
+
if (supervisor.isPaused() || supervisor.timeScale() !== 1) {
|
|
57
|
+
applyRate(supervisor.isPaused(), supervisor.timeScale());
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
next();
|
|
61
|
+
return {
|
|
62
|
+
id: supervisor.id,
|
|
63
|
+
cancel: () => supervisor.cancel(),
|
|
64
|
+
pause: () => supervisor.pause(),
|
|
65
|
+
resume: () => supervisor.resume(),
|
|
66
|
+
setTimeScale: (s) => supervisor.setTimeScale(s),
|
|
67
|
+
timeScale: () => supervisor.timeScale(),
|
|
68
|
+
isPaused: () => supervisor.isPaused()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function createTweenLoop(animator, createSupervisor, opts) {
|
|
72
|
+
const direction = opts.direction ?? "restart";
|
|
73
|
+
return createLoop(
|
|
74
|
+
(o) => createSupervisor({ ...o, kind: "tweenLoop" }),
|
|
75
|
+
(i, next) => {
|
|
76
|
+
const flipped = direction === "reverse" || direction === "alternate" && i % 2 === 1;
|
|
77
|
+
const from = flipped ? opts.to : opts.from;
|
|
78
|
+
const to = flipped ? opts.from : opts.to;
|
|
79
|
+
return animator.tween({
|
|
80
|
+
from,
|
|
81
|
+
to,
|
|
82
|
+
ms: opts.ms,
|
|
83
|
+
easing: opts.easing,
|
|
84
|
+
interpolate: opts.interpolate,
|
|
85
|
+
onTick: opts.onTick,
|
|
86
|
+
onDone: next
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
{ count: opts.count, onDone: opts.onDone, cancelKey: opts.cancelKey, label: opts.label }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/stagger.ts
|
|
94
|
+
var CompositeHandle = class {
|
|
95
|
+
constructor(supervisor, totalItems) {
|
|
96
|
+
this.supervisor = supervisor;
|
|
97
|
+
this.id = supervisor.id;
|
|
98
|
+
this.outstanding = totalItems;
|
|
99
|
+
supervisor.setOnCancel(() => this.handleSupervisorCancel());
|
|
100
|
+
supervisor.setOnRate((paused, timeScale) => {
|
|
101
|
+
if (paused) this.applyPause();
|
|
102
|
+
else this.applyResume();
|
|
103
|
+
this.applyTimeScale(timeScale);
|
|
104
|
+
});
|
|
105
|
+
supervisor.setOnFrame((virtualNow) => this.fireDue(virtualNow));
|
|
106
|
+
}
|
|
107
|
+
supervisor;
|
|
108
|
+
id;
|
|
109
|
+
canceled = false;
|
|
110
|
+
paused_ = false;
|
|
111
|
+
timeScale_ = 1;
|
|
112
|
+
children = [];
|
|
113
|
+
pending = [];
|
|
114
|
+
outstanding;
|
|
115
|
+
/** Children started and not yet done. */
|
|
116
|
+
running = 0;
|
|
117
|
+
finished = false;
|
|
118
|
+
/** Invoked when the supervisor is canceled externally (animator.cancel /
|
|
119
|
+
* cancelKey) OR via this composite's own `cancel`. */
|
|
120
|
+
handleSupervisorCancel() {
|
|
121
|
+
if (this.canceled) return;
|
|
122
|
+
this.canceled = true;
|
|
123
|
+
this.pending = [];
|
|
124
|
+
const kids = this.children.slice();
|
|
125
|
+
this.children = [];
|
|
126
|
+
for (const c of kids) c.cancel();
|
|
127
|
+
}
|
|
128
|
+
noteChildDone() {
|
|
129
|
+
if (this.canceled || this.finished) return;
|
|
130
|
+
this.outstanding -= 1;
|
|
131
|
+
this.running -= 1;
|
|
132
|
+
if (this.outstanding <= 0) this.finish();
|
|
133
|
+
else this.rewound();
|
|
134
|
+
}
|
|
135
|
+
finish() {
|
|
136
|
+
this.finished = true;
|
|
137
|
+
this.children = [];
|
|
138
|
+
this.pending = [];
|
|
139
|
+
this.supervisor.finish();
|
|
140
|
+
}
|
|
141
|
+
/** Played backward, it ends once every item running has run back to its start: the items
|
|
142
|
+
* still waiting are not reached going that way. */
|
|
143
|
+
rewound() {
|
|
144
|
+
if (this.running <= 0 && this.supervisor.rate() < 0) this.finish();
|
|
145
|
+
}
|
|
146
|
+
addChild(h) {
|
|
147
|
+
if (this.canceled) {
|
|
148
|
+
h.cancel();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.running += 1;
|
|
152
|
+
this.children.push(h);
|
|
153
|
+
if (this.paused_) h.pause();
|
|
154
|
+
if (this.timeScale_ !== 1) h.setTimeScale(this.timeScale_);
|
|
155
|
+
}
|
|
156
|
+
schedule(delayMs, fire) {
|
|
157
|
+
if (this.canceled) return;
|
|
158
|
+
this.pending.push({ at: delayMs, fire });
|
|
159
|
+
}
|
|
160
|
+
fireDue(virtualNow) {
|
|
161
|
+
if (this.canceled || this.finished) return;
|
|
162
|
+
this.rewound();
|
|
163
|
+
if (this.finished || this.pending.length === 0) return;
|
|
164
|
+
const due = this.pending.filter((p) => p.at <= virtualNow).sort((a, b) => a.at - b.at);
|
|
165
|
+
this.pending = this.pending.filter((p) => p.at > virtualNow);
|
|
166
|
+
for (const p of due) {
|
|
167
|
+
if (this.canceled) return;
|
|
168
|
+
p.fire();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
cancel() {
|
|
172
|
+
this.supervisor.cancel();
|
|
173
|
+
}
|
|
174
|
+
pause() {
|
|
175
|
+
this.supervisor.pause();
|
|
176
|
+
}
|
|
177
|
+
resume() {
|
|
178
|
+
this.supervisor.resume();
|
|
179
|
+
}
|
|
180
|
+
setTimeScale(s) {
|
|
181
|
+
this.supervisor.setTimeScale(s);
|
|
182
|
+
}
|
|
183
|
+
applyPause() {
|
|
184
|
+
if (this.paused_) return;
|
|
185
|
+
this.paused_ = true;
|
|
186
|
+
for (const c of this.children) c.pause();
|
|
187
|
+
}
|
|
188
|
+
applyResume() {
|
|
189
|
+
if (!this.paused_) return;
|
|
190
|
+
this.paused_ = false;
|
|
191
|
+
for (const c of this.children) c.resume();
|
|
192
|
+
}
|
|
193
|
+
applyTimeScale(s) {
|
|
194
|
+
if (s === this.timeScale_) return;
|
|
195
|
+
this.timeScale_ = s;
|
|
196
|
+
for (const c of this.children) c.setTimeScale(s);
|
|
197
|
+
}
|
|
198
|
+
timeScale() {
|
|
199
|
+
return this.timeScale_;
|
|
200
|
+
}
|
|
201
|
+
isPaused() {
|
|
202
|
+
return this.paused_;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
function delayFor(delay, i) {
|
|
206
|
+
return typeof delay === "function" ? delay(i) : delay * i;
|
|
207
|
+
}
|
|
208
|
+
function createStagger(animator, createSupervisor, watchCompletion, items, delay, factory, opts) {
|
|
209
|
+
if (factory) return runStagger(createSupervisor, watchCompletion, items, delay, factory, opts);
|
|
210
|
+
return makeBuilder(animator, createSupervisor, watchCompletion, items, delay);
|
|
211
|
+
}
|
|
212
|
+
function runStagger(createSupervisor, watchCompletion, items, delay, factory, opts) {
|
|
213
|
+
const supervisor = createSupervisor({ kind: "stagger", cancelKey: opts?.cancelKey, label: opts?.label });
|
|
214
|
+
const composite = new CompositeHandle(supervisor, items.length);
|
|
215
|
+
if (items.length === 0) {
|
|
216
|
+
supervisor.finish();
|
|
217
|
+
return composite;
|
|
218
|
+
}
|
|
219
|
+
supervisor.starting(false, () => items.forEach((item, i) => {
|
|
220
|
+
const ms = delayFor(delay, i);
|
|
221
|
+
const fire = () => {
|
|
222
|
+
const child = factory(item, i);
|
|
223
|
+
watchCompletion(child.id, () => composite.noteChildDone());
|
|
224
|
+
composite.addChild(child);
|
|
225
|
+
};
|
|
226
|
+
if (ms <= 0) fire();
|
|
227
|
+
else composite.schedule(ms, fire);
|
|
228
|
+
}));
|
|
229
|
+
return composite;
|
|
230
|
+
}
|
|
231
|
+
function resolvePerItem(v, item, i) {
|
|
232
|
+
return typeof v === "function" ? v(item, i) : v;
|
|
233
|
+
}
|
|
234
|
+
function perItem(opts, item, i) {
|
|
235
|
+
const target = opts.target !== void 0 ? resolvePerItem(opts.target, item, i) : isTarget(item) ? item : null;
|
|
236
|
+
return {
|
|
237
|
+
target,
|
|
238
|
+
from: opts.from === void 0 ? void 0 : resolvePerItem(opts.from, item, i),
|
|
239
|
+
to: resolvePerItem(opts.to, item, i),
|
|
240
|
+
onTick: opts.onTick && ((v) => opts.onTick(v, item, i)),
|
|
241
|
+
onDone: opts.onDone && (() => opts.onDone(item, i))
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function makeBuilder(animator, createSupervisor, watchCompletion, items, delay) {
|
|
245
|
+
const run = (method, opts, start) => {
|
|
246
|
+
if (opts.target === void 0 && !opts.onTick && !items.every(isTarget)) {
|
|
247
|
+
throw new Error(`animator.stagger: \`.${method}\` needs a \`target\`, items that are targets, or an \`onTick\``);
|
|
248
|
+
}
|
|
249
|
+
return runStagger(createSupervisor, watchCompletion, items, delay, (item, i) => start(perItem(opts, item, i), item, i), opts);
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
each: (factory, opts) => runStagger(createSupervisor, watchCompletion, items, delay, factory, opts),
|
|
253
|
+
tween: ((opts) => run("tween", opts, ({ target, ...own }, item, i) => {
|
|
254
|
+
const shared = {
|
|
255
|
+
...own,
|
|
256
|
+
ms: resolvePerItem(opts.ms, item, i),
|
|
257
|
+
easing: opts.easing,
|
|
258
|
+
interpolate: opts.interpolate,
|
|
259
|
+
interpolator: opts.interpolator
|
|
260
|
+
};
|
|
261
|
+
return target ? animator.tween({ ...shared, target }) : animator.tween(shared);
|
|
262
|
+
})),
|
|
263
|
+
spring: ((opts) => run("spring", opts, ({ target, ...own }, item, i) => {
|
|
264
|
+
const shared = {
|
|
265
|
+
...own,
|
|
266
|
+
velocity: opts.velocity === void 0 ? void 0 : resolvePerItem(opts.velocity, item, i),
|
|
267
|
+
preset: opts.preset,
|
|
268
|
+
stiffness: opts.stiffness,
|
|
269
|
+
damping: opts.damping,
|
|
270
|
+
mass: opts.mass,
|
|
271
|
+
restThreshold: opts.restThreshold
|
|
272
|
+
};
|
|
273
|
+
if (target) return animator.spring({ ...shared, target });
|
|
274
|
+
const { interpolate, add, subtract, scale, magnitude } = opts;
|
|
275
|
+
return animator.spring({ ...shared, interpolate, add, subtract, scale, magnitude });
|
|
276
|
+
}))
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/supervisor.ts
|
|
281
|
+
function supervisors(host) {
|
|
282
|
+
return ({ kind, cancelKey, label }) => {
|
|
283
|
+
const id = host.nextId();
|
|
284
|
+
const atEnd = host.atEnd();
|
|
285
|
+
let onCancelCb;
|
|
286
|
+
let onRateCb;
|
|
287
|
+
let onFrameCb;
|
|
288
|
+
const base = host.register({
|
|
289
|
+
id,
|
|
290
|
+
kind,
|
|
291
|
+
cancelKey,
|
|
292
|
+
label,
|
|
293
|
+
tick: (virtualNow) => {
|
|
294
|
+
if (onFrameCb) host.afterTicks(() => onFrameCb?.(virtualNow));
|
|
295
|
+
return false;
|
|
296
|
+
},
|
|
297
|
+
onCancel: () => onCancelCb?.(),
|
|
298
|
+
onRate: (paused, timeScale) => onRateCb?.(paused, timeScale)
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
id: base.id,
|
|
302
|
+
cancel: base.cancel,
|
|
303
|
+
pause: base.pause,
|
|
304
|
+
resume: base.resume,
|
|
305
|
+
setTimeScale: base.setTimeScale,
|
|
306
|
+
timeScale: base.timeScale,
|
|
307
|
+
isPaused: base.isPaused,
|
|
308
|
+
rate: () => host.rateOf(id),
|
|
309
|
+
atEnd,
|
|
310
|
+
starting: host.starting,
|
|
311
|
+
setOnCancel: (cb) => {
|
|
312
|
+
onCancelCb = cb;
|
|
313
|
+
},
|
|
314
|
+
setOnRate: (cb) => {
|
|
315
|
+
onRateCb = cb;
|
|
316
|
+
},
|
|
317
|
+
setOnFrame: (cb) => {
|
|
318
|
+
onFrameCb = cb;
|
|
319
|
+
},
|
|
320
|
+
finish: () => host.finish(id),
|
|
321
|
+
cancelKey
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/engine/decay.ts
|
|
327
|
+
function decayAsPhysics(o) {
|
|
328
|
+
const shared = {
|
|
329
|
+
from: o.from,
|
|
330
|
+
to: null,
|
|
331
|
+
velocity: o.velocity,
|
|
332
|
+
stiffness: 0,
|
|
333
|
+
damping: -Math.log(o.friction ?? 0.95),
|
|
334
|
+
mass: 1,
|
|
335
|
+
restThreshold: o.threshold ?? 0.5,
|
|
336
|
+
onTick: o.onTick,
|
|
337
|
+
onDone: o.onDone,
|
|
338
|
+
cancelKey: o.cancelKey,
|
|
339
|
+
label: o.label
|
|
340
|
+
};
|
|
341
|
+
if (o.target) return { ...shared, target: o.target, interrupt: o.interrupt };
|
|
342
|
+
return {
|
|
343
|
+
...shared,
|
|
344
|
+
from: o.from,
|
|
345
|
+
onTick: o.onTick,
|
|
346
|
+
add: o.add,
|
|
347
|
+
// DecayOptions doesn't carry subtract; derive from add+scale.
|
|
348
|
+
subtract: (a, b) => o.add(a, o.scale(b, -1)),
|
|
349
|
+
scale: o.scale,
|
|
350
|
+
magnitude: o.magnitude
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/engine/holds.ts
|
|
355
|
+
function holdsOf(s) {
|
|
356
|
+
if (s.holds) return s.holds;
|
|
357
|
+
if (s.row) return s.holds = { store: s.row.table.holds(), row: s.row.index };
|
|
358
|
+
const store = createHoldStore(s.axes.count, 1, (_, out, at) => committedInto(s, out, at), s.axes.angles);
|
|
359
|
+
return s.holds = { store, row: 0 };
|
|
360
|
+
}
|
|
361
|
+
function both(s, skip, committed) {
|
|
362
|
+
const { store, row } = s.holds;
|
|
363
|
+
const n = committed.length;
|
|
364
|
+
const all = { delta: new Array(n), total: new Array(n) };
|
|
365
|
+
const rest = { delta: new Array(n), total: new Array(n) };
|
|
366
|
+
store.fold(row, committed, 0, null, all.delta, all.total);
|
|
367
|
+
store.fold(row, committed, 0, skip, rest.delta, rest.total);
|
|
368
|
+
return { all, rest, own: store.valueOf(skip, row) };
|
|
369
|
+
}
|
|
370
|
+
function settle(s, f, covers, committed) {
|
|
371
|
+
const { all, rest, own } = f;
|
|
372
|
+
const angles = s.axes.angles ?? [];
|
|
373
|
+
return committed.map((c, a) => {
|
|
374
|
+
if (covers && !covers[a]) return 0;
|
|
375
|
+
if (rest.total[a] >= 1 - 1e-9) return angles.includes(a) ? shortWay(own[a] - c) : own[a] - c;
|
|
376
|
+
return (all.delta[a] - rest.delta[a]) / (1 - rest.total[a]);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function dim(s, before, after) {
|
|
380
|
+
for (const b of s.behind) {
|
|
381
|
+
for (let a = 0; a < before.length; a++) {
|
|
382
|
+
const open = 1 - Math.min(after[a], 1);
|
|
383
|
+
const was = 1 - Math.min(before[a], 1);
|
|
384
|
+
if (open < 1e-9 || open === was) continue;
|
|
385
|
+
b.kept ??= new Array(before.length).fill(1);
|
|
386
|
+
b.kept[a] = b.kept[a] * was / open;
|
|
387
|
+
s.dimmed = true;
|
|
388
|
+
}
|
|
389
|
+
if (b.kept) b.move?.kept(b.kept);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function coverOf(wanted, axes) {
|
|
393
|
+
if (!wanted) return null;
|
|
394
|
+
const covers = new Uint8Array(axes.count);
|
|
395
|
+
for (const a of axisIndexes(axes, wanted)) covers[a] = 1;
|
|
396
|
+
return covers;
|
|
397
|
+
}
|
|
398
|
+
function enterHold(ctx, s, layer, sources) {
|
|
399
|
+
ctx.targets.attach(s);
|
|
400
|
+
s.held += 1;
|
|
401
|
+
const { store, row } = holdsOf(s);
|
|
402
|
+
const l = layer(store, row);
|
|
403
|
+
store.join(l, row, sources);
|
|
404
|
+
ctx.targets.holding(store, 1);
|
|
405
|
+
return { store, row, layer: l };
|
|
406
|
+
}
|
|
407
|
+
function leaveHold(ctx, s, layer, covers, commit) {
|
|
408
|
+
const { store, row } = s.holds;
|
|
409
|
+
if (commit && !s.discard) {
|
|
410
|
+
store.refold(row);
|
|
411
|
+
const committed = s.axes.to(s.get());
|
|
412
|
+
const f = both(s, layer, committed);
|
|
413
|
+
absorb(s, settle(s, f, covers, committed));
|
|
414
|
+
dim(s, f.all.total, f.rest.total);
|
|
415
|
+
}
|
|
416
|
+
store.part(layer, row);
|
|
417
|
+
ctx.targets.holding(store, -1);
|
|
418
|
+
s.held -= 1;
|
|
419
|
+
if (s.held === 0) {
|
|
420
|
+
s.holds = null;
|
|
421
|
+
const beside = s.beside;
|
|
422
|
+
if (beside) {
|
|
423
|
+
setBeside(s, null);
|
|
424
|
+
absorb(s, beside);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
ctx.targets.detach(s);
|
|
428
|
+
}
|
|
429
|
+
function openHold(ctx, o) {
|
|
430
|
+
const s = o.target;
|
|
431
|
+
const ax = s.axes;
|
|
432
|
+
const state = s;
|
|
433
|
+
axesFor(ax, o.value(), "value");
|
|
434
|
+
let gone = false;
|
|
435
|
+
const covers = coverOf(o.axes, ax);
|
|
436
|
+
const { layer } = enterHold(ctx, state, (store, row) => store.free(row), {
|
|
437
|
+
value: ax.write ? (out, at) => ax.write(o.value(), out, at) : (out, at) => out.set(ax.to(o.value()), at),
|
|
438
|
+
weight: o.weight,
|
|
439
|
+
covers,
|
|
440
|
+
fixed: o.fixed === true
|
|
441
|
+
});
|
|
442
|
+
ctx.touch(state, true);
|
|
443
|
+
return {
|
|
444
|
+
touch: () => {
|
|
445
|
+
if (!gone) ctx.touch(state);
|
|
446
|
+
},
|
|
447
|
+
leave(commit) {
|
|
448
|
+
if (gone) return;
|
|
449
|
+
gone = true;
|
|
450
|
+
leaveHold(ctx, state, layer, covers, commit);
|
|
451
|
+
ctx.touch(state, true);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/engine/holdAnimation.ts
|
|
457
|
+
var isFn = (x) => typeof x === "function";
|
|
458
|
+
function startHold(ctx, o) {
|
|
459
|
+
const target = stateOf(o.target);
|
|
460
|
+
const id = ctx.nextId();
|
|
461
|
+
const { value, weight = 1 } = o;
|
|
462
|
+
const fixed = !isFn(value) && !isFn(weight);
|
|
463
|
+
const seed = {
|
|
464
|
+
id,
|
|
465
|
+
kind: "hold",
|
|
466
|
+
cancelKey: o.cancelKey,
|
|
467
|
+
label: o.label,
|
|
468
|
+
target,
|
|
469
|
+
keepExisting: o.interrupt === false,
|
|
470
|
+
idle: fixed,
|
|
471
|
+
// It leaves when released or canceled, and has nothing to do on a frame.
|
|
472
|
+
due: Infinity
|
|
473
|
+
};
|
|
474
|
+
if (o.cancelKey != null && o.interrupt !== false) ctx.interrupt(o.cancelKey, seed);
|
|
475
|
+
const held = openHold(ctx, {
|
|
476
|
+
target,
|
|
477
|
+
value: isFn(value) ? value : () => value,
|
|
478
|
+
weight: isFn(weight) ? weight : () => weight,
|
|
479
|
+
axes: o.axes,
|
|
480
|
+
fixed
|
|
481
|
+
});
|
|
482
|
+
seed.onCancel = () => held.leave(true);
|
|
483
|
+
seed.tick = () => {
|
|
484
|
+
if (!ctx.tripwire()) return false;
|
|
485
|
+
held.leave(true);
|
|
486
|
+
return true;
|
|
487
|
+
};
|
|
488
|
+
const base = ctx.register(seed);
|
|
489
|
+
return {
|
|
490
|
+
...base,
|
|
491
|
+
touch: held.touch,
|
|
492
|
+
release(r) {
|
|
493
|
+
if (!ctx.has(id)) return;
|
|
494
|
+
held.leave(r?.commit !== false);
|
|
495
|
+
ctx.finish(id);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/engine/holdRows.ts
|
|
501
|
+
function startHoldRows(ctx, o) {
|
|
502
|
+
const { table } = o;
|
|
503
|
+
const rows = rowTableOf(table);
|
|
504
|
+
const n = table.axes;
|
|
505
|
+
const store = rows.holds();
|
|
506
|
+
const id = ctx.nextId();
|
|
507
|
+
const seed = {
|
|
508
|
+
id,
|
|
509
|
+
kind: "hold",
|
|
510
|
+
cancelKey: o.cancelKey,
|
|
511
|
+
label: o.label,
|
|
512
|
+
keepExisting: o.interrupt === false,
|
|
513
|
+
idle: true,
|
|
514
|
+
due: Infinity
|
|
515
|
+
};
|
|
516
|
+
if (o.cancelKey != null && o.interrupt !== false) ctx.interrupt(o.cancelKey, seed);
|
|
517
|
+
let layer;
|
|
518
|
+
const held = /* @__PURE__ */ new Map();
|
|
519
|
+
const weight = o.weight ?? 1;
|
|
520
|
+
let covers = null;
|
|
521
|
+
const letGo = (row, commit) => {
|
|
522
|
+
const h = held.get(row);
|
|
523
|
+
if (!h) return;
|
|
524
|
+
held.delete(row);
|
|
525
|
+
h.s.parts.delete(h.part);
|
|
526
|
+
leaveHold(ctx, h.s, layer, covers, commit);
|
|
527
|
+
rows.mark(row);
|
|
528
|
+
};
|
|
529
|
+
for (const row of o.rows ? Array.from(o.rows) : rows.rows()) {
|
|
530
|
+
if (held.has(row)) continue;
|
|
531
|
+
const s = stateOf(table.target(row));
|
|
532
|
+
covers ??= coverOf(o.axes, s.axes);
|
|
533
|
+
enterHold(ctx, s, (st) => layer ??= st.layer(), { covers, fixed: true });
|
|
534
|
+
layer.values.set(table.committed.subarray(row * n, (row + 1) * n), row * n);
|
|
535
|
+
layer.weights[row] = weight;
|
|
536
|
+
const part = () => letGo(row, true);
|
|
537
|
+
s.parts.add(part);
|
|
538
|
+
held.set(row, { s, part });
|
|
539
|
+
}
|
|
540
|
+
layer ??= store.layer();
|
|
541
|
+
const touch = () => {
|
|
542
|
+
for (const row of held.keys()) store.refold(row);
|
|
543
|
+
rows.refill();
|
|
544
|
+
ctx.wake();
|
|
545
|
+
};
|
|
546
|
+
const leave = (commit) => {
|
|
547
|
+
for (const row of [...held.keys()]) letGo(row, commit);
|
|
548
|
+
store.drop(layer);
|
|
549
|
+
rows.refill();
|
|
550
|
+
ctx.wake();
|
|
551
|
+
};
|
|
552
|
+
touch();
|
|
553
|
+
seed.onCancel = () => leave(true);
|
|
554
|
+
seed.tick = () => {
|
|
555
|
+
if (!ctx.tripwire()) return false;
|
|
556
|
+
leave(true);
|
|
557
|
+
return true;
|
|
558
|
+
};
|
|
559
|
+
const base = ctx.register(seed);
|
|
560
|
+
return {
|
|
561
|
+
...base,
|
|
562
|
+
get values() {
|
|
563
|
+
return layer.values;
|
|
564
|
+
},
|
|
565
|
+
get weights() {
|
|
566
|
+
return layer.weights;
|
|
567
|
+
},
|
|
568
|
+
touch,
|
|
569
|
+
release(r) {
|
|
570
|
+
if (!ctx.has(id)) return;
|
|
571
|
+
leave(r?.commit !== false);
|
|
572
|
+
ctx.finish(id);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/engine/integrator.ts
|
|
578
|
+
function stepSpring(ops, value, velocity, target, k, damping, mass, dt) {
|
|
579
|
+
const ref = target ?? value;
|
|
580
|
+
const displacement = ops.subtract(value, ref);
|
|
581
|
+
const springForce = ops.scale(displacement, target == null ? 0 : -k);
|
|
582
|
+
const dampingForce = ops.scale(velocity, -damping);
|
|
583
|
+
const accel = ops.scale(ops.add(springForce, dampingForce), 1 / mass);
|
|
584
|
+
const v = ops.add(velocity, ops.scale(accel, dt));
|
|
585
|
+
return { value: ops.add(value, ops.scale(v, dt)), velocity: v };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/engine/physicsAnimation.ts
|
|
589
|
+
function resolveSpringConstants(o) {
|
|
590
|
+
const preset = o.preset ? SPRING_PRESETS[o.preset] : null;
|
|
591
|
+
return {
|
|
592
|
+
stiffness: o.stiffness ?? preset?.stiffness ?? 170,
|
|
593
|
+
damping: o.damping ?? preset?.damping ?? 26,
|
|
594
|
+
mass: o.mass ?? preset?.mass ?? 1
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function startPhysics(ctx, o, kind) {
|
|
598
|
+
const { codec } = ctx;
|
|
599
|
+
const id = ctx.nextId();
|
|
600
|
+
const isNumeric = typeof o.from === "number";
|
|
601
|
+
if (!isNumeric && (!o.add || !o.subtract || !o.scale || !o.magnitude)) {
|
|
602
|
+
throw new Error("physics: add/subtract/scale/magnitude are required for non-numeric T");
|
|
603
|
+
}
|
|
604
|
+
const add = o.add ?? ((a, b) => a + b);
|
|
605
|
+
const subtract = o.subtract ?? ((a, b) => a - b);
|
|
606
|
+
const scale = o.scale ?? ((v, k) => v * k);
|
|
607
|
+
const magnitude = o.magnitude ?? ((v) => Math.abs(v));
|
|
608
|
+
const { stiffness: kBase, damping, mass } = resolveSpringConstants(o);
|
|
609
|
+
const restThreshold = o.restThreshold ?? 0.01;
|
|
610
|
+
let target = o.to ?? null;
|
|
611
|
+
let value = o.from;
|
|
612
|
+
let velocity = o.velocity ?? scale(o.from, 0);
|
|
613
|
+
let lastTime = null;
|
|
614
|
+
const solvable = damping > 0 && mass > 0 && (kBase > 0 || kind === "decay");
|
|
615
|
+
const fromAxes = solvable ? axesOf(o.from) : null;
|
|
616
|
+
const sameShape = (v) => v == null || axesOf(v)?.shape === fromAxes.shape;
|
|
617
|
+
const axes = fromAxes && sameShape(o.to) && sameShape(o.velocity) ? fromAxes : null;
|
|
618
|
+
let reader = null;
|
|
619
|
+
if (axes) {
|
|
620
|
+
ctx.seedClock();
|
|
621
|
+
reader = codec.spring({
|
|
622
|
+
id,
|
|
623
|
+
axes: axes.count,
|
|
624
|
+
from: axes.to(o.from),
|
|
625
|
+
to: o.to == null ? null : axes.to(o.to),
|
|
626
|
+
velocity: o.velocity == null ? new Array(axes.count).fill(0) : axes.to(o.velocity),
|
|
627
|
+
stiffness: kBase,
|
|
628
|
+
damping,
|
|
629
|
+
mass
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
const end = () => {
|
|
633
|
+
codec.stop(id);
|
|
634
|
+
if (ctx.has(id)) {
|
|
635
|
+
ctx.reportEnd(id);
|
|
636
|
+
o.onDone?.();
|
|
637
|
+
}
|
|
638
|
+
return true;
|
|
639
|
+
};
|
|
640
|
+
const baseHandle = ctx.register({
|
|
641
|
+
id,
|
|
642
|
+
kind,
|
|
643
|
+
cancelKey: o.cancelKey,
|
|
644
|
+
label: o.label,
|
|
645
|
+
tick(nowMs, rate) {
|
|
646
|
+
if (ctx.tripwire()) {
|
|
647
|
+
codec.stop(id);
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
if (rate < 0 && (nowMs <= 0 || axes && codec.rewound(id))) {
|
|
651
|
+
o.onTick(axes ? axes.from(reader.motion().value) : o.from);
|
|
652
|
+
return end();
|
|
653
|
+
}
|
|
654
|
+
if (lastTime == null) {
|
|
655
|
+
lastTime = nowMs;
|
|
656
|
+
if (target == null && magnitude(velocity) < restThreshold) return end();
|
|
657
|
+
if (!axes) {
|
|
658
|
+
o.onTick(value);
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (axes) {
|
|
663
|
+
const m = reader.motion();
|
|
664
|
+
value = axes.from(m.value);
|
|
665
|
+
velocity = axes.from(m.velocity);
|
|
666
|
+
} else {
|
|
667
|
+
const dt = Math.max(-0.064, Math.min(0.064, (nowMs - lastTime) / 1e3));
|
|
668
|
+
const next = stepSpring({ add, subtract, scale }, value, velocity, target, kBase, damping, mass, dt);
|
|
669
|
+
value = next.value;
|
|
670
|
+
velocity = next.velocity;
|
|
671
|
+
}
|
|
672
|
+
lastTime = nowMs;
|
|
673
|
+
o.onTick(value);
|
|
674
|
+
const velRested = magnitude(velocity) < restThreshold;
|
|
675
|
+
const posRested = target == null ? true : magnitude(subtract(value, target)) < restThreshold;
|
|
676
|
+
if (!velRested || !posRested) return false;
|
|
677
|
+
if (target != null) o.onTick(target);
|
|
678
|
+
return end();
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
const checkShape = (v, what) => {
|
|
682
|
+
if (!sameShape(v)) throw new Error(`physics: ${what} is not the same shape as \`from\``);
|
|
683
|
+
};
|
|
684
|
+
return {
|
|
685
|
+
...baseHandle,
|
|
686
|
+
setTarget: (newTo) => {
|
|
687
|
+
if (axes && newTo != null) checkShape(newTo, "setTarget");
|
|
688
|
+
target = newTo;
|
|
689
|
+
if (axes && codec.has(id)) codec.retarget(id, newTo == null ? null : axes.to(newTo));
|
|
690
|
+
},
|
|
691
|
+
setVelocity: (v) => {
|
|
692
|
+
if (axes) checkShape(v, "setVelocity");
|
|
693
|
+
velocity = v;
|
|
694
|
+
if (axes && codec.has(id)) codec.push(id, axes.to(v));
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/engine/springRest.ts
|
|
700
|
+
function earliestRest(value, velocity, stiffness, damping, mass, threshold) {
|
|
701
|
+
let energy = 0;
|
|
702
|
+
for (let i = 0; i < value.length; i++) energy += stiffness * value[i] ** 2 + mass * velocity[i] ** 2;
|
|
703
|
+
const atRest = (stiffness + mass) * threshold ** 2;
|
|
704
|
+
if (!(energy > atRest)) return 0;
|
|
705
|
+
return 1e3 * mass / (2 * damping) * Math.log(energy / atRest);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/engine/springVoices.ts
|
|
709
|
+
function createSpringVoices(codec, s, o) {
|
|
710
|
+
const n = s.axes.count;
|
|
711
|
+
const zeros2 = () => new Array(n).fill(0);
|
|
712
|
+
const moving = { id: -1 };
|
|
713
|
+
const resting = { id: -1 };
|
|
714
|
+
let first = true;
|
|
715
|
+
let rate = 1;
|
|
716
|
+
let coasting = false;
|
|
717
|
+
const of = (v) => (v.id >= 0 ? codec.own(v.id) : void 0) ?? { value: zeros2(), velocity: zeros2() };
|
|
718
|
+
const adjust = (v, delta, sign) => {
|
|
719
|
+
if (v === moving) bumpOn(s, o.behind, delta, sign);
|
|
720
|
+
else bump(s, delta, sign);
|
|
721
|
+
};
|
|
722
|
+
const cue = (v, from, velocity, to) => {
|
|
723
|
+
if (v.id >= 0) {
|
|
724
|
+
const was = codec.own(v.id).value;
|
|
725
|
+
codec.recue(v.id, from.slice(), velocity.slice(), to);
|
|
726
|
+
adjust(v, minus(from, was), 1);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
v.id = first ? o.id : o.nextId();
|
|
730
|
+
first = false;
|
|
731
|
+
const on = v === moving ? runsOn(s, o.behind) : s;
|
|
732
|
+
seat(s, on, codec.spring({
|
|
733
|
+
id: v.id,
|
|
734
|
+
on,
|
|
735
|
+
axes: n,
|
|
736
|
+
from: from.slice(),
|
|
737
|
+
to,
|
|
738
|
+
velocity: velocity.slice(),
|
|
739
|
+
stiffness: o.stiffness,
|
|
740
|
+
damping: o.damping,
|
|
741
|
+
mass: o.mass
|
|
742
|
+
}));
|
|
743
|
+
if (rate !== 1) codec.rate(v.id, rate);
|
|
744
|
+
adjust(v, from, 1);
|
|
745
|
+
};
|
|
746
|
+
const drop = (v) => {
|
|
747
|
+
if (v.id < 0) return;
|
|
748
|
+
const own = codec.own(v.id);
|
|
749
|
+
if (own) adjust(v, own.value, -1);
|
|
750
|
+
codec.stop(v.id);
|
|
751
|
+
v.id = -1;
|
|
752
|
+
};
|
|
753
|
+
return {
|
|
754
|
+
own() {
|
|
755
|
+
const a = of(moving);
|
|
756
|
+
const b = of(resting);
|
|
757
|
+
return { value: plus(a.value, b.value), velocity: plus(a.velocity, b.velocity) };
|
|
758
|
+
},
|
|
759
|
+
moved: () => of(moving).value,
|
|
760
|
+
move(delta) {
|
|
761
|
+
const m = of(moving);
|
|
762
|
+
cue(moving, plus(m.value, delta), m.velocity, zeros2());
|
|
763
|
+
},
|
|
764
|
+
settleMove: () => drop(moving),
|
|
765
|
+
rest(from, velocity, coast) {
|
|
766
|
+
coasting = coast;
|
|
767
|
+
cue(resting, from, velocity, coast ? null : zeros2());
|
|
768
|
+
},
|
|
769
|
+
push(velocity) {
|
|
770
|
+
const v = minus(velocity, of(moving).velocity);
|
|
771
|
+
if (resting.id < 0) cue(resting, zeros2(), v, coasting ? null : zeros2());
|
|
772
|
+
else codec.push(resting.id, v);
|
|
773
|
+
},
|
|
774
|
+
rate(r) {
|
|
775
|
+
rate = r;
|
|
776
|
+
for (const v of [moving, resting]) if (v.id >= 0 && codec.has(v.id)) codec.rate(v.id, r);
|
|
777
|
+
},
|
|
778
|
+
rewound: () => (moving.id >= 0 || resting.id >= 0) && [moving, resting].every((v) => v.id < 0 || codec.rewound(v.id)),
|
|
779
|
+
stop() {
|
|
780
|
+
drop(moving);
|
|
781
|
+
drop(resting);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/engine/offsetPhysics.ts
|
|
787
|
+
var any = (a) => a.some((x) => x !== 0);
|
|
788
|
+
function constantsFor(o, kind, toward) {
|
|
789
|
+
const c = resolveSpringConstants(o);
|
|
790
|
+
if (!(c.damping > 0) || !(c.mass > 0) || toward && !(c.stiffness > 0)) {
|
|
791
|
+
throw new Error(`${kind}: an animation on a target needs positive damping and mass, and stiffness to reach \`to\``);
|
|
792
|
+
}
|
|
793
|
+
return c;
|
|
794
|
+
}
|
|
795
|
+
function restWaits(ctx, id, quiet, c, threshold) {
|
|
796
|
+
const until = (value, velocity, coast) => earliestRest(value, velocity, coast ? 0 : c.stiffness, c.damping, c.mass, threshold);
|
|
797
|
+
return {
|
|
798
|
+
/** The `due` it registers with. */
|
|
799
|
+
first: (value, velocity, coast) => quiet ? until(value, velocity, coast) : void 0,
|
|
800
|
+
/** Leaves it unticked until then. Answers false, as the tick of one still moving does. */
|
|
801
|
+
again(value, velocity, coast) {
|
|
802
|
+
if (quiet) ctx.defer(id, until(value, velocity, coast));
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function startVoices(ctx, s, o, move, start, velocity, coast) {
|
|
808
|
+
const behind = goBehind(s, () => voices.moved(), () => voices.moved(), new Array(move.length).fill(1));
|
|
809
|
+
const voices = createSpringVoices(ctx.codec, s, { ...o, behind });
|
|
810
|
+
if (any(move)) voices.move(move);
|
|
811
|
+
const top = minus(start, move);
|
|
812
|
+
if (!any(move) || any(top) || any(velocity)) voices.rest(top, velocity, coast);
|
|
813
|
+
return { voices, behind };
|
|
814
|
+
}
|
|
815
|
+
function startOffsetPhysics(ctx, o, kind) {
|
|
816
|
+
const s = stateOf(o.target);
|
|
817
|
+
const ax = s.axes;
|
|
818
|
+
let dest = o.to == null ? null : axesFor(ax, o.to, "to");
|
|
819
|
+
const constants = constantsFor(o, kind, dest != null);
|
|
820
|
+
const { stiffness } = constants;
|
|
821
|
+
const given = o.from === void 0 ? null : axesFor(ax, o.from, "from");
|
|
822
|
+
let velocity = o.velocity === void 0 ? null : axesFor(ax, o.velocity, "velocity");
|
|
823
|
+
const restThreshold = o.restThreshold ?? 0.01;
|
|
824
|
+
const zeros2 = () => new Array(ax.count).fill(0);
|
|
825
|
+
const id = ctx.nextId();
|
|
826
|
+
const waits = restWaits(ctx, id, !o.onTick, constants, restThreshold);
|
|
827
|
+
const interrupts = o.interrupt !== false;
|
|
828
|
+
const seed = {
|
|
829
|
+
id,
|
|
830
|
+
kind,
|
|
831
|
+
cancelKey: o.cancelKey,
|
|
832
|
+
label: o.label,
|
|
833
|
+
target: s,
|
|
834
|
+
keepExisting: !interrupts
|
|
835
|
+
};
|
|
836
|
+
if (o.cancelKey != null && interrupts) {
|
|
837
|
+
velocity ??= ctx.velocityUnder(o.cancelKey, s);
|
|
838
|
+
ctx.interrupt(o.cancelKey, seed);
|
|
839
|
+
}
|
|
840
|
+
const before = ax.to(s.get());
|
|
841
|
+
const committed = dest ? takeUp(s) : before;
|
|
842
|
+
const start = travel(ax, given ?? committed, dest ?? committed);
|
|
843
|
+
const move = dest ? travel(ax, before, dest) : zeros2();
|
|
844
|
+
ctx.targets.attach(s);
|
|
845
|
+
if (o.to != null) s.set(o.to);
|
|
846
|
+
ctx.seedClock();
|
|
847
|
+
const { voices, behind } = startVoices(
|
|
848
|
+
ctx,
|
|
849
|
+
s,
|
|
850
|
+
{ id, nextId: ctx.nextId, ...constants },
|
|
851
|
+
move,
|
|
852
|
+
start,
|
|
853
|
+
velocity ?? zeros2(),
|
|
854
|
+
dest == null
|
|
855
|
+
);
|
|
856
|
+
let left = false;
|
|
857
|
+
const leave = (commit) => {
|
|
858
|
+
if (left) return;
|
|
859
|
+
left = true;
|
|
860
|
+
const moved = voices.moved();
|
|
861
|
+
const shown = comeOut(s, behind, moved);
|
|
862
|
+
if (commit) commitOffset(s, voices.own().value, moved, shown);
|
|
863
|
+
voices.stop();
|
|
864
|
+
ctx.targets.detach(s);
|
|
865
|
+
};
|
|
866
|
+
const finish = (commit = dest == null) => {
|
|
867
|
+
leave(commit);
|
|
868
|
+
ctx.reportEnd(id);
|
|
869
|
+
o.onDone?.();
|
|
870
|
+
return true;
|
|
871
|
+
};
|
|
872
|
+
const shows = (own) => ax.from(plus(dest ?? ax.to(s.get()), own));
|
|
873
|
+
let ticked = false;
|
|
874
|
+
seed.due = waits.first(start, velocity ?? zeros2(), dest == null);
|
|
875
|
+
seed.onCancel = () => leave(true);
|
|
876
|
+
seed.onRate = (_paused, _timeScale, rate) => voices.rate(rate);
|
|
877
|
+
seed.velocity = () => voices.own().velocity;
|
|
878
|
+
seed.tick = (nowMs, scale) => {
|
|
879
|
+
if (ctx.tripwire()) {
|
|
880
|
+
leave(true);
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
const own = voices.own();
|
|
884
|
+
if (scale < 0 && (nowMs <= 0 || voices.rewound())) {
|
|
885
|
+
o.onTick?.(shows(own.value));
|
|
886
|
+
return ctx.has(id) ? finish(true) : true;
|
|
887
|
+
}
|
|
888
|
+
if (!ticked && dest == null && norm(own.velocity) < restThreshold) return finish();
|
|
889
|
+
ticked = true;
|
|
890
|
+
o.onTick?.(shows(own.value));
|
|
891
|
+
if (!ctx.has(id)) return true;
|
|
892
|
+
if (norm(own.velocity) >= restThreshold || dest && norm(own.value) >= restThreshold) {
|
|
893
|
+
return waits.again(own.value, own.velocity, dest == null);
|
|
894
|
+
}
|
|
895
|
+
if (dest) o.onTick?.(ax.from(dest));
|
|
896
|
+
return ctx.has(id) ? finish() : true;
|
|
897
|
+
};
|
|
898
|
+
const base = ctx.register(seed);
|
|
899
|
+
return {
|
|
900
|
+
...base,
|
|
901
|
+
setTarget(next) {
|
|
902
|
+
if (left) return;
|
|
903
|
+
const own = voices.own();
|
|
904
|
+
if (next == null) {
|
|
905
|
+
if (dest == null) return;
|
|
906
|
+
const moved = voices.moved();
|
|
907
|
+
absorb(s, behind.kept ? times(moved, behind.kept) : moved);
|
|
908
|
+
dest = null;
|
|
909
|
+
voices.rest(minus(own.value, moved), own.velocity, true);
|
|
910
|
+
voices.settleMove();
|
|
911
|
+
waits.again(own.value, own.velocity, true);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const to = axesFor(ax, next, "setTarget");
|
|
915
|
+
if (!(stiffness > 0)) throw new Error(`${kind}: setTarget needs a positive stiffness`);
|
|
916
|
+
const shift = travel(ax, ax.to(s.get()), to);
|
|
917
|
+
s.set(next);
|
|
918
|
+
if (dest == null) voices.rest(own.value, own.velocity, false);
|
|
919
|
+
dest = to;
|
|
920
|
+
voices.move(shift);
|
|
921
|
+
waits.again(plus(own.value, shift), own.velocity, false);
|
|
922
|
+
ctx.targets.showNow(s);
|
|
923
|
+
},
|
|
924
|
+
setVelocity(v) {
|
|
925
|
+
if (left) return;
|
|
926
|
+
const pushed = axesFor(ax, v, "setVelocity");
|
|
927
|
+
waits.again(voices.own().value, pushed, dest == null);
|
|
928
|
+
voices.push(pushed);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function startOffsetTween(ctx, o) {
|
|
933
|
+
const { codec } = ctx;
|
|
934
|
+
const s = stateOf(o.target);
|
|
935
|
+
const ax = s.axes;
|
|
936
|
+
const to = axesFor(ax, o.to, "to");
|
|
937
|
+
const given = o.from === void 0 ? null : axesFor(ax, o.from, "from");
|
|
938
|
+
const id = ctx.nextId();
|
|
939
|
+
const interrupts = o.interrupt !== false;
|
|
940
|
+
const seed = {
|
|
941
|
+
id,
|
|
942
|
+
kind: "tween",
|
|
943
|
+
cancelKey: o.cancelKey,
|
|
944
|
+
label: o.label,
|
|
945
|
+
target: s,
|
|
946
|
+
keepExisting: !interrupts
|
|
947
|
+
};
|
|
948
|
+
if (o.cancelKey != null && interrupts) ctx.interrupt(o.cancelKey, seed);
|
|
949
|
+
const committed = ax.to(s.get());
|
|
950
|
+
const base = takeUp(s);
|
|
951
|
+
const from = o.from === void 0 ? ax.from(base) : o.from;
|
|
952
|
+
const path = o.interpolator ? o.interpolator(from, o.to) : o.interpolate ? (u) => o.interpolate(from, o.to, u) : null;
|
|
953
|
+
const easing = resolveEasing(o.easing ?? easeOut);
|
|
954
|
+
const ms = Math.max(o.ms, 1);
|
|
955
|
+
ctx.targets.attach(s);
|
|
956
|
+
s.set(o.to);
|
|
957
|
+
const atEnd = ctx.atEnd();
|
|
958
|
+
ctx.seedClock();
|
|
959
|
+
const offsetAt = (u) => minus(ax.to(path(easing(u))), to);
|
|
960
|
+
const first = path ? offsetAt(0) : travel(ax, given ?? base, to);
|
|
961
|
+
const share = shareBehind(ax, committed, to, travel(ax, given ?? base, to));
|
|
962
|
+
const own = () => codec.own(id).value;
|
|
963
|
+
const behind = goBehind(s, own, () => times(own(), share), share);
|
|
964
|
+
const on = runsOn(s, behind);
|
|
965
|
+
seat(s, on, path ? codec.path({ id, on, ms, axes: ax.count, at: offsetAt, atEnd }) : codec.tween({ id, on, ms, ease: easing, from: first, to: new Array(ax.count).fill(0), atEnd }));
|
|
966
|
+
bumpOn(s, behind, own(), 1);
|
|
967
|
+
let left = false;
|
|
968
|
+
const leave = (commit, at) => {
|
|
969
|
+
if (left) return;
|
|
970
|
+
left = true;
|
|
971
|
+
const own2 = codec.own(id);
|
|
972
|
+
const value = at ?? own2?.value;
|
|
973
|
+
const moved = value ? times(value, share) : [];
|
|
974
|
+
const shown = comeOut(s, behind, moved);
|
|
975
|
+
if (own2) {
|
|
976
|
+
if (commit) commitOffset(s, value, moved, shown);
|
|
977
|
+
if (!behind.move) bump(s, own2.value, -1);
|
|
978
|
+
}
|
|
979
|
+
codec.stop(id);
|
|
980
|
+
ctx.targets.detach(s);
|
|
981
|
+
};
|
|
982
|
+
const end = (atStart) => {
|
|
983
|
+
o.onTick?.(atStart ? from : o.to);
|
|
984
|
+
if (!ctx.has(id)) return true;
|
|
985
|
+
leave(atStart, atStart ? first : void 0);
|
|
986
|
+
ctx.reportEnd(id);
|
|
987
|
+
o.onDone?.();
|
|
988
|
+
return true;
|
|
989
|
+
};
|
|
990
|
+
if (atEnd) seed.startAt = Math.max(o.ms, 0);
|
|
991
|
+
seed.progress = (virtualNow) => o.ms <= 0 ? 1 : Math.min(1, Math.max(0, virtualNow / o.ms));
|
|
992
|
+
if (!o.onTick) seed.due = Math.max(o.ms, 0);
|
|
993
|
+
seed.onCancel = () => leave(true);
|
|
994
|
+
seed.tick = (nowMs, scale) => {
|
|
995
|
+
if (ctx.tripwire()) {
|
|
996
|
+
leave(true);
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
if (scale < 0 && (nowMs <= 0 || codec.rewound(id))) return end(true);
|
|
1000
|
+
if (o.ms <= 0 || nowMs >= o.ms) return end(false);
|
|
1001
|
+
o.onTick?.(ax.from(plus(to, codec.own(id).value)));
|
|
1002
|
+
return false;
|
|
1003
|
+
};
|
|
1004
|
+
return ctx.register(seed);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/engine/completions.ts
|
|
1008
|
+
function createCompletions(has) {
|
|
1009
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1010
|
+
return {
|
|
1011
|
+
fire(id) {
|
|
1012
|
+
const ls = listeners.get(id);
|
|
1013
|
+
if (!ls) return;
|
|
1014
|
+
listeners.delete(id);
|
|
1015
|
+
for (const cb of ls) cb();
|
|
1016
|
+
},
|
|
1017
|
+
watch(id, cb) {
|
|
1018
|
+
if (!has(id)) {
|
|
1019
|
+
cb();
|
|
1020
|
+
return () => {
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
const ls = listeners.get(id) ?? [];
|
|
1024
|
+
ls.push(cb);
|
|
1025
|
+
listeners.set(id, ls);
|
|
1026
|
+
return () => {
|
|
1027
|
+
const cur = listeners.get(id);
|
|
1028
|
+
if (!cur) return;
|
|
1029
|
+
const idx = cur.indexOf(cb);
|
|
1030
|
+
if (idx >= 0) cur.splice(idx, 1);
|
|
1031
|
+
if (cur.length === 0) listeners.delete(id);
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/engine/wakes.ts
|
|
1038
|
+
function createWakes() {
|
|
1039
|
+
const heap = [];
|
|
1040
|
+
const place = (w, i) => {
|
|
1041
|
+
heap[i] = w;
|
|
1042
|
+
w.heapAt = i;
|
|
1043
|
+
};
|
|
1044
|
+
const up = (w, i) => {
|
|
1045
|
+
while (i > 0) {
|
|
1046
|
+
const parent = i - 1 >> 1;
|
|
1047
|
+
const p = heap[parent];
|
|
1048
|
+
if (p.wakeAt <= w.wakeAt) break;
|
|
1049
|
+
place(p, i);
|
|
1050
|
+
i = parent;
|
|
1051
|
+
}
|
|
1052
|
+
place(w, i);
|
|
1053
|
+
};
|
|
1054
|
+
const down = (w, i) => {
|
|
1055
|
+
const n = heap.length;
|
|
1056
|
+
for (; ; ) {
|
|
1057
|
+
let child = 2 * i + 1;
|
|
1058
|
+
if (child >= n) break;
|
|
1059
|
+
if (child + 1 < n && heap[child + 1].wakeAt < heap[child].wakeAt) child += 1;
|
|
1060
|
+
const c = heap[child];
|
|
1061
|
+
if (c.wakeAt >= w.wakeAt) break;
|
|
1062
|
+
place(c, i);
|
|
1063
|
+
i = child;
|
|
1064
|
+
}
|
|
1065
|
+
place(w, i);
|
|
1066
|
+
};
|
|
1067
|
+
const remove = (w) => {
|
|
1068
|
+
const i = w.heapAt;
|
|
1069
|
+
if (i < 0) return;
|
|
1070
|
+
w.heapAt = -1;
|
|
1071
|
+
const last = heap.pop();
|
|
1072
|
+
if (last === w) return;
|
|
1073
|
+
if (i > 0 && last.wakeAt < heap[i - 1 >> 1].wakeAt) up(last, i);
|
|
1074
|
+
else down(last, i);
|
|
1075
|
+
};
|
|
1076
|
+
return {
|
|
1077
|
+
set(w, at) {
|
|
1078
|
+
remove(w);
|
|
1079
|
+
w.wakeAt = at;
|
|
1080
|
+
heap.push(w);
|
|
1081
|
+
up(w, heap.length - 1);
|
|
1082
|
+
},
|
|
1083
|
+
remove,
|
|
1084
|
+
due(now) {
|
|
1085
|
+
const out = [];
|
|
1086
|
+
while (heap.length > 0 && heap[0].wakeAt <= now) {
|
|
1087
|
+
const w = heap[0];
|
|
1088
|
+
remove(w);
|
|
1089
|
+
out.push(w);
|
|
1090
|
+
}
|
|
1091
|
+
if (out.length > 1) out.sort((a, b) => a.seq - b.seq);
|
|
1092
|
+
return out;
|
|
1093
|
+
},
|
|
1094
|
+
clear() {
|
|
1095
|
+
for (const w of heap) w.heapAt = -1;
|
|
1096
|
+
heap.length = 0;
|
|
1097
|
+
},
|
|
1098
|
+
get size() {
|
|
1099
|
+
return heap.length;
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/engine/schedule.ts
|
|
1105
|
+
function createSchedule(elapsed, direction = () => 1) {
|
|
1106
|
+
const every = /* @__PURE__ */ new Set();
|
|
1107
|
+
const wakes = createWakes();
|
|
1108
|
+
let seq = 0;
|
|
1109
|
+
let awake = 0;
|
|
1110
|
+
let waiting = 0;
|
|
1111
|
+
const rateOf = (a) => a.paused ? 0 : a.timeScale * direction();
|
|
1112
|
+
const clockOf = (a) => a.base + (elapsed() - a.anchor) * rateOf(a);
|
|
1113
|
+
const rebase = (a) => {
|
|
1114
|
+
a.base = clockOf(a);
|
|
1115
|
+
a.anchor = elapsed();
|
|
1116
|
+
};
|
|
1117
|
+
const wait = (a) => {
|
|
1118
|
+
const rate = rateOf(a);
|
|
1119
|
+
if (rate < 0 && a.due < Infinity) {
|
|
1120
|
+
if (every.has(a)) return;
|
|
1121
|
+
wakes.remove(a);
|
|
1122
|
+
waiting -= 1;
|
|
1123
|
+
every.add(a);
|
|
1124
|
+
a.rewinding = true;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (a.rewinding) {
|
|
1128
|
+
a.rewinding = false;
|
|
1129
|
+
every.delete(a);
|
|
1130
|
+
waiting += 1;
|
|
1131
|
+
}
|
|
1132
|
+
if (rate > 0 && a.due < Infinity) wakes.set(a, a.anchor + (a.due - a.base) / rate);
|
|
1133
|
+
else wakes.remove(a);
|
|
1134
|
+
};
|
|
1135
|
+
return {
|
|
1136
|
+
clockOf,
|
|
1137
|
+
add(a) {
|
|
1138
|
+
a.base = a.startAt ?? 0;
|
|
1139
|
+
a.anchor = elapsed();
|
|
1140
|
+
a.seq = seq++;
|
|
1141
|
+
a.heapAt = -1;
|
|
1142
|
+
a.wakeAt = Infinity;
|
|
1143
|
+
a.rewinding = false;
|
|
1144
|
+
if (!a.idle) awake += 1;
|
|
1145
|
+
if (a.due === void 0) {
|
|
1146
|
+
every.add(a);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
waiting += 1;
|
|
1150
|
+
wait(a);
|
|
1151
|
+
},
|
|
1152
|
+
remove(a) {
|
|
1153
|
+
if (!a.idle) awake -= 1;
|
|
1154
|
+
if (every.delete(a)) return;
|
|
1155
|
+
waiting -= 1;
|
|
1156
|
+
wakes.remove(a);
|
|
1157
|
+
},
|
|
1158
|
+
pace(a, paused, timeScale) {
|
|
1159
|
+
rebase(a);
|
|
1160
|
+
a.paused = paused;
|
|
1161
|
+
a.timeScale = timeScale;
|
|
1162
|
+
if (!every.has(a) || a.rewinding) wait(a);
|
|
1163
|
+
},
|
|
1164
|
+
rateOf,
|
|
1165
|
+
turn(all, flip) {
|
|
1166
|
+
const each = [...all];
|
|
1167
|
+
for (const a of each) rebase(a);
|
|
1168
|
+
flip();
|
|
1169
|
+
for (const a of each) if (!every.has(a) || a.rewinding) wait(a);
|
|
1170
|
+
},
|
|
1171
|
+
defer(a, ms) {
|
|
1172
|
+
if (!a) return;
|
|
1173
|
+
a.due = clockOf(a) + ms;
|
|
1174
|
+
if (every.delete(a)) waiting += 1;
|
|
1175
|
+
a.rewinding = false;
|
|
1176
|
+
wait(a);
|
|
1177
|
+
},
|
|
1178
|
+
ticks() {
|
|
1179
|
+
const steady = [...every];
|
|
1180
|
+
const woke = wakes.due(elapsed());
|
|
1181
|
+
if (woke.length === 0) return steady;
|
|
1182
|
+
waiting -= woke.length;
|
|
1183
|
+
const out = [];
|
|
1184
|
+
let i = 0;
|
|
1185
|
+
for (const a of woke) {
|
|
1186
|
+
a.base = Math.max(clockOf(a), a.due);
|
|
1187
|
+
a.anchor = elapsed();
|
|
1188
|
+
every.add(a);
|
|
1189
|
+
while (i < steady.length && steady[i].seq < a.seq) out.push(steady[i++]);
|
|
1190
|
+
out.push(a);
|
|
1191
|
+
}
|
|
1192
|
+
while (i < steady.length) out.push(steady[i++]);
|
|
1193
|
+
return out;
|
|
1194
|
+
},
|
|
1195
|
+
get awake() {
|
|
1196
|
+
return awake > 0;
|
|
1197
|
+
},
|
|
1198
|
+
get waiting() {
|
|
1199
|
+
return waiting;
|
|
1200
|
+
},
|
|
1201
|
+
clear() {
|
|
1202
|
+
every.clear();
|
|
1203
|
+
wakes.clear();
|
|
1204
|
+
awake = 0;
|
|
1205
|
+
waiting = 0;
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/engine/roster.ts
|
|
1211
|
+
var NONE = [];
|
|
1212
|
+
function file(index, key, a, on) {
|
|
1213
|
+
let set = index.get(key);
|
|
1214
|
+
if (on) {
|
|
1215
|
+
if (!set) index.set(key, set = /* @__PURE__ */ new Set());
|
|
1216
|
+
set.add(a);
|
|
1217
|
+
} else if (set?.delete(a) && set.size === 0) index.delete(key);
|
|
1218
|
+
}
|
|
1219
|
+
function createRoster(elapsed, direction) {
|
|
1220
|
+
const animations = /* @__PURE__ */ new Map();
|
|
1221
|
+
const schedule = createSchedule(elapsed, direction);
|
|
1222
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1223
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1224
|
+
const index = (a, on) => {
|
|
1225
|
+
if (a.cancelKey != null) file(byKey, a.cancelKey, a, on);
|
|
1226
|
+
if (a.target) file(byTarget, a.target, a, on);
|
|
1227
|
+
};
|
|
1228
|
+
const remove = (a) => {
|
|
1229
|
+
animations.delete(a.id);
|
|
1230
|
+
schedule.remove(a);
|
|
1231
|
+
index(a, false);
|
|
1232
|
+
};
|
|
1233
|
+
return {
|
|
1234
|
+
animations,
|
|
1235
|
+
schedule,
|
|
1236
|
+
under: (key) => byKey.get(key) ?? NONE,
|
|
1237
|
+
on: (target) => byTarget.get(target) ?? NONE,
|
|
1238
|
+
add(a) {
|
|
1239
|
+
const had = animations.get(a.id);
|
|
1240
|
+
if (had) remove(had);
|
|
1241
|
+
animations.set(a.id, a);
|
|
1242
|
+
schedule.add(a);
|
|
1243
|
+
index(a, true);
|
|
1244
|
+
},
|
|
1245
|
+
remove,
|
|
1246
|
+
clear() {
|
|
1247
|
+
animations.clear();
|
|
1248
|
+
schedule.clear();
|
|
1249
|
+
byKey.clear();
|
|
1250
|
+
byTarget.clear();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// src/engine/table.ts
|
|
1256
|
+
function timeScaleOf(scale) {
|
|
1257
|
+
if (!Number.isFinite(scale)) throw new Error(`animator: a time scale is a finite number, and ${scale} was given`);
|
|
1258
|
+
return scale;
|
|
1259
|
+
}
|
|
1260
|
+
function infoOf(a) {
|
|
1261
|
+
if (!a.info) {
|
|
1262
|
+
const info = { id: a.id, kind: a.kind };
|
|
1263
|
+
if (a.cancelKey != null) info.key = a.cancelKey;
|
|
1264
|
+
if (a.label != null) info.label = a.label;
|
|
1265
|
+
a.info = info;
|
|
1266
|
+
}
|
|
1267
|
+
return a.info;
|
|
1268
|
+
}
|
|
1269
|
+
function createTable(host) {
|
|
1270
|
+
const { codec, hub } = host;
|
|
1271
|
+
const roster = createRoster(host.elapsed, host.direction);
|
|
1272
|
+
const { animations, schedule, remove } = roster;
|
|
1273
|
+
const { fire: fireCompletion, watch: watchCompletion } = createCompletions((id) => animations.has(id));
|
|
1274
|
+
const reportEnd = (id) => {
|
|
1275
|
+
const a = animations.get(id);
|
|
1276
|
+
if (!a || a.ended) return;
|
|
1277
|
+
a.ended = true;
|
|
1278
|
+
if (hub.watched) hub.emit({ type: "end", animation: infoOf(a) });
|
|
1279
|
+
};
|
|
1280
|
+
const retire = (id, by, error) => {
|
|
1281
|
+
const a = animations.get(id);
|
|
1282
|
+
if (!a) return;
|
|
1283
|
+
a.onCancel?.();
|
|
1284
|
+
codec.stop(id);
|
|
1285
|
+
remove(a);
|
|
1286
|
+
if (host.resting()) host.requestFrame();
|
|
1287
|
+
fireCompletion(id);
|
|
1288
|
+
if (!hub.watched || a.ended || error) return;
|
|
1289
|
+
hub.emit(by ? { type: "interrupt", animation: infoOf(a), by: infoOf(by) } : { type: "cancel", animation: infoOf(a) });
|
|
1290
|
+
};
|
|
1291
|
+
const cancelByKey = (key, by) => {
|
|
1292
|
+
for (const a of [...roster.under(key)]) retire(a.id, by);
|
|
1293
|
+
};
|
|
1294
|
+
const rerate = (a) => {
|
|
1295
|
+
const rate = schedule.rateOf(a);
|
|
1296
|
+
if (codec.has(a.id)) codec.rate(a.id, rate);
|
|
1297
|
+
a.onRate?.(a.paused, a.timeScale, rate);
|
|
1298
|
+
};
|
|
1299
|
+
const pace = (a, paused, timeScale) => {
|
|
1300
|
+
schedule.pace(a, paused, timeScale);
|
|
1301
|
+
rerate(a);
|
|
1302
|
+
};
|
|
1303
|
+
const register = (seed) => {
|
|
1304
|
+
const anim = seed;
|
|
1305
|
+
if (seed.cancelKey != null && !seed.keepExisting) cancelByKey(seed.cancelKey, anim);
|
|
1306
|
+
anim.paused = false;
|
|
1307
|
+
anim.timeScale = 1;
|
|
1308
|
+
host.seedClock();
|
|
1309
|
+
roster.add(anim);
|
|
1310
|
+
if (host.direction() < 0) rerate(anim);
|
|
1311
|
+
if (anim.target) host.showNow(anim.target);
|
|
1312
|
+
if (hub.watched) hub.emit({ type: "start", animation: infoOf(anim) });
|
|
1313
|
+
if (animations.size > 0 && !host.framing()) host.requestFrame();
|
|
1314
|
+
const live = () => animations.get(anim.id);
|
|
1315
|
+
return {
|
|
1316
|
+
id: anim.id,
|
|
1317
|
+
cancel: () => retire(anim.id),
|
|
1318
|
+
pause: () => {
|
|
1319
|
+
const a = live();
|
|
1320
|
+
if (a) pace(a, true, a.timeScale);
|
|
1321
|
+
},
|
|
1322
|
+
resume: () => {
|
|
1323
|
+
const a = live();
|
|
1324
|
+
if (a) pace(a, false, a.timeScale);
|
|
1325
|
+
},
|
|
1326
|
+
setTimeScale: (s) => {
|
|
1327
|
+
const a = live();
|
|
1328
|
+
if (a) pace(a, a.paused, timeScaleOf(s));
|
|
1329
|
+
},
|
|
1330
|
+
timeScale: () => live()?.timeScale ?? 1,
|
|
1331
|
+
isPaused: () => live()?.paused ?? false
|
|
1332
|
+
};
|
|
1333
|
+
};
|
|
1334
|
+
return {
|
|
1335
|
+
animations,
|
|
1336
|
+
infoOf,
|
|
1337
|
+
reportEnd,
|
|
1338
|
+
retire,
|
|
1339
|
+
cancelByKey,
|
|
1340
|
+
under: roster.under,
|
|
1341
|
+
on: roster.on,
|
|
1342
|
+
pace,
|
|
1343
|
+
turn(flip) {
|
|
1344
|
+
schedule.turn(animations.values(), flip);
|
|
1345
|
+
for (const a of [...animations.values()]) if (animations.get(a.id) === a) rerate(a);
|
|
1346
|
+
},
|
|
1347
|
+
clockOf: schedule.clockOf,
|
|
1348
|
+
rateOf: schedule.rateOf,
|
|
1349
|
+
defer: (id, ms) => schedule.defer(animations.get(id), ms),
|
|
1350
|
+
ticks: schedule.ticks,
|
|
1351
|
+
get awake() {
|
|
1352
|
+
return schedule.awake;
|
|
1353
|
+
},
|
|
1354
|
+
get waiting() {
|
|
1355
|
+
return schedule.waiting;
|
|
1356
|
+
},
|
|
1357
|
+
register,
|
|
1358
|
+
finish(id) {
|
|
1359
|
+
const a = animations.get(id);
|
|
1360
|
+
if (!a) return;
|
|
1361
|
+
reportEnd(id);
|
|
1362
|
+
remove(a);
|
|
1363
|
+
fireCompletion(id);
|
|
1364
|
+
},
|
|
1365
|
+
drop(a) {
|
|
1366
|
+
remove(a);
|
|
1367
|
+
fireCompletion(a.id);
|
|
1368
|
+
},
|
|
1369
|
+
cancelAll(cleared) {
|
|
1370
|
+
const retired = [];
|
|
1371
|
+
for (const a of animations.values()) {
|
|
1372
|
+
a.onCancel?.();
|
|
1373
|
+
codec.stop(a.id);
|
|
1374
|
+
retired.push(a);
|
|
1375
|
+
}
|
|
1376
|
+
roster.clear();
|
|
1377
|
+
cleared();
|
|
1378
|
+
for (const a of retired) fireCompletion(a.id);
|
|
1379
|
+
if (hub.watched) for (const a of retired) hub.emit({ type: "cancel", animation: infoOf(a) });
|
|
1380
|
+
},
|
|
1381
|
+
watchCompletion
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function startTween(ctx, o) {
|
|
1385
|
+
const { codec } = ctx;
|
|
1386
|
+
const fromAxes = o.interpolate || o.interpolator ? null : axesOf(o.from);
|
|
1387
|
+
const toAxes = fromAxes && axesOf(o.to);
|
|
1388
|
+
const axes = fromAxes && toAxes && toAxes.shape === fromAxes.shape ? fromAxes : null;
|
|
1389
|
+
if (!axes && !o.interpolate && !o.interpolator) {
|
|
1390
|
+
throw new Error("tween: interpolate or interpolator is required for non-numeric T");
|
|
1391
|
+
}
|
|
1392
|
+
const id = ctx.nextId();
|
|
1393
|
+
const easing = resolveEasing(o.easing ?? easeOut);
|
|
1394
|
+
const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;
|
|
1395
|
+
let lastValueEmitted = false;
|
|
1396
|
+
const atEnd = ctx.atEnd();
|
|
1397
|
+
ctx.seedClock();
|
|
1398
|
+
const reader = codec.tween({
|
|
1399
|
+
id,
|
|
1400
|
+
ms: Math.max(o.ms, 1),
|
|
1401
|
+
ease: easing,
|
|
1402
|
+
from: axes ? axes.to(o.from) : [0],
|
|
1403
|
+
to: axes ? axes.to(o.to) : [1],
|
|
1404
|
+
atEnd
|
|
1405
|
+
});
|
|
1406
|
+
return ctx.register({
|
|
1407
|
+
id,
|
|
1408
|
+
kind: "tween",
|
|
1409
|
+
cancelKey: o.cancelKey,
|
|
1410
|
+
label: o.label,
|
|
1411
|
+
startAt: atEnd ? Math.max(o.ms, 0) : 0,
|
|
1412
|
+
progress: (virtualNow) => o.ms <= 0 ? 1 : Math.min(1, Math.max(0, virtualNow / o.ms)),
|
|
1413
|
+
// `nowMs` is the call's own virtual time, so it still decides when the tween ends; the codec
|
|
1414
|
+
// supplies only the value in between.
|
|
1415
|
+
tick(nowMs, scale) {
|
|
1416
|
+
if (ctx.tripwire()) {
|
|
1417
|
+
codec.stop(id);
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
const atStart = scale < 0 && (nowMs <= 0 || codec.rewound(id));
|
|
1421
|
+
const t = atStart ? 0 : o.ms <= 0 ? 1 : Math.min(1, nowMs / o.ms);
|
|
1422
|
+
const ends = atStart || t >= 1;
|
|
1423
|
+
let value;
|
|
1424
|
+
if (axes) {
|
|
1425
|
+
value = ends ? atStart ? o.from : o.to : axes.from(reader.cols, reader.at);
|
|
1426
|
+
} else {
|
|
1427
|
+
const eased = ends ? easing(t) : reader.cols[reader.at];
|
|
1428
|
+
value = factoryFn ? factoryFn(eased) : o.interpolate(o.from, o.to, eased);
|
|
1429
|
+
}
|
|
1430
|
+
o.onTick(value);
|
|
1431
|
+
if (ends && !lastValueEmitted) {
|
|
1432
|
+
lastValueEmitted = true;
|
|
1433
|
+
codec.stop(id);
|
|
1434
|
+
if (ctx.has(id)) {
|
|
1435
|
+
ctx.reportEnd(id);
|
|
1436
|
+
o.onDone?.();
|
|
1437
|
+
}
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/engine/animatorApi.ts
|
|
1446
|
+
function eachUnder(table, key, pace) {
|
|
1447
|
+
for (const a of table.under(key)) table.pace(a, ...pace(a));
|
|
1448
|
+
}
|
|
1449
|
+
function liveRows(table) {
|
|
1450
|
+
const rows = [];
|
|
1451
|
+
for (const a of table.animations.values()) {
|
|
1452
|
+
rows.push({
|
|
1453
|
+
...table.infoOf(a),
|
|
1454
|
+
paused: a.paused,
|
|
1455
|
+
timeScale: a.timeScale,
|
|
1456
|
+
elapsed: table.clockOf(a),
|
|
1457
|
+
...a.progress ? { progress: a.progress(table.clockOf(a)) } : {}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
return rows;
|
|
1461
|
+
}
|
|
1462
|
+
function startTimeline(host, o) {
|
|
1463
|
+
const { ctx, table, hub } = host;
|
|
1464
|
+
const id = ctx.nextId();
|
|
1465
|
+
const info = () => {
|
|
1466
|
+
const a = table.animations.get(id);
|
|
1467
|
+
return a && table.infoOf(a);
|
|
1468
|
+
};
|
|
1469
|
+
const hold = (target, value, weight, axes, fixed) => openHold(ctx, { target: stateOf(target), value, weight, axes, fixed });
|
|
1470
|
+
return createTimeline(table.register, id, o, {
|
|
1471
|
+
get watched() {
|
|
1472
|
+
return hub.watched;
|
|
1473
|
+
},
|
|
1474
|
+
fire: (track, path, event, lateBy) => {
|
|
1475
|
+
const animation = info();
|
|
1476
|
+
if (animation) hub.emit({ type: "fire", animation, track, path: path.slice(), event, lateBy });
|
|
1477
|
+
},
|
|
1478
|
+
lap: (lap) => {
|
|
1479
|
+
const animation = info();
|
|
1480
|
+
if (animation) hub.emit({ type: "lap", animation, lap });
|
|
1481
|
+
}
|
|
1482
|
+
}, hold);
|
|
1483
|
+
}
|
|
1484
|
+
function createAnimatorApi(host) {
|
|
1485
|
+
const { ctx, table, clock, hub } = host;
|
|
1486
|
+
const { animations } = table;
|
|
1487
|
+
const createSupervisor = supervisors({
|
|
1488
|
+
nextId: ctx.nextId,
|
|
1489
|
+
atEnd: ctx.atEnd,
|
|
1490
|
+
starting: ctx.starting,
|
|
1491
|
+
register: table.register,
|
|
1492
|
+
finish: table.finish,
|
|
1493
|
+
afterTicks: host.afterTicks,
|
|
1494
|
+
rateOf: (id) => {
|
|
1495
|
+
const a = animations.get(id);
|
|
1496
|
+
return a ? table.rateOf(a) : 0;
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
const physics = (o, kind = "physics") => o.target ? startOffsetPhysics(ctx, o, kind) : startPhysics(ctx, o, kind);
|
|
1500
|
+
const api = {
|
|
1501
|
+
tween: (o) => o.target ? startOffsetTween(ctx, o) : startTween(ctx, o),
|
|
1502
|
+
spring: (o) => physics(o, "spring"),
|
|
1503
|
+
decay: (o) => physics(decayAsPhysics(o), "decay"),
|
|
1504
|
+
physics,
|
|
1505
|
+
hold: (o) => startHold(ctx, o),
|
|
1506
|
+
holdRows: (o) => startHoldRows(ctx, o),
|
|
1507
|
+
cancel: (handle) => table.retire(handle.id),
|
|
1508
|
+
cancelKey: (key) => table.cancelByKey(key),
|
|
1509
|
+
cancelAll: () => {
|
|
1510
|
+
table.cancelAll(() => {
|
|
1511
|
+
clock.frameT = null;
|
|
1512
|
+
ctx.targets.show();
|
|
1513
|
+
});
|
|
1514
|
+
host.halt();
|
|
1515
|
+
},
|
|
1516
|
+
isActive: (key) => {
|
|
1517
|
+
if (key == null) return animations.size > 0;
|
|
1518
|
+
for (const _ of table.under(key)) return true;
|
|
1519
|
+
return false;
|
|
1520
|
+
},
|
|
1521
|
+
isTicking: host.ticking,
|
|
1522
|
+
pause: () => {
|
|
1523
|
+
clock.paused = true;
|
|
1524
|
+
},
|
|
1525
|
+
resume: () => {
|
|
1526
|
+
clock.paused = false;
|
|
1527
|
+
},
|
|
1528
|
+
isPaused: () => clock.paused,
|
|
1529
|
+
setTimeScale: (s) => {
|
|
1530
|
+
const scale = timeScaleOf(s);
|
|
1531
|
+
if (scale < 0 === clock.timeScale < 0) clock.timeScale = scale;
|
|
1532
|
+
else table.turn(() => {
|
|
1533
|
+
clock.timeScale = scale;
|
|
1534
|
+
});
|
|
1535
|
+
},
|
|
1536
|
+
timeScale: () => clock.timeScale,
|
|
1537
|
+
pauseKey: (key) => eachUnder(table, key, (a) => [true, a.timeScale]),
|
|
1538
|
+
resumeKey: (key) => eachUnder(table, key, (a) => [false, a.timeScale]),
|
|
1539
|
+
setTimeScaleByKey: (key, s) => {
|
|
1540
|
+
const scale = timeScaleOf(s);
|
|
1541
|
+
eachUnder(table, key, (a) => [a.paused, scale]);
|
|
1542
|
+
},
|
|
1543
|
+
loop: (factory, loopOpts) => createLoop(createSupervisor, factory, loopOpts),
|
|
1544
|
+
tweenLoop: (tweenLoopOpts) => createTweenLoop(api, createSupervisor, tweenLoopOpts),
|
|
1545
|
+
stagger: ((items, delay, factory, staggerOpts) => createStagger(
|
|
1546
|
+
api,
|
|
1547
|
+
createSupervisor,
|
|
1548
|
+
table.watchCompletion,
|
|
1549
|
+
items,
|
|
1550
|
+
delay,
|
|
1551
|
+
factory,
|
|
1552
|
+
staggerOpts
|
|
1553
|
+
)),
|
|
1554
|
+
timeline: (o) => startTimeline(host, o),
|
|
1555
|
+
colorOverrides: host.colorOverrides,
|
|
1556
|
+
onTick: (cb) => host.subscribers.subscribe(cb),
|
|
1557
|
+
keepAlive: () => {
|
|
1558
|
+
const sup = createSupervisor({ kind: "keepAlive" });
|
|
1559
|
+
return () => sup.cancel();
|
|
1560
|
+
},
|
|
1561
|
+
watch: (listener) => hub.add(listener),
|
|
1562
|
+
live: () => liveRows(table)
|
|
1563
|
+
};
|
|
1564
|
+
return api;
|
|
1565
|
+
}
|
|
1566
|
+
function depart(s) {
|
|
1567
|
+
const l = s.lane;
|
|
1568
|
+
l.dirty = true;
|
|
1569
|
+
if (s.index < 0) {
|
|
1570
|
+
l.pending.delete(s);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const lastKey = l.keys.pop();
|
|
1574
|
+
const moved = l.slots.pop();
|
|
1575
|
+
if (moved === s) return;
|
|
1576
|
+
const n = l.axes;
|
|
1577
|
+
const from = l.keys.length * n;
|
|
1578
|
+
l.cols.v.copyWithin(s.index * n, from, from + n);
|
|
1579
|
+
l.keys[s.index] = lastKey;
|
|
1580
|
+
l.slots[s.index] = moved;
|
|
1581
|
+
l.tags[s.index] = moved.tag;
|
|
1582
|
+
moved.index = s.index;
|
|
1583
|
+
moved.at = s.index * n;
|
|
1584
|
+
}
|
|
1585
|
+
function rebuild(l) {
|
|
1586
|
+
const keys = l.keys.slice();
|
|
1587
|
+
const had = keys.length;
|
|
1588
|
+
for (const s of l.pending) {
|
|
1589
|
+
s.index = keys.length;
|
|
1590
|
+
s.at = s.index * l.axes;
|
|
1591
|
+
s.cols = l.cols.v;
|
|
1592
|
+
keys.push(s.key);
|
|
1593
|
+
l.slots.push(s);
|
|
1594
|
+
}
|
|
1595
|
+
l.pending.clear();
|
|
1596
|
+
l.keys = keys;
|
|
1597
|
+
if (l.tags.length < keys.length) {
|
|
1598
|
+
const tags = new Int32Array(Math.max(keys.length, l.tags.length * 2));
|
|
1599
|
+
tags.set(l.tags);
|
|
1600
|
+
l.tags = tags;
|
|
1601
|
+
}
|
|
1602
|
+
for (let i = had; i < keys.length; i++) l.tags[i] = l.slots[i].tag;
|
|
1603
|
+
if (l.cols.v.length !== keys.length * l.axes) {
|
|
1604
|
+
l.cols = { v: new Float64Array(keys.length * l.axes) };
|
|
1605
|
+
for (const s of l.slots) s.cols = l.cols.v;
|
|
1606
|
+
}
|
|
1607
|
+
l.dirty = false;
|
|
1608
|
+
}
|
|
1609
|
+
function createLanes(placeOf) {
|
|
1610
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
1611
|
+
const slots = /* @__PURE__ */ new Map();
|
|
1612
|
+
const gone = [];
|
|
1613
|
+
let now = 0;
|
|
1614
|
+
const laneOf = (axes, group) => {
|
|
1615
|
+
const key = group ?? axes;
|
|
1616
|
+
let l = lanes.get(key);
|
|
1617
|
+
if (!l) {
|
|
1618
|
+
const m = mix(kit({ v: vec(axes, sum()) }));
|
|
1619
|
+
m.sync(now);
|
|
1620
|
+
l = {
|
|
1621
|
+
key,
|
|
1622
|
+
axes,
|
|
1623
|
+
mix: m,
|
|
1624
|
+
keys: [],
|
|
1625
|
+
slots: [],
|
|
1626
|
+
tags: new Int32Array(0),
|
|
1627
|
+
pending: /* @__PURE__ */ new Set(),
|
|
1628
|
+
dirty: false,
|
|
1629
|
+
idle: 0,
|
|
1630
|
+
cols: { v: new Float64Array(0) }
|
|
1631
|
+
};
|
|
1632
|
+
lanes.set(key, l);
|
|
1633
|
+
}
|
|
1634
|
+
return l;
|
|
1635
|
+
};
|
|
1636
|
+
return {
|
|
1637
|
+
join(axes, key, held) {
|
|
1638
|
+
const had = slots.get(key);
|
|
1639
|
+
if (had) {
|
|
1640
|
+
if (had.lane.axes !== axes) {
|
|
1641
|
+
throw new Error(`codec: a subject on ${had.lane.axes} axes was given a voice on ${axes}`);
|
|
1642
|
+
}
|
|
1643
|
+
had.refs += 1;
|
|
1644
|
+
return had;
|
|
1645
|
+
}
|
|
1646
|
+
const place = typeof key === "object" && key !== null ? placeOf?.(key) : void 0;
|
|
1647
|
+
const lane = laneOf(axes, place?.group);
|
|
1648
|
+
if (lane.axes !== axes) throw new Error(`codec: a group on ${lane.axes} axes was given a subject on ${axes}`);
|
|
1649
|
+
const own = Float64Array.from(held);
|
|
1650
|
+
const s = { key, lane, refs: 1, index: -1, held: own, cols: own, at: 0, tag: place?.tag ?? -1 };
|
|
1651
|
+
slots.set(key, s);
|
|
1652
|
+
lane.pending.add(s);
|
|
1653
|
+
lane.dirty = true;
|
|
1654
|
+
return s;
|
|
1655
|
+
},
|
|
1656
|
+
leave(s) {
|
|
1657
|
+
s.refs -= 1;
|
|
1658
|
+
if (s.refs > 0) return;
|
|
1659
|
+
const n = s.lane.axes;
|
|
1660
|
+
s.held.set(s.cols.subarray(s.at, s.at + n));
|
|
1661
|
+
s.lane.mix.drop(s.key);
|
|
1662
|
+
depart(s);
|
|
1663
|
+
s.cols = s.held;
|
|
1664
|
+
s.at = 0;
|
|
1665
|
+
gone.push(s);
|
|
1666
|
+
slots.delete(s.key);
|
|
1667
|
+
},
|
|
1668
|
+
frame(time) {
|
|
1669
|
+
now = time;
|
|
1670
|
+
for (const s of gone) s.held.fill(0);
|
|
1671
|
+
gone.length = 0;
|
|
1672
|
+
for (const l of lanes.values()) {
|
|
1673
|
+
if (l.dirty) rebuild(l);
|
|
1674
|
+
if (l.keys.length === 0 && ++l.idle >= 2) {
|
|
1675
|
+
lanes.delete(l.key);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
l.mix.sync(time);
|
|
1679
|
+
if (l.keys.length === 0) continue;
|
|
1680
|
+
l.idle = 0;
|
|
1681
|
+
l.mix.pull(l.keys, l.cols);
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
columns(group) {
|
|
1685
|
+
const l = lanes.get(group);
|
|
1686
|
+
return l ? { cols: l.cols.v, tags: l.tags, count: l.keys.length } : null;
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/engine/rewinds.ts
|
|
1692
|
+
var firstOf = (v) => v.spring?.from ?? v.from;
|
|
1693
|
+
function createRewinds() {
|
|
1694
|
+
const backward = /* @__PURE__ */ new Set();
|
|
1695
|
+
const back = /* @__PURE__ */ new Set();
|
|
1696
|
+
return {
|
|
1697
|
+
rate(v, rate) {
|
|
1698
|
+
if (rate < 0) backward.add(v);
|
|
1699
|
+
else backward.delete(v);
|
|
1700
|
+
},
|
|
1701
|
+
frame() {
|
|
1702
|
+
for (const v of backward) if (v.handle?.state === "done") back.add(v);
|
|
1703
|
+
for (const v of back) {
|
|
1704
|
+
const { cols, at } = v.slot;
|
|
1705
|
+
const first = firstOf(v);
|
|
1706
|
+
for (let i = 0; i < first.length; i++) cols[at + i] = cols[at + i] + first[i];
|
|
1707
|
+
}
|
|
1708
|
+
},
|
|
1709
|
+
has: (v) => back.has(v),
|
|
1710
|
+
recued: (v) => {
|
|
1711
|
+
back.delete(v);
|
|
1712
|
+
},
|
|
1713
|
+
remove(v) {
|
|
1714
|
+
backward.delete(v);
|
|
1715
|
+
back.delete(v);
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/engine/codec.ts
|
|
1721
|
+
function springVoice(slot, sp) {
|
|
1722
|
+
const patch = sp.to == null ? glide("v", { from: sp.from, ms: 1e3 * sp.mass / sp.damping }) : spring("v", {
|
|
1723
|
+
from: sp.from,
|
|
1724
|
+
to: sp.from,
|
|
1725
|
+
stiffness: sp.stiffness,
|
|
1726
|
+
damping: sp.damping,
|
|
1727
|
+
mass: sp.mass
|
|
1728
|
+
});
|
|
1729
|
+
const handle = slot.lane.mix.cue({ patch, subjects: [slot.key] });
|
|
1730
|
+
if (sp.to) patch.to(slot.key, sp.to, 0);
|
|
1731
|
+
patch.push(slot.key, sp.velocity, 0);
|
|
1732
|
+
return { handle, patch };
|
|
1733
|
+
}
|
|
1734
|
+
var zeros = (n) => new Array(n).fill(0);
|
|
1735
|
+
function createCodec(placeOf) {
|
|
1736
|
+
const lanes = createLanes(placeOf);
|
|
1737
|
+
const voices = /* @__PURE__ */ new Map();
|
|
1738
|
+
const queue = [];
|
|
1739
|
+
const rewinds = createRewinds();
|
|
1740
|
+
let time = 0;
|
|
1741
|
+
const get = (id) => {
|
|
1742
|
+
const v = voices.get(id);
|
|
1743
|
+
if (!v) throw new Error(`codec: no subject ${id}`);
|
|
1744
|
+
return v;
|
|
1745
|
+
};
|
|
1746
|
+
const current = (v) => v.patch.read(v.slot.key) ?? { value: v.spring.from.slice(), velocity: v.spring.velocity.slice() };
|
|
1747
|
+
const respring = (v, next) => {
|
|
1748
|
+
const rate = v.handle.rate;
|
|
1749
|
+
v.handle.fade({ over: 0 });
|
|
1750
|
+
const { handle, patch } = springVoice(v.slot, next);
|
|
1751
|
+
handle.rate = rate;
|
|
1752
|
+
v.handle = handle;
|
|
1753
|
+
v.patch = patch;
|
|
1754
|
+
v.spring = next;
|
|
1755
|
+
rewinds.recued(v);
|
|
1756
|
+
};
|
|
1757
|
+
function stop(id) {
|
|
1758
|
+
const v = voices.get(id);
|
|
1759
|
+
if (!v) return;
|
|
1760
|
+
if (v.queued) v.queued = void 0;
|
|
1761
|
+
else v.handle.fade({ over: 0 });
|
|
1762
|
+
rewinds.remove(v);
|
|
1763
|
+
lanes.leave(v.slot);
|
|
1764
|
+
voices.delete(id);
|
|
1765
|
+
}
|
|
1766
|
+
return {
|
|
1767
|
+
frame(dtMs) {
|
|
1768
|
+
for (const v of queue) {
|
|
1769
|
+
const q = v.queued;
|
|
1770
|
+
if (!q) continue;
|
|
1771
|
+
v.queued = void 0;
|
|
1772
|
+
const key = v.slot.key;
|
|
1773
|
+
const patch = tween("v", { from: q.from.slice(), to: q.from.slice(), ms: q.ms, ease: q.ease });
|
|
1774
|
+
v.handle = v.slot.lane.mix.cue({ patch, subjects: [key], seek: q.atEnd ? q.ms : 0 });
|
|
1775
|
+
v.patch = patch;
|
|
1776
|
+
patch.to(key, q.to.slice(), 0);
|
|
1777
|
+
if (v.queuedRate !== void 0) v.handle.rate = v.queuedRate;
|
|
1778
|
+
}
|
|
1779
|
+
queue.length = 0;
|
|
1780
|
+
time += dtMs;
|
|
1781
|
+
lanes.frame(time);
|
|
1782
|
+
rewinds.frame();
|
|
1783
|
+
},
|
|
1784
|
+
tween(s) {
|
|
1785
|
+
if (s.to.length !== s.from.length) {
|
|
1786
|
+
throw new Error(`codec: tween ${s.id} goes from ${s.from.length} axes to ${s.to.length}`);
|
|
1787
|
+
}
|
|
1788
|
+
const shared = s.on !== void 0;
|
|
1789
|
+
const slot = lanes.join(s.from.length, shared ? s.on : s.id, shared ? zeros(s.from.length) : s.atEnd ? s.to : s.from);
|
|
1790
|
+
const v = { kind: "tween", slot, from: s.from, queued: s };
|
|
1791
|
+
voices.set(s.id, v);
|
|
1792
|
+
queue.push(v);
|
|
1793
|
+
return slot;
|
|
1794
|
+
},
|
|
1795
|
+
spring(s) {
|
|
1796
|
+
const sp = { ...s, from: s.from.slice(), to: s.to && s.to.slice(), velocity: s.velocity.slice() };
|
|
1797
|
+
const shared = s.on !== void 0;
|
|
1798
|
+
const slot = lanes.join(sp.axes, shared ? s.on : s.id, shared ? new Array(sp.axes).fill(0) : sp.from);
|
|
1799
|
+
const { handle, patch } = springVoice(slot, sp);
|
|
1800
|
+
const v = { kind: "spring", slot, handle, patch, from: sp.from, spring: sp };
|
|
1801
|
+
voices.set(s.id, v);
|
|
1802
|
+
const reader = slot;
|
|
1803
|
+
if (!shared) reader.motion = () => current(v);
|
|
1804
|
+
return reader;
|
|
1805
|
+
},
|
|
1806
|
+
path(s) {
|
|
1807
|
+
const shared = s.on !== void 0;
|
|
1808
|
+
const from = s.at(0);
|
|
1809
|
+
const last = s.atEnd ? s.at(1) : void 0;
|
|
1810
|
+
const slot = lanes.join(s.axes, shared ? s.on : s.id, shared ? zeros(s.axes) : last ?? from);
|
|
1811
|
+
const v = { kind: "path", slot, from, last };
|
|
1812
|
+
const patch$1 = patch(s.ms, (phase) => {
|
|
1813
|
+
v.last = s.at(phase);
|
|
1814
|
+
return { v: v.last };
|
|
1815
|
+
}, { writes: ["v"] });
|
|
1816
|
+
v.handle = slot.lane.mix.cue({ patch: patch$1, subjects: [slot.key], loop: false, freeze: "after", seek: s.atEnd ? s.ms : 0 });
|
|
1817
|
+
voices.set(s.id, v);
|
|
1818
|
+
return slot;
|
|
1819
|
+
},
|
|
1820
|
+
retarget(id, to) {
|
|
1821
|
+
const v = get(id);
|
|
1822
|
+
if (v.kind !== "spring") throw new Error(`codec: subject ${id} is a ${v.kind}; ${v.kind}s are not retargeted`);
|
|
1823
|
+
const sp = v.spring;
|
|
1824
|
+
if (to == null === (sp.to == null)) {
|
|
1825
|
+
if (!to) return;
|
|
1826
|
+
sp.to = to.slice();
|
|
1827
|
+
v.patch.to(v.slot.key, sp.to);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
const now = current(v);
|
|
1831
|
+
respring(v, { ...sp, to: to && to.slice(), from: now.value, velocity: now.velocity });
|
|
1832
|
+
},
|
|
1833
|
+
recue(id, from, velocity, to) {
|
|
1834
|
+
const v = get(id);
|
|
1835
|
+
if (v.kind !== "spring") throw new Error(`codec: subject ${id} is a ${v.kind}; only springs are re-cued`);
|
|
1836
|
+
respring(v, { ...v.spring, from: from.slice(), velocity: velocity.slice(), to: to && to.slice() });
|
|
1837
|
+
},
|
|
1838
|
+
push(id, velocity) {
|
|
1839
|
+
const v = get(id);
|
|
1840
|
+
if (v.spring && !v.patch.read(v.slot.key)) v.spring.velocity = velocity.slice();
|
|
1841
|
+
v.patch.push?.(v.slot.key, velocity);
|
|
1842
|
+
},
|
|
1843
|
+
rate(id, rate) {
|
|
1844
|
+
const v = get(id);
|
|
1845
|
+
rewinds.rate(v, rate);
|
|
1846
|
+
if (v.queued) v.queuedRate = rate;
|
|
1847
|
+
else v.handle.rate = rate;
|
|
1848
|
+
},
|
|
1849
|
+
rewound: (id) => {
|
|
1850
|
+
const v = voices.get(id);
|
|
1851
|
+
return v !== void 0 && rewinds.has(v);
|
|
1852
|
+
},
|
|
1853
|
+
stop,
|
|
1854
|
+
has: (id) => voices.has(id),
|
|
1855
|
+
own(id) {
|
|
1856
|
+
const v = voices.get(id);
|
|
1857
|
+
if (!v) return void 0;
|
|
1858
|
+
if (v.kind === "spring") return current(v);
|
|
1859
|
+
if (v.kind === "path") {
|
|
1860
|
+
return { value: v.last ?? v.from, velocity: new Array(v.from.length).fill(0) };
|
|
1861
|
+
}
|
|
1862
|
+
return !v.queued && v.patch.read(v.slot.key) || { value: v.queued?.atEnd ? v.queued.to : v.from, velocity: zeros(v.from.length) };
|
|
1863
|
+
},
|
|
1864
|
+
voiceCount: () => voices.size,
|
|
1865
|
+
columns: (group) => lanes.columns(group)
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// src/engine/frame.ts
|
|
1870
|
+
function createFrame(host) {
|
|
1871
|
+
const { clock, table, hub } = host;
|
|
1872
|
+
const { animations } = table;
|
|
1873
|
+
let framing = false;
|
|
1874
|
+
let tickDepth = 0;
|
|
1875
|
+
const tickEach = () => {
|
|
1876
|
+
const finished = [];
|
|
1877
|
+
const threw = [];
|
|
1878
|
+
const all = table.waiting > 0 && host.tripwire();
|
|
1879
|
+
for (const anim of all ? [...animations.values()] : table.ticks()) {
|
|
1880
|
+
if (animations.get(anim.id) !== anim) continue;
|
|
1881
|
+
const scale = clock.paused ? 0 : clock.timeScale * (anim.paused ? 0 : anim.timeScale);
|
|
1882
|
+
tickDepth += 1;
|
|
1883
|
+
try {
|
|
1884
|
+
if (anim.tick(table.clockOf(anim), scale)) finished.push(anim);
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
threw.push([anim, error]);
|
|
1887
|
+
} finally {
|
|
1888
|
+
tickDepth -= 1;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
for (const [anim, error] of threw) {
|
|
1892
|
+
console.error("useAnimator: animation tick threw; retiring it", error);
|
|
1893
|
+
if (animations.get(anim.id) !== anim) continue;
|
|
1894
|
+
if (anim.ended) table.drop(anim);
|
|
1895
|
+
else table.retire(anim.id, void 0, true);
|
|
1896
|
+
if (hub.watched) hub.emit({ type: "error", animation: table.infoOf(anim), error });
|
|
1897
|
+
}
|
|
1898
|
+
return finished;
|
|
1899
|
+
};
|
|
1900
|
+
return {
|
|
1901
|
+
tick(t) {
|
|
1902
|
+
const frameDt = clock.frameT == null ? 0 : Math.max(0, t - clock.frameT);
|
|
1903
|
+
clock.frameT = t;
|
|
1904
|
+
framing = true;
|
|
1905
|
+
const dt = frameDt * (clock.paused ? 0 : Math.abs(clock.timeScale));
|
|
1906
|
+
clock.elapsed += dt;
|
|
1907
|
+
host.codec.frame(dt);
|
|
1908
|
+
host.targets.settle();
|
|
1909
|
+
for (const anim of tickEach()) {
|
|
1910
|
+
if (animations.get(anim.id) === anim) table.finish(anim.id);
|
|
1911
|
+
}
|
|
1912
|
+
for (const cb of host.afterTicks.splice(0)) {
|
|
1913
|
+
try {
|
|
1914
|
+
cb();
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
console.error("useAnimator: a staggered start threw", err);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
host.refold();
|
|
1920
|
+
framing = false;
|
|
1921
|
+
host.targets.show();
|
|
1922
|
+
host.subscribers.emit();
|
|
1923
|
+
if (table.awake) {
|
|
1924
|
+
host.requestFrame();
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
clock.frameT = null;
|
|
1928
|
+
},
|
|
1929
|
+
framing: () => framing,
|
|
1930
|
+
ticking: () => tickDepth > 0
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
function createAnimatorCore(host) {
|
|
1934
|
+
const clock = { frameT: null, elapsed: 0, timeScale: 1, paused: false };
|
|
1935
|
+
const hub = new AnimatorEventHub();
|
|
1936
|
+
const codec = createCodec((on) => {
|
|
1937
|
+
if ("move" in on) return on.move?.place;
|
|
1938
|
+
const { row } = on;
|
|
1939
|
+
return row ? { group: row.table, tag: row.index } : void 0;
|
|
1940
|
+
});
|
|
1941
|
+
const afterTicks = [];
|
|
1942
|
+
const subscribers = createEmitter();
|
|
1943
|
+
let nextId = 1;
|
|
1944
|
+
let atEnd = false;
|
|
1945
|
+
const seedClock = () => {
|
|
1946
|
+
if (clock.frameT == null) clock.frameT = host.now();
|
|
1947
|
+
};
|
|
1948
|
+
const touched = /* @__PURE__ */ new Set();
|
|
1949
|
+
const refold = (s) => {
|
|
1950
|
+
if (s.holds) s.holds.store.refold(s.holds.row);
|
|
1951
|
+
if (s.row && !frame.framing()) s.row.table.mark(s.row.index);
|
|
1952
|
+
};
|
|
1953
|
+
const refoldTouched = () => {
|
|
1954
|
+
for (const s of touched) refold(s);
|
|
1955
|
+
touched.clear();
|
|
1956
|
+
};
|
|
1957
|
+
const table = createTable({
|
|
1958
|
+
codec,
|
|
1959
|
+
hub,
|
|
1960
|
+
elapsed: () => clock.elapsed,
|
|
1961
|
+
direction: () => clock.timeScale < 0 ? -1 : 1,
|
|
1962
|
+
seedClock,
|
|
1963
|
+
showNow: (target) => targets.showNow(target),
|
|
1964
|
+
resting: () => targets.resting,
|
|
1965
|
+
framing: () => frame.framing(),
|
|
1966
|
+
requestFrame: host.requestFrame
|
|
1967
|
+
});
|
|
1968
|
+
const targets = createTargetBook({
|
|
1969
|
+
stop: (s) => {
|
|
1970
|
+
for (const a of [...table.on(s)]) table.retire(a.id);
|
|
1971
|
+
for (const part of [...s.parts]) part();
|
|
1972
|
+
},
|
|
1973
|
+
columns: (table2) => codec.columns(table2)
|
|
1974
|
+
});
|
|
1975
|
+
const frame = createFrame({
|
|
1976
|
+
clock,
|
|
1977
|
+
table,
|
|
1978
|
+
codec,
|
|
1979
|
+
targets,
|
|
1980
|
+
hub,
|
|
1981
|
+
afterTicks,
|
|
1982
|
+
subscribers,
|
|
1983
|
+
refold: refoldTouched,
|
|
1984
|
+
requestFrame: host.requestFrame,
|
|
1985
|
+
tripwire: host.tripwire
|
|
1986
|
+
});
|
|
1987
|
+
const ctx = {
|
|
1988
|
+
codec,
|
|
1989
|
+
targets,
|
|
1990
|
+
nextId: () => nextId++,
|
|
1991
|
+
atEnd: () => atEnd,
|
|
1992
|
+
starting: (end, start) => {
|
|
1993
|
+
const was = atEnd;
|
|
1994
|
+
atEnd = end;
|
|
1995
|
+
try {
|
|
1996
|
+
return start();
|
|
1997
|
+
} finally {
|
|
1998
|
+
atEnd = was;
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
seedClock,
|
|
2002
|
+
register: table.register,
|
|
2003
|
+
has: (id) => table.animations.has(id),
|
|
2004
|
+
defer: table.defer,
|
|
2005
|
+
reportEnd: table.reportEnd,
|
|
2006
|
+
finish: table.finish,
|
|
2007
|
+
touch: (target, at) => {
|
|
2008
|
+
if (frame.framing() && !at) {
|
|
2009
|
+
touched.add(target);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
touched.delete(target);
|
|
2013
|
+
refold(target);
|
|
2014
|
+
if (!frame.framing()) host.requestFrame();
|
|
2015
|
+
},
|
|
2016
|
+
wake: () => {
|
|
2017
|
+
if (!frame.framing()) host.requestFrame();
|
|
2018
|
+
},
|
|
2019
|
+
tripwire: host.tripwire,
|
|
2020
|
+
interrupt: (key, by) => table.cancelByKey(key, by),
|
|
2021
|
+
velocityUnder: (key, target) => {
|
|
2022
|
+
let sum2 = null;
|
|
2023
|
+
for (const a of table.under(key)) {
|
|
2024
|
+
if (a.target !== target) continue;
|
|
2025
|
+
const v = a.velocity?.() ?? codec.own(a.id)?.velocity;
|
|
2026
|
+
if (v) sum2 = sum2 ? sum2.map((x, i) => x + v[i]) : v.slice();
|
|
2027
|
+
}
|
|
2028
|
+
return sum2;
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
const api = createAnimatorApi({
|
|
2032
|
+
ctx,
|
|
2033
|
+
table,
|
|
2034
|
+
clock,
|
|
2035
|
+
hub,
|
|
2036
|
+
ticking: frame.ticking,
|
|
2037
|
+
afterTicks: (cb) => {
|
|
2038
|
+
afterTicks.push(cb);
|
|
2039
|
+
},
|
|
2040
|
+
subscribers,
|
|
2041
|
+
colorOverrides: host.colorOverrides,
|
|
2042
|
+
halt: host.cancelFrame
|
|
2043
|
+
});
|
|
2044
|
+
return {
|
|
2045
|
+
api,
|
|
2046
|
+
tick: frame.tick,
|
|
2047
|
+
resumed() {
|
|
2048
|
+
clock.frameT = null;
|
|
2049
|
+
}
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// src/useAnimator.ts
|
|
2054
|
+
function useAnimator(opts = {}) {
|
|
2055
|
+
const optsRef = useLatest(opts);
|
|
2056
|
+
const colorOverrides = useRef(new ColorOverrideRegistry());
|
|
2057
|
+
const coreRef = useRef(null);
|
|
2058
|
+
const frameLoop = useVisibleRaf(
|
|
2059
|
+
useCallback((t) => {
|
|
2060
|
+
coreRef.current?.tick(t);
|
|
2061
|
+
}, []),
|
|
2062
|
+
{
|
|
2063
|
+
requestFrame: opts.requestFrame,
|
|
2064
|
+
cancelFrame: opts.cancelFrame,
|
|
2065
|
+
onResume: () => coreRef.current?.resumed()
|
|
2066
|
+
}
|
|
2067
|
+
);
|
|
2068
|
+
const frameLoopRef = useLatest(frameLoop);
|
|
2069
|
+
const mountedRef = useRef(true);
|
|
2070
|
+
const trippedRef = useRef(false);
|
|
2071
|
+
const core = useMemo(() => {
|
|
2072
|
+
const tripwire = () => {
|
|
2073
|
+
if (mountedRef.current) return false;
|
|
2074
|
+
if (!trippedRef.current) {
|
|
2075
|
+
trippedRef.current = true;
|
|
2076
|
+
if (isDev()) {
|
|
2077
|
+
console.error(
|
|
2078
|
+
"weasel useAnimator: animation tick fired after the host component unmounted. This usually means cleanup logic was bypassed \u2014 check for refactors that removed the unmount effect or replaced cancelAll with a custom path."
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return true;
|
|
2083
|
+
};
|
|
2084
|
+
const now = () => (optsRef.current.now ?? (typeof performance !== "undefined" ? performance.now.bind(performance) : Date.now))();
|
|
2085
|
+
return createAnimatorCore({
|
|
2086
|
+
now,
|
|
2087
|
+
requestFrame: () => frameLoopRef.current.request(),
|
|
2088
|
+
cancelFrame: () => frameLoopRef.current.cancel(),
|
|
2089
|
+
tripwire,
|
|
2090
|
+
colorOverrides: colorOverrides.current
|
|
2091
|
+
});
|
|
2092
|
+
}, [frameLoopRef, optsRef]);
|
|
2093
|
+
useInsertionEffect(() => {
|
|
2094
|
+
coreRef.current = core;
|
|
2095
|
+
}, [core]);
|
|
2096
|
+
const { api } = core;
|
|
2097
|
+
useEffect(() => {
|
|
2098
|
+
const overrides = colorOverrides.current;
|
|
2099
|
+
mountedRef.current = true;
|
|
2100
|
+
return () => {
|
|
2101
|
+
mountedRef.current = false;
|
|
2102
|
+
api.cancelAll();
|
|
2103
|
+
overrides.clearAll();
|
|
2104
|
+
};
|
|
2105
|
+
}, [api]);
|
|
2106
|
+
return api;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
export { useAnimator };
|
|
2110
|
+
//# sourceMappingURL=react.js.map
|
|
2111
|
+
//# sourceMappingURL=react.js.map
|