@moku-labs/game 0.0.2 → 0.0.3
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 +89 -21
- package/dist/assets.d.mts +58 -6
- package/dist/assets.mjs +1630 -234
- package/dist/commands-BOxqlv7w.d.mts +99 -0
- package/dist/control.d.mts +3 -92
- package/dist/control.mjs +3 -757
- package/dist/define-BMo0pHe_.d.mts +51 -0
- package/dist/{registry-DlpRCibU.mjs → dev-CcwFrwok.mjs} +30 -1
- package/dist/index.d.mts +217 -29
- package/dist/index.mjs +6179 -727
- package/dist/inspect.d.mts +10 -8
- package/dist/inspect.mjs +2 -518
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
- package/dist/run-tlcn1w7y.mjs +944 -0
- package/dist/{runtime-DiOTkDZz.d.mts → runtime-BstIkrhH.d.mts} +1 -1
- package/dist/sources-BooL2MsX.mjs +545 -0
- package/dist/testing.d.mts +305 -3
- package/dist/testing.mjs +2056 -2
- package/dist/types-BTycq8_Z.d.mts +1196 -0
- package/dist/{types-yg_ywtT-.d.mts → types-C0SLQsx0.d.mts} +230 -31
- package/dist/types-Cjf3ywai.d.mts +5982 -0
- package/dist/{define-sFoO3y6X.d.mts → types-D0uYTTC3.d.mts} +180 -51
- package/dist/{types-DWILGrPn.d.mts → types-DgwbzDLP.d.mts} +87 -19
- package/dist/types-KlE_GL1z.d.mts +1444 -0
- package/dist/{visual-BDUHSRvf.mjs → visual-NPrDkvUk.mjs} +19 -13
- package/package.json +17 -2
- package/dist/headless-CvamCUcR.mjs +0 -186
- package/dist/headless-KaSWcd0s.d.mts +0 -181
- package/dist/types-BxkNNYul.d.mts +0 -2840
- package/dist/types-CTPS9GBu.d.mts +0 -742
- package/dist/types-DD-QrG_z.d.mts +0 -588
- package/dist/types-DYLnSgMI.d.mts +0 -570
- package/dist/types-JNc_UQBo.d.mts +0 -2896
|
@@ -0,0 +1,1444 @@
|
|
|
1
|
+
import { A as TrackSegment, At as Json, I as ComponentType, M as AnyComponent, P as AnyComponentValue, R as Entity, St as Api, T as MotionHandle, _t as TypeTag, h as Api$2, k as TrackOptions, nn as Require, q as Api$1, t as Api$3, x as Ease } from "./types-Cjf3ywai.mjs";
|
|
2
|
+
import { o as Events$1, t as Api$4 } from "./types-DgwbzDLP.mjs";
|
|
3
|
+
import { F as Events$2, H as InputApi, I as I18nApi, L as Message, c as UiNode, i as Finding, n as ElementChange, o as JsxState, r as ElementMotion, v as StylesState } from "./types-C0SLQsx0.mjs";
|
|
4
|
+
import { Log } from "@moku-labs/common/browser";
|
|
5
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
6
|
+
import { Node, Yoga } from "yoga-layout/load";
|
|
7
|
+
|
|
8
|
+
//#region src/plugins/text/types.d.ts
|
|
9
|
+
declare namespace types_d_exports$2 {
|
|
10
|
+
export { AdvanceTable, BundleLoaded, BundleUnloaded, Config$2 as Config, Deps$2 as Deps, DrawnLabel, IconRun, KernelSlice$2 as KernelSlice, LayoutOptions, Line, LocaleChanged, Point, Run, SeenText, Size, State$2 as State, TextAlign, TextApi, TextBind, TextCtx, TextLayout, TextRun, TextShadow, TextStyle, TextStyleInput, TextStyles, TextValue, TextWrap, Warn };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A point in reference units: where a label sits, and which point of the block sits there.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const anchor: Point = { x: 0.5, y: 0.5 };
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
type Point = {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The size of a measured block, in reference pixels.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const size: Size = { width: 57.6, height: 38.4 };
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
type Size = {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Which numeric component field a label shows. The component is named, not imported: a HUD is
|
|
38
|
+
* written before the game's components are in scope.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const bind: TextBind = { component: "Counter", field: "value" };
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
type TextBind = {
|
|
46
|
+
component: string;
|
|
47
|
+
field: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* What the `Text` component holds. `resolved` is engine-owned: a game writes `content`, `style`,
|
|
51
|
+
* `bind`, `anchor` and `alpha`, and reads `resolved`. `alpha` fades every run, shadow and icon of
|
|
52
|
+
* the block together, 1 by default; a change is written in place and a tween can drive it.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const value: TextValue = {
|
|
57
|
+
* content: "+5", style: "body", bind: undefined, anchor: { x: 0.5, y: 0.5 }, alpha: 1,
|
|
58
|
+
* resolved: "+5"
|
|
59
|
+
* };
|
|
60
|
+
* // The hint of an empty name field, at half alpha.
|
|
61
|
+
* const hint: TextValue = { ...value, content: "Your name", alpha: 0.5, resolved: "Your name" };
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
type TextValue = {
|
|
65
|
+
content: string | Message;
|
|
66
|
+
style: string;
|
|
67
|
+
bind: TextBind | undefined;
|
|
68
|
+
anchor: Point;
|
|
69
|
+
alpha: number;
|
|
70
|
+
resolved: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Where the lines of a block sit inside the widest of them.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const align: TextAlign = "center";
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
type TextAlign = "left" | "center" | "right";
|
|
81
|
+
/**
|
|
82
|
+
* The wrap width of a style in reference pixels, or `"none"` for one line per `\n` segment.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const wrap: TextWrap = 480;
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
type TextWrap = number | "none";
|
|
90
|
+
/**
|
|
91
|
+
* The drop shadow of a style as the plugin stores it: a copy of every glyph run in `color`, at
|
|
92
|
+
* `alpha`, moved by `dx` and `dy` reference pixels.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const shadow: TextShadow = { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 };
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
type TextShadow = {
|
|
100
|
+
color: number;
|
|
101
|
+
dx: number;
|
|
102
|
+
dy: number;
|
|
103
|
+
alpha: number;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* One style as a game writes it: the font and the size are required, the rest has defaults.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* // A popup title: cream display glyphs over a brown shadow 4 px down.
|
|
111
|
+
* const title: TextStyleInput = {
|
|
112
|
+
* font: "ui.font-display", size: 56, fill: 0xfff3d6, shadow: { color: 0x5b3a1e, dx: 0, dy: 4 }
|
|
113
|
+
* };
|
|
114
|
+
*
|
|
115
|
+
* defineTextStyles({ "popup.title": title }).map["popup.title"]?.shadow;
|
|
116
|
+
* // { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
type TextStyleInput = {
|
|
120
|
+
font: string;
|
|
121
|
+
bold?: string;
|
|
122
|
+
italic?: string;
|
|
123
|
+
size: number;
|
|
124
|
+
fill: number; /** The outline colour. Drawn only when `strokeWidth` is above 0. */
|
|
125
|
+
stroke?: number;
|
|
126
|
+
/**
|
|
127
|
+
* The outline width in reference px: 8 copies of each glyph run on a circle this wide (12 from 6
|
|
128
|
+
* on), tinted `stroke`. 0 draws none. It never changes what `measure` answers.
|
|
129
|
+
*/
|
|
130
|
+
strokeWidth?: number;
|
|
131
|
+
letterSpacing?: number;
|
|
132
|
+
align?: TextAlign;
|
|
133
|
+
wrap?: TextWrap;
|
|
134
|
+
digits?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* A drop shadow under every glyph run, in reference px at the style size. `alpha` is 1 when
|
|
137
|
+
* left out. It never changes what `measure` answers.
|
|
138
|
+
*/
|
|
139
|
+
shadow?: {
|
|
140
|
+
color: number;
|
|
141
|
+
dx: number;
|
|
142
|
+
dy: number;
|
|
143
|
+
alpha?: number;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* One style as the plugin stores it: every field filled, `bold` and `italic` only when the game
|
|
148
|
+
* shipped those MSDF fonts, `shadow` only when the style has one.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const style: TextStyle = {
|
|
153
|
+
* font: "ui.font-body", bold: undefined, italic: undefined, size: 32, fill: 0xffffff,
|
|
154
|
+
* stroke: 0x000000, strokeWidth: 0, letterSpacing: 0, align: "left", wrap: "none",
|
|
155
|
+
* digits: false, shadow: undefined
|
|
156
|
+
* };
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
type TextStyle = {
|
|
160
|
+
font: string;
|
|
161
|
+
bold: string | undefined;
|
|
162
|
+
italic: string | undefined;
|
|
163
|
+
size: number;
|
|
164
|
+
fill: number;
|
|
165
|
+
stroke: number;
|
|
166
|
+
strokeWidth: number;
|
|
167
|
+
letterSpacing: number;
|
|
168
|
+
align: TextAlign;
|
|
169
|
+
wrap: TextWrap;
|
|
170
|
+
digits: boolean;
|
|
171
|
+
shadow: TextShadow | undefined;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* What `defineTextStyles` returns and a feature registers under its `textStyles` key. Structural
|
|
175
|
+
* on purpose: `flow` carries the map, `text` reads it.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const styles: TextStyles = { kind: "textStyles", map: { "hud.title": bodyStyle } };
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
type TextStyles = {
|
|
183
|
+
readonly kind: "textStyles";
|
|
184
|
+
readonly map: Record<string, TextStyle>;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* The advances of one font at the size the `.fnt` was exported with. Widths scale by
|
|
188
|
+
* `style.size / size`; kerning pairs are ignored.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* const table: AdvanceTable = { size: 32, lineHeight: 40, advances: new Map([["1", 18]]) };
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
type AdvanceTable = {
|
|
196
|
+
size: number;
|
|
197
|
+
lineHeight: number;
|
|
198
|
+
advances: Map<string, number>;
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* One run of glyphs: the text and the flags every glyph in it shares.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* const run: TextRun = { kind: "text", text: "+5", bold: true, italic: false, color: 0xffe082 };
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
type TextRun = {
|
|
209
|
+
kind: "text";
|
|
210
|
+
text: string;
|
|
211
|
+
bold: boolean;
|
|
212
|
+
italic: boolean;
|
|
213
|
+
color: number | undefined;
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* One inline icon: an asset key drawn square at the line height.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* const run: IconRun = { kind: "icon", key: "hud.coin" };
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
type IconRun = {
|
|
224
|
+
kind: "icon";
|
|
225
|
+
key: string;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* A piece of a line: glyphs or one inline icon.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* const runs: Run[] = [{ kind: "icon", key: "hud.coin" }];
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
type Run = TextRun | IconRun;
|
|
236
|
+
/**
|
|
237
|
+
* One laid-out line and how wide it is.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* const line: Line = { runs: [{ kind: "icon", key: "hud.coin" }], width: 38.4 };
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
type Line = {
|
|
245
|
+
runs: Run[];
|
|
246
|
+
width: number;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* A laid-out block: its lines and the size `measure` answers with.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```ts
|
|
253
|
+
* const layout: TextLayout = { lines: [], width: 0, height: 0 };
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
type TextLayout = {
|
|
257
|
+
lines: Line[];
|
|
258
|
+
width: number;
|
|
259
|
+
height: number;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* What one label container was last filled from: the style object and the laid-out block. An
|
|
263
|
+
* update with the same style object, the same anchor and the same lines and runs writes the new
|
|
264
|
+
* text and positions into the objects already there.
|
|
265
|
+
*/
|
|
266
|
+
type DrawnLabel = {
|
|
267
|
+
style: TextStyle;
|
|
268
|
+
layout: TextLayout;
|
|
269
|
+
};
|
|
270
|
+
/**
|
|
271
|
+
* A dev warning that is written once per key. Pure modules take it as an argument, so nothing
|
|
272
|
+
* below the plugin context reaches the log itself.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* const warn: Warn = (key, message) => seen.has(key) || log.warn(message);
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
type Warn = (key: string, message: string, data?: Record<string, unknown>) => void;
|
|
280
|
+
/**
|
|
281
|
+
* What the pure layout needs next to the runs, the style and the tables.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* const options: LayoutOptions = { warn: () => undefined };
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
type LayoutOptions = {
|
|
289
|
+
warn: Warn;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* What was resolved for one entity last, so the frame step knows whether anything moved.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```ts
|
|
296
|
+
* const seen: SeenText = { content: "+5", style: "body", bind: undefined, locale: "ru" };
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
type SeenText = {
|
|
300
|
+
content: unknown;
|
|
301
|
+
style: string;
|
|
302
|
+
bind: string | undefined;
|
|
303
|
+
locale: string;
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* text plugin config.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* createApp({ pluginConfigs: { text: { fonts: { body: "ui.body", digits: "ui.digits" } } } });
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
type Config$2 = {
|
|
314
|
+
/** The two fonts every game ships in its boot bundle. They back the built-in styles. */fonts: {
|
|
315
|
+
body: string;
|
|
316
|
+
digits: string;
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
/**
|
|
320
|
+
* text plugin state.
|
|
321
|
+
*/
|
|
322
|
+
type State$2 = {
|
|
323
|
+
/** Built-in styles first, then the styles of every feature in feature order. */styles: Map<string, TextStyle>; /** Style name to the feature that brought it, for the duplicate error. */
|
|
324
|
+
styleOwner: Map<string, string>; /** The config fonts plus every `font`, `bold` and `italic` of every style. */
|
|
325
|
+
fontKeys: Set<string>; /** The parsed advance table per font key, filled when `assets` has the font. */
|
|
326
|
+
tables: Map<string, AdvanceTable>; /** The font keys handed to `renderer.sync.fonts.install`. */
|
|
327
|
+
installed: Set<string>; /** What was resolved for an entity last. */
|
|
328
|
+
seen: Map<Entity, SeenText>; /** `bind.component` to its world type; `undefined` after the one warning. */
|
|
329
|
+
bindTypes: Map<string, AnyComponent | undefined>; /** The measured size of every live label, for `ui`. */
|
|
330
|
+
measured: Map<Entity, Size>; /** Layout per `style + resolved`, oldest dropped first. */
|
|
331
|
+
cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
|
|
332
|
+
drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
|
|
333
|
+
dirty: Set<Entity>; /** Warning keys already written, so a style, a tag, a glyph or a font warns once. */
|
|
334
|
+
warned: Set<string>; /** The system, the two world hooks and the display adapter. */
|
|
335
|
+
removers: Array<() => void>;
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* text plugin API, `app.text`. Two questions: how big a piece of text is, and which styles the
|
|
339
|
+
* game registered. Everything else happens in the frame.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* app.text.measure("120", "digits"); // { width: 57.6, height: 38.4 }
|
|
344
|
+
* app.text.styles(); // ["body", "digits"]
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
type TextApi = {
|
|
348
|
+
/**
|
|
349
|
+
* The size of a piece of text in reference pixels, from the advance table of the style's font.
|
|
350
|
+
* A `Message` is formatted in the current locale first. Never touches a canvas, so the answer
|
|
351
|
+
* is the same in a browser and in plain Bun. Cached per style and resolved string.
|
|
352
|
+
*
|
|
353
|
+
* @param content - A plain string, or what `tr` returned.
|
|
354
|
+
* @param style - A registered style name. An unknown one warns once and measures as `body`.
|
|
355
|
+
* @returns The width and height of the block.
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* // `ui.layout` asks once per invalidation, from the Yoga measure function of a text element.
|
|
359
|
+
* const text = ctx.require(textPlugin);
|
|
360
|
+
*
|
|
361
|
+
* text.measure("120", "digits"); // { width: 57.6, height: 38.4 } on the 0.6 em fallback
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
measure(content: string | Message, style: string): Size;
|
|
365
|
+
/**
|
|
366
|
+
* Whether the font of a style has a glyph for one character, the lookup Pixi draws by. A
|
|
367
|
+
* character its font lacks is drawn as nothing and `measure` counts it 0 wide. While the font is
|
|
368
|
+
* not loaded every character counts, as the 0.6 em fallback measures it. An unknown style warns
|
|
369
|
+
* once and looks up `body`.
|
|
370
|
+
*
|
|
371
|
+
* @param char - One character, a whole code point.
|
|
372
|
+
* @param style - A registered style name.
|
|
373
|
+
* @returns False only for a character a loaded font has no glyph for.
|
|
374
|
+
* @example
|
|
375
|
+
* ```ts
|
|
376
|
+
* // `ui` measures the caret of a text field over the characters the font draws.
|
|
377
|
+
* const text = ctx.require(textPlugin);
|
|
378
|
+
*
|
|
379
|
+
* text.hasGlyph("A", "body"); // true
|
|
380
|
+
* text.hasGlyph("😀", "body"); // false once the body font is loaded: it has no emoji glyph
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
hasGlyph(char: string, style: string): boolean;
|
|
384
|
+
/**
|
|
385
|
+
* The registered style names: the two built-ins first, then the styles of every feature in
|
|
386
|
+
* feature order.
|
|
387
|
+
*
|
|
388
|
+
* @returns A frozen list of style names.
|
|
389
|
+
* @example
|
|
390
|
+
* ```ts
|
|
391
|
+
* // A dev overlay lists what a game may write in a `style` prop.
|
|
392
|
+
* app.text.styles(); // ["body", "digits", "hud.digits"]
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
styles(): readonly string[];
|
|
396
|
+
};
|
|
397
|
+
/**
|
|
398
|
+
* Resolved dependency APIs.
|
|
399
|
+
*/
|
|
400
|
+
type Deps$2 = {
|
|
401
|
+
time: Api;
|
|
402
|
+
flow: Api$1;
|
|
403
|
+
world: Api$2;
|
|
404
|
+
renderer: Api$3;
|
|
405
|
+
assets: Api$4;
|
|
406
|
+
i18n: I18nApi;
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* What the kernel context offers before the deps are attached.
|
|
410
|
+
*
|
|
411
|
+
* `text` owns no event, so `emit` is the kernel's and never called here.
|
|
412
|
+
*/
|
|
413
|
+
type KernelSlice$2 = PluginCtx<Config$2, State$2> & {
|
|
414
|
+
readonly global: object;
|
|
415
|
+
readonly log: Log.LogApi;
|
|
416
|
+
readonly require: Require;
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
|
|
420
|
+
*/
|
|
421
|
+
type TextCtx = KernelSlice$2 & {
|
|
422
|
+
readonly deps: Deps$2;
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Payload of the `assets:bundle-loaded` hook: a bundle landed, so a font may be readable now.
|
|
426
|
+
*/
|
|
427
|
+
type BundleLoaded = Events$1["assets:bundle-loaded"];
|
|
428
|
+
/**
|
|
429
|
+
* Payload of the `assets:bundle-unloaded` hook: `keys` names the assets that left.
|
|
430
|
+
*/
|
|
431
|
+
type BundleUnloaded = Events$1["assets:bundle-unloaded"];
|
|
432
|
+
/**
|
|
433
|
+
* Payload of the `i18n:locale-changed` hook: every message has to be resolved again.
|
|
434
|
+
*/
|
|
435
|
+
type LocaleChanged = Events$2["i18n:locale-changed"];
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/plugins/ui/layout/types.d.ts
|
|
438
|
+
/**
|
|
439
|
+
* layout module state. `nodes` is attach minus detach: Yoga 3.2.1 has no instance counter.
|
|
440
|
+
*/
|
|
441
|
+
type LayoutState = {
|
|
442
|
+
yoga: Yoga | undefined;
|
|
443
|
+
byEntity: Map<Entity, Node>;
|
|
444
|
+
nodes: number;
|
|
445
|
+
measured: number;
|
|
446
|
+
solves: number;
|
|
447
|
+
scrolling: Entity | undefined;
|
|
448
|
+
scrollStart: {
|
|
449
|
+
pointerY: number;
|
|
450
|
+
offset: number;
|
|
451
|
+
};
|
|
452
|
+
order: number;
|
|
453
|
+
cleanups: Array<() => void>;
|
|
454
|
+
};
|
|
455
|
+
declare namespace types_d_exports$1 {
|
|
456
|
+
export { Config$1 as Config, Deps$1 as Deps, ElementChange, ElementMotion, Finding, FocusRing, KernelSlice$1 as KernelSlice, State$1 as State, TextInputLook, UiApi, UiCtx, UiNode };
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* The look of the keyboard focus ring: a dashed ring with a solid halo under it, drawn around the
|
|
460
|
+
* focused control. Colours are `0xRRGGBB`, lengths reference units.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```ts
|
|
464
|
+
* // A dashed ink ring with a cream halo, 9 u outside the control.
|
|
465
|
+
* const ring: FocusRing = {
|
|
466
|
+
* stroke: 0x3a2212, strokeWidth: 4, dash: 10, offset: 9, halo: 0xfff3d6, haloWidth: 12
|
|
467
|
+
* };
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
type FocusRing = {
|
|
471
|
+
/** The colour of the dashed ring. */stroke: number; /** The stroke width of the dashed ring. */
|
|
472
|
+
strokeWidth: number; /** The dash length of the ring, with gaps of half a dash; 0 draws it solid. */
|
|
473
|
+
dash: number; /** How far outside the control's rect the ring runs, on every side. */
|
|
474
|
+
offset: number; /** The colour of the solid halo drawn under the ring. */
|
|
475
|
+
halo: number; /** The stroke width of the halo, centred on the same path as the ring. */
|
|
476
|
+
haloWidth: number;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* The look of a text field while it is edited: the caret, the selection box and the IME
|
|
480
|
+
* underline. Colours are `0xRRGGBB`, lengths reference units, `keyboardMargin` CSS px. The caret
|
|
481
|
+
* does not blink.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* ```ts
|
|
485
|
+
* // A thick ink caret, a pale blue selection, the field 16 px above the keyboard.
|
|
486
|
+
* const look: TextInputLook = {
|
|
487
|
+
* caretWidth: 3, caret: 0x3a2212, selection: 0x3390ff, selectionAlpha: 0.35,
|
|
488
|
+
* composingUnderline: 3, keyboardMargin: 16
|
|
489
|
+
* };
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
type TextInputLook = {
|
|
493
|
+
/** The width of the caret. */caretWidth: number; /** The colour of the caret and of the underline under an IME composing range. */
|
|
494
|
+
caret: number; /** The colour of the selection box. */
|
|
495
|
+
selection: number; /** The alpha of the selection box. */
|
|
496
|
+
selectionAlpha: number; /** The height of the underline under an IME composing range. */
|
|
497
|
+
composingUnderline: number; /** How far above the keyboard the bottom of the edited field is lifted, in CSS px. */
|
|
498
|
+
keyboardMargin: number;
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* ui plugin config.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```ts
|
|
505
|
+
* createApp({ pluginConfigs: { ui: { tapTargetPt: 48, breakpoints: { tall: 2, wide: 1.5 } } } });
|
|
506
|
+
* // A game with a white focus ring on a dark board.
|
|
507
|
+
* createApp({
|
|
508
|
+
* pluginConfigs: {
|
|
509
|
+
* ui: {
|
|
510
|
+
* focusRing: {
|
|
511
|
+
* stroke: 0xffffff, strokeWidth: 4, dash: 10, offset: 9, halo: 0x000000, haloWidth: 12
|
|
512
|
+
* }
|
|
513
|
+
* }
|
|
514
|
+
* }
|
|
515
|
+
* });
|
|
516
|
+
* // A game whose text fields have an ink caret.
|
|
517
|
+
* createApp({
|
|
518
|
+
* pluginConfigs: {
|
|
519
|
+
* ui: {
|
|
520
|
+
* textInput: {
|
|
521
|
+
* caretWidth: 3, caret: 0x3a2212, selection: 0x3390ff, selectionAlpha: 0.35,
|
|
522
|
+
* composingUnderline: 3, keyboardMargin: 16
|
|
523
|
+
* }
|
|
524
|
+
* }
|
|
525
|
+
* }
|
|
526
|
+
* });
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
type Config$1 = {
|
|
530
|
+
/** Smallest tap target `lint()` accepts, in CSS px (points). */tapTargetPt: number; /** `when` flags: `tall` when height / width is at least `tall`, `wide` the other way round. */
|
|
531
|
+
breakpoints: {
|
|
532
|
+
tall: number;
|
|
533
|
+
wide: number;
|
|
534
|
+
};
|
|
535
|
+
/**
|
|
536
|
+
* The ring drawn around the control the keyboard focused. Shallow merge: a game that sets
|
|
537
|
+
* `focusRing` gives all six fields.
|
|
538
|
+
*/
|
|
539
|
+
focusRing: FocusRing;
|
|
540
|
+
/**
|
|
541
|
+
* The caret, the selection and the IME underline of a text field. Shallow merge: a game that
|
|
542
|
+
* sets `textInput` gives all six fields.
|
|
543
|
+
*/
|
|
544
|
+
textInput: TextInputLook;
|
|
545
|
+
};
|
|
546
|
+
/**
|
|
547
|
+
* ui plugin state: one branch per module.
|
|
548
|
+
*/
|
|
549
|
+
type State$1 = {
|
|
550
|
+
jsx: JsxState;
|
|
551
|
+
styles: StylesState;
|
|
552
|
+
layout: LayoutState;
|
|
553
|
+
};
|
|
554
|
+
/**
|
|
555
|
+
* Resolved dependency APIs. `anim` is not here: its edge is validation only, and its work
|
|
556
|
+
* reaches `ui` through the tween driver behind every `ViewHandle`.
|
|
557
|
+
*/
|
|
558
|
+
type Deps$1 = {
|
|
559
|
+
time: Api;
|
|
560
|
+
flow: Api$1;
|
|
561
|
+
world: Api$2;
|
|
562
|
+
renderer: Api$3;
|
|
563
|
+
input: InputApi;
|
|
564
|
+
i18n: I18nApi;
|
|
565
|
+
text: TextApi;
|
|
566
|
+
};
|
|
567
|
+
/**
|
|
568
|
+
* What the kernel context offers before the deps are attached.
|
|
569
|
+
*
|
|
570
|
+
* `ui` owns no event, so `emit` is the kernel's and never called here.
|
|
571
|
+
*/
|
|
572
|
+
type KernelSlice$1 = PluginCtx<Config$1, State$1> & {
|
|
573
|
+
readonly global: object;
|
|
574
|
+
readonly log: Log.LogApi;
|
|
575
|
+
readonly require: Require;
|
|
576
|
+
};
|
|
577
|
+
/**
|
|
578
|
+
* Domain context shared by the three modules: the kernel slice plus the resolved deps.
|
|
579
|
+
*/
|
|
580
|
+
type UiCtx = KernelSlice$1 & {
|
|
581
|
+
readonly deps: Deps$1;
|
|
582
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* ui plugin API, `app.ui`. The screen is one more projection, so three members read it the way a
|
|
585
|
+
* test reads the board: a snapshot, a key lookup and a list of findings; the fourth types into a
|
|
586
|
+
* text field.
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```ts
|
|
590
|
+
* // A headless scenario taps the claim button of the reward popup.
|
|
591
|
+
* app.ui.tree().type; // "row", the root element of the HUD
|
|
592
|
+
* app.input.tap(app.ui.find("claim") ?? 0);
|
|
593
|
+
* app.ui.lint(); // []
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
type UiApi = {
|
|
597
|
+
/**
|
|
598
|
+
* The live screen as plain data: every root in layer order, every element in child order, with
|
|
599
|
+
* its rect in root coordinates, its resolved style and its seven state flags. A rect is natural:
|
|
600
|
+
* under a `fit: "contain"` element it is the rect before that scale, and the fitted element
|
|
601
|
+
* adds `fitScale`. Works headless.
|
|
602
|
+
*
|
|
603
|
+
* @returns The root node; several roots come back under one `screen` node.
|
|
604
|
+
* @example
|
|
605
|
+
* ```ts
|
|
606
|
+
* // On an iPhone SE the 970 u board is drawn smaller to fit the slot between the bars.
|
|
607
|
+
* const screen = app.ui.tree();
|
|
608
|
+
* screen.children.map(child => child.key); // ["fitTop", "slot", "fitBottom"]
|
|
609
|
+
* const board = screen.children[1]?.children[0];
|
|
610
|
+
* board?.rect.w; // 970, the natural width
|
|
611
|
+
* board?.fitScale; // 0.805, the 781 u the slot leaves over 970 u
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
tree(): UiNode;
|
|
615
|
+
/**
|
|
616
|
+
* The entity of a keyed element, so a test can tap it and `anim` can aim at it. Popup roots
|
|
617
|
+
* come first, then the projection roots in layer order.
|
|
618
|
+
*
|
|
619
|
+
* @param key - The `key` prop of the element.
|
|
620
|
+
* @returns The entity, or `undefined` for an unknown or exiting element.
|
|
621
|
+
* @example
|
|
622
|
+
* ```ts
|
|
623
|
+
* // A headless scenario answers the gate through the button of the popup.
|
|
624
|
+
* app.input.tap(app.ui.find("claim") ?? 0); // true
|
|
625
|
+
* app.ui.find("nothing"); // undefined
|
|
626
|
+
* ```
|
|
627
|
+
*/
|
|
628
|
+
find(key: string): Entity | undefined;
|
|
629
|
+
/**
|
|
630
|
+
* Reads the live screen against the five rules: a tap target under `tapTargetPt` at the size
|
|
631
|
+
* it is drawn (a `fit: "contain"` on it or above it shrinks it), a text that does not fit its
|
|
632
|
+
* box in some registered locale, an absolute element with no `reason`, a clipping element
|
|
633
|
+
* (`scroll`, `overflow: "hidden"`) whose style names a nine-slice it never draws, and a root
|
|
634
|
+
* element whose style sets a `zIndex` it ignores. Never throws; empty when nothing is mounted.
|
|
635
|
+
*
|
|
636
|
+
* @returns One finding per rule and element.
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* // A game test keeps the board honest on an iPhone SE: a 140 u cell in the 0.8 slot.
|
|
640
|
+
* app.ui.lint(); // [{ rule: "tap-target", key: "cell", detail: "39 x 39 pt" }]
|
|
641
|
+
* // A list styled with a nine-slice: the clip keeps it from drawing.
|
|
642
|
+
* app.ui.lint(); // [{ rule: "nine-slice-clipped", key: "orders", detail: "scroll" }]
|
|
643
|
+
* // A screen root that asks to be drawn over the others: the layer decides that.
|
|
644
|
+
* app.ui.lint(); // [{ rule: "z-index-on-root", key: "board", detail: "zIndex 2" }]
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
lint(): readonly Finding[];
|
|
648
|
+
/**
|
|
649
|
+
* Types into a text field the way a player does, for a headless test or an agent: the field
|
|
650
|
+
* becomes the one being edited, `value` cut to its `maxLength` is written into it, and the
|
|
651
|
+
* local field it names is written, so the component re-renders on the next frame. Nothing
|
|
652
|
+
* reaches the gate: Enter submits.
|
|
653
|
+
*
|
|
654
|
+
* @param key - The `key` prop of the `input` element.
|
|
655
|
+
* @param value - The text to type.
|
|
656
|
+
* @returns True when the field took the text; false with one warning when no live `input` has
|
|
657
|
+
* the key.
|
|
658
|
+
* @example
|
|
659
|
+
* ```ts
|
|
660
|
+
* // A headless test renames the player through the Rename popup.
|
|
661
|
+
* app.ui.fill("nameField", "Alex"); // true
|
|
662
|
+
* app.time.step(16);
|
|
663
|
+
* app.input.pressKey("Enter"); // true: the gate took { intent: "save", payload: { name: "Alex" } }
|
|
664
|
+
* app.ui.fill("nothing", "Alex"); // false, with the warning "ui:fill-without-input"
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
fill(key: string, value: string): boolean;
|
|
668
|
+
};
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/plugins/anim/tween/types.d.ts
|
|
671
|
+
/**
|
|
672
|
+
* One running track: the numeric fields of one component of one entity moving from the values
|
|
673
|
+
* read at the end of the delay to the exact target, straight or through keyframe segments. A
|
|
674
|
+
* track with repeats walks again from its first segment each time a run ends.
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
* ```ts
|
|
678
|
+
* const track: Track = {
|
|
679
|
+
* id: 1, entity: 1_048_576, component: Transform, to: { x: 200 },
|
|
680
|
+
* keys: { x: "1048576:Transform:x" }, ms: 350, ease: "out", delayMs: 0, elapsed: 0,
|
|
681
|
+
* from: undefined, muted: () => new Set(), additive: false, driven: false, bornFrame: 4,
|
|
682
|
+
* ended: false, segments: undefined, repeatsLeft: 0, held: false
|
|
683
|
+
* };
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
type Track = {
|
|
687
|
+
readonly id: number;
|
|
688
|
+
readonly entity: Entity;
|
|
689
|
+
readonly component: AnyComponent; /** The target fields. Retarget deletes the fields a newer absolute track took over. */
|
|
690
|
+
to: Record<string, number>;
|
|
691
|
+
/**
|
|
692
|
+
* The key of the owner, bases and offsets tables per field, built once when the track starts.
|
|
693
|
+
* It keeps a field `to` lost, which nothing reads again.
|
|
694
|
+
*/
|
|
695
|
+
readonly keys: Readonly<Record<string, string>>;
|
|
696
|
+
readonly ms: number;
|
|
697
|
+
readonly ease: Ease;
|
|
698
|
+
readonly delayMs: number;
|
|
699
|
+
elapsed: number; /** Read when the delay ends, never earlier. */
|
|
700
|
+
from: Record<string, number> | undefined;
|
|
701
|
+
readonly muted: () => ReadonlySet<string>;
|
|
702
|
+
readonly additive: boolean; /** True for a track a timeline step advances by hand; the frame step leaves it alone. */
|
|
703
|
+
readonly driven: boolean; /** The frame step the track was born in. That step does not advance it a second time. */
|
|
704
|
+
readonly bornFrame: number;
|
|
705
|
+
ended: boolean;
|
|
706
|
+
/**
|
|
707
|
+
* The keyframe segments the track walks over `ms`, or `undefined` for a straight track. A
|
|
708
|
+
* segment without an ease runs on the track's `ease`.
|
|
709
|
+
*/
|
|
710
|
+
readonly segments: readonly TrackSegment[] | undefined;
|
|
711
|
+
/**
|
|
712
|
+
* The runs still to go after the one that plays: `0` for a track that runs once, `Infinity`
|
|
713
|
+
* for a loop (`repeat: "forever"`), which never ends by itself.
|
|
714
|
+
*/
|
|
715
|
+
repeatsLeft: number;
|
|
716
|
+
/**
|
|
717
|
+
* True while a loop stands on its first key and has written it: the hold writes once, and the
|
|
718
|
+
* walk clears it, so the next hold writes the first key again.
|
|
719
|
+
*/
|
|
720
|
+
held: boolean;
|
|
721
|
+
};
|
|
722
|
+
/**
|
|
723
|
+
* A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
|
|
724
|
+
* born inside a frame step still consumes the remainder of that step's delta.
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* ```ts
|
|
728
|
+
* const motion: StepMotion = { ...inert, advance: () => 0 };
|
|
729
|
+
* motion.advance(16); // 0: nothing is left over
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
type StepMotion = MotionHandle & {
|
|
733
|
+
/**
|
|
734
|
+
* Advances the track by one delta.
|
|
735
|
+
*
|
|
736
|
+
* @param deltaMs - Milliseconds of game time to consume.
|
|
737
|
+
* @returns The milliseconds left over past the end of the track.
|
|
738
|
+
*/
|
|
739
|
+
advance(deltaMs: number): number;
|
|
740
|
+
};
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region src/plugins/anim/timeline/types.d.ts
|
|
743
|
+
/**
|
|
744
|
+
* The value of the component a step names, with its shape erased: the value type of
|
|
745
|
+
* `AnyComponent`. A step is built against the real component, so `set(target, Sprite, patch)`
|
|
746
|
+
* checks the patch; the step tree and the runtime then carry the component erased, which lets one
|
|
747
|
+
* choreography mix components.
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```ts
|
|
751
|
+
* const patch: ErasedFields = { texture: "hud.coin-gold" };
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
type ErasedFields = AnyComponent extends ComponentType<infer Value> ? Value : never;
|
|
755
|
+
/**
|
|
756
|
+
* A sound, owned by `anim` and handled by `audio`. It is a `flow` descriptor and a timeline step
|
|
757
|
+
* at once: `fx(sfx("board.merge"))` in a node, or `sfx("board.merge")` inside a `sequence`.
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```ts
|
|
761
|
+
* const descriptor: SfxDescriptor = {
|
|
762
|
+
* kind: "sfx", payload: { key: "board.merge", bus: "sfx" }, cosmetic: true
|
|
763
|
+
* };
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
type SfxDescriptor = {
|
|
767
|
+
readonly kind: "sfx";
|
|
768
|
+
readonly payload: {
|
|
769
|
+
readonly key: string;
|
|
770
|
+
readonly bus: string;
|
|
771
|
+
};
|
|
772
|
+
readonly cosmetic: true;
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* A haptic tick, owned by `anim` and handled by `platform`. Like `sfx` it is a descriptor and a
|
|
776
|
+
* timeline step at once.
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* const descriptor: HapticDescriptor = {
|
|
781
|
+
* kind: "haptic", payload: { kind: "light" }, cosmetic: true
|
|
782
|
+
* };
|
|
783
|
+
* ```
|
|
784
|
+
*/
|
|
785
|
+
type HapticDescriptor = {
|
|
786
|
+
readonly kind: "haptic";
|
|
787
|
+
readonly payload: {
|
|
788
|
+
readonly kind: string;
|
|
789
|
+
};
|
|
790
|
+
readonly cosmetic: true;
|
|
791
|
+
};
|
|
792
|
+
/**
|
|
793
|
+
* The two descriptors `anim` owns. As a step, the descriptor is dispatched through
|
|
794
|
+
* `flow.fx.dispatch` when the step is reached; a kind nobody handles is silent.
|
|
795
|
+
*
|
|
796
|
+
* @example
|
|
797
|
+
* ```ts
|
|
798
|
+
* const step: FxStep = { kind: "haptic", payload: { kind: "light" }, cosmetic: true };
|
|
799
|
+
* ```
|
|
800
|
+
*/
|
|
801
|
+
type FxStep = SfxDescriptor | HapticDescriptor;
|
|
802
|
+
/**
|
|
803
|
+
* The descriptor `play(animation, slots)` builds. The payload names the animation, so the
|
|
804
|
+
* handler resolves the definition from the registry instead of carrying a function.
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* const descriptor: PlayDescriptor = {
|
|
809
|
+
* kind: "play",
|
|
810
|
+
* payload: { animation: "hud.coinsFly", slots: { from: { projection: "hud", key: "coins" } } },
|
|
811
|
+
* cosmetic: true
|
|
812
|
+
* };
|
|
813
|
+
* ```
|
|
814
|
+
*/
|
|
815
|
+
type PlayDescriptor = {
|
|
816
|
+
readonly kind: "play";
|
|
817
|
+
readonly payload: {
|
|
818
|
+
readonly animation: string;
|
|
819
|
+
readonly slots: Json;
|
|
820
|
+
};
|
|
821
|
+
readonly cosmetic: true;
|
|
822
|
+
};
|
|
823
|
+
/**
|
|
824
|
+
* One step of a choreography: plain frozen data with no callback inside, so a step tree can be
|
|
825
|
+
* read, logged and compared. A `spawn` step carries the component values of a temporary entity,
|
|
826
|
+
* made when the step is reached and despawned when the timeline ends.
|
|
827
|
+
*
|
|
828
|
+
* @example
|
|
829
|
+
* ```ts
|
|
830
|
+
* const step: Step = { kind: "wait", ms: 120 };
|
|
831
|
+
* ```
|
|
832
|
+
*/
|
|
833
|
+
type Step = {
|
|
834
|
+
readonly kind: "sequence";
|
|
835
|
+
readonly steps: readonly Step[];
|
|
836
|
+
} | {
|
|
837
|
+
readonly kind: "parallel";
|
|
838
|
+
readonly steps: readonly Step[];
|
|
839
|
+
} | {
|
|
840
|
+
readonly kind: "wait";
|
|
841
|
+
readonly ms: number;
|
|
842
|
+
} | {
|
|
843
|
+
readonly kind: "mark";
|
|
844
|
+
readonly name: string;
|
|
845
|
+
} | {
|
|
846
|
+
readonly kind: "tween";
|
|
847
|
+
readonly target: Target;
|
|
848
|
+
readonly component: AnyComponent;
|
|
849
|
+
readonly to: Readonly<Record<string, number>>;
|
|
850
|
+
readonly ms: number;
|
|
851
|
+
readonly ease: Ease;
|
|
852
|
+
readonly delayMs: number;
|
|
853
|
+
readonly additive: boolean; /** `"root"`: the `Transform` fields are a root pose, turned local when the track starts. */
|
|
854
|
+
readonly space: "local" | "root";
|
|
855
|
+
} | {
|
|
856
|
+
readonly kind: "set";
|
|
857
|
+
readonly target: Target;
|
|
858
|
+
readonly component: AnyComponent;
|
|
859
|
+
readonly patch: Readonly<ErasedFields>;
|
|
860
|
+
} | {
|
|
861
|
+
readonly kind: "frames";
|
|
862
|
+
readonly target: Target;
|
|
863
|
+
readonly keys: readonly string[];
|
|
864
|
+
readonly fps: number;
|
|
865
|
+
readonly loop: boolean;
|
|
866
|
+
} | {
|
|
867
|
+
readonly kind: "spawn";
|
|
868
|
+
readonly id: string;
|
|
869
|
+
readonly components: readonly AnyComponentValue[];
|
|
870
|
+
readonly layer: string;
|
|
871
|
+
readonly order: number;
|
|
872
|
+
} | FxStep | {
|
|
873
|
+
readonly kind: "use";
|
|
874
|
+
readonly id: string;
|
|
875
|
+
readonly step: Step;
|
|
876
|
+
};
|
|
877
|
+
/**
|
|
878
|
+
* Reserved for Spine and the other external players: `external` throws, and this is the shape it
|
|
879
|
+
* would take. Exported as a type only.
|
|
880
|
+
*
|
|
881
|
+
* @example
|
|
882
|
+
* ```ts
|
|
883
|
+
* const player: ExternalPlayer = { name: "spine", play: () => undefined };
|
|
884
|
+
* ```
|
|
885
|
+
*/
|
|
886
|
+
type ExternalPlayer = {
|
|
887
|
+
readonly name: string;
|
|
888
|
+
play(clip: string): unknown;
|
|
889
|
+
};
|
|
890
|
+
/**
|
|
891
|
+
* Where one step of a running timeline stands. One mutable record per step of the tree, built
|
|
892
|
+
* when the timeline starts.
|
|
893
|
+
*/
|
|
894
|
+
type Cursor = {
|
|
895
|
+
readonly step: Step;
|
|
896
|
+
readonly children: readonly Cursor[]; /** Position in a `sequence`, and the last written key of a `frames` step. */
|
|
897
|
+
index: number; /** Consumed milliseconds of a `wait` or a `frames` step. */
|
|
898
|
+
elapsed: number;
|
|
899
|
+
motion: StepMotion | undefined;
|
|
900
|
+
started: boolean;
|
|
901
|
+
ended: boolean; /** The entity a running `frames` step holds against its `Frames` loop, until the step ends. */
|
|
902
|
+
held: Entity | undefined;
|
|
903
|
+
};
|
|
904
|
+
/**
|
|
905
|
+
* What the cursor reaches the rest of the engine through. Injected, so `timeline` never imports
|
|
906
|
+
* the run-time code of `tween` or of another plugin.
|
|
907
|
+
*/
|
|
908
|
+
type TimelineRuntime = {
|
|
909
|
+
/**
|
|
910
|
+
* Resolves a target to an entity.
|
|
911
|
+
*
|
|
912
|
+
* @param target - The projection key, the entity or the spawned id a step names.
|
|
913
|
+
* @param spawned - The spawn table of the timeline that asks; a spawned id resolves only there.
|
|
914
|
+
* @returns The entity, or `undefined` when no live view, element or spawned entity holds it.
|
|
915
|
+
*/
|
|
916
|
+
entityOf(target: Target, spawned?: ReadonlyMap<string, Entity>): Entity | undefined;
|
|
917
|
+
/**
|
|
918
|
+
* Spawns a temporary entity owned by `anim`. The timeline that spawned it despawns it when it
|
|
919
|
+
* ends, whichever way it ends.
|
|
920
|
+
*
|
|
921
|
+
* @param components - The component values the entity starts with, `Layer` and `Order` included.
|
|
922
|
+
* @returns The new entity.
|
|
923
|
+
*/
|
|
924
|
+
spawn(components: readonly AnyComponentValue[]): Entity;
|
|
925
|
+
/**
|
|
926
|
+
* Reads a component of an entity, which is also the liveness check of a step.
|
|
927
|
+
*
|
|
928
|
+
* @param entity - The entity to read.
|
|
929
|
+
* @param component - The component to read.
|
|
930
|
+
* @returns The stored value, or `undefined`.
|
|
931
|
+
*/
|
|
932
|
+
read(entity: Entity, component: AnyComponent): Readonly<ErasedFields> | undefined;
|
|
933
|
+
/**
|
|
934
|
+
* Writes a patch through `ecs.set`, so `changed()` sees it.
|
|
935
|
+
*
|
|
936
|
+
* @param entity - The entity to write.
|
|
937
|
+
* @param component - The component to write.
|
|
938
|
+
* @param patch - The fields to overwrite.
|
|
939
|
+
*/
|
|
940
|
+
write(entity: Entity, component: AnyComponent, patch: Partial<ErasedFields>): void;
|
|
941
|
+
/**
|
|
942
|
+
* Turns the root-space target of a `tween` step into the local target under the entity's
|
|
943
|
+
* parent, through `localPoseOf` of `renderer`.
|
|
944
|
+
*
|
|
945
|
+
* @param entity - The entity the step moves.
|
|
946
|
+
* @param component - The component the step drives; only `Transform` is converted.
|
|
947
|
+
* @param to - The numeric target fields, in root space.
|
|
948
|
+
* @returns The target fields in the space of the entity's parent.
|
|
949
|
+
*/
|
|
950
|
+
toLocal(entity: Entity, component: AnyComponent, to: Readonly<Record<string, number>>): Record<string, number>;
|
|
951
|
+
/**
|
|
952
|
+
* Starts one track on the tween core.
|
|
953
|
+
*
|
|
954
|
+
* @param entity - The entity to animate.
|
|
955
|
+
* @param component - The component to animate.
|
|
956
|
+
* @param to - The numeric target fields.
|
|
957
|
+
* @param options - Duration, easing, delay and the additive flag.
|
|
958
|
+
* @returns The motion of the track, advanceable by hand.
|
|
959
|
+
*/
|
|
960
|
+
start(entity: Entity, component: AnyComponent, to: Record<string, number>, options: TrackOptions): StepMotion;
|
|
961
|
+
/**
|
|
962
|
+
* Hands a descriptor step to `flow.fx.dispatch`.
|
|
963
|
+
*
|
|
964
|
+
* @param descriptor - The `sfx` or `haptic` step that was reached.
|
|
965
|
+
*/
|
|
966
|
+
dispatch(descriptor: FxStep): void;
|
|
967
|
+
/**
|
|
968
|
+
* Reports a mark: the event, the `onMark` listeners and the timeline's own list.
|
|
969
|
+
*
|
|
970
|
+
* @param animation - Id of the animation the mark belongs to.
|
|
971
|
+
* @param name - Name of the mark.
|
|
972
|
+
*/
|
|
973
|
+
mark(animation: string, name: string): void;
|
|
974
|
+
/**
|
|
975
|
+
* Lifts the idle frame rate, because something started to move.
|
|
976
|
+
*/
|
|
977
|
+
wake(): void;
|
|
978
|
+
/**
|
|
979
|
+
* Claims the sprite of an entity for a running `frames` step: the `Frames` loop of the entity
|
|
980
|
+
* writes nothing while any claim is open.
|
|
981
|
+
*
|
|
982
|
+
* @param entity - The entity the step writes.
|
|
983
|
+
*/
|
|
984
|
+
holdFrames(entity: Entity): void;
|
|
985
|
+
/**
|
|
986
|
+
* Gives one claim back. At the last one the `Frames` loop writes its current key again on its
|
|
987
|
+
* next step, so the sprite does not stay on the step's last key.
|
|
988
|
+
*
|
|
989
|
+
* @param entity - The entity the step wrote.
|
|
990
|
+
*/
|
|
991
|
+
releaseFrames(entity: Entity): void;
|
|
992
|
+
};
|
|
993
|
+
/**
|
|
994
|
+
* One running timeline: its cursor, the marks it reached, the entities it counts on, the entities
|
|
995
|
+
* it spawned and despawns at its end, and the resolver of its `done` promise.
|
|
996
|
+
*/
|
|
997
|
+
type RunningTimeline = {
|
|
998
|
+
readonly id: number;
|
|
999
|
+
readonly animation: string;
|
|
1000
|
+
readonly cursor: Cursor;
|
|
1001
|
+
readonly marks: string[];
|
|
1002
|
+
readonly entities: readonly Entity[]; /** The entities its `spawn` steps made, by spawn id. Despawned when the timeline ends. */
|
|
1003
|
+
readonly spawned: Map<string, Entity>;
|
|
1004
|
+
readonly resolve: () => void;
|
|
1005
|
+
ended: boolean;
|
|
1006
|
+
};
|
|
1007
|
+
declare namespace types_d_exports {
|
|
1008
|
+
export { AnimApi, AnimCtx, AnimationDefinition, AnimationSpec, AnyAnimationDefinition, BuildTools, Config, Cursor, Deps, ErasedFields, Events, ExternalPlayer, FrameLoop, FramesValue, FxStep, HapticDescriptor, KernelSlice, MarkListener, MotionKeyframe, PlayDescriptor, PlayHandle, Pose, RunningTimeline, SfxDescriptor, SlotRecord, SlotTags, SlotValue, SlotValues, State, Step, StepMotion, Target, TimelineRuntime, Track };
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* What a step and a slot are pointed at: the projection key of a live view or of an element a
|
|
1012
|
+
* plugin above registered with `registerKey`, an entity, or the id of an entity a `spawn` step of
|
|
1013
|
+
* the same timeline made (`spawned(id)`). A node passes keys, never entities.
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
* ```ts
|
|
1017
|
+
* const counter: Target = { projection: "hud", key: "coins" };
|
|
1018
|
+
* const flyingCoin: Target = spawned("coin1"); // { spawned: "coin1" }
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
type Target = {
|
|
1022
|
+
projection: string;
|
|
1023
|
+
key: string;
|
|
1024
|
+
} | Entity | {
|
|
1025
|
+
spawned: string;
|
|
1026
|
+
};
|
|
1027
|
+
/**
|
|
1028
|
+
* The pose `at(target)` answers with: where the target rests in root (reference) space, its rest
|
|
1029
|
+
* `Transform` composed through the `Parent` chain, or the identity pose when nothing is there.
|
|
1030
|
+
*
|
|
1031
|
+
* @example
|
|
1032
|
+
* ```ts
|
|
1033
|
+
* const pose: Pose = { x: 540, y: 960, rotation: 0, scale: 1 };
|
|
1034
|
+
* ```
|
|
1035
|
+
*/
|
|
1036
|
+
type Pose = {
|
|
1037
|
+
x: number;
|
|
1038
|
+
y: number;
|
|
1039
|
+
rotation: number;
|
|
1040
|
+
scale: number;
|
|
1041
|
+
};
|
|
1042
|
+
/**
|
|
1043
|
+
* The slot declaration of an animation: one type tag per slot, each carrying one target or a
|
|
1044
|
+
* list of targets.
|
|
1045
|
+
*
|
|
1046
|
+
* @example
|
|
1047
|
+
* ```ts
|
|
1048
|
+
* const slots: SlotTags = { items: type<Target[]>(), card: type<Target>() };
|
|
1049
|
+
* ```
|
|
1050
|
+
*/
|
|
1051
|
+
type SlotTags = Readonly<Record<string, TypeTag<Target> | TypeTag<Target[]>>>;
|
|
1052
|
+
/**
|
|
1053
|
+
* The targets `play` and `use` take, read off the slot tags of the animation.
|
|
1054
|
+
*
|
|
1055
|
+
* @example
|
|
1056
|
+
* ```ts
|
|
1057
|
+
* type Slots = SlotValues<{ card: TypeTag<Target> }>; // { card: Target }
|
|
1058
|
+
* ```
|
|
1059
|
+
*/
|
|
1060
|
+
type SlotValues<Tags extends SlotTags> = { [Name in keyof Tags]: SlotValue<Tags[Name]> };
|
|
1061
|
+
/**
|
|
1062
|
+
* What one slot tag stands for: a list of targets when the tag declared one, a single target
|
|
1063
|
+
* otherwise. Distributive on purpose, so the erased `SlotTags` widens to both.
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```ts
|
|
1067
|
+
* type One = SlotValue<TypeTag<Target>>; // Target
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
type SlotValue<Tag> = Tag extends TypeTag<Target[]> ? Target[] : Target;
|
|
1071
|
+
/**
|
|
1072
|
+
* The slots of a running animation with their tags erased, as `build` and the `play` handler
|
|
1073
|
+
* read them.
|
|
1074
|
+
*
|
|
1075
|
+
* @example
|
|
1076
|
+
* ```ts
|
|
1077
|
+
* const slots: SlotRecord = { card: { projection: "hud", key: "order" } };
|
|
1078
|
+
* ```
|
|
1079
|
+
*/
|
|
1080
|
+
type SlotRecord = SlotValues<SlotTags>;
|
|
1081
|
+
/**
|
|
1082
|
+
* What `build` is handed next to the slots: `at` reads where a target rests at play time, in root
|
|
1083
|
+
* space (its rest `Transform` composed through the `Parent` chain, every pivot applied), so a
|
|
1084
|
+
* choreography can fly one element to where another one rests, even inside a scaled slot. A view
|
|
1085
|
+
* that has a `Parent` itself is aimed at that pose with a `tween` in `space: "root"`.
|
|
1086
|
+
*
|
|
1087
|
+
* @example
|
|
1088
|
+
* ```ts
|
|
1089
|
+
* const tools: BuildTools = { at: () => ({ x: 0, y: 0, rotation: 0, scale: 1 }) };
|
|
1090
|
+
* ```
|
|
1091
|
+
*/
|
|
1092
|
+
type BuildTools = {
|
|
1093
|
+
at(target: Target): Pose;
|
|
1094
|
+
};
|
|
1095
|
+
/**
|
|
1096
|
+
* One animation: an id, the slots it takes and the pure function that builds its step tree. The
|
|
1097
|
+
* same definition may play twice at once, because `build` runs per play.
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```ts
|
|
1101
|
+
* const coinsFly: AnimationDefinition<{ from: TypeTag<Target> }> = defineAnimation("hud.coinsFly", {
|
|
1102
|
+
* slots: { from: type<Target>() },
|
|
1103
|
+
* build: ({ from }) => tween(from, Transform, { scale: 0.4 }, { ms: 600 })
|
|
1104
|
+
* });
|
|
1105
|
+
* coinsFly.id; // "hud.coinsFly"
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
type AnimationDefinition<Tags extends SlotTags = SlotTags> = {
|
|
1109
|
+
readonly id: string;
|
|
1110
|
+
readonly slots: Tags;
|
|
1111
|
+
build(slots: SlotValues<Tags>, tools: BuildTools): Step;
|
|
1112
|
+
};
|
|
1113
|
+
/**
|
|
1114
|
+
* What `defineAnimation` is given: the slots the animation takes and the pure function that
|
|
1115
|
+
* builds its step tree.
|
|
1116
|
+
*
|
|
1117
|
+
* @example
|
|
1118
|
+
* ```ts
|
|
1119
|
+
* const spec: AnimationSpec<{ card: TypeTag<Target> }> = {
|
|
1120
|
+
* slots: { card: type<Target>() },
|
|
1121
|
+
* build: ({ card }) => tween(card, Transform, { scale: 1.2 }, { ms: 120 })
|
|
1122
|
+
* };
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
type AnimationSpec<Tags extends SlotTags> = {
|
|
1126
|
+
readonly slots: Tags;
|
|
1127
|
+
build(slots: SlotValues<Tags>, tools: BuildTools): Step;
|
|
1128
|
+
};
|
|
1129
|
+
/**
|
|
1130
|
+
* An animation with its slot tags erased, as the registry stores it.
|
|
1131
|
+
*
|
|
1132
|
+
* @example
|
|
1133
|
+
* ```ts
|
|
1134
|
+
* const stored: AnyAnimationDefinition = { id: "hud.coinsFly", slots: {}, build: () => wait(0) };
|
|
1135
|
+
* ```
|
|
1136
|
+
*/
|
|
1137
|
+
type AnyAnimationDefinition = AnimationDefinition<SlotTags>;
|
|
1138
|
+
/**
|
|
1139
|
+
* What `play` hands back: the motion contract over the whole step tree, the promise of its end
|
|
1140
|
+
* and the marks it reached so far.
|
|
1141
|
+
*
|
|
1142
|
+
* @example
|
|
1143
|
+
* ```ts
|
|
1144
|
+
* const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
|
|
1145
|
+
* build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
|
|
1146
|
+
* const handle: PlayHandle = app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
|
|
1147
|
+
* handle.marks(); // []: nothing reached yet
|
|
1148
|
+
* ```
|
|
1149
|
+
*/
|
|
1150
|
+
type PlayHandle = MotionHandle & {
|
|
1151
|
+
readonly done: Promise<void>;
|
|
1152
|
+
marks(): readonly string[];
|
|
1153
|
+
};
|
|
1154
|
+
/**
|
|
1155
|
+
* A listener of `onMark`: the animation id and the name of the mark that was reached.
|
|
1156
|
+
*
|
|
1157
|
+
* @example
|
|
1158
|
+
* ```ts
|
|
1159
|
+
* const reached: string[] = [];
|
|
1160
|
+
* const listener: MarkListener = (_animation, mark) => reached.push(mark);
|
|
1161
|
+
* ```
|
|
1162
|
+
*/
|
|
1163
|
+
type MarkListener = (animation: string, mark: string) => void;
|
|
1164
|
+
/**
|
|
1165
|
+
* anim plugin events.
|
|
1166
|
+
*
|
|
1167
|
+
* @example
|
|
1168
|
+
* ```ts
|
|
1169
|
+
* // A feature plugin logs every mark a choreography reaches.
|
|
1170
|
+
* createPlugin("rewardLog", {
|
|
1171
|
+
* depends: [animPlugin],
|
|
1172
|
+
* hooks: ctx => ({ "anim:mark": ({ mark }) => ctx.log.info("mark", { mark }) })
|
|
1173
|
+
* }); // a reached mark("done") step logs { mark: "done" }
|
|
1174
|
+
* ```
|
|
1175
|
+
*/
|
|
1176
|
+
type Events = {
|
|
1177
|
+
/** A `mark` step was reached, or jumped by `finish()`. */"anim:mark": {
|
|
1178
|
+
animation: string;
|
|
1179
|
+
mark: string;
|
|
1180
|
+
}; /** A timeline ended or was finished. Not emitted on `cancel()`. */
|
|
1181
|
+
"anim:finished": {
|
|
1182
|
+
animation: string;
|
|
1183
|
+
};
|
|
1184
|
+
};
|
|
1185
|
+
/**
|
|
1186
|
+
* One key of a keyframe track in `defineMotion`: where on the track it sits (`at`, 0..1 of
|
|
1187
|
+
* `transition.ms`), the curve of the segment that ends on it (default `"inOut"`) and the pose.
|
|
1188
|
+
* `dx` and `dy` are offsets from the rest pose in reference units; `rotation` (radians), `scale`
|
|
1189
|
+
* and `alpha` are absolute. A field the key leaves out holds the previous key's value.
|
|
1190
|
+
*
|
|
1191
|
+
* An enter walk starts on the first key and ends on the rest pose at `at: 1`; an exit walk starts
|
|
1192
|
+
* where the view is and ends on the last key, the pose the element leaves in.
|
|
1193
|
+
*
|
|
1194
|
+
* In a loop (`loop: { track }`) every Transform field is an offset added to the rest pose: `dx`, `dy`, `rotation`
|
|
1195
|
+
* and `scale` (`scale: 0.05` grows a scale-1 view to 1.05). `alpha` stays absolute. The loop
|
|
1196
|
+
* stands on its first key until that key's `at`, and its last key repeats its first one.
|
|
1197
|
+
*
|
|
1198
|
+
* @example
|
|
1199
|
+
* ```ts
|
|
1200
|
+
* // A popup board at 42 % of its swing: 14 u below its rest, tilted 5°, slightly larger.
|
|
1201
|
+
* const key: MotionKeyframe = { at: 0.42, ease: "out", Transform: { dy: 14, rotation: 0.087, scale: 1.04 } };
|
|
1202
|
+
* ```
|
|
1203
|
+
*/
|
|
1204
|
+
type MotionKeyframe = {
|
|
1205
|
+
readonly at: number;
|
|
1206
|
+
readonly ease?: Ease;
|
|
1207
|
+
readonly Transform?: {
|
|
1208
|
+
readonly dx?: number;
|
|
1209
|
+
readonly dy?: number;
|
|
1210
|
+
readonly rotation?: number;
|
|
1211
|
+
readonly scale?: number;
|
|
1212
|
+
};
|
|
1213
|
+
readonly Shape?: {
|
|
1214
|
+
readonly alpha?: number;
|
|
1215
|
+
};
|
|
1216
|
+
readonly Sprite?: {
|
|
1217
|
+
readonly alpha?: number;
|
|
1218
|
+
};
|
|
1219
|
+
readonly NineSlice?: {
|
|
1220
|
+
readonly alpha?: number;
|
|
1221
|
+
};
|
|
1222
|
+
};
|
|
1223
|
+
/**
|
|
1224
|
+
* The value of the `Frames` component: a frame loop that lives as long as its entity. `keys` and
|
|
1225
|
+
* `fps` mean what they mean on the `frames` step; `loop: false` stops on the last key and
|
|
1226
|
+
* `playing: false` holds the key on the screen. A new `keys` array restarts at key 0, the same
|
|
1227
|
+
* array keeps its phase.
|
|
1228
|
+
*
|
|
1229
|
+
* @example
|
|
1230
|
+
* ```ts
|
|
1231
|
+
* const spin: FramesValue = { keys: ["items.coin-0", "items.coin-1"], fps: 12, loop: true, playing: true };
|
|
1232
|
+
* ```
|
|
1233
|
+
*/
|
|
1234
|
+
type FramesValue = {
|
|
1235
|
+
keys: readonly string[];
|
|
1236
|
+
fps: number;
|
|
1237
|
+
loop: boolean;
|
|
1238
|
+
playing: boolean;
|
|
1239
|
+
};
|
|
1240
|
+
/**
|
|
1241
|
+
* Where the `Frames` loop of one entity stands. `keys` is the array last seen, for the restart
|
|
1242
|
+
* check; `index` is the key the clock last landed on, `-1` before the first one; `written` is
|
|
1243
|
+
* false while that key still has to reach the sprite.
|
|
1244
|
+
*/
|
|
1245
|
+
type FrameLoop = {
|
|
1246
|
+
keys: readonly string[];
|
|
1247
|
+
elapsed: number;
|
|
1248
|
+
index: number;
|
|
1249
|
+
written: boolean;
|
|
1250
|
+
};
|
|
1251
|
+
/**
|
|
1252
|
+
* anim plugin config. The timings live in the steps and in `defineMotion`, not here: a
|
|
1253
|
+
* choreography that reads its duration from a config cannot be read as text.
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```ts
|
|
1257
|
+
* createApp({ plugins: [...screen], pluginConfigs: { anim: { maxTracks: 500, reducedMotion: true } } });
|
|
1258
|
+
* ```
|
|
1259
|
+
*/
|
|
1260
|
+
type Config = {
|
|
1261
|
+
/** Dev guard: one warning each time the running track count rises past this number. */maxTracks: number;
|
|
1262
|
+
/**
|
|
1263
|
+
* Start value of reduced motion: every track but a loop takes 0 ms and every loop stands on
|
|
1264
|
+
* its first key. `app.anim.setReducedMotion(on)` switches it while the game runs.
|
|
1265
|
+
*/
|
|
1266
|
+
reducedMotion: boolean;
|
|
1267
|
+
};
|
|
1268
|
+
/**
|
|
1269
|
+
* anim plugin state: the one track table, the field bookkeeping of the retarget policy and the
|
|
1270
|
+
* offsets accumulator, the running timelines and the animation registry.
|
|
1271
|
+
*/
|
|
1272
|
+
type State = {
|
|
1273
|
+
/** The one track table. Insertion order is advance order. */tracks: Map<number, Track>; /** Ids of tracks and timelines. */
|
|
1274
|
+
nextId: number; /** `"<entity>:<component>:<field>"` to the id of the absolute track that drives the field. */
|
|
1275
|
+
owner: Map<string, number>; /** Per field key, the delta each additive track contributes. */
|
|
1276
|
+
offsets: Map<string, Map<number, number>>; /** Per field key, the value the offsets are added to. Dropped when no track drives the field. */
|
|
1277
|
+
bases: Map<string, number>; /** The running timelines, by id. */
|
|
1278
|
+
timelines: Map<number, RunningTimeline>; /** The `animations` of every feature, read in `onStart`. */
|
|
1279
|
+
registry: Map<string, AnyAnimationDefinition>; /** `onMark` subscribers, in subscription order. */
|
|
1280
|
+
markListeners: Set<MarkListener>; /** Counter of the frame steps. A track born in the running step is not advanced by it. */
|
|
1281
|
+
frame: number; /** True while the track count is over `maxTracks`, so the warning is one per crossing. */
|
|
1282
|
+
overMaxTracks: boolean; /** The live reduced-motion switch. The frozen `Config.reducedMotion` only seeds it. */
|
|
1283
|
+
reducedMotion: boolean; /** Remover of `world.projection.setDriver`. */
|
|
1284
|
+
removeDriver: (() => void) | undefined; /** Remover of `time.onFrame("animate")`. */
|
|
1285
|
+
offFrame: (() => void) | undefined; /** Remover of `flow.fx.handle("play")`. */
|
|
1286
|
+
offPlay: (() => void) | undefined; /** Bound in `onInit`, so the teardown context can end everything with the state alone. */
|
|
1287
|
+
finishAll: (() => void) | undefined; /** One loop per entity that carries `Frames`, kept by the two world hooks. */
|
|
1288
|
+
frameLoops: Map<Entity, FrameLoop>; /** How many running `frames` steps write this entity now. Its loop writes nothing above 0. */
|
|
1289
|
+
framesHeld: Map<Entity, number>;
|
|
1290
|
+
/**
|
|
1291
|
+
* Per entity that carries `Frames`, the remover of its `world.projection.mute` on
|
|
1292
|
+
* `Sprite.texture`: the loop owns that field, so a projection never corrects it back.
|
|
1293
|
+
*/
|
|
1294
|
+
frameMutes: Map<Entity, () => void>; /** Removers of the `onAdded` and `onRemoved` hooks on `Frames`. */
|
|
1295
|
+
offFrames: Array<() => void>;
|
|
1296
|
+
};
|
|
1297
|
+
/**
|
|
1298
|
+
* anim plugin API, `app.anim`: play a choreography, end everything, count what runs and listen
|
|
1299
|
+
* to the marks. A choreography may spawn temporary entities; they live exactly as long as its
|
|
1300
|
+
* timeline.
|
|
1301
|
+
*
|
|
1302
|
+
* @example
|
|
1303
|
+
* ```ts
|
|
1304
|
+
* // A choreography runs in game time: two frames of 50 ms play the 100 ms slide of a card.
|
|
1305
|
+
* const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
|
|
1306
|
+
* build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
|
|
1307
|
+
* app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
|
|
1308
|
+
* app.time.step(50); // app.anim.active() is 1: the card slides
|
|
1309
|
+
* app.time.step(50); // app.anim.active() is 0: the card rests at x 100
|
|
1310
|
+
* ```
|
|
1311
|
+
*/
|
|
1312
|
+
type AnimApi = {
|
|
1313
|
+
/**
|
|
1314
|
+
* Builds the step tree of an animation with the given targets and starts it. The tree runs
|
|
1315
|
+
* from the next frame step on, in game time, so `time.setScale` and a pause apply to it. `at`
|
|
1316
|
+
* inside `build` answers where a target rests in root space, even inside a scaled slot. A
|
|
1317
|
+
* `tween` with `space: "root"` lands a hosted view on such a pose: a board item inside the board
|
|
1318
|
+
* slot flies onto an order card of the HUD.
|
|
1319
|
+
*
|
|
1320
|
+
* @param animation - What `defineAnimation` returned.
|
|
1321
|
+
* @param slots - One target, or a list of targets, per declared slot.
|
|
1322
|
+
* @returns The handle of the running timeline.
|
|
1323
|
+
* @throws {Error} When the tree spawns one id twice.
|
|
1324
|
+
* @example
|
|
1325
|
+
* ```ts
|
|
1326
|
+
* // The player delivers order card "a": it slides to x 100 in 100 ms, then reaches "done".
|
|
1327
|
+
* const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
|
|
1328
|
+
* build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
|
|
1329
|
+
* const handle = app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
|
|
1330
|
+
* app.time.step(100);
|
|
1331
|
+
* handle.marks(); // ["done"], and handle.done resolves
|
|
1332
|
+
* ```
|
|
1333
|
+
*/
|
|
1334
|
+
play<Tags extends SlotTags>(animation: AnimationDefinition<Tags>, slots: SlotValues<Tags>): PlayHandle;
|
|
1335
|
+
/**
|
|
1336
|
+
* Ends every track and every timeline now: each writes its exact target, the marks of a
|
|
1337
|
+
* running timeline are jumped in tree order, every entity a timeline spawned is despawned and
|
|
1338
|
+
* every pending `done` resolves.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```ts
|
|
1342
|
+
* // A /control tool skips the delivery mid-flight: the card lands on x 100 at once.
|
|
1343
|
+
* const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
|
|
1344
|
+
* build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
|
|
1345
|
+
* app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
|
|
1346
|
+
* app.time.step(50);
|
|
1347
|
+
* app.anim.finishAll(); // app.anim.active() is 0
|
|
1348
|
+
* ```
|
|
1349
|
+
*/
|
|
1350
|
+
finishAll(): void;
|
|
1351
|
+
/**
|
|
1352
|
+
* How many tracks are in the table, the delayed ones and the running loops included. A loop
|
|
1353
|
+
* never ends by itself, so a screen at rest counts one track per loop lane it shows. A `Frames`
|
|
1354
|
+
* component is not a track: a spinning coin counts nothing.
|
|
1355
|
+
*
|
|
1356
|
+
* @returns The number of running tracks.
|
|
1357
|
+
* @example
|
|
1358
|
+
* ```ts
|
|
1359
|
+
* // The assertion every motion test ends with: the screen came to rest.
|
|
1360
|
+
* const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
|
|
1361
|
+
* build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
|
|
1362
|
+
* app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
|
|
1363
|
+
* app.time.step(100);
|
|
1364
|
+
* app.anim.active(); // 0
|
|
1365
|
+
* ```
|
|
1366
|
+
*/
|
|
1367
|
+
active(): number;
|
|
1368
|
+
/**
|
|
1369
|
+
* Whether reduced motion is on. It starts at `Config.reducedMotion` and `setReducedMotion`
|
|
1370
|
+
* switches it.
|
|
1371
|
+
*
|
|
1372
|
+
* @returns True while every new track takes 0 ms and every loop stands on its first key.
|
|
1373
|
+
* @example
|
|
1374
|
+
* ```ts
|
|
1375
|
+
* // A settings popup shows the switch as the player left it.
|
|
1376
|
+
* app.anim.reducedMotion(); // false: the start value of Config.reducedMotion
|
|
1377
|
+
* ```
|
|
1378
|
+
*/
|
|
1379
|
+
reducedMotion(): boolean;
|
|
1380
|
+
/**
|
|
1381
|
+
* Switches reduced motion. While it is on, every track started from then on takes 0 ms: enter
|
|
1382
|
+
* and exit, state changes, change and settle motions, drag returns and timeline tweens land on
|
|
1383
|
+
* their target at the next frame, and their marks and sounds still fire. Every loop stands on
|
|
1384
|
+
* its first key, a running one at once. Tracks already running keep their length. A `Frames`
|
|
1385
|
+
* loop stops on the frame it shows and walks on from that frame when the switch goes off.
|
|
1386
|
+
*
|
|
1387
|
+
* @param on - True for less motion, false for the full motion.
|
|
1388
|
+
* @example
|
|
1389
|
+
* ```ts
|
|
1390
|
+
* // The player turns on "Less motion" in the settings popup.
|
|
1391
|
+
* app.anim.setReducedMotion(true);
|
|
1392
|
+
* app.anim.reducedMotion(); // true: a popup opened now stands in its rest pose next frame
|
|
1393
|
+
* ```
|
|
1394
|
+
*/
|
|
1395
|
+
setReducedMotion(on: boolean): void;
|
|
1396
|
+
/**
|
|
1397
|
+
* Registers a listener called for every mark a timeline reaches, next to the `anim:mark`
|
|
1398
|
+
* event. A listener that throws is logged and the listeners after it still run.
|
|
1399
|
+
*
|
|
1400
|
+
* @param fn - Called with the animation id and the mark name.
|
|
1401
|
+
* @returns The remover; calling it twice is a no-op.
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```ts
|
|
1404
|
+
* // A test waits for the "done" mark instead of counting frames.
|
|
1405
|
+
* const ping = defineAnimation("fx.ping", { slots: {}, build: () => sequence(wait(100), mark("done")) });
|
|
1406
|
+
* const reached: string[] = [];
|
|
1407
|
+
* app.anim.onMark((animation, name) => reached.push(`${animation}:${name}`));
|
|
1408
|
+
* app.anim.play(ping, {});
|
|
1409
|
+
* app.time.step(100); // reached: ["fx.ping:done"]
|
|
1410
|
+
* ```
|
|
1411
|
+
*/
|
|
1412
|
+
onMark(fn: MarkListener): () => void;
|
|
1413
|
+
};
|
|
1414
|
+
/**
|
|
1415
|
+
* Resolved dependency APIs. `renderer` is not among them: `anim` writes its `Sprite` and reads
|
|
1416
|
+
* its `Transform` through the component objects, which are plain data, composes the root pose
|
|
1417
|
+
* of `at` with the pure `rootPoseOf` of `renderer/sync/pose.ts`, and turns the target of a
|
|
1418
|
+
* `tween` in `space: "root"` local with the pure `localPoseOf` of the same module.
|
|
1419
|
+
*/
|
|
1420
|
+
type Deps = {
|
|
1421
|
+
time: Api;
|
|
1422
|
+
flow: Api$1;
|
|
1423
|
+
world: Api$2;
|
|
1424
|
+
};
|
|
1425
|
+
/**
|
|
1426
|
+
* What the kernel context offers before the deps are attached.
|
|
1427
|
+
*
|
|
1428
|
+
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
1429
|
+
* row 8), so the own events reach the context the kernel hands the factories and `emit` is the
|
|
1430
|
+
* kernel's own, anim-typed one. No member of the context is cast.
|
|
1431
|
+
*/
|
|
1432
|
+
type KernelSlice = PluginCtx<Config, State, Events> & {
|
|
1433
|
+
readonly global: object;
|
|
1434
|
+
readonly log: Log.LogApi;
|
|
1435
|
+
readonly require: Require;
|
|
1436
|
+
};
|
|
1437
|
+
/**
|
|
1438
|
+
* Domain context of the anim plugin: the kernel slice and the three resolved dependencies.
|
|
1439
|
+
*/
|
|
1440
|
+
type AnimCtx = KernelSlice & {
|
|
1441
|
+
readonly deps: Deps;
|
|
1442
|
+
};
|
|
1443
|
+
//#endregion
|
|
1444
|
+
export { Point as C, TextStyles as D, TextStyleInput as E, TextValue as O, Config$2 as S, TextApi as T, Step as _, Config as a, UiApi as b, SlotTags as c, Target as d, types_d_exports as f, SfxDescriptor as g, PlayDescriptor as h, BuildTools as i, types_d_exports$2 as k, SlotValues as l, HapticDescriptor as m, AnimationDefinition as n, FramesValue as o, ExternalPlayer as p, AnimationSpec as r, MotionKeyframe as s, AnimApi as t, State as u, Config$1 as v, State$2 as w, types_d_exports$1 as x, State$1 as y };
|