canvasengine 2.0.0-beta.39 → 2.0.0-beta.40
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/dist/{DebugRenderer-Rrw9FlTd.js → DebugRenderer-DgECR3yZ.js} +2 -2
- package/dist/{DebugRenderer-Rrw9FlTd.js.map → DebugRenderer-DgECR3yZ.js.map} +1 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/directives/Controls.d.ts +3 -1
- package/dist/directives/Controls.d.ts.map +1 -1
- package/dist/engine/animation.d.ts +19 -5
- package/dist/engine/animation.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts +10 -0
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/engine/trigger.d.ts.map +1 -1
- package/dist/{index-BQ99FClW.js → index-gb763Hyx.js} +4460 -4245
- package/dist/index-gb763Hyx.js.map +1 -0
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +40 -37
- package/package.json +2 -2
- package/src/components/Canvas.ts +5 -0
- package/src/components/Container.ts +2 -0
- package/src/components/DisplayObject.ts +33 -12
- package/src/components/Sprite.ts +14 -1
- package/src/directives/Controls.ts +45 -7
- package/src/engine/animation.ts +137 -19
- package/src/engine/reactive.ts +123 -3
- package/src/engine/signal.ts +5 -0
- package/src/engine/trigger.ts +19 -2
- package/dist/components/Container.d.ts +0 -84
- package/dist/components/Container.d.ts.map +0 -1
- package/dist/components/DOMContainer.d.ts +0 -81
- package/dist/components/DOMContainer.d.ts.map +0 -1
- package/dist/components/DisplayObject.d.ts +0 -86
- package/dist/components/DisplayObject.d.ts.map +0 -1
- package/dist/components/Mesh.d.ts +0 -206
- package/dist/components/Mesh.d.ts.map +0 -1
- package/dist/components/Sprite.d.ts +0 -240
- package/dist/components/Sprite.d.ts.map +0 -1
- package/dist/components/Viewport.d.ts +0 -110
- package/dist/components/Viewport.d.ts.map +0 -1
- package/dist/index-BQ99FClW.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { h as a } from "./index-
|
|
2
|
-
import { A as r,
|
|
1
|
+
import { h as a } from "./index-gb763Hyx.js";
|
|
2
|
+
import { A as r, _ as o, $ as n, t as l, x as c, v as p, C as S, d as m, Y as u, Z as d, an as g, f as b, D as C, am as k, ak as h, y as j, j as T, G as w, w as D, c as E, I as f, a0 as v, J as y, K as O, M as P, X as V, O as x, P as A, al as B, R as G, H, S as M, g as F, e as J, L as N, B as R, Q as q, U as z, T as I, z as K, b as U, N as L, W as Q, V as W, aj as X, ai as Y, ag as Z, k as _, a7 as $, a5 as aa, a8 as sa, l as ea, ac as ta, ah as ia, m as ra, n as oa, a1 as na, a4 as la, o as ca, i as pa, a2 as Sa, p as ma, ad as ua, q as da, a6 as ga, aa as ba, a9 as Ca, af as ka, a3 as ha, s as ja, ab as Ta, ae as wa, r as Da, a as Ea, u as fa } from "./index-gb763Hyx.js";
|
|
3
3
|
const e = a.Howler;
|
|
4
4
|
export {
|
|
5
5
|
r as ArraySubject,
|
|
@@ -9,23 +9,23 @@ export {
|
|
|
9
9
|
c as Circle,
|
|
10
10
|
p as Container,
|
|
11
11
|
S as ControlsBase,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
m as ControlsDirective,
|
|
13
|
+
u as DOMContainer,
|
|
14
|
+
d as DOMElement,
|
|
15
|
+
g as DisplayObject,
|
|
16
16
|
b as Drag,
|
|
17
17
|
C as Drop,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
k as EVENTS,
|
|
19
|
+
h as Easing,
|
|
20
|
+
j as Ellipse,
|
|
21
|
+
T as Flash,
|
|
22
22
|
w as GamepadControls,
|
|
23
23
|
D as Graphics,
|
|
24
|
-
|
|
24
|
+
E as Howl,
|
|
25
25
|
e as Howler,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
f as Input,
|
|
27
|
+
v as Joystick,
|
|
28
|
+
y as JoystickControls,
|
|
29
29
|
O as KeyboardControls,
|
|
30
30
|
P as Mesh,
|
|
31
31
|
V as NineSliceSprite,
|
|
@@ -35,15 +35,15 @@ export {
|
|
|
35
35
|
G as Rect,
|
|
36
36
|
H as Scene,
|
|
37
37
|
M as Scheduler,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
F as Shake,
|
|
39
|
+
J as Sound,
|
|
40
|
+
N as Sprite,
|
|
41
|
+
R as Svg,
|
|
42
|
+
q as Text,
|
|
43
|
+
z as TilingSprite,
|
|
44
|
+
I as Transition,
|
|
45
|
+
K as Triangle,
|
|
46
|
+
U as Utils,
|
|
47
47
|
L as Video,
|
|
48
48
|
Q as Viewport,
|
|
49
49
|
W as ViewportFollow,
|
|
@@ -60,20 +60,23 @@ export {
|
|
|
60
60
|
ra as isArraySubject,
|
|
61
61
|
oa as isComputed,
|
|
62
62
|
na as isElement,
|
|
63
|
-
la as
|
|
64
|
-
ca as
|
|
65
|
-
pa as
|
|
66
|
-
Sa as
|
|
63
|
+
la as isElementFrozen,
|
|
64
|
+
ca as isObjectSubject,
|
|
65
|
+
pa as isObservable,
|
|
66
|
+
Sa as isPrimitive,
|
|
67
|
+
ma as isSignal,
|
|
67
68
|
ua as isTrigger,
|
|
68
|
-
|
|
69
|
-
ga as
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ha as
|
|
74
|
-
ja as
|
|
75
|
-
Ta as
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
da as linkedSignal,
|
|
70
|
+
ga as loop,
|
|
71
|
+
ba as mount,
|
|
72
|
+
Ca as mountTracker,
|
|
73
|
+
ka as on,
|
|
74
|
+
ha as registerComponent,
|
|
75
|
+
ja as signal,
|
|
76
|
+
Ta as tick,
|
|
77
|
+
wa as trigger,
|
|
78
|
+
Da as untracked,
|
|
79
|
+
Ea as useDefineProps,
|
|
80
|
+
fa as useProps
|
|
78
81
|
};
|
|
79
82
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasengine",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@barvynkoa/particle-emitter": "^0.0.1",
|
|
12
12
|
"@pixi/layout": "^3.0.2",
|
|
13
|
-
"@signe/reactive": "^2.
|
|
13
|
+
"@signe/reactive": "^2.6.0",
|
|
14
14
|
"howler": "^2.2.4",
|
|
15
15
|
"joypad.js": "^2.3.5",
|
|
16
16
|
"pixi-filters": "^6.0.5",
|
package/src/components/Canvas.ts
CHANGED
|
@@ -69,8 +69,13 @@ export const Canvas: ComponentFunction<CanvasProps> = async (props = {}) => {
|
|
|
69
69
|
frame: 0,
|
|
70
70
|
deltaRatio: 1,
|
|
71
71
|
});
|
|
72
|
+
} else {
|
|
73
|
+
options.context!.tick = props.tick;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// Register the tick signal globally so animatedSignal can use it by default
|
|
77
|
+
(globalThis as any).__CANVAS_ENGINE_TICK__ = options.context!.tick;
|
|
78
|
+
|
|
74
79
|
const canvasElement = createComponent("Canvas", options) as CanvasElement;
|
|
75
80
|
|
|
76
81
|
canvasElement.render = (rootElement: HTMLElement, app?: Application) => {
|
|
@@ -59,5 +59,7 @@ export interface CanvasContainer extends DisplayObjectProps {}
|
|
|
59
59
|
registerComponent("Container", CanvasContainer);
|
|
60
60
|
|
|
61
61
|
export const Container: ComponentFunction<ContainerProps> = (props) => {
|
|
62
|
+
// Ensure component is registered (useful in tests if module cache differs)
|
|
63
|
+
registerComponent("Container", CanvasContainer);
|
|
62
64
|
return createComponent("Container", props);
|
|
63
65
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Element, isElement, Props } from "../engine/reactive";
|
|
1
|
+
import { Element, isElement, Props, isElementFrozen } from "../engine/reactive";
|
|
2
2
|
import { setObservablePoint } from "../engine/utils";
|
|
3
3
|
import type {
|
|
4
4
|
AlignContent,
|
|
@@ -120,6 +120,16 @@ export function DisplayObject(extendClass) {
|
|
|
120
120
|
#registeredEvents: Map<string, Function> = new Map();
|
|
121
121
|
// Store computed layout box dimensions
|
|
122
122
|
#computedLayoutBox: { width?: number; height?: number } | null = null;
|
|
123
|
+
// Store reference to element for freeze checking
|
|
124
|
+
#element: Element<DisplayObject> | null = null;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get the element reference for freeze checking
|
|
128
|
+
* @returns The element reference or null
|
|
129
|
+
*/
|
|
130
|
+
protected getElement(): Element<DisplayObject> | null {
|
|
131
|
+
return this.#element;
|
|
132
|
+
}
|
|
123
133
|
|
|
124
134
|
get deltaRatio() {
|
|
125
135
|
return this.#canvasContext?.scheduler?.tick.value.deltaRatio;
|
|
@@ -135,15 +145,24 @@ export function DisplayObject(extendClass) {
|
|
|
135
145
|
for (let event of EVENTS) {
|
|
136
146
|
if (props[event] && !this.overrideProps.includes(event)) {
|
|
137
147
|
this.eventMode = "static";
|
|
138
|
-
const
|
|
148
|
+
const originalEventHandler = props[event];
|
|
149
|
+
|
|
150
|
+
// Wrap event handler to check freeze state
|
|
151
|
+
const wrappedHandler = (...args: any[]) => {
|
|
152
|
+
// Check if element is frozen before executing handler
|
|
153
|
+
if (this.#element && isElementFrozen(this.#element)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
return originalEventHandler(...args);
|
|
157
|
+
};
|
|
139
158
|
|
|
140
|
-
// Store the event handler for cleanup
|
|
159
|
+
// Store the wrapped event handler for cleanup
|
|
141
160
|
if (event === 'click') {
|
|
142
|
-
this.on('pointertap',
|
|
143
|
-
this.#registeredEvents.set('pointertap',
|
|
161
|
+
this.on('pointertap', wrappedHandler);
|
|
162
|
+
this.#registeredEvents.set('pointertap', wrappedHandler);
|
|
144
163
|
} else {
|
|
145
|
-
this.on(event,
|
|
146
|
-
this.#registeredEvents.set(event,
|
|
164
|
+
this.on(event, wrappedHandler);
|
|
165
|
+
this.#registeredEvents.set(event, wrappedHandler);
|
|
147
166
|
}
|
|
148
167
|
}
|
|
149
168
|
}
|
|
@@ -171,11 +190,12 @@ export function DisplayObject(extendClass) {
|
|
|
171
190
|
this.subjectInit.next(this);
|
|
172
191
|
}
|
|
173
192
|
|
|
174
|
-
async onMount(
|
|
193
|
+
async onMount(element: Element<DisplayObject>, index?: number) {
|
|
175
194
|
if (this.destroyed) return
|
|
176
|
-
this.#
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
this.#element = element;
|
|
196
|
+
this.#canvasContext = element.props.context;
|
|
197
|
+
if (element.parent) {
|
|
198
|
+
const instance = element.parent.componentInstance as DisplayObject;
|
|
179
199
|
if (instance.isFlex && !this.layout && !this.disableLayout) {
|
|
180
200
|
try {
|
|
181
201
|
this.layout = {};
|
|
@@ -189,7 +209,7 @@ export function DisplayObject(extendClass) {
|
|
|
189
209
|
instance.addChildAt(this, index);
|
|
190
210
|
}
|
|
191
211
|
this.isMounted = true;
|
|
192
|
-
this.onUpdate(props);
|
|
212
|
+
this.onUpdate(element.props);
|
|
193
213
|
|
|
194
214
|
// Listen to layout events to store computed layout dimensions
|
|
195
215
|
const layoutHandler = (event: any) => {
|
|
@@ -316,6 +336,7 @@ export function DisplayObject(extendClass) {
|
|
|
316
336
|
this.off(eventName, eventHandler);
|
|
317
337
|
}
|
|
318
338
|
this.#registeredEvents.clear();
|
|
339
|
+
this.#element = null;
|
|
319
340
|
|
|
320
341
|
if (this.onBeforeDestroy) {
|
|
321
342
|
await this.onBeforeDestroy();
|
package/src/components/Sprite.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
createComponent,
|
|
15
15
|
isElement,
|
|
16
16
|
registerComponent,
|
|
17
|
+
isElementFrozen,
|
|
17
18
|
} from "../engine/reactive";
|
|
18
19
|
import { arrayEquals, isFunction } from "../engine/utils";
|
|
19
20
|
import { DisplayObject } from "./DisplayObject";
|
|
@@ -299,6 +300,10 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
async onMount(params: Element<CanvasSprite>) {
|
|
303
|
+
// Set #element manually for freeze checking before calling super.onMount
|
|
304
|
+
// We need to set it early so update() can check freeze state
|
|
305
|
+
(this as any)['#element'] = params;
|
|
306
|
+
|
|
302
307
|
const { props, propObservables } = params;
|
|
303
308
|
const tick: Signal = props.context.tick;
|
|
304
309
|
const sheet = props.sheet ?? {};
|
|
@@ -421,7 +426,7 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
421
426
|
|
|
422
427
|
if (sheet?.params) this.sheetParams = sheet?.params;
|
|
423
428
|
|
|
424
|
-
if (sheet?.playing && this.isMounted) {
|
|
429
|
+
if (sheet?.playing && this.isMounted && this.spritesheet && this.animations.size > 0) {
|
|
425
430
|
this.sheetCurrentAnimation = sheet?.playing;
|
|
426
431
|
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
427
432
|
}
|
|
@@ -587,6 +592,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
587
592
|
}
|
|
588
593
|
|
|
589
594
|
update({ deltaRatio }) {
|
|
595
|
+
// Block animation update if element is frozen
|
|
596
|
+
const element = this.getElement();
|
|
597
|
+
if (element && isElementFrozen(element)) {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
590
601
|
if (
|
|
591
602
|
!this.isPlaying() ||
|
|
592
603
|
!this.currentAnimation ||
|
|
@@ -752,5 +763,7 @@ export type SpritePropTypes = SpritePropsWithImage | SpritePropsWithSheet;
|
|
|
752
763
|
|
|
753
764
|
// Update the Sprite function to use the props interface
|
|
754
765
|
export const Sprite: ComponentFunction<SpritePropTypes> = (props) => {
|
|
766
|
+
// Ensure component is registered in test environments where module cache may differ
|
|
767
|
+
registerComponent("Sprite", CanvasSprite);
|
|
755
768
|
return createComponent("Sprite", props);
|
|
756
769
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Directive, registerDirective } from "../engine/directive";
|
|
2
|
-
import { Element } from "../engine/reactive";
|
|
2
|
+
import { Element, isElementFrozen } from "../engine/reactive";
|
|
3
3
|
import { ControlsBase, Controls } from "./ControlsBase";
|
|
4
4
|
import { KeyboardControls } from "./KeyboardControls";
|
|
5
5
|
import { GamepadControls, GamepadConfig } from "./GamepadControls";
|
|
6
6
|
import { JoystickControls, JoystickConfig } from "./JoystickControls";
|
|
7
|
+
import { Signal, isSignal } from "@signe/reactive";
|
|
8
|
+
import { Subscription } from "rxjs";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Controls directive that coordinates keyboard, gamepad, and joystick input systems
|
|
@@ -27,12 +29,15 @@ export class ControlsDirective extends Directive {
|
|
|
27
29
|
private keyboardControls: KeyboardControls | null = null;
|
|
28
30
|
private gamepadControls: GamepadControls | null = null;
|
|
29
31
|
private joystickControls: JoystickControls | null = null;
|
|
32
|
+
private freezeSubscription: Subscription | null = null;
|
|
33
|
+
private element: Element | null = null;
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* Initialize the controls directive
|
|
33
37
|
* Sets up keyboard, gamepad, and joystick controls if available
|
|
34
38
|
*/
|
|
35
39
|
onInit(element: Element) {
|
|
40
|
+
this.element = element;
|
|
36
41
|
const value = element.props.controls?.value ?? element.props.controls;
|
|
37
42
|
if (!value) return;
|
|
38
43
|
|
|
@@ -57,6 +62,23 @@ export class ControlsDirective extends Directive {
|
|
|
57
62
|
this.joystickControls.setInputs(value as Controls & { joystick?: JoystickConfig });
|
|
58
63
|
this.joystickControls.start();
|
|
59
64
|
}
|
|
65
|
+
|
|
66
|
+
// Check initial freeze state
|
|
67
|
+
if (isElementFrozen(element)) {
|
|
68
|
+
this.stopInputs();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Subscribe to freeze prop if it's a signal
|
|
72
|
+
const freezeProp = element.propObservables?.freeze ?? element.props?.freeze;
|
|
73
|
+
if (isSignal(freezeProp)) {
|
|
74
|
+
this.freezeSubscription = (freezeProp as Signal<boolean>).observable.subscribe((isFrozen) => {
|
|
75
|
+
if (isFrozen) {
|
|
76
|
+
this.stopInputs();
|
|
77
|
+
} else {
|
|
78
|
+
this.listenInputs();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
/**
|
|
@@ -68,16 +90,25 @@ export class ControlsDirective extends Directive {
|
|
|
68
90
|
* Update controls configuration
|
|
69
91
|
* Updates both keyboard and gamepad controls
|
|
70
92
|
*/
|
|
71
|
-
onUpdate(props: any) {
|
|
93
|
+
onUpdate(props: any, element: Element) {
|
|
72
94
|
const value = props.controls?.value ?? props.controls;
|
|
73
|
-
if (
|
|
95
|
+
if (value) {
|
|
96
|
+
if (this.keyboardControls) {
|
|
97
|
+
this.keyboardControls.setInputs(value as Controls);
|
|
98
|
+
}
|
|
74
99
|
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
if (this.gamepadControls) {
|
|
101
|
+
this.gamepadControls.setInputs(value as Controls & { gamepad?: GamepadConfig });
|
|
102
|
+
}
|
|
77
103
|
}
|
|
78
104
|
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
// Handle freeze prop update
|
|
106
|
+
if (props.freeze !== undefined && this.element) {
|
|
107
|
+
if (isElementFrozen(this.element)) {
|
|
108
|
+
this.stopInputs();
|
|
109
|
+
} else {
|
|
110
|
+
this.listenInputs();
|
|
111
|
+
}
|
|
81
112
|
}
|
|
82
113
|
}
|
|
83
114
|
|
|
@@ -85,6 +116,11 @@ export class ControlsDirective extends Directive {
|
|
|
85
116
|
* Cleanup and destroy all control systems
|
|
86
117
|
*/
|
|
87
118
|
onDestroy(element: Element) {
|
|
119
|
+
if (this.freezeSubscription) {
|
|
120
|
+
this.freezeSubscription.unsubscribe();
|
|
121
|
+
this.freezeSubscription = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
88
124
|
if (this.keyboardControls) {
|
|
89
125
|
this.keyboardControls.destroy();
|
|
90
126
|
this.keyboardControls = null;
|
|
@@ -99,6 +135,8 @@ export class ControlsDirective extends Directive {
|
|
|
99
135
|
this.joystickControls.destroy();
|
|
100
136
|
this.joystickControls = null;
|
|
101
137
|
}
|
|
138
|
+
|
|
139
|
+
this.element = null;
|
|
102
140
|
}
|
|
103
141
|
|
|
104
142
|
/**
|
package/src/engine/animation.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
-
import { effect, signal, type WritableSignal } from "@signe/reactive";
|
|
1
|
+
import { effect, signal, type WritableSignal, type Signal } from "@signe/reactive";
|
|
2
2
|
import { animate as animatePopmotion } from "popmotion";
|
|
3
|
+
import { Tick } from "../directives/Scheduler";
|
|
4
|
+
import { Subscription } from "rxjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Gets the global tick signal from the engine context.
|
|
8
|
+
* This is automatically set by the Canvas component when it initializes.
|
|
9
|
+
*
|
|
10
|
+
* @returns The global tick signal if available, undefined otherwise
|
|
11
|
+
*/
|
|
12
|
+
function getGlobalTickSignal(): Signal<Tick> | undefined {
|
|
13
|
+
return (globalThis as any).__CANVAS_ENGINE_TICK__;
|
|
14
|
+
}
|
|
3
15
|
|
|
4
16
|
export interface AnimateOptions<T> {
|
|
5
17
|
duration?: number;
|
|
6
18
|
ease?: (t: number) => number;
|
|
7
19
|
onUpdate?: (value: T) => void;
|
|
8
20
|
onComplete?: () => void;
|
|
21
|
+
tick?: Signal<Tick>;
|
|
9
22
|
}
|
|
10
23
|
|
|
11
24
|
export interface AnimatedState<T> {
|
|
@@ -19,12 +32,62 @@ export interface AnimatedSignal<T> extends Omit<WritableSignal<T>, 'set'> {
|
|
|
19
32
|
set: (newValue: T, options?: AnimateOptions<T>) => Promise<void>;
|
|
20
33
|
animatedState: WritableSignal<AnimatedState<T>>;
|
|
21
34
|
update: (updater: (value: T) => T) => void;
|
|
35
|
+
pause: () => void;
|
|
36
|
+
resume: () => void;
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
export function isAnimatedSignal(signal: WritableSignal<any>): boolean {
|
|
25
40
|
return (signal as unknown as AnimatedSignal<any>).animatedState !== undefined;
|
|
26
41
|
}
|
|
27
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Creates a popmotion driver that uses the engine's tick system.
|
|
45
|
+
* The driver subscribes to the tick signal and calls the update function with deltaTime on each tick.
|
|
46
|
+
*
|
|
47
|
+
* @param tickSignal - The tick signal from the engine context
|
|
48
|
+
* @returns A driver function for popmotion
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const driver = createTickDriver(context.tick);
|
|
52
|
+
* animate({
|
|
53
|
+
* to: 100,
|
|
54
|
+
* driver: driver
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
function createTickDriver(tickSignal: Signal<Tick>) {
|
|
59
|
+
return (update: (delta: number) => void) => {
|
|
60
|
+
let subscription: Subscription | undefined;
|
|
61
|
+
let lastTimestamp: number | null = null;
|
|
62
|
+
|
|
63
|
+
const start = () => {
|
|
64
|
+
if (subscription) return;
|
|
65
|
+
|
|
66
|
+
subscription = (tickSignal.observable as any).subscribe((result: any) => {
|
|
67
|
+
const tick = result?.value ?? result;
|
|
68
|
+
if (!tick) return;
|
|
69
|
+
|
|
70
|
+
if (lastTimestamp === null) {
|
|
71
|
+
lastTimestamp = tick.timestamp;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const delta = tick.deltaTime;
|
|
76
|
+
lastTimestamp = tick.timestamp;
|
|
77
|
+
update(delta);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const stop = () => {
|
|
82
|
+
subscription?.unsubscribe();
|
|
83
|
+
subscription = undefined;
|
|
84
|
+
lastTimestamp = null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return { start, stop };
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
28
91
|
/**
|
|
29
92
|
* Creates an animated signal with the given initial value and animation options.
|
|
30
93
|
* It's a writable signal that can be animated using popmotion. Properties of the animated signal are:
|
|
@@ -32,16 +95,26 @@ export function isAnimatedSignal(signal: WritableSignal<any>): boolean {
|
|
|
32
95
|
* - start: the start value of the animation.
|
|
33
96
|
* - end: the end value of the animation.
|
|
34
97
|
*
|
|
98
|
+
* If a tick signal is provided in options, the animation will use the engine's tick system.
|
|
99
|
+
* Otherwise, it will automatically use the global tick signal from the Canvas context if available.
|
|
100
|
+
* If no tick signal is available, it will use requestAnimationFrame by default.
|
|
101
|
+
*
|
|
35
102
|
* @param initialValue The initial value of the signal.
|
|
36
|
-
* @param options The animation options.
|
|
103
|
+
* @param options The animation options. Can include a `tick` signal to use a specific tick system.
|
|
37
104
|
* @returns The animated signal.
|
|
38
105
|
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* // Automatically uses the Canvas tick system if available, otherwise requestAnimationFrame
|
|
39
108
|
* const animatedValue = animatedSignal(0, { duration: 1000 });
|
|
40
109
|
* animatedValue.set(10);
|
|
41
|
-
* animatedValue.update((value) => value + 1);
|
|
42
|
-
* console.log(animatedValue()); // 11
|
|
43
110
|
*
|
|
44
|
-
*
|
|
111
|
+
* // Explicitly using a specific tick signal
|
|
112
|
+
* mount((element) => {
|
|
113
|
+
* const tickSignal = element.props.context.tick;
|
|
114
|
+
* const animatedValue = animatedSignal(0, { duration: 1000, tick: tickSignal });
|
|
115
|
+
* animatedValue.set(10);
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
45
118
|
*/
|
|
46
119
|
export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> = {}): AnimatedSignal<T> {
|
|
47
120
|
const state: AnimatedState<T> = {
|
|
@@ -49,7 +122,9 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
49
122
|
start: initialValue,
|
|
50
123
|
end: initialValue,
|
|
51
124
|
};
|
|
52
|
-
|
|
125
|
+
const DEFAULT_DURATION = 20;
|
|
126
|
+
let animation: { stop: () => void } | null = null;
|
|
127
|
+
let isPaused = false;
|
|
53
128
|
|
|
54
129
|
const publicSignal = signal(initialValue);
|
|
55
130
|
const privateSignal = signal(state);
|
|
@@ -76,24 +151,43 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
76
151
|
|
|
77
152
|
privateSignal.set(newState);
|
|
78
153
|
|
|
154
|
+
// Stop any running animation
|
|
79
155
|
if (animation) {
|
|
80
156
|
animation.stop();
|
|
157
|
+
animation = null;
|
|
81
158
|
}
|
|
82
159
|
|
|
160
|
+
isPaused = false;
|
|
161
|
+
const mergedConfig = { ...options, ...animationConfig };
|
|
162
|
+
const duration = mergedConfig.duration ?? DEFAULT_DURATION;
|
|
163
|
+
const ease = mergedConfig.ease ?? ((t: number) => t);
|
|
164
|
+
const tickSignal = mergedConfig.tick || getGlobalTickSignal();
|
|
165
|
+
|
|
166
|
+
const startValue = prevState.current;
|
|
167
|
+
const endValue = newValue;
|
|
168
|
+
|
|
169
|
+
const onCompleteCb = animationConfig.onComplete ?? options.onComplete;
|
|
170
|
+
|
|
171
|
+
|
|
83
172
|
animation = animatePopmotion({
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
173
|
+
from: startValue as any,
|
|
174
|
+
to: endValue as any,
|
|
175
|
+
duration,
|
|
176
|
+
ease,
|
|
177
|
+
|
|
178
|
+
onUpdate: (value: any) => {
|
|
179
|
+
if (isPaused) return;
|
|
180
|
+
const nextValue = value as T;
|
|
181
|
+
privateSignal.update(s => ({ ...s, current: nextValue }));
|
|
182
|
+
mergedConfig.onUpdate?.(nextValue);
|
|
183
|
+
animationConfig.onUpdate?.(nextValue);
|
|
95
184
|
},
|
|
185
|
+
onComplete: () => {
|
|
186
|
+
privateSignal.update(s => ({ ...s, current: endValue }));
|
|
187
|
+
onCompleteCb?.();
|
|
188
|
+
}
|
|
96
189
|
});
|
|
190
|
+
|
|
97
191
|
}
|
|
98
192
|
|
|
99
193
|
const fn = function() {
|
|
@@ -110,11 +204,35 @@ export function animatedSignal<T>(initialValue: T, options: AnimateOptions<T> =
|
|
|
110
204
|
}
|
|
111
205
|
fn.set = async (newValue: T, animationConfig: AnimateOptions<T> = {}) => {
|
|
112
206
|
return new Promise<void>((resolve) => {
|
|
207
|
+
const userOnComplete = animationConfig.onComplete;
|
|
113
208
|
animatedSignal(newValue, {
|
|
114
209
|
...animationConfig,
|
|
115
|
-
onComplete:
|
|
210
|
+
onComplete: () => {
|
|
211
|
+
if (userOnComplete) {
|
|
212
|
+
userOnComplete();
|
|
213
|
+
} else if (options.onComplete) {
|
|
214
|
+
options.onComplete();
|
|
215
|
+
}
|
|
216
|
+
resolve();
|
|
217
|
+
}
|
|
116
218
|
});
|
|
117
|
-
})
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
fn.pause = () => {
|
|
222
|
+
if (isPaused) return;
|
|
223
|
+
isPaused = true;
|
|
224
|
+
if (animation) {
|
|
225
|
+
animation.stop();
|
|
226
|
+
animation = null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
fn.resume = () => {
|
|
230
|
+
if (!isPaused) return;
|
|
231
|
+
isPaused = false;
|
|
232
|
+
const currentState = privateSignal();
|
|
233
|
+
if (currentState.current !== currentState.end) {
|
|
234
|
+
animatedSignal(currentState.end, options);
|
|
235
|
+
}
|
|
118
236
|
}
|
|
119
237
|
|
|
120
238
|
return fn as any
|