lecodes-sdk 1.1.0 → 2.0.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 +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +11 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Particles.d.ts +15 -0
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +59 -3
- package/dist/types/gl/terrain/Terrain.d.ts +8 -0
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +8 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +142 -142
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +77 -54
- package/prompts/dist/3d-app.md +75 -52
- package/prompts/dist/ar-app.md +74 -51
- package/prompts/dist/design.md +30 -8
- package/prompts/dist/ui-app.md +73 -46
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +262 -378
- package/src/animate/tween/animateValue.ts +103 -100
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +505 -479
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +162 -161
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +13 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Model.ts +172 -167
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +20 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/Locomotion.ts +77 -8
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/terrain/Terrain.ts +33 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +233 -236
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +21 -8
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +154 -148
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4730
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1760
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
|
@@ -1,378 +1,262 @@
|
|
|
1
|
-
// The animation HANDLE (docs/timeline-plan.md §2.2): what `animateTo` / `animateFrom` / `Timeline`
|
|
2
|
-
// return. Owns one spec; every `play()` builds the blob (target ids resolve at play time), hands it
|
|
3
|
-
// to the host (`
|
|
4
|
-
// and finish). On a host without the tween core the UI tracks fall back to the old
|
|
5
|
-
// `animateTo` / `animateFrom` bridge pair (single-value tweens only) and the clock is a timer —
|
|
6
|
-
// see `fallback` below.
|
|
7
|
-
|
|
8
|
-
import { CLOCK_GAME, CLOCK_UI, DOM_UI, DOM_VALUE, buildBlob, evaluateTrack, type Track, type TweenSpec } from "./spec"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
rate
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// ---- host events -------------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
eventsRegistered
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
else if (kind ===
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
private
|
|
88
|
-
private
|
|
89
|
-
private
|
|
90
|
-
private
|
|
91
|
-
private
|
|
92
|
-
private
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this._settled
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
get
|
|
116
|
-
get
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
get time(): number {
|
|
126
|
-
if (this.
|
|
127
|
-
return this.
|
|
128
|
-
}
|
|
129
|
-
set time(ms: number) { this.seek(ms) }
|
|
130
|
-
|
|
131
|
-
get progress(): number {
|
|
132
|
-
const total = this._totalMs()
|
|
133
|
-
return total > 0 ? Math.min(1, Math.max(0, this._elapsedMs() / total)) : 1
|
|
134
|
-
}
|
|
135
|
-
set progress(p: number) {
|
|
136
|
-
const total = this._totalMs()
|
|
137
|
-
this._seekTotal(Math.min(1, Math.max(0, p)) * total)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private _totalMs(): number {
|
|
141
|
-
const s = this._spec
|
|
142
|
-
const n = s.iterations < 0 ? 1 : s.iterations
|
|
143
|
-
return s.durationMs * n * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
144
|
-
}
|
|
145
|
-
private _elapsedMs(): number {
|
|
146
|
-
if (this.
|
|
147
|
-
return this.
|
|
148
|
-
}
|
|
149
|
-
private _seekTotal(ms: number): void {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (this.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this
|
|
211
|
-
return this
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this._settle(true)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ---- old hosts ------------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
private _ensureFallback(): Fallback {
|
|
269
|
-
if (!this._fb) this._fb = new Fallback(this)
|
|
270
|
-
return this._fb
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/** Old-host path: UI tracks go through the legacy `animateTo` / `animateFrom` pair (one value per
|
|
275
|
-
* prop — the last explicit key for a to-tween, the first for a from-tween; no easing, no seek), a
|
|
276
|
-
* track on any other domain is dropped with one warning, and the clock is a timer that fires the
|
|
277
|
-
* calls and the finish. Good enough for the simple cases on web / Apple until their core lands. */
|
|
278
|
-
class Fallback {
|
|
279
|
-
time = 0
|
|
280
|
-
elapsed = 0
|
|
281
|
-
private _timers: ReturnType<typeof setTimeout>[] = []
|
|
282
|
-
private _startedAt = 0
|
|
283
|
-
private _rate = 1
|
|
284
|
-
private _warned = false
|
|
285
|
-
private _loop?: ReturnType<typeof setInterval>
|
|
286
|
-
private _valueTracks: Track[] = []
|
|
287
|
-
private _scratch = new Float32Array(16)
|
|
288
|
-
private _lanes: number[] = []
|
|
289
|
-
|
|
290
|
-
private a: TweenAnimation
|
|
291
|
-
constructor(a: TweenAnimation) { this.a = a }
|
|
292
|
-
|
|
293
|
-
setRate(r: number): void { this._rate = r }
|
|
294
|
-
|
|
295
|
-
play(): void {
|
|
296
|
-
const s = this.a._spec
|
|
297
|
-
this.cancel()
|
|
298
|
-
this._startedAt = Date.now()
|
|
299
|
-
const byTarget = new Map<Track["target"], { to: Record<string, unknown>, from: Record<string, unknown>, at: number, dur: number, commit: boolean }>()
|
|
300
|
-
this._valueTracks = []
|
|
301
|
-
for (const t of s.tracks) {
|
|
302
|
-
if (t.channel.domain === DOM_VALUE) { this._valueTracks.push(t); continue }
|
|
303
|
-
if (t.channel.domain !== DOM_UI) {
|
|
304
|
-
if (!this._warned) { this._warned = true; console.warn("[tween] this host animates UI elements and values only (no tween core) — 3D / 2D tracks skipped") }
|
|
305
|
-
continue
|
|
306
|
-
}
|
|
307
|
-
const rec = byTarget.get(t.target) ?? { to: {}, from: {}, at: t.atMs, dur: t.durMs, commit: t.commit }
|
|
308
|
-
byTarget.set(t.target, rec)
|
|
309
|
-
const first = t.keys[0]!, last = t.keys[t.keys.length - 1]!
|
|
310
|
-
if (last.value === undefined) rec.from[t.prop] = first.raw // animateFrom shape
|
|
311
|
-
else rec.to[t.prop] = last.raw
|
|
312
|
-
rec.at = Math.min(rec.at, t.atMs)
|
|
313
|
-
rec.dur = Math.max(rec.dur, t.durMs)
|
|
314
|
-
}
|
|
315
|
-
for (const [target, rec] of byTarget) {
|
|
316
|
-
// Sent even for an unmounted element (id 0), exactly as the direct call used to be — the host
|
|
317
|
-
// ignores it; a stub host in tests records it.
|
|
318
|
-
const id = ((target as any)._id as number) ?? 0
|
|
319
|
-
const delay = rec.at + s.delayMs
|
|
320
|
-
const meta: Record<string, unknown> = { duration: rec.dur }
|
|
321
|
-
if (delay > 0) meta.delay = delay
|
|
322
|
-
if (s.iterations !== 1) { meta.loop = s.iterations < 0 ? true : s.iterations; meta.loopMode = s.pingPong ? "ping-pong" : "restart" }
|
|
323
|
-
// The host merges its animate layer into base on commit (the SDK committed its own style already)
|
|
324
|
-
if (Object.keys(rec.to).length) _creatorUI.animateTo?.(id, { ...rec.to, ...meta, ...(rec.commit ? {} : { commit: false }) })
|
|
325
|
-
if (Object.keys(rec.from).length) _creatorUI.animateFrom?.(id, { ...rec.from, ...meta })
|
|
326
|
-
}
|
|
327
|
-
const scale = this._rate > 0 ? 1 / this._rate : 0
|
|
328
|
-
if (scale === 0) return
|
|
329
|
-
for (let i = 0; i < s.calls.length; i++) {
|
|
330
|
-
this._timers.push(setTimeout(() => this.a._fireCall(i), (s.delayMs + s.calls[i]!) * scale))
|
|
331
|
-
}
|
|
332
|
-
if (s.iterations === 1) {
|
|
333
|
-
this._timers.push(setTimeout(() => { this._applyValues(s.durationMs); this.elapsed = this.time = s.durationMs; this.a._hostFinished() }, (s.delayMs + s.durationMs) * scale))
|
|
334
|
-
}
|
|
335
|
-
// VALUE tracks are evaluated here in JS (the C evaluator's twin), ~60 times a second
|
|
336
|
-
if (this._valueTracks.length) {
|
|
337
|
-
this._applyValues(0)
|
|
338
|
-
this._loop = setInterval(() => {
|
|
339
|
-
const total = (Date.now() - this._startedAt) * this._rate - s.delayMs
|
|
340
|
-
if (total < 0) return
|
|
341
|
-
const period = s.durationMs * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
342
|
-
let local = period > 0 ? total % period : 0
|
|
343
|
-
if (s.iterations !== 1 && s.pingPong && local > s.durationMs) local = 2 * s.durationMs - local
|
|
344
|
-
if (s.iterations === 1) local = Math.min(total, s.durationMs)
|
|
345
|
-
this.elapsed = total
|
|
346
|
-
this.time = local
|
|
347
|
-
this._applyValues(local)
|
|
348
|
-
}, 16)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
private _applyValues(local: number): void {
|
|
352
|
-
for (const t of this._valueTracks) {
|
|
353
|
-
evaluateTrack(t, local, this._lanes)
|
|
354
|
-
for (let i = 0; i < t.lanes; i++) this._scratch[i] = this._lanes[i]!
|
|
355
|
-
this.a._fireValue(t.channel.id() as number, this._scratch)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
pause(): void { this.cancel() }
|
|
359
|
-
resume(): void { /* no partial resume on the timer clock: the tween already ran on the host */ }
|
|
360
|
-
seek(ms: number): void { this.elapsed = ms; this.time = ms % Math.max(1, this.a._spec.durationMs); this._applyValues(this.time) }
|
|
361
|
-
finish(): void { this.cancel(); this._applyValues(this.a._spec.durationMs); this.a._hostFinished() }
|
|
362
|
-
cancel(): void {
|
|
363
|
-
for (const t of this._timers) clearTimeout(t)
|
|
364
|
-
this._timers = []
|
|
365
|
-
if (this._loop !== undefined) { clearInterval(this._loop); this._loop = undefined }
|
|
366
|
-
if (this._startedAt) this.elapsed = this.time = Math.min(this.a._spec.durationMs, (Date.now() - this._startedAt) * this._rate)
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** @internal Build a one-bag animation for a single target (the `animateTo` / `animateFrom` path). */
|
|
371
|
-
export const singleTargetSpec = (tracks: Track[], clock: number, iterations: number, pingPong: boolean, delayMs: number): TweenSpec => {
|
|
372
|
-
let durationMs = 0
|
|
373
|
-
for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
|
|
374
|
-
// `delay` lives on the tracks (the first key holds through it, §3.1); the spec delay is 0 here
|
|
375
|
-
return { clock, durationMs, delayMs, iterations, pingPong, rate: 1, tracks, calls: [] }
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export { CLOCK_UI, CLOCK_GAME }
|
|
1
|
+
// The animation HANDLE (docs/timeline-plan.md §2.2): what `animateTo` / `animateFrom` / `Timeline`
|
|
2
|
+
// return. Owns one spec; every `play()` builds the blob (target ids resolve at play time), hands it
|
|
3
|
+
// to the host (`_creatorTree.tweenCreate`) and gets events back (`registerTweenEvent`: time-callbacks
|
|
4
|
+
// and finish). On a host without the tween core the UI tracks fall back to the old
|
|
5
|
+
// `animateTo` / `animateFrom` bridge pair (single-value tweens only) and the clock is a timer —
|
|
6
|
+
// see `fallback` below.
|
|
7
|
+
|
|
8
|
+
import { CLOCK_GAME, CLOCK_UI, DOM_UI, DOM_VALUE, buildBlob, evaluateTrack, type Track, type TweenSpec } from "./spec"
|
|
9
|
+
import { DEAD_HANDLE, Pins } from "../../core/pins"
|
|
10
|
+
|
|
11
|
+
const OP_PLAY = 0
|
|
12
|
+
const OP_PAUSE = 1
|
|
13
|
+
const OP_RESUME = 2
|
|
14
|
+
const OP_CANCEL = 3
|
|
15
|
+
const OP_FINISH = 4
|
|
16
|
+
|
|
17
|
+
const EV_CALL = 0
|
|
18
|
+
const EV_FINISH = 1
|
|
19
|
+
const EV_VALUE = 2 // (id, 2, slot, lane0, lane1, …) — a VALUE track's frame, synchronous from the host
|
|
20
|
+
|
|
21
|
+
/** Playback control shared by element tweens and timelines. Times are **milliseconds**. */
|
|
22
|
+
export interface Animation {
|
|
23
|
+
/** One iteration, ms. */
|
|
24
|
+
readonly duration: number
|
|
25
|
+
/** Position inside the current iteration, ms. Writable = seek. */
|
|
26
|
+
time: number
|
|
27
|
+
/** 0..1 of the whole animation (iterations included). Writable = seek. */
|
|
28
|
+
progress: number
|
|
29
|
+
/** Playback rate; negative runs backwards, 0 freezes. */
|
|
30
|
+
rate: number
|
|
31
|
+
readonly playing: boolean
|
|
32
|
+
/** Resolves `true` when the animation reaches its end, `false` when cancelled, replaced or replayed. Never rejects. */
|
|
33
|
+
readonly finished: Promise<boolean>
|
|
34
|
+
/** Start from t = 0 (a running animation restarts). */
|
|
35
|
+
play(): this
|
|
36
|
+
pause(): this
|
|
37
|
+
resume(): this
|
|
38
|
+
/** Jump to `ms`; playback state is unchanged. */
|
|
39
|
+
seek(ms: number): this
|
|
40
|
+
/** Jump to the end: values land, commits apply, `finished` resolves `true`. */
|
|
41
|
+
finish(): this
|
|
42
|
+
/** Stop where it is — no commit, `finished` resolves `false`. */
|
|
43
|
+
cancel(): this
|
|
44
|
+
onFinish(fn: (done: boolean) => void): this
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
// ---- host events -------------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/** The animations native owns right now (playing), by id — what the host's events resolve to. */
|
|
51
|
+
const tweens = new Pins<TweenAnimation>()
|
|
52
|
+
let eventsRegistered = false
|
|
53
|
+
const ensureEvents = (): void => {
|
|
54
|
+
if (eventsRegistered) return
|
|
55
|
+
eventsRegistered = true
|
|
56
|
+
_creatorTree.registerTweenEvent(function (this: unknown, id: number, kind: number, index: number) {
|
|
57
|
+
const a = tweens.get(id)
|
|
58
|
+
if (!a) return
|
|
59
|
+
if (kind === EV_CALL) a._fireCall(index)
|
|
60
|
+
else if (kind === EV_FINISH) a._hostFinished()
|
|
61
|
+
else if (kind === EV_VALUE) {
|
|
62
|
+
// lanes ride as plain number arguments (no per-frame array on either side)
|
|
63
|
+
const n = arguments.length - 3
|
|
64
|
+
for (let i = 0; i < n; i++) valueScratch[i] = arguments[i + 3] as number
|
|
65
|
+
a._fireValue(index, valueScratch)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Reused lane buffer for VALUE deliveries (the host's largest kind is 10 lanes). */
|
|
71
|
+
const valueScratch = new Float32Array(16)
|
|
72
|
+
|
|
73
|
+
/** The concrete handle — what the SDK instantiates behind `animateTo` / `animateFrom` / `Timeline`.
|
|
74
|
+
* User code sees the {@link Animation} interface; the class itself must stay in the public
|
|
75
|
+
* declarations because `TimelineImpl` extends it — marking it internal (even mentioning the tag in
|
|
76
|
+
* this comment: the strip is a substring match) drops the timeline's playback members. */
|
|
77
|
+
export class TweenAnimation implements Animation {
|
|
78
|
+
/** @internal */
|
|
79
|
+
_spec: TweenSpec
|
|
80
|
+
/** @internal Time-callbacks by call index (the spec's `calls[i]`). */
|
|
81
|
+
_calls: Array<() => void> = []
|
|
82
|
+
/** @internal VALUE-track deliverers by slot (`animate()` / `Timeline.animate`). */
|
|
83
|
+
_values = new Map<number, (lanes: Float32Array) => void>()
|
|
84
|
+
|
|
85
|
+
/** @internal The host animation's owned handle (DEAD_HANDLE while there is none). */
|
|
86
|
+
_h: Handle = DEAD_HANDLE
|
|
87
|
+
private _started = false // play() ran at least once (the first play keeps the constructor's promise)
|
|
88
|
+
private _endTime = 0 // `time` after the host freed a finished animation
|
|
89
|
+
private _playing = false
|
|
90
|
+
private _rate: number
|
|
91
|
+
private _finished!: Promise<boolean>
|
|
92
|
+
private _resolve!: (done: boolean) => void
|
|
93
|
+
private _settled = true
|
|
94
|
+
private _listeners: Array<(done: boolean) => void> = []
|
|
95
|
+
|
|
96
|
+
constructor(spec: TweenSpec) {
|
|
97
|
+
this._spec = spec
|
|
98
|
+
this._rate = spec.rate
|
|
99
|
+
this._newPromise()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private _newPromise(): void {
|
|
103
|
+
this._finished = new Promise<boolean>((r) => { this._resolve = r })
|
|
104
|
+
this._settled = false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private _settle(done: boolean): void {
|
|
108
|
+
if (this._settled) return
|
|
109
|
+
this._settled = true
|
|
110
|
+
this._playing = false
|
|
111
|
+
this._resolve(done)
|
|
112
|
+
for (const fn of this._listeners) fn(done)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get duration(): number { return this._spec.durationMs }
|
|
116
|
+
get playing(): boolean { return this._playing }
|
|
117
|
+
get finished(): Promise<boolean> { return this._finished }
|
|
118
|
+
|
|
119
|
+
get rate(): number { return this._rate }
|
|
120
|
+
set rate(r: number) {
|
|
121
|
+
this._rate = r
|
|
122
|
+
if (this._h.id) _creatorTree.tweenSetRate(this._h.id, r)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get time(): number {
|
|
126
|
+
if (this._h.id) return _creatorTree.tweenGetTime(this._h.id)
|
|
127
|
+
return this._endTime
|
|
128
|
+
}
|
|
129
|
+
set time(ms: number) { this.seek(ms) }
|
|
130
|
+
|
|
131
|
+
get progress(): number {
|
|
132
|
+
const total = this._totalMs()
|
|
133
|
+
return total > 0 ? Math.min(1, Math.max(0, this._elapsedMs() / total)) : 1
|
|
134
|
+
}
|
|
135
|
+
set progress(p: number) {
|
|
136
|
+
const total = this._totalMs()
|
|
137
|
+
this._seekTotal(Math.min(1, Math.max(0, p)) * total)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private _totalMs(): number {
|
|
141
|
+
const s = this._spec
|
|
142
|
+
const n = s.iterations < 0 ? 1 : s.iterations
|
|
143
|
+
return s.durationMs * n * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
144
|
+
}
|
|
145
|
+
private _elapsedMs(): number {
|
|
146
|
+
if (this._h.id) return _creatorTree.tweenGetElapsed(this._h.id)
|
|
147
|
+
return this._endTime > 0 ? this._totalMs() : 0
|
|
148
|
+
}
|
|
149
|
+
private _seekTotal(ms: number): void {
|
|
150
|
+
this._ensure()
|
|
151
|
+
_creatorTree.tweenSeek(this._h.id, ms)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Create the host animation (paused at t = 0) if it doesn't exist. */
|
|
155
|
+
private _ensure(): void {
|
|
156
|
+
if (this._h.id) return
|
|
157
|
+
ensureEvents()
|
|
158
|
+
const blob = buildBlob(this._spec)
|
|
159
|
+
this._h = _creatorTree.tweenCreate(blob.data, blob.strings, blob.targets as number[]) ?? DEAD_HANDLE
|
|
160
|
+
if (this._h.id && this._rate !== 1) _creatorTree.tweenSetRate(this._h.id, this._rate)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private _destroy(): void {
|
|
164
|
+
if (this._h.id) {
|
|
165
|
+
tweens.unpin(this._h.id)
|
|
166
|
+
_creatorTree.tweenControl(this._h.id, OP_CANCEL) // the runtime frees it; drop the handle too
|
|
167
|
+
this._h = DEAD_HANDLE
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
play(): this {
|
|
172
|
+
// A replay: whoever waited on the previous run gets `false` (unless it already ended), the host
|
|
173
|
+
// object is rebuilt (ids may have changed — a screen re-mounts its nodes on every open). The
|
|
174
|
+
// FIRST play keeps the promise handed out before it — unless a `cancel()` before any play has
|
|
175
|
+
// already settled that one, in which case this run gets a fresh promise.
|
|
176
|
+
if (this._started) {
|
|
177
|
+
this._settle(false)
|
|
178
|
+
this._destroy()
|
|
179
|
+
}
|
|
180
|
+
if (this._settled) this._newPromise()
|
|
181
|
+
this._started = true
|
|
182
|
+
this._endTime = 0
|
|
183
|
+
// Commit = the last keyframe becomes the stored state NOW (as `animateTo` always did), so reads
|
|
184
|
+
// and later `.style()` merges see the final state while the host is still tweening.
|
|
185
|
+
for (const t of this._spec.tracks) {
|
|
186
|
+
if (!t.commit || !t.channel.commit) continue
|
|
187
|
+
const last = t.keys[t.keys.length - 1]
|
|
188
|
+
if (last && last.value !== undefined) t.channel.commit(last.raw)
|
|
189
|
+
}
|
|
190
|
+
this._playing = true
|
|
191
|
+
this._ensure()
|
|
192
|
+
_creatorTree.tweenControl(this._h.id, OP_PLAY)
|
|
193
|
+
tweens.pin(this) // playing: native owns it, its events must find it
|
|
194
|
+
return this
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pause(): this {
|
|
198
|
+
this._ensure()
|
|
199
|
+
_creatorTree.tweenControl(this._h.id, OP_PAUSE)
|
|
200
|
+
this._playing = false
|
|
201
|
+
tweens.unpin(this._h.id) // paused: only the app's reference keeps it
|
|
202
|
+
return this
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
resume(): this {
|
|
206
|
+
if (this._settled) return this.play()
|
|
207
|
+
this._ensure()
|
|
208
|
+
_creatorTree.tweenControl(this._h.id, OP_RESUME)
|
|
209
|
+
this._playing = true
|
|
210
|
+
tweens.pin(this)
|
|
211
|
+
return this
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
seek(ms: number): this {
|
|
215
|
+
// `time` is inside the current iteration; a seek by time on a looping animation stays in it.
|
|
216
|
+
const s = this._spec
|
|
217
|
+
const period = s.durationMs * (s.pingPong && s.iterations !== 1 ? 2 : 1)
|
|
218
|
+
const iter = period > 0 ? Math.floor(this._elapsedMs() / period) : 0
|
|
219
|
+
this._seekTotal(iter * period + Math.max(0, ms))
|
|
220
|
+
return this
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
finish(): this {
|
|
224
|
+
this._ensure()
|
|
225
|
+
_creatorTree.tweenControl(this._h.id, OP_FINISH) // the host answers with EV_FINISH
|
|
226
|
+
return this
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
cancel(): this {
|
|
230
|
+
this._destroy()
|
|
231
|
+
this._settle(false)
|
|
232
|
+
return this
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
onFinish(fn: (done: boolean) => void): this {
|
|
236
|
+
this._listeners.push(fn)
|
|
237
|
+
return this
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** @internal */
|
|
241
|
+
_fireCall(index: number): void { this._calls[index]?.() }
|
|
242
|
+
/** @internal */
|
|
243
|
+
_fireValue(slot: number, lanes: Float32Array): void { this._values.get(slot)?.(lanes) }
|
|
244
|
+
|
|
245
|
+
/** @internal The host reached the end (naturally or via finish()) and has freed the animation. */
|
|
246
|
+
_hostFinished(): void {
|
|
247
|
+
if (this._h.id) { tweens.unpin(this._h.id); this._h = DEAD_HANDLE } // the runtime freed it
|
|
248
|
+
this._endTime = this._spec.durationMs
|
|
249
|
+
this._settle(true)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** @internal Build a one-bag animation for a single target (the `animateTo` / `animateFrom` path). */
|
|
255
|
+
export const singleTargetSpec = (tracks: Track[], clock: number, iterations: number, pingPong: boolean, delayMs: number): TweenSpec => {
|
|
256
|
+
let durationMs = 0
|
|
257
|
+
for (const t of tracks) durationMs = Math.max(durationMs, t.atMs + t.durMs)
|
|
258
|
+
// `delay` lives on the tracks (the first key holds through it, §3.1); the spec delay is 0 here
|
|
259
|
+
return { clock, durationMs, delayMs, iterations, pingPong, rate: 1, tracks, calls: [] }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export { CLOCK_UI, CLOCK_GAME }
|