@moku-labs/game 0.8.0 → 0.10.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 +88 -26
- package/bin/moku-game.mjs +4 -0
- package/dist/app/page.d.mts +41 -0
- package/dist/app/page.mjs +376 -0
- package/dist/app/system.d.mts +129 -0
- package/dist/app/system.mjs +307 -0
- package/dist/app.d.mts +37 -0
- package/dist/app.mjs +235 -0
- package/dist/assets-BHwTLVta.mjs +7391 -0
- package/dist/assets.d.mts +97 -33
- package/dist/assets.mjs +1 -7150
- package/dist/{capture-BX5Gfoty.mjs → capture-fpd83Mi5.mjs} +384 -1
- package/dist/cli.d.mts +70 -0
- package/dist/cli.mjs +1650 -0
- package/dist/{commands-C1kIfOG5.d.mts → commands-C4QlqijJ.d.mts} +5 -6
- package/dist/config-CdARrrhU.mjs +81 -0
- package/dist/control.d.mts +11 -10
- package/dist/control.mjs +3 -3
- package/dist/{define-BPpZcpxW.d.mts → define-CCc0ozKe.d.mts} +2 -2
- package/dist/{dev-wKLgOw7c.mjs → dev-Cc50Dk7H.mjs} +1 -1
- package/dist/{memory-CvgdnsQO.mjs → drafts-BawpSaVp.mjs} +1 -104
- package/dist/fake-MpzwZhIZ.mjs +87 -0
- package/dist/{headless-CvamCUcR.mjs → headless-BsNdJs9W.mjs} +1 -1
- package/dist/{headless-q98SwADR.d.mts → headless-G4nuZ-xz.d.mts} +1 -1
- package/dist/hot.d.mts +8 -8
- package/dist/hot.mjs +15 -14
- package/dist/index-xxSxKJJW.d.mts +1611 -0
- package/dist/index.d.mts +6 -1597
- package/dist/index.mjs +4 -34343
- package/dist/inspect-la_KkjCS.mjs +1 -0
- package/dist/inspect.d.mts +7 -8
- package/dist/inspect.mjs +3 -2
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.d.mts +55 -4
- package/dist/lint.mjs +999 -42
- package/dist/memory-CWlqhS7l.mjs +136 -0
- package/dist/memory-DwDhfFWK.d.mts +50 -0
- package/dist/project.d.mts +29 -13
- package/dist/project.mjs +1120 -631
- package/dist/{run-CUPSFM85.mjs → run-wYty_Lwo.mjs} +4 -4
- package/dist/{runtime-CvDZZg1S.d.mts → runtime-DOBYM6g4.d.mts} +1 -1
- package/dist/{input-z_35Jwfn.mjs → session-DgukhR_7.mjs} +20 -20
- package/dist/{sources-BBUd15vQ.mjs → sources-Duzj6lhV.mjs} +3 -3
- package/dist/src-DSl1eNB1.mjs +34366 -0
- package/dist/testing.d.mts +115 -34
- package/dist/testing.mjs +175 -70
- package/dist/{types-DlgsfOmW.d.mts → types-BM4shh8K.d.mts} +300 -263
- package/dist/{types-DgdMLZnV.d.mts → types-BUwDHKXm.d.mts} +1372 -1373
- package/dist/{types-DF5kkW-C.d.mts → types-CefgYzFt.d.mts} +4 -5
- package/dist/{types-CDSKa0vl.d.mts → types-DAdwEJMp.d.mts} +2 -2
- package/dist/{types-BWzCSRM2.d.mts → types-ZQLYXo5X.d.mts} +2983 -20
- package/dist/types-hQgI_wrq.d.mts +544 -0
- package/dist/{visual-DUvvPPCt.mjs → visual-dMW8iqAE.mjs} +8 -4
- package/dist/visual.d.mts +10 -11
- package/dist/visual.mjs +5 -5
- package/llms.txt +170 -57
- package/package.json +30 -2
- package/dist/registry-DlpRCibU.mjs +0 -384
- package/dist/types-ERb2Y9U5.d.mts +0 -2967
|
@@ -1,1914 +1,1913 @@
|
|
|
1
|
-
import { A as Api,
|
|
2
|
-
import { B as ComponentValue, D as Point$2, F as AnyComponentType, I as AnyComponentValue, O as ViewportSize, P as AnyComponent, V as Entity, Z as ChangeHook, at as ProjectionMotion, it as NumericFields, k as Api$3, lt as ViewHandle, m as Api$4, nt as Motion, o as Events$1, rt as MotionHandle, t as Api$5, z as ComponentType } from "./types-BWzCSRM2.mjs";
|
|
1
|
+
import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, I as AnyComponentType, L as AnyComponentValue, Lt as Descriptor, O as Point$2, Q as ChangeHook, Sn as Require, V as ComponentValue, Vt as Api$5, Xt as Json, at as NumericFields, dt as Api$1, h as Api$3, hn as Api, it as MotionHandle, k as ViewportSize, ot as ProjectionMotion, rt as Motion, s as Events$1, t as Api$4, ut as ViewHandle, zt as Hint } from "./types-ZQLYXo5X.mjs";
|
|
3
2
|
import { Log } from "@moku-labs/common/browser";
|
|
4
3
|
import { PluginCtx } from "@moku-labs/core";
|
|
5
4
|
|
|
6
|
-
//#region src/plugins/
|
|
5
|
+
//#region src/plugins/ui/styles/types.d.ts
|
|
7
6
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* lands; the default pivot `{ x: 0, y: 0 }` is the view's own origin.
|
|
7
|
+
* A length that may also come from the notch. The four tokens are replaced by `resolve` with the
|
|
8
|
+
* matching `viewport.safeArea` value.
|
|
11
9
|
*
|
|
12
10
|
* @example
|
|
13
11
|
* ```ts
|
|
14
|
-
*
|
|
15
|
-
* const value: TransformValue = {
|
|
16
|
-
* x: 640, y: 340, rotation: 0, scale: 1.1, pivot: { x: 100, y: 40 }
|
|
17
|
-
* };
|
|
12
|
+
* const top: SafeAreaToken = "safeArea.top";
|
|
18
13
|
* ```
|
|
19
14
|
*/
|
|
20
|
-
type
|
|
21
|
-
x: number;
|
|
22
|
-
y: number;
|
|
23
|
-
rotation: number;
|
|
24
|
-
scale: number;
|
|
25
|
-
pivot: Point$2;
|
|
26
|
-
};
|
|
15
|
+
type SafeAreaToken = "safeArea.top" | "safeArea.right" | "safeArea.bottom" | "safeArea.left";
|
|
27
16
|
/**
|
|
28
|
-
*
|
|
29
|
-
* box and centres, `"cover"` scales uniformly over the box and crops the overflow.
|
|
17
|
+
* A length in reference units, or one of the safe-area tokens.
|
|
30
18
|
*
|
|
31
19
|
* @example
|
|
32
20
|
* ```ts
|
|
33
|
-
* const
|
|
21
|
+
* const length: Length = 24;
|
|
34
22
|
* ```
|
|
35
23
|
*/
|
|
36
|
-
type
|
|
24
|
+
type Length = number | SafeAreaToken;
|
|
37
25
|
/**
|
|
38
|
-
*
|
|
39
|
-
* `width` and `height` are the box in reference units; 0 on an axis keeps the texture's own size
|
|
40
|
-
* there. `anchor` is the point of the box that sits on the `Transform`. `alpha` fades the quad
|
|
41
|
-
* and the entity's children.
|
|
26
|
+
* Padding or margin: one number for every edge, or a value per edge.
|
|
42
27
|
*
|
|
43
28
|
* @example
|
|
44
29
|
* ```ts
|
|
45
|
-
* const
|
|
46
|
-
* texture: "board.bg-forest-meadow", tint: 0xffffff, alpha: 1, anchor: { x: 0, y: 0 },
|
|
47
|
-
* width: 1080, height: 1920, fit: "cover"
|
|
48
|
-
* };
|
|
30
|
+
* const padding: Edges = { top: "safeArea.top", left: 16 };
|
|
49
31
|
* ```
|
|
50
32
|
*/
|
|
51
|
-
type
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
width: number;
|
|
57
|
-
height: number;
|
|
58
|
-
fit: SpriteFit;
|
|
33
|
+
type Edges = Length | {
|
|
34
|
+
top?: Length;
|
|
35
|
+
right?: Length;
|
|
36
|
+
bottom?: Length;
|
|
37
|
+
left?: Length;
|
|
59
38
|
};
|
|
60
39
|
/**
|
|
61
|
-
* A
|
|
62
|
-
* strokes the bounds and the four cut lines over the panel: cyan, red when the corners overlap or
|
|
63
|
-
* the texture is missing. `clip` masks the children of the entity to the `width × height` box,
|
|
64
|
-
* the same rule as `Shape.clip`: the mask is a filled rectangle and is never drawn, and the panel
|
|
65
|
-
* itself is never masked. `alpha` fades the panel and the entity's children.
|
|
40
|
+
* A main-axis or cross-axis size: reference units, a percent of the parent, or `"auto"`.
|
|
66
41
|
*
|
|
67
42
|
* @example
|
|
68
43
|
* ```ts
|
|
69
|
-
* const
|
|
70
|
-
* texture: "ui.panel", width: 600, height: 320, alpha: 1, tint: 0xffffff, debug: false,
|
|
71
|
-
* clip: false
|
|
72
|
-
* };
|
|
73
|
-
* // The board tray, outlined while its insets are checked.
|
|
74
|
-
* const tray: NineSliceValue = {
|
|
75
|
-
* ...value, texture: "board.board-tray", width: 1000, height: 1040, debug: true
|
|
76
|
-
* };
|
|
77
|
-
* // A text field: a long value never draws outside the frame.
|
|
78
|
-
* const field: NineSliceValue = {
|
|
79
|
-
* ...value, texture: "ui.field", width: 480, height: 96, clip: true
|
|
80
|
-
* };
|
|
44
|
+
* const width: Extent = "50%";
|
|
81
45
|
* ```
|
|
82
46
|
*/
|
|
83
|
-
type
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
47
|
+
type Extent = number | `${number}%` | "auto";
|
|
48
|
+
/**
|
|
49
|
+
* The flow group of the vocabulary: what Yoga's flexbox calls are set from.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const flow: FlowStyle = { direction: "row", gap: 12, justify: "between" };
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
type FlowStyle = {
|
|
57
|
+
direction?: "row" | "column";
|
|
58
|
+
wrap?: boolean;
|
|
59
|
+
justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
|
|
60
|
+
align?: "start" | "center" | "end" | "stretch";
|
|
61
|
+
alignSelf?: "auto" | "start" | "center" | "end" | "stretch";
|
|
62
|
+
gap?: number;
|
|
63
|
+
grow?: number;
|
|
64
|
+
shrink?: number;
|
|
91
65
|
};
|
|
92
66
|
/**
|
|
93
|
-
* The
|
|
67
|
+
* The box group: what the element takes up before its children are placed.
|
|
94
68
|
*
|
|
95
69
|
* @example
|
|
96
70
|
* ```ts
|
|
97
|
-
*
|
|
71
|
+
* // The board slot: 970 u of cells, scaled down on a short phone.
|
|
72
|
+
* const box: BoxStyle = { width: 970, height: 970, fit: "contain" };
|
|
98
73
|
* ```
|
|
99
74
|
*/
|
|
100
|
-
type
|
|
101
|
-
|
|
75
|
+
type BoxStyle = {
|
|
76
|
+
padding?: Edges;
|
|
77
|
+
margin?: Edges;
|
|
78
|
+
width?: Extent;
|
|
79
|
+
height?: Extent;
|
|
80
|
+
minWidth?: Extent;
|
|
81
|
+
minHeight?: Extent;
|
|
82
|
+
maxWidth?: Extent;
|
|
83
|
+
maxHeight?: Extent;
|
|
84
|
+
aspect?: number; /** `"hidden"` clips the children to the rect, as a scroll container does. */
|
|
85
|
+
overflow?: "visible" | "hidden" | "scroll";
|
|
86
|
+
/**
|
|
87
|
+
* `"contain"`: the element keeps its own size and leaves the flow, then is scaled down to fit
|
|
88
|
+
* the content box of its parent and centred in it. Its children keep their natural rects.
|
|
89
|
+
*/
|
|
90
|
+
fit?: "contain";
|
|
102
91
|
};
|
|
103
92
|
/**
|
|
104
|
-
*
|
|
105
|
-
* picks the outline: `"rect"` (the default) or `"triangle"`, which fills its `w × h` box pointing
|
|
106
|
-
* right (rotate the element for another direction) and ignores `radius`. `fillAlpha` is the alpha
|
|
107
|
-
* of the fill alone: 0 draws only the stroke, a ring. `alpha` fades the whole shape and the
|
|
108
|
-
* entity's children: a disabled button at 0.6 draws its icon at 0.6 too. `dash` is the
|
|
109
|
-
* dash length of the stroke in reference units, with gaps of half a dash; 0 strokes a solid line.
|
|
110
|
-
* `clip` masks the children of the entity to the shape, always solid, which is how `ui` draws a
|
|
111
|
-
* scroll and an overflow. The shape itself is never masked, so its stroke is drawn whole.
|
|
93
|
+
* The position group. `reason` is the comment `lint()` asks for next to an absolute element.
|
|
112
94
|
*
|
|
113
95
|
* @example
|
|
114
96
|
* ```ts
|
|
115
|
-
* const
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* };
|
|
119
|
-
* // The honey ring on a selected cell: a stroke and no fill.
|
|
120
|
-
* const ring: ShapeValue = {
|
|
121
|
-
* kind: "rect", w: 140, h: 140, fill: 0xffffff, fillAlpha: 0, alpha: 1, radius: 24,
|
|
122
|
-
* stroke: 0xffc233, strokeWidth: 6, dash: 0, clip: false
|
|
123
|
-
* };
|
|
124
|
-
* // The play glyph of a watch button, and a dashed focus ring: 10 u dashes, 5 u gaps.
|
|
125
|
-
* const play: ShapeValue = { ...ring, kind: "triangle", w: 36, h: 40, fillAlpha: 1, strokeWidth: 0 };
|
|
126
|
-
* const focus: ShapeValue = { ...ring, w: 200, h: 80, stroke: 0x3a2212, strokeWidth: 4, dash: 10 };
|
|
97
|
+
* const pinned: PositionStyle = { position: "absolute", top: 0, right: 0, reason: "badge" };
|
|
98
|
+
* // The order strip of the board drawn over the tray that follows it.
|
|
99
|
+
* const strip: PositionStyle = { zIndex: 1 };
|
|
127
100
|
* ```
|
|
128
101
|
*/
|
|
129
|
-
type
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
102
|
+
type PositionStyle = {
|
|
103
|
+
position?: "relative" | "absolute";
|
|
104
|
+
left?: Length;
|
|
105
|
+
top?: Length;
|
|
106
|
+
right?: Length;
|
|
107
|
+
bottom?: Length;
|
|
108
|
+
reason?: string;
|
|
109
|
+
/**
|
|
110
|
+
* The draw order among the siblings, an integer: a higher one draws over a lower one, and a
|
|
111
|
+
* sibling without it draws at 0 in markup order. Ignored on a root element, which `lint()`
|
|
112
|
+
* reports; a root draws at the order of its layer.
|
|
113
|
+
*/
|
|
114
|
+
zIndex?: number;
|
|
141
115
|
};
|
|
142
116
|
/**
|
|
143
|
-
*
|
|
117
|
+
* The visual group: what is written to `Shape`, `Sprite` or `NineSlice`. There is no font size
|
|
118
|
+
* here — the size of a text comes from its text style key.
|
|
144
119
|
*
|
|
145
120
|
* @example
|
|
146
121
|
* ```ts
|
|
147
|
-
*
|
|
122
|
+
* // A wooden button: the nine-slice of the style, greyed while disabled.
|
|
123
|
+
* const visual: VisualStyle = { nineSlice: "ui.button-wood", alpha: 1, tint: 0xffffff };
|
|
124
|
+
* // The popup board while its insets are checked: the slice lines drawn over it.
|
|
125
|
+
* const checked: VisualStyle = { nineSlice: "ui.panel-signboard", debug: true };
|
|
126
|
+
* // The play glyph of the Watch button, and a dashed ring around a slot.
|
|
127
|
+
* const play: VisualStyle = { shape: "triangle", fill: 0xfffbe8 };
|
|
128
|
+
* const slot: VisualStyle = { stroke: 0x3a2212, strokeWidth: 4, dash: 10, radius: 24 };
|
|
148
129
|
* ```
|
|
149
130
|
*/
|
|
150
|
-
type
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
131
|
+
type VisualStyle<Asset extends string = string> = {
|
|
132
|
+
fill?: number;
|
|
133
|
+
stroke?: number;
|
|
134
|
+
strokeWidth?: number;
|
|
135
|
+
radius?: number;
|
|
154
136
|
alpha?: number;
|
|
155
|
-
|
|
156
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The shape of the rectangle an element without a nine-slice is drawn with: `"rect"` (the
|
|
139
|
+
* default) or `"triangle"`, which fills the box pointing right and ignores `radius`. Turn the
|
|
140
|
+
* element with `rotation` for another direction.
|
|
141
|
+
*/
|
|
142
|
+
shape?: "rect" | "triangle"; /** The dash length of the stroke in reference units, with gaps of half a dash; 0 is solid. */
|
|
143
|
+
dash?: number;
|
|
144
|
+
/**
|
|
145
|
+
* The asset key of a nine-slice drawn at the rect instead of the rounded rectangle. Any tag
|
|
146
|
+
* but `image`, `icon` and `text` takes it; a clipping element (`scroll`, `overflow: "hidden"`)
|
|
147
|
+
* keeps its rectangle, which carries the clip.
|
|
148
|
+
*/
|
|
149
|
+
nineSlice?: Asset; /** Multiplies the colour of a nine-slice or an image. */
|
|
150
|
+
tint?: number;
|
|
151
|
+
/**
|
|
152
|
+
* Outlines the nine-slice of the style: its bounds and the four slice lines, cyan, red when
|
|
153
|
+
* the corners overlap or the texture is missing. A look for checking art, never for a player.
|
|
154
|
+
*/
|
|
155
|
+
debug?: boolean;
|
|
157
156
|
};
|
|
158
157
|
/**
|
|
159
|
-
* Where
|
|
160
|
-
*
|
|
158
|
+
* Where an element turns and scales: its centre, the middle of its top edge, its top-left
|
|
159
|
+
* corner, or a point given in fractions of its box.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* // A sign that hangs from its ropes swings around the middle of its top edge.
|
|
164
|
+
* const origin: Origin = "top";
|
|
165
|
+
* ```
|
|
161
166
|
*/
|
|
162
|
-
|
|
167
|
+
type Origin = "center" | "top" | "topLeft" | {
|
|
168
|
+
x: number;
|
|
169
|
+
y: number;
|
|
170
|
+
};
|
|
163
171
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
172
|
+
* The transform group: how an element is drawn, never where it is laid out. Written into the
|
|
173
|
+
* rest `Transform`, so a hover lift or a pressed sink never moves a sibling.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* // A button that lifts under the mouse.
|
|
178
|
+
* const lift: TransformStyle = { offsetY: -6, scale: 1.05, origin: "center" };
|
|
179
|
+
* // The title plaque of a popup, tilted by 1.5 degrees around the middle of its top edge.
|
|
180
|
+
* const plaque: TransformStyle = { rotation: -0.026, origin: "top" };
|
|
181
|
+
* ```
|
|
166
182
|
*/
|
|
167
|
-
|
|
183
|
+
type TransformStyle = {
|
|
184
|
+
/** Moves the drawn element right, in reference units. */offsetX?: number; /** Moves the drawn element down, in reference units. */
|
|
185
|
+
offsetY?: number; /** Uniform scale around the origin. */
|
|
186
|
+
scale?: number; /** Turns the drawn element around the origin, in radians, clockwise; 0 by default. */
|
|
187
|
+
rotation?: number; /** The point the element scales and turns around; the centre by default. */
|
|
188
|
+
origin?: Origin;
|
|
189
|
+
};
|
|
168
190
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
191
|
+
* Everything but the variants: the five groups of the vocabulary.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* const base: BaseStyle = { direction: "row", gap: 8, fill: 0x101018 };
|
|
196
|
+
* ```
|
|
171
197
|
*/
|
|
172
|
-
|
|
173
|
-
texture: string;
|
|
174
|
-
width: number;
|
|
175
|
-
height: number;
|
|
176
|
-
alpha: number;
|
|
177
|
-
tint: number;
|
|
178
|
-
debug: boolean;
|
|
179
|
-
clip: boolean;
|
|
180
|
-
}>;
|
|
181
|
-
/**
|
|
182
|
-
* The entity this view moves with. `0` means no parent.
|
|
183
|
-
*/
|
|
184
|
-
declare const Parent: ComponentType<{
|
|
185
|
-
entity: number;
|
|
186
|
-
}>;
|
|
187
|
-
/**
|
|
188
|
-
* A display object the game owns. Never pooled, never destroyed by `sync`.
|
|
189
|
-
*/
|
|
190
|
-
declare const Display: ComponentType<DisplayValue>;
|
|
198
|
+
type BaseStyle<Asset extends string = string> = FlowStyle & BoxStyle & PositionStyle & VisualStyle<Asset> & TransformStyle;
|
|
191
199
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
200
|
+
* The seven state variants of a style, applied in the order disabled, active, selected, hover,
|
|
201
|
+
* focus, pressed, covered. While `disabled` is true, `hover` and `pressed` are not applied.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* // One rule set for every control: lift on hover, sink when pressed, swap the texture when off.
|
|
206
|
+
* const variants: IsVariants = {
|
|
207
|
+
* hover: { offsetY: -6, scale: 1.05 },
|
|
208
|
+
* focus: { scale: 1.05 },
|
|
209
|
+
* pressed: { offsetY: 4, scale: 0.95 },
|
|
210
|
+
* disabled: { nineSlice: "ui.button-disabled" }
|
|
211
|
+
* };
|
|
212
|
+
* ```
|
|
195
213
|
*/
|
|
196
|
-
|
|
214
|
+
type IsVariants<Asset extends string = string> = {
|
|
215
|
+
pressed?: BaseStyle<Asset>;
|
|
216
|
+
disabled?: BaseStyle<Asset>;
|
|
217
|
+
active?: BaseStyle<Asset>;
|
|
218
|
+
selected?: BaseStyle<Asset>; /** The mouse or pen is over the element. Touch never hovers. */
|
|
219
|
+
hover?: BaseStyle<Asset>; /** The keyboard focus is on the element: Tab moved it there, and no pointer tapped since. */
|
|
220
|
+
focus?: BaseStyle<Asset>; /** The popup the element belongs to is kept under another one. */
|
|
221
|
+
covered?: BaseStyle<Asset>;
|
|
222
|
+
};
|
|
197
223
|
/**
|
|
198
|
-
*
|
|
224
|
+
* The four viewport variants of a style, applied in the order portrait, landscape, tall, wide.
|
|
199
225
|
*
|
|
200
|
-
* @param options - Texture key, position, and the four values that have defaults.
|
|
201
|
-
* @returns The `Sprite` and the `Transform` value, in that order.
|
|
202
226
|
* @example
|
|
203
227
|
* ```ts
|
|
204
|
-
*
|
|
205
|
-
* // [Sprite({ texture: "board.cell", tint: 0xffffff, alpha: 1, anchor: { x: 0.5, y: 0.5 },
|
|
206
|
-
* // width: 0, height: 0, fit: "fill" }),
|
|
207
|
-
* // Transform({ x: 540, y: 300, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } })]
|
|
228
|
+
* const variants: WhenVariants = { landscape: { direction: "row" } };
|
|
208
229
|
* ```
|
|
209
230
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
231
|
+
type WhenVariants<Asset extends string = string> = {
|
|
232
|
+
portrait?: BaseStyle<Asset>;
|
|
233
|
+
landscape?: BaseStyle<Asset>;
|
|
234
|
+
tall?: BaseStyle<Asset>;
|
|
235
|
+
wide?: BaseStyle<Asset>;
|
|
236
|
+
};
|
|
214
237
|
/**
|
|
215
|
-
*
|
|
238
|
+
* One style as a game writes it: the base vocabulary plus the two variant tables. `Asset` is the
|
|
239
|
+
* game's asset key union: `uiFor` narrows `nineSlice` to it, the way it narrows `texture`.
|
|
216
240
|
*
|
|
217
241
|
* @example
|
|
218
242
|
* ```ts
|
|
219
|
-
* const
|
|
243
|
+
* const topBar: Style = { direction: "row", gap: 12, when: { landscape: { justify: "end" } } };
|
|
220
244
|
* ```
|
|
221
245
|
*/
|
|
222
|
-
type
|
|
223
|
-
|
|
224
|
-
|
|
246
|
+
type Style<Asset extends string = string> = BaseStyle<Asset> & {
|
|
247
|
+
is?: IsVariants<Asset>;
|
|
248
|
+
when?: WhenVariants<Asset>;
|
|
225
249
|
};
|
|
226
250
|
/**
|
|
227
|
-
*
|
|
251
|
+
* What `resolve` answers with: the base vocabulary with every safe-area token replaced by a
|
|
252
|
+
* number and every variant already merged in. Frozen.
|
|
228
253
|
*
|
|
229
254
|
* @example
|
|
230
255
|
* ```ts
|
|
231
|
-
* const
|
|
256
|
+
* const resolved: ResolvedStyle = { direction: "row", gap: 12, padding: 24 };
|
|
232
257
|
* ```
|
|
233
258
|
*/
|
|
234
|
-
type
|
|
259
|
+
type ResolvedStyle = Omit<BaseStyle, "padding" | "margin" | "left" | "top" | "right" | "bottom"> & {
|
|
260
|
+
padding?: number | {
|
|
261
|
+
top?: number;
|
|
262
|
+
right?: number;
|
|
263
|
+
bottom?: number;
|
|
264
|
+
left?: number;
|
|
265
|
+
};
|
|
266
|
+
margin?: number | {
|
|
267
|
+
top?: number;
|
|
268
|
+
right?: number;
|
|
269
|
+
bottom?: number;
|
|
270
|
+
left?: number;
|
|
271
|
+
};
|
|
272
|
+
left?: number;
|
|
273
|
+
top?: number;
|
|
274
|
+
right?: number;
|
|
275
|
+
bottom?: number;
|
|
276
|
+
};
|
|
235
277
|
/**
|
|
236
|
-
*
|
|
237
|
-
* addresses a view in the despawn queue.
|
|
278
|
+
* The viewport flags of a style: which `when` variants apply this frame.
|
|
238
279
|
*
|
|
239
280
|
* @example
|
|
240
281
|
* ```ts
|
|
241
|
-
* const
|
|
282
|
+
* const flags: WhenFlags = { portrait: true, landscape: false, tall: true, wide: false };
|
|
242
283
|
* ```
|
|
243
284
|
*/
|
|
244
|
-
type
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
285
|
+
type WhenFlags = {
|
|
286
|
+
portrait: boolean;
|
|
287
|
+
landscape: boolean;
|
|
288
|
+
tall: boolean;
|
|
289
|
+
wide: boolean;
|
|
290
|
+
};
|
|
248
291
|
/**
|
|
249
|
-
*
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
292
|
+
* The state flags of an element: what the markup declared (`disabled`, `active`, `selected`),
|
|
293
|
+
* `pressed` and `hover` from the pointer, `focus` from the keyboard, and `covered` from the root
|
|
294
|
+
* it belongs to.
|
|
252
295
|
*
|
|
253
296
|
* @example
|
|
254
297
|
* ```ts
|
|
255
|
-
* const
|
|
256
|
-
*
|
|
298
|
+
* const flags: IsFlags = {
|
|
299
|
+
* pressed: false, hover: true, focus: false, disabled: false, active: true, selected: false,
|
|
300
|
+
* covered: false
|
|
257
301
|
* };
|
|
258
302
|
* ```
|
|
259
303
|
*/
|
|
260
|
-
type
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
304
|
+
type IsFlags = {
|
|
305
|
+
pressed: boolean;
|
|
306
|
+
hover: boolean;
|
|
307
|
+
focus: boolean;
|
|
308
|
+
disabled: boolean;
|
|
309
|
+
active: boolean;
|
|
310
|
+
selected: boolean;
|
|
311
|
+
covered: boolean;
|
|
266
312
|
};
|
|
267
313
|
/**
|
|
268
|
-
*
|
|
314
|
+
* A flat table of design tokens a game reads in its styles.
|
|
269
315
|
*
|
|
270
316
|
* @example
|
|
271
317
|
* ```ts
|
|
272
|
-
* const
|
|
318
|
+
* const tokens: Tokens = { space: { md: 16 }, color: { panel: 0x101018 } };
|
|
273
319
|
* ```
|
|
274
320
|
*/
|
|
275
|
-
type
|
|
321
|
+
type Tokens = {
|
|
322
|
+
readonly space?: Readonly<Record<string, number>>;
|
|
323
|
+
readonly color?: Readonly<Record<string, number>>;
|
|
324
|
+
readonly radius?: Readonly<Record<string, number>>;
|
|
325
|
+
readonly font?: Readonly<Record<string, string>>;
|
|
326
|
+
};
|
|
276
327
|
/**
|
|
277
|
-
*
|
|
328
|
+
* styles module state: the flags of the frame and the viewport they were read from.
|
|
329
|
+
*/
|
|
330
|
+
type StylesState = {
|
|
331
|
+
flags: WhenFlags;
|
|
332
|
+
viewport: ViewportSize | undefined;
|
|
333
|
+
};
|
|
334
|
+
declare namespace types_d_exports$2 {
|
|
335
|
+
export { Argument, CompiledMessage, CompiledMessages, Config$2 as Config, DurationStyle, ElementNode, Events, I18nApi, I18nCtx, I18nKit, IntlKit, KeysWithoutParameters, Message, Part, RegisteredModule, ResolvedModule, State$2 as State, StringTable, StringsLoader, TypedTr };
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* One interface element handed to a message as a parameter. Structural on purpose: it mirrors
|
|
339
|
+
* the `DescriptionNode` of `world/projection`, which sits beside `i18n`, so neither plugin
|
|
340
|
+
* imports the other (seam resolution 1).
|
|
278
341
|
*
|
|
279
342
|
* @example
|
|
280
343
|
* ```ts
|
|
281
|
-
* const
|
|
282
|
-
* const listener: TapListener = entity => tapped.push(entity);
|
|
344
|
+
* const icon: ElementNode = { type: "icon", props: { name: "hud.coin" }, children: [] };
|
|
283
345
|
* ```
|
|
284
346
|
*/
|
|
285
|
-
type
|
|
347
|
+
type ElementNode = {
|
|
348
|
+
type: string;
|
|
349
|
+
key?: string;
|
|
350
|
+
props: object;
|
|
351
|
+
children: readonly unknown[];
|
|
352
|
+
};
|
|
286
353
|
/**
|
|
287
|
-
* What
|
|
288
|
-
*
|
|
354
|
+
* What a message parameter may be: a word, a number, a list of words, or an element the
|
|
355
|
+
* interface draws in the middle of the sentence.
|
|
289
356
|
*
|
|
290
357
|
* @example
|
|
291
358
|
* ```ts
|
|
292
|
-
* const
|
|
293
|
-
* const
|
|
294
|
-
* if (sample.kind === "up") lifts.push(sample);
|
|
295
|
-
* };
|
|
359
|
+
* const reward: Argument = 25;
|
|
360
|
+
* const players: Argument = ["Ann", "Bo"];
|
|
296
361
|
* ```
|
|
297
362
|
*/
|
|
298
|
-
type
|
|
363
|
+
type Argument = string | number | readonly string[] | ElementNode;
|
|
299
364
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
365
|
+
* What `tr` returns: the key and the parameters, frozen. No locale and no string is read at the
|
|
366
|
+
* call site, so the same value survives a locale change and can be compared and logged.
|
|
302
367
|
*
|
|
303
368
|
* @example
|
|
304
369
|
* ```ts
|
|
305
|
-
* const
|
|
370
|
+
* const orders: Message<"hud.orders", { n: number }> = { key: "hud.orders", params: { n: 3 } };
|
|
306
371
|
* ```
|
|
307
372
|
*/
|
|
308
|
-
type
|
|
309
|
-
key:
|
|
310
|
-
|
|
373
|
+
type Message<Key extends string = string, Values = Record<string, Argument>> = {
|
|
374
|
+
key: Key;
|
|
375
|
+
params?: Values;
|
|
311
376
|
};
|
|
312
377
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
378
|
+
* One piece of a formatted message: a run of text, or an element to draw in place. The consumer
|
|
379
|
+
* never joins the pieces itself, which is what keeps an icon inside a sentence possible.
|
|
315
380
|
*
|
|
316
381
|
* @example
|
|
317
382
|
* ```ts
|
|
318
|
-
* const
|
|
383
|
+
* const parts: Part[] = [
|
|
384
|
+
* { kind: "element", node: { type: "icon", props: { name: "hud.coin" }, children: [] } },
|
|
385
|
+
* { kind: "text", text: " 25" }
|
|
386
|
+
* ];
|
|
319
387
|
* ```
|
|
320
388
|
*/
|
|
321
|
-
type
|
|
389
|
+
type Part = {
|
|
390
|
+
kind: "text";
|
|
391
|
+
text: string;
|
|
392
|
+
} | {
|
|
393
|
+
kind: "element";
|
|
394
|
+
node: ElementNode;
|
|
395
|
+
};
|
|
322
396
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
397
|
+
* The `Intl.*` formatters of one locale, each memoised by the JSON of its options. Compiled
|
|
398
|
+
* messages are handed one and never construct a formatter themselves.
|
|
325
399
|
*
|
|
326
400
|
* @example
|
|
327
401
|
* ```ts
|
|
328
|
-
*
|
|
329
|
-
* //
|
|
330
|
-
* // screen is drawn at 0.54 from the grab to the drop, even after a press squashed it, then
|
|
331
|
-
* // flies home at 0.5.
|
|
332
|
-
* createApp({ plugins: [...screen], pluginConfigs: { input: { heldScale: 1.08 } } });
|
|
333
|
-
* // A game with its own cursors: the config merges shallowly, so both values are given.
|
|
334
|
-
* createApp({ plugins: [...screen], pluginConfigs: { input: { cursor: { control: "grab", idle: "default" } } } });
|
|
335
|
-
* // A word grid with 40 px cells: the finger's path is hit-tested every 16 px, below the
|
|
336
|
-
* // shortest cell span, so a fast finger skips no letter.
|
|
337
|
-
* createApp({ plugins: [...screen], pluginConfigs: { input: { traceStepPx: 16 } } });
|
|
402
|
+
* const intl = createIntlKit("ru");
|
|
403
|
+
* intl.plural().select(3); // "few"
|
|
338
404
|
* ```
|
|
339
405
|
*/
|
|
340
|
-
type
|
|
341
|
-
/**
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
406
|
+
type IntlKit = {
|
|
407
|
+
/** The locale every formatter of this kit is built for. */locale: string; /** The plural rules of the locale; `{ type: "ordinal" }` for `selectordinal`. */
|
|
408
|
+
plural(options?: Intl.PluralRulesOptions): Intl.PluralRules; /** The number format of the locale. */
|
|
409
|
+
number(options?: Intl.NumberFormatOptions): Intl.NumberFormat; /** The list format of the locale, which ICU MessageFormat 1 has no syntax for. */
|
|
410
|
+
list(options?: Intl.ListFormatOptions): Intl.ListFormat; /** The date and time format of the locale. */
|
|
411
|
+
date(options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
|
|
346
412
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
413
|
+
* The duration format of the locale. A compiled `{left, duration}` reads it with
|
|
414
|
+
* `secondsDisplay: "always"`, so a countdown at zero still says "0 sec".
|
|
415
|
+
*
|
|
416
|
+
* @param options - The `Intl.DurationFormat` options.
|
|
417
|
+
* @returns The memoised formatter for these options.
|
|
418
|
+
* @throws {Error} When the runtime has no `Intl.DurationFormat`.
|
|
419
|
+
* @example
|
|
420
|
+
* ```ts
|
|
421
|
+
* const intl = createIntlKit("en");
|
|
422
|
+
* intl.duration({ style: "short" }).format({ minutes: 1, seconds: 35 }); // "1 min, 35 sec"
|
|
423
|
+
* ```
|
|
351
424
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
425
|
+
duration(options?: Intl.DurationFormatOptions): Intl.DurationFormat;
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* How a duration reads: `"long"` (1 minute, 35 seconds), `"short"` (1 min, 35 sec), `"narrow"`
|
|
429
|
+
* (1m 35s) or `"digital"` (0:01:35). A string file names one in `{left, duration, short}`.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* const style: DurationStyle = "digital"; // app.i18n.duration(95_000, style) is "0:01:35"
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
type DurationStyle = "long" | "short" | "narrow" | "digital";
|
|
437
|
+
/**
|
|
438
|
+
* One message as the build wrote it: a plain function of the parameters and the locale's kit.
|
|
439
|
+
* There is no ICU parser at run time.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* const hello: CompiledMessage = p => [{ kind: "text", text: `Hi ${String(p.name)}` }];
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
type CompiledMessage = (params: Record<string, unknown>, intl: IntlKit) => Part[];
|
|
447
|
+
/**
|
|
448
|
+
* One compiled module: every message of one locale by its key.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```ts
|
|
452
|
+
* const en: CompiledMessages = { "home.play": () => [{ kind: "text", text: "Play" }] };
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
type CompiledMessages = Record<string, CompiledMessage>;
|
|
456
|
+
/**
|
|
457
|
+
* A compiled module that is imported on first use. A module namespace is unwrapped to its
|
|
458
|
+
* `default` member (seam resolution 2).
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```ts
|
|
462
|
+
* const loadEnglish: StringsLoader = () => import("./generated/strings.en");
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
type StringsLoader = () => Promise<CompiledMessages | {
|
|
466
|
+
default: CompiledMessages;
|
|
467
|
+
}>;
|
|
468
|
+
/**
|
|
469
|
+
* i18n plugin config.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* ```ts
|
|
473
|
+
* createApp({ pluginConfigs: { i18n: { locale: "ru", fallback: "en", locales: {} } } });
|
|
474
|
+
* ```
|
|
475
|
+
*/
|
|
476
|
+
type Config$2 = {
|
|
477
|
+
/** The locale at start. */locale: string; /** The locale a missing key is read from before it is reported missing. */
|
|
478
|
+
fallback: string;
|
|
368
479
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
480
|
+
* Compiled modules outside features, per locale; a loader is imported on first use and its
|
|
481
|
+
* `default` is unwrapped.
|
|
371
482
|
*/
|
|
372
|
-
|
|
483
|
+
locales: Record<string, CompiledMessages | StringsLoader>;
|
|
373
484
|
};
|
|
374
485
|
/**
|
|
375
|
-
* One
|
|
376
|
-
*
|
|
486
|
+
* One module registered for one locale, with the name it can be blamed under when two of them
|
|
487
|
+
* carry the same key.
|
|
377
488
|
*/
|
|
378
|
-
type
|
|
379
|
-
/** The
|
|
380
|
-
|
|
381
|
-
unmute: () => void;
|
|
489
|
+
type RegisteredModule = {
|
|
490
|
+
/** The feature that brought the module, or `"pluginConfigs.i18n"` for a configured one. */from: string; /** The module itself, or the loader that fetches it on first use. */
|
|
491
|
+
messages: CompiledMessages | StringsLoader;
|
|
382
492
|
};
|
|
383
493
|
/**
|
|
384
|
-
*
|
|
494
|
+
* One registered module whose loader already ran, ready to be merged into a locale.
|
|
495
|
+
*/
|
|
496
|
+
type ResolvedModule = {
|
|
497
|
+
/** The feature that brought the module, or `"pluginConfigs.i18n"` for a configured one. */from: string; /** The module itself. */
|
|
498
|
+
messages: CompiledMessages;
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* i18n plugin state.
|
|
385
502
|
*/
|
|
386
503
|
type State$2 = {
|
|
387
|
-
/**
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
key: string;
|
|
394
|
-
} | undefined; /** Reference px at pointer down. */
|
|
395
|
-
start: Point$1; /** `Transform` minus the pointer at the moment of the grab. */
|
|
396
|
-
grabOffset: Point$1; /** Sum of `time.delta` since pointer down. No device clock is ever read. */
|
|
397
|
-
pressedMs: number; /** The drop target that carries `Hovered` during a drag. */
|
|
398
|
-
hovered: Entity | undefined; /** The view that carries `PointerOver`: a press would take it, and a mouse or a pen is over it. */
|
|
399
|
-
pointerOver: Entity | undefined; /** The `Parent` the held view had at the grab. It is hung back under it on the release. */
|
|
400
|
-
parent: Entity | undefined; /** The rest scale of the held view in root space: the base of `heldScale`, set back on drop. */
|
|
401
|
-
restScale: number | undefined;
|
|
402
|
-
/**
|
|
403
|
-
* The root pose of the held view, written at the grab and at every move. A carried view is
|
|
404
|
-
* placed from it when the held view despawns before the release and takes its `Transform`.
|
|
405
|
-
*/
|
|
406
|
-
heldPose: TransformValue | undefined; /** The remover `world.projection.mute` returned, while a drag runs. */
|
|
407
|
-
unmute: (() => void) | undefined; /** The views a drag carries on top of the held one, in `carry` order; empty outside a drag. */
|
|
408
|
-
carried: Carried[]; /** The cells of the trace in progress, in path order; empty outside a trace. */
|
|
409
|
-
path: Entity[]; /** The intent of the first cell of the trace; a cell of another intent closes the path. */
|
|
410
|
-
traceIntent: string | undefined; /** True once a cell of another intent was crossed: the path neither grows nor pops. */
|
|
411
|
-
traceClosed: boolean; /** Where the finger was at the last frame of the trace: the segment starts there. */
|
|
412
|
-
lastPoint: Point$1 | undefined;
|
|
413
|
-
canvas: HTMLCanvasElement | undefined;
|
|
414
|
-
offFrame: (() => void) | undefined;
|
|
415
|
-
detach: (() => void) | undefined; /** Registered through `onTap`, called in this order on every tap. */
|
|
416
|
-
tapListeners: TapListener[]; /** Registered through `onKey`, called in this order on every key. */
|
|
417
|
-
keyListeners: KeyListener[]; /** Registered through `onPointer`, called in this order inside the DOM listener. */
|
|
418
|
-
pointerListeners: PointerListener[]; /** Takes the one `keydown` listener off `window`; set while a canvas is attached. */
|
|
419
|
-
detachKeys: (() => void) | undefined; /** `time.wake`, bound in `onInit`: every pointer sample leaves the idle frame rate. */
|
|
420
|
-
wake: (() => void) | undefined; /** The cursor last written on the attached canvas; `undefined` while nothing was written. */
|
|
421
|
-
cursor: string | undefined; /** Component types other plugins registered through `controls.add`, once per registration. */
|
|
422
|
-
controls: AnyComponentType[];
|
|
504
|
+
/** The current locale; `config.locale` until `setLocale`. */locale: string; /** Per locale, one entry per feature and one for the config, filled in `onStart`. */
|
|
505
|
+
registered: Map<string, RegisteredModule[]>; /** Merged and resolved modules per locale. */
|
|
506
|
+
loaded: Map<string, CompiledMessages>; /** One kit per locale, built on first use. */
|
|
507
|
+
intl: Map<string, IntlKit>; /** Keys already reported missing, so each warns once. */
|
|
508
|
+
warned: Set<string>; /** The `setLocale` in flight; a later call waits for it. */
|
|
509
|
+
loading: Promise<void> | undefined;
|
|
423
510
|
};
|
|
424
511
|
/**
|
|
425
|
-
*
|
|
426
|
-
|
|
427
|
-
|
|
512
|
+
* i18n plugin events.
|
|
513
|
+
*/
|
|
514
|
+
type Events = {
|
|
515
|
+
/** The current locale changed, or its messages were replaced. */"i18n:locale-changed": {
|
|
516
|
+
locale: string;
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* i18n plugin API, `app.i18n`. Messages are data everywhere else in the game; this is the one
|
|
521
|
+
* place a locale is read and a sentence becomes parts.
|
|
428
522
|
*
|
|
429
523
|
* @example
|
|
430
524
|
* ```ts
|
|
431
|
-
* // A
|
|
432
|
-
* app.
|
|
433
|
-
* app.
|
|
525
|
+
* // A settings screen shows the languages the game was built with and switches to one.
|
|
526
|
+
* app.i18n.locales(); // ["en", "ru"]
|
|
527
|
+
* await app.i18n.setLocale("en");
|
|
528
|
+
* app.i18n.plain(tr("hud.orders", { n: 3 })); // "3 orders"
|
|
434
529
|
* ```
|
|
435
530
|
*/
|
|
436
|
-
type
|
|
531
|
+
type I18nApi = {
|
|
437
532
|
/**
|
|
438
|
-
*
|
|
533
|
+
* The locale every `format` without a second argument reads.
|
|
439
534
|
*
|
|
440
|
-
* @
|
|
441
|
-
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
535
|
+
* @returns The current locale.
|
|
442
536
|
* @example
|
|
443
537
|
* ```ts
|
|
444
|
-
* //
|
|
445
|
-
* app.input.tap({ projection: "scene.objects", key: "lamp" });
|
|
446
|
-
* // true: answers { intent: "found", payload: { id: "lamp" } }
|
|
538
|
+
* app.i18n.locale(); // "ru", the pluginConfigs.i18n.locale the game started with
|
|
447
539
|
* ```
|
|
448
540
|
*/
|
|
449
|
-
|
|
541
|
+
locale(): string;
|
|
450
542
|
/**
|
|
451
|
-
*
|
|
543
|
+
* Switches the current locale. A lazy module is imported once, then `i18n:locale-changed` is
|
|
544
|
+
* emitted; `text` re-resolves every message on it. A call for the locale already current
|
|
545
|
+
* resolves at once and emits nothing.
|
|
452
546
|
*
|
|
453
|
-
* @param
|
|
454
|
-
* @returns
|
|
547
|
+
* @param locale - The locale to switch to.
|
|
548
|
+
* @returns A promise that settles when the messages of that locale are loaded.
|
|
549
|
+
* @throws {Error} When no feature and no config brought a module for the locale.
|
|
455
550
|
* @example
|
|
456
551
|
* ```ts
|
|
457
|
-
* //
|
|
458
|
-
* app.
|
|
459
|
-
* //
|
|
552
|
+
* // features/settings/nodes.ts: the language button of the settings screen.
|
|
553
|
+
* await app.i18n.setLocale("en"); // loads the compiled module, then emits i18n:locale-changed
|
|
554
|
+
* app.i18n.locale(); // "en"
|
|
460
555
|
* ```
|
|
461
556
|
*/
|
|
462
|
-
|
|
557
|
+
setLocale(locale: string): Promise<void>;
|
|
463
558
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
559
|
+
* Formats a message in the current locale. Resolution order is the current locale, then
|
|
560
|
+
* `config.fallback`, then missing: a missing key gives one text part `⟦key⟧` and one warning.
|
|
466
561
|
*
|
|
467
|
-
* @param
|
|
468
|
-
* @
|
|
469
|
-
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
562
|
+
* @param message - What `tr` returned.
|
|
563
|
+
* @returns The parts of the sentence, adjacent text merged.
|
|
470
564
|
* @example
|
|
471
565
|
* ```ts
|
|
472
|
-
* //
|
|
473
|
-
* app.
|
|
474
|
-
* // true: answers { intent: "merge", payload: { from: "c2", to: "c3" } }
|
|
566
|
+
* // text resolves the `content` of a Text component every time the locale changes.
|
|
567
|
+
* app.i18n.format(tr("hud.orders", { n: 3 })); // [{ kind: "text", text: "3 заказа" }]
|
|
475
568
|
* ```
|
|
476
569
|
*/
|
|
477
|
-
|
|
570
|
+
format(message: Message): readonly Part[];
|
|
478
571
|
/**
|
|
479
|
-
*
|
|
572
|
+
* Formats a message in another registered locale, without switching. `ui.lint` measures a
|
|
573
|
+
* label in every locale this way.
|
|
480
574
|
*
|
|
481
|
-
* @param
|
|
482
|
-
* @param
|
|
483
|
-
* @returns
|
|
575
|
+
* @param message - What `tr` returned.
|
|
576
|
+
* @param locale - The registered locale to read.
|
|
577
|
+
* @returns The parts of the sentence in that locale.
|
|
578
|
+
* @throws {Error} When the locale is not registered, or is registered but not loaded yet.
|
|
484
579
|
* @example
|
|
485
580
|
* ```ts
|
|
486
|
-
* //
|
|
487
|
-
* app.
|
|
488
|
-
* //
|
|
581
|
+
* // ui.lint does this for every entry of app.i18n.locales() and keeps the widest result.
|
|
582
|
+
* app.i18n.format(tr("hud.orders", { n: 3 }), "en"); // [{ kind: "text", text: "3 orders" }]
|
|
583
|
+
* app.i18n.locale(); // "ru": reading another locale never switches
|
|
489
584
|
* ```
|
|
490
585
|
*/
|
|
491
|
-
|
|
586
|
+
format(message: Message, locale: string): readonly Part[];
|
|
492
587
|
/**
|
|
493
|
-
*
|
|
494
|
-
* cells would: the intent of the cells and `{ path }`, the cell payloads in order. Every cell
|
|
495
|
-
* must name the same intent. An empty list, a cell with no `Traceable` and two intents warn and
|
|
496
|
-
* answer `false` without calling the gate. Nothing is tagged `Traced`.
|
|
588
|
+
* The parts joined into one string, elements dropped. For logs, tests and `flow.describe()`.
|
|
497
589
|
*
|
|
498
|
-
* @param
|
|
499
|
-
* @returns
|
|
590
|
+
* @param message - What `tr` returned.
|
|
591
|
+
* @returns The sentence as text.
|
|
500
592
|
* @example
|
|
501
593
|
* ```ts
|
|
502
|
-
* // A
|
|
503
|
-
* app.
|
|
504
|
-
* { projection: "board.cells", key: "b3" },
|
|
505
|
-
* { projection: "board.cells", key: "c3" },
|
|
506
|
-
* { projection: "board.cells", key: "d3" }
|
|
507
|
-
* ]);
|
|
508
|
-
* // true: answers { intent: "word", payload: { path: [{ cell: "b3" }, { cell: "c3" }, { cell: "d3" }] } }
|
|
594
|
+
* // A test asserts what the HUD says without touching the renderer.
|
|
595
|
+
* app.i18n.plain(tr("hud.coins", { n: 25, icon: coinIcon })); // "25": the icon is dropped
|
|
509
596
|
* ```
|
|
510
597
|
*/
|
|
511
|
-
|
|
598
|
+
plain(message: Message): string;
|
|
512
599
|
/**
|
|
513
|
-
*
|
|
514
|
-
* topmost view the hit test accepted, before the `Tappable` answer. A view that carries only
|
|
515
|
-
* `Touchable` reaches the listeners and answers nothing. A listener that throws is logged with
|
|
516
|
-
* its entity, and the listeners after it still run.
|
|
600
|
+
* Tells whether a key exists in the current locale or in the fallback.
|
|
517
601
|
*
|
|
518
|
-
* @param
|
|
519
|
-
* @returns
|
|
602
|
+
* @param key - The message key.
|
|
603
|
+
* @returns True when `format` would find a message for it.
|
|
520
604
|
* @example
|
|
521
605
|
* ```ts
|
|
522
|
-
* //
|
|
523
|
-
*
|
|
524
|
-
* const write = ctx.require(worldPlugin).ecs.get(entity, LocalWrite);
|
|
525
|
-
*
|
|
526
|
-
* if (write !== undefined) applyLocal(entity, write.patch);
|
|
527
|
-
* });
|
|
528
|
-
* off(); // in onStop
|
|
606
|
+
* // A projection hides the label of an order that has no description yet.
|
|
607
|
+
* app.i18n.has("orders.hint"); // false: no locale brought that key
|
|
529
608
|
* ```
|
|
530
609
|
*/
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Registers a listener called synchronously inside the DOM pointer listener, right after the
|
|
534
|
-
* sample is queued, for `pointerdown`, `pointerup` and `pointercancel`. It is the one place a
|
|
535
|
-
* browser call that needs the user gesture can run, such as `focus()` on a text field: in the
|
|
536
|
-
* next frame iOS shows no keyboard. Never called for a move, a leave or a lost capture, and never
|
|
537
|
-
* by `tap`, `press`, `drag` or `swipe`, which have no DOM moment. The sample is raw: client px,
|
|
538
|
-
* no hit test. Listeners run in registration order; a listener that throws is logged with the
|
|
539
|
-
* sample kind, and the listeners after it still run.
|
|
540
|
-
*
|
|
541
|
-
* @param fn - What to run with the raw sample.
|
|
542
|
-
* @returns The remover; call it to stop listening.
|
|
543
|
-
* @example
|
|
544
|
-
* ```ts
|
|
545
|
-
* // ui opens the keyboard the moment the finger lifts on a text field, inside the DOM listener.
|
|
546
|
-
* const off = ctx.require(inputPlugin).onPointer(sample => {
|
|
547
|
-
* if (sample.kind === "up" && fieldAt(sample.clientX, sample.clientY) !== undefined) element.focus({ preventScroll: true });
|
|
548
|
-
* if (sample.kind === "down" && fieldAt(sample.clientX, sample.clientY) === undefined) element.blur();
|
|
549
|
-
* });
|
|
550
|
-
* // a tap on the Rename field on an iPhone: the keyboard shows; a tap on the parchment next to it: the keyboard hides
|
|
551
|
-
* off(); // in onStop
|
|
552
|
-
* ```
|
|
553
|
-
*/
|
|
554
|
-
onPointer(fn: PointerListener): () => void;
|
|
610
|
+
has(key: string): boolean;
|
|
555
611
|
/**
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
* returns `true` marks the key handled, and input calls `preventDefault()` on the DOM event. A
|
|
561
|
-
* listener that throws is logged with its key, and the listeners after it still run.
|
|
612
|
+
* Formats milliseconds in the current locale through `Intl.DurationFormat`. The value is rounded
|
|
613
|
+
* up to whole seconds, so a countdown never reads zero while time is left; a negative or
|
|
614
|
+
* non-finite value reads as zero; the seconds are always shown. `text` formats a field bound
|
|
615
|
+
* with `format: "duration"` through this call.
|
|
562
616
|
*
|
|
563
|
-
* @param
|
|
564
|
-
* @
|
|
617
|
+
* @param ms - The duration in milliseconds.
|
|
618
|
+
* @param style - How the duration reads. Default `"short"`.
|
|
619
|
+
* @returns The duration as text.
|
|
620
|
+
* @throws {Error} When the runtime has no `Intl.DurationFormat`.
|
|
565
621
|
* @example
|
|
566
622
|
* ```ts
|
|
567
|
-
* //
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
* return true;
|
|
573
|
-
* });
|
|
574
|
-
* off(); // in onStop
|
|
623
|
+
* // The out-of-energy popup shows when the next refill comes, locale "en".
|
|
624
|
+
* app.i18n.duration(95_000); // "1 min, 35 sec"
|
|
625
|
+
* app.i18n.duration(95_000, "digital"); // "0:01:35"
|
|
626
|
+
* app.i18n.duration(500); // "1 sec": rounded up
|
|
575
627
|
* ```
|
|
576
628
|
*/
|
|
577
|
-
|
|
629
|
+
duration(ms: number, style?: DurationStyle): string;
|
|
578
630
|
/**
|
|
579
|
-
*
|
|
631
|
+
* Every locale at least one feature or the config brought a module for, sorted.
|
|
580
632
|
*
|
|
581
|
-
* @
|
|
582
|
-
* @param options - How the key is pressed.
|
|
583
|
-
* @param options.shift - True presses it with Shift held; the default is `false`.
|
|
584
|
-
* @returns True when a listener handled the key.
|
|
633
|
+
* @returns The registered locales.
|
|
585
634
|
* @example
|
|
586
635
|
* ```ts
|
|
587
|
-
* //
|
|
588
|
-
* app.
|
|
589
|
-
* app.input.pressKey("Escape"); // true: ui tapped the popup's close button
|
|
590
|
-
* app.input.pressKey("q"); // false: no listener handles it
|
|
636
|
+
* // The language menu is built from this list, and ui.lint measures the widest locale.
|
|
637
|
+
* app.i18n.locales(); // ["en", "ru"]
|
|
591
638
|
* ```
|
|
592
639
|
*/
|
|
593
|
-
|
|
594
|
-
shift?: boolean;
|
|
595
|
-
}): boolean;
|
|
640
|
+
locales(): readonly string[];
|
|
596
641
|
/**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
642
|
+
* Swaps the compiled module of one feature in a loaded locale, for the dev hot swap. The one
|
|
643
|
+
* registered module whose keys overlap `messages` takes them, the locale is merged again and
|
|
644
|
+
* every key may warn once more. When the locale is the current one or the fallback,
|
|
645
|
+
* `i18n:locale-changed` is emitted with the current locale, so `text` re-resolves every label.
|
|
646
|
+
* A locale not loaded yet is left alone: its loader imports the new module later.
|
|
599
647
|
*
|
|
600
|
-
* @
|
|
648
|
+
* @param locale - The locale of the saved `generated/strings.<locale>.ts`.
|
|
649
|
+
* @param messages - The `default` export of the new module.
|
|
650
|
+
* @throws {Error} When no registered module, or more than one, shares a key with `messages`.
|
|
601
651
|
* @example
|
|
602
652
|
* ```ts
|
|
603
|
-
* //
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* app.input.cursor(); // ""
|
|
653
|
+
* // ui's hot swap: generated/strings.ru.ts was written again with "hud.orders" as "3 заказа!".
|
|
654
|
+
* ctx.require(i18nPlugin).replace("ru", next.default); // emits i18n:locale-changed
|
|
655
|
+
* app.i18n.plain(tr("hud.orders", { n: 3 })); // "3 заказа!": text redraws the label
|
|
607
656
|
* ```
|
|
608
657
|
*/
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* The components that make a view a control besides input's own: the cursor shows
|
|
612
|
-
* `config.cursor.control` over a view that carries one of them.
|
|
613
|
-
*/
|
|
614
|
-
controls: {
|
|
615
|
-
/**
|
|
616
|
-
* Registers a component type of another plugin as a control. A type registered twice stays
|
|
617
|
-
* a control until both removers ran.
|
|
618
|
-
*
|
|
619
|
-
* @param component - The component or tag type that makes a view answer a press.
|
|
620
|
-
* @returns The remover; call it to take the registration back.
|
|
621
|
-
* @example
|
|
622
|
-
* ```ts
|
|
623
|
-
* // ui counts its local-state buttons as controls, so the mouse over a tab shows a hand.
|
|
624
|
-
* const off = ctx.require(inputPlugin).controls.add(LocalWrite);
|
|
625
|
-
* app.input.cursor(); // "pointer" while the mouse rests on the Audio tab
|
|
626
|
-
* off(); // in onStop
|
|
627
|
-
* ```
|
|
628
|
-
*/
|
|
629
|
-
add(component: AnyComponentType): () => void;
|
|
630
|
-
};
|
|
631
|
-
};
|
|
632
|
-
/**
|
|
633
|
-
* Resolved dependency APIs.
|
|
634
|
-
*/
|
|
635
|
-
type Deps$1 = {
|
|
636
|
-
time: Api;
|
|
637
|
-
flow: Api$1;
|
|
638
|
-
world: Api$3;
|
|
639
|
-
renderer: Api$4;
|
|
658
|
+
replace(locale: string, messages: CompiledMessages): void;
|
|
640
659
|
};
|
|
641
660
|
/**
|
|
642
|
-
* What the kernel context offers
|
|
661
|
+
* What the kernel context offers this plugin.
|
|
643
662
|
*
|
|
644
|
-
* `
|
|
663
|
+
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
664
|
+
* row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
|
|
665
|
+
* kernel's own, i18n-typed one. No member of the context is cast.
|
|
645
666
|
*/
|
|
646
|
-
type
|
|
667
|
+
type I18nCtx = PluginCtx<Config$2, State$2, Events> & {
|
|
647
668
|
readonly global: object;
|
|
648
669
|
readonly log: Log.LogApi;
|
|
649
670
|
readonly require: Require;
|
|
650
671
|
};
|
|
651
672
|
/**
|
|
652
|
-
*
|
|
653
|
-
|
|
654
|
-
type InputCtx = KernelSlice$1 & {
|
|
655
|
-
readonly deps: Deps$1;
|
|
656
|
-
};
|
|
657
|
-
declare namespace types_d_exports$1 {
|
|
658
|
-
export { Argument, CompiledMessage, CompiledMessages, Config$1 as Config, DurationStyle, ElementNode, Events, I18nApi, I18nCtx, I18nKit, IntlKit, KeysWithoutParameters, Message, Part, RegisteredModule, ResolvedModule, State$1 as State, StringTable, StringsLoader, TypedTr };
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* One interface element handed to a message as a parameter. Structural on purpose: it mirrors
|
|
662
|
-
* the `DescriptionNode` of `world/projection`, which sits beside `i18n`, so neither plugin
|
|
663
|
-
* imports the other (seam resolution 1).
|
|
673
|
+
* The key-to-parameters table a game generates: `generated/strings.ts` exports it as `Strings`.
|
|
674
|
+
* A key with no parameters carries `Record<never, never>`.
|
|
664
675
|
*
|
|
665
676
|
* @example
|
|
666
677
|
* ```ts
|
|
667
|
-
* const
|
|
678
|
+
* const table: StringTable = { "hud.orders": { n: 3 }, "orders.complete": {} };
|
|
668
679
|
* ```
|
|
669
680
|
*/
|
|
670
|
-
type
|
|
671
|
-
type: string;
|
|
672
|
-
key?: string;
|
|
673
|
-
props: object;
|
|
674
|
-
children: readonly unknown[];
|
|
675
|
-
};
|
|
681
|
+
type StringTable = Record<string, unknown>;
|
|
676
682
|
/**
|
|
677
|
-
*
|
|
678
|
-
*
|
|
683
|
+
* The keys of a table whose message takes no parameters, so `tr` may be called with the key
|
|
684
|
+
* alone.
|
|
679
685
|
*
|
|
680
686
|
* @example
|
|
681
687
|
* ```ts
|
|
682
|
-
*
|
|
683
|
-
*
|
|
688
|
+
* type Keys = KeysWithoutParameters<{ "home.play": Record<never, never>; "hud.orders": { n: number } }>;
|
|
689
|
+
* // "home.play"
|
|
684
690
|
* ```
|
|
685
691
|
*/
|
|
686
|
-
type
|
|
692
|
+
type KeysWithoutParameters<Table extends StringTable> = { [Key in keyof Table]: Record<never, never> extends Table[Key] ? Key : never }[keyof Table] & string;
|
|
687
693
|
/**
|
|
688
|
-
*
|
|
689
|
-
*
|
|
694
|
+
* `tr` as one game reads it: the key is checked against the generated table, and the parameters
|
|
695
|
+
* are the ones that key declares. A key with no parameters takes no second argument.
|
|
690
696
|
*
|
|
691
697
|
* @example
|
|
692
698
|
* ```ts
|
|
693
|
-
* const
|
|
699
|
+
* const { tr } = i18nFor<{ "hud.orders": { n: number } }>();
|
|
700
|
+
* tr("hud.orders", { n: 3 }); // { key: "hud.orders", params: { n: 3 } }
|
|
694
701
|
* ```
|
|
695
702
|
*/
|
|
696
|
-
type
|
|
697
|
-
key: Key
|
|
698
|
-
params
|
|
703
|
+
type TypedTr<Table extends StringTable> = {
|
|
704
|
+
<Key extends KeysWithoutParameters<Table>>(key: Key): Message<Key, Table[Key]>;
|
|
705
|
+
<Key extends keyof Table & string>(key: Key, params: Table[Key]): Message<Key, Table[Key]>;
|
|
699
706
|
};
|
|
700
707
|
/**
|
|
701
|
-
*
|
|
702
|
-
* never joins the pieces itself, which is what keeps an icon inside a sentence possible.
|
|
708
|
+
* What `defineGame` spreads from this plugin.
|
|
703
709
|
*
|
|
704
710
|
* @example
|
|
705
711
|
* ```ts
|
|
706
|
-
* const
|
|
707
|
-
*
|
|
708
|
-
* { kind: "text", text: " 25" }
|
|
709
|
-
* ];
|
|
712
|
+
* const kit: I18nKit<{ "home.play": Record<never, never> }> = i18nFor();
|
|
713
|
+
* kit.tr("home.play"); // { key: "home.play" }
|
|
710
714
|
* ```
|
|
711
715
|
*/
|
|
712
|
-
type
|
|
713
|
-
|
|
714
|
-
text: string;
|
|
715
|
-
} | {
|
|
716
|
-
kind: "element";
|
|
717
|
-
node: ElementNode;
|
|
716
|
+
type I18nKit<Table extends StringTable> = {
|
|
717
|
+
tr: TypedTr<Table>;
|
|
718
718
|
};
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/plugins/renderer/components.d.ts
|
|
719
721
|
/**
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
+
* Where a view sits, in reference units and radians. Relative to the `Parent` when there is one.
|
|
723
|
+
* `pivot` is the local point the view turns and scales around, and `x`, `y` is where that point
|
|
724
|
+
* lands; the default pivot `{ x: 0, y: 0 }` is the view's own origin.
|
|
722
725
|
*
|
|
723
726
|
* @example
|
|
724
727
|
* ```ts
|
|
725
|
-
*
|
|
726
|
-
*
|
|
728
|
+
* // A 200 x 80 button that grows around its centre.
|
|
729
|
+
* const value: TransformValue = {
|
|
730
|
+
* x: 640, y: 340, rotation: 0, scale: 1.1, pivot: { x: 100, y: 40 }
|
|
731
|
+
* };
|
|
727
732
|
* ```
|
|
728
733
|
*/
|
|
729
|
-
type
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* The duration format of the locale. A compiled `{left, duration}` reads it with
|
|
737
|
-
* `secondsDisplay: "always"`, so a countdown at zero still says "0 sec".
|
|
738
|
-
*
|
|
739
|
-
* @param options - The `Intl.DurationFormat` options.
|
|
740
|
-
* @returns The memoised formatter for these options.
|
|
741
|
-
* @throws {Error} When the runtime has no `Intl.DurationFormat`.
|
|
742
|
-
* @example
|
|
743
|
-
* ```ts
|
|
744
|
-
* const intl = createIntlKit("en");
|
|
745
|
-
* intl.duration({ style: "short" }).format({ minutes: 1, seconds: 35 }); // "1 min, 35 sec"
|
|
746
|
-
* ```
|
|
747
|
-
*/
|
|
748
|
-
duration(options?: Intl.DurationFormatOptions): Intl.DurationFormat;
|
|
734
|
+
type TransformValue = {
|
|
735
|
+
x: number;
|
|
736
|
+
y: number;
|
|
737
|
+
rotation: number;
|
|
738
|
+
scale: number;
|
|
739
|
+
pivot: Point$2;
|
|
749
740
|
};
|
|
750
741
|
/**
|
|
751
|
-
* How a
|
|
752
|
-
*
|
|
742
|
+
* How a sized sprite fills its box: `"fill"` stretches, `"contain"` scales uniformly inside the
|
|
743
|
+
* box and centres, `"cover"` scales uniformly over the box and crops the overflow.
|
|
753
744
|
*
|
|
754
745
|
* @example
|
|
755
746
|
* ```ts
|
|
756
|
-
* const
|
|
747
|
+
* const fit: SpriteFit = "cover";
|
|
757
748
|
* ```
|
|
758
749
|
*/
|
|
759
|
-
type
|
|
750
|
+
type SpriteFit = "fill" | "contain" | "cover";
|
|
760
751
|
/**
|
|
761
|
-
* One
|
|
762
|
-
*
|
|
752
|
+
* One textured quad. `texture` is an asset key; `defineGame` narrows it to the game's keys.
|
|
753
|
+
* `width` and `height` are the box in reference units; 0 on an axis keeps the texture's own size
|
|
754
|
+
* there. `anchor` is the point of the box that sits on the `Transform`. `alpha` fades the quad
|
|
755
|
+
* and the entity's children.
|
|
763
756
|
*
|
|
764
757
|
* @example
|
|
765
758
|
* ```ts
|
|
766
|
-
* const
|
|
759
|
+
* const value: SpriteValue = {
|
|
760
|
+
* texture: "board.bg-forest-meadow", tint: 0xffffff, alpha: 1, anchor: { x: 0, y: 0 },
|
|
761
|
+
* width: 1080, height: 1920, fit: "cover"
|
|
762
|
+
* };
|
|
767
763
|
* ```
|
|
768
764
|
*/
|
|
769
|
-
type
|
|
765
|
+
type SpriteValue = {
|
|
766
|
+
texture: string;
|
|
767
|
+
tint: number;
|
|
768
|
+
alpha: number;
|
|
769
|
+
anchor: Point$2;
|
|
770
|
+
width: number;
|
|
771
|
+
height: number;
|
|
772
|
+
fit: SpriteFit;
|
|
773
|
+
};
|
|
770
774
|
/**
|
|
771
|
-
*
|
|
775
|
+
* A stretchable panel. The slice borders come with the texture, not with the component. `debug`
|
|
776
|
+
* strokes the bounds and the four cut lines over the panel: cyan, red when the corners overlap or
|
|
777
|
+
* the texture is missing. `clip` masks the children of the entity to the `width × height` box,
|
|
778
|
+
* the same rule as `Shape.clip`: the mask is a filled rectangle and is never drawn, and the panel
|
|
779
|
+
* itself is never masked. `alpha` fades the panel and the entity's children.
|
|
772
780
|
*
|
|
773
781
|
* @example
|
|
774
782
|
* ```ts
|
|
775
|
-
* const
|
|
783
|
+
* const value: NineSliceValue = {
|
|
784
|
+
* texture: "ui.panel", width: 600, height: 320, alpha: 1, tint: 0xffffff, debug: false,
|
|
785
|
+
* clip: false
|
|
786
|
+
* };
|
|
787
|
+
* // The board tray, outlined while its insets are checked.
|
|
788
|
+
* const tray: NineSliceValue = {
|
|
789
|
+
* ...value, texture: "board.board-tray", width: 1000, height: 1040, debug: true
|
|
790
|
+
* };
|
|
791
|
+
* // A text field: a long value never draws outside the frame.
|
|
792
|
+
* const field: NineSliceValue = {
|
|
793
|
+
* ...value, texture: "ui.field", width: 480, height: 96, clip: true
|
|
794
|
+
* };
|
|
776
795
|
* ```
|
|
777
796
|
*/
|
|
778
|
-
type
|
|
797
|
+
type NineSliceValue = {
|
|
798
|
+
texture: string;
|
|
799
|
+
width: number;
|
|
800
|
+
height: number;
|
|
801
|
+
alpha: number;
|
|
802
|
+
tint: number;
|
|
803
|
+
debug: boolean;
|
|
804
|
+
clip: boolean;
|
|
805
|
+
};
|
|
779
806
|
/**
|
|
780
|
-
*
|
|
781
|
-
* `default` member (seam resolution 2).
|
|
807
|
+
* The escape hatch: the game owns a Pixi object and `sync` only attaches and detaches it.
|
|
782
808
|
*
|
|
783
809
|
* @example
|
|
784
810
|
* ```ts
|
|
785
|
-
* const
|
|
811
|
+
* const value: DisplayValue = { object: spineAnimation };
|
|
786
812
|
* ```
|
|
787
813
|
*/
|
|
788
|
-
type
|
|
789
|
-
|
|
790
|
-
}
|
|
814
|
+
type DisplayValue = {
|
|
815
|
+
object: unknown;
|
|
816
|
+
};
|
|
791
817
|
/**
|
|
792
|
-
*
|
|
818
|
+
* A filled rounded rectangle or a triangle, anchored at the top left of the `Transform`. `kind`
|
|
819
|
+
* picks the outline: `"rect"` (the default) or `"triangle"`, which fills its `w × h` box pointing
|
|
820
|
+
* right (rotate the element for another direction) and ignores `radius`. `fillAlpha` is the alpha
|
|
821
|
+
* of the fill alone: 0 draws only the stroke, a ring. `alpha` fades the whole shape and the
|
|
822
|
+
* entity's children: a disabled button at 0.6 draws its icon at 0.6 too. `dash` is the
|
|
823
|
+
* dash length of the stroke in reference units, with gaps of half a dash; 0 strokes a solid line.
|
|
824
|
+
* `clip` masks the children of the entity to the shape, always solid, which is how `ui` draws a
|
|
825
|
+
* scroll and an overflow. The shape itself is never masked, so its stroke is drawn whole.
|
|
793
826
|
*
|
|
794
827
|
* @example
|
|
795
828
|
* ```ts
|
|
796
|
-
*
|
|
829
|
+
* const value: ShapeValue = {
|
|
830
|
+
* kind: "rect", w: 320, h: 96, fill: 0x101018, fillAlpha: 1, alpha: 1, radius: 16,
|
|
831
|
+
* stroke: 0x000000, strokeWidth: 0, dash: 0, clip: false
|
|
832
|
+
* };
|
|
833
|
+
* // The honey ring on a selected cell: a stroke and no fill.
|
|
834
|
+
* const ring: ShapeValue = {
|
|
835
|
+
* kind: "rect", w: 140, h: 140, fill: 0xffffff, fillAlpha: 0, alpha: 1, radius: 24,
|
|
836
|
+
* stroke: 0xffc233, strokeWidth: 6, dash: 0, clip: false
|
|
837
|
+
* };
|
|
838
|
+
* // The play glyph of a watch button, and a dashed focus ring: 10 u dashes, 5 u gaps.
|
|
839
|
+
* const play: ShapeValue = { ...ring, kind: "triangle", w: 36, h: 40, fillAlpha: 1, strokeWidth: 0 };
|
|
840
|
+
* const focus: ShapeValue = { ...ring, w: 200, h: 80, stroke: 0x3a2212, strokeWidth: 4, dash: 10 };
|
|
797
841
|
* ```
|
|
798
842
|
*/
|
|
799
|
-
type
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
843
|
+
type ShapeValue = {
|
|
844
|
+
kind: "rect" | "triangle";
|
|
845
|
+
w: number;
|
|
846
|
+
h: number;
|
|
847
|
+
fill: number;
|
|
848
|
+
fillAlpha: number;
|
|
849
|
+
alpha: number;
|
|
850
|
+
radius: number;
|
|
851
|
+
stroke: number;
|
|
852
|
+
strokeWidth: number;
|
|
853
|
+
dash: number;
|
|
854
|
+
clip: boolean;
|
|
807
855
|
};
|
|
808
856
|
/**
|
|
809
|
-
*
|
|
810
|
-
*
|
|
857
|
+
* What `sprite()` takes: the two things every visual needs, and the four that have defaults.
|
|
858
|
+
*
|
|
859
|
+
* @example
|
|
860
|
+
* ```ts
|
|
861
|
+
* const options: SpriteOptions = { texture: "board.cell", at: { x: 540, y: 300 } };
|
|
862
|
+
* ```
|
|
811
863
|
*/
|
|
812
|
-
type
|
|
813
|
-
|
|
814
|
-
|
|
864
|
+
type SpriteOptions = {
|
|
865
|
+
texture: string;
|
|
866
|
+
at: Point$2;
|
|
867
|
+
tint?: number;
|
|
868
|
+
alpha?: number;
|
|
869
|
+
anchor?: Point$2;
|
|
870
|
+
scale?: number;
|
|
815
871
|
};
|
|
816
872
|
/**
|
|
817
|
-
*
|
|
873
|
+
* Where a view sits: reference units, radians, uniform scale, and the local point it turns
|
|
874
|
+
* around.
|
|
818
875
|
*/
|
|
819
|
-
|
|
820
|
-
/** The feature that brought the module, or `"pluginConfigs.i18n"` for a configured one. */from: string; /** The module itself. */
|
|
821
|
-
messages: CompiledMessages;
|
|
822
|
-
};
|
|
876
|
+
declare const Transform: ComponentType<TransformValue>;
|
|
823
877
|
/**
|
|
824
|
-
*
|
|
878
|
+
* One textured quad. `texture` is an asset key, resolved through the texture providers; a size
|
|
879
|
+
* and a `fit` draw it into a box.
|
|
825
880
|
*/
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
881
|
+
declare const Sprite: ComponentType<SpriteValue>;
|
|
882
|
+
/**
|
|
883
|
+
* A stretchable panel, sized in reference units, with its own alpha and tint. `debug: true` draws
|
|
884
|
+
* the slice outline over it. `clip: true` masks the children of the entity to the panel box.
|
|
885
|
+
*/
|
|
886
|
+
declare const NineSlice: ComponentType<{
|
|
887
|
+
texture: string;
|
|
888
|
+
width: number;
|
|
889
|
+
height: number;
|
|
890
|
+
alpha: number;
|
|
891
|
+
tint: number;
|
|
892
|
+
debug: boolean;
|
|
893
|
+
clip: boolean;
|
|
894
|
+
}>;
|
|
895
|
+
/**
|
|
896
|
+
* The entity this view moves with. `0` means no parent.
|
|
897
|
+
*/
|
|
898
|
+
declare const Parent: ComponentType<{
|
|
899
|
+
entity: number;
|
|
900
|
+
}>;
|
|
901
|
+
/**
|
|
902
|
+
* A display object the game owns. Never pooled, never destroyed by `sync`.
|
|
903
|
+
*/
|
|
904
|
+
declare const Display: ComponentType<DisplayValue>;
|
|
905
|
+
/**
|
|
906
|
+
* A filled rounded rectangle or a right-pointing triangle, drawn with Pixi `Graphics` and redrawn
|
|
907
|
+
* only when a field changed. `fillAlpha: 0` draws only the stroke, `dash` above 0 dashes it.
|
|
908
|
+
* `clip: true` masks the children of the entity to the shape.
|
|
909
|
+
*/
|
|
910
|
+
declare const Shape: ComponentType<ShapeValue>;
|
|
911
|
+
/**
|
|
912
|
+
* Bundles the two components every visual needs, so a projection `view` reads as one line.
|
|
913
|
+
*
|
|
914
|
+
* @param options - Texture key, position, and the four values that have defaults.
|
|
915
|
+
* @returns The `Sprite` and the `Transform` value, in that order.
|
|
916
|
+
* @example
|
|
917
|
+
* ```ts
|
|
918
|
+
* sprite({ texture: "board.cell", at: { x: 540, y: 300 } });
|
|
919
|
+
* // [Sprite({ texture: "board.cell", tint: 0xffffff, alpha: 1, anchor: { x: 0.5, y: 0.5 },
|
|
920
|
+
* // width: 0, height: 0, fit: "fill" }),
|
|
921
|
+
* // Transform({ x: 540, y: 300, rotation: 0, scale: 1, pivot: { x: 0, y: 0 } })]
|
|
922
|
+
* ```
|
|
923
|
+
*/
|
|
924
|
+
declare function sprite(options: SpriteOptions): [ComponentValue<SpriteValue>, ComponentValue<TransformValue>];
|
|
925
|
+
declare namespace types_d_exports$1 {
|
|
926
|
+
export { Carried, Config$1 as Config, Deps$1 as Deps, Direction, GesturePhase, InputApi, InputCtx, KernelSlice$1 as KernelSlice, KeyInput, KeyListener, Point$1 as Point, PointerListener, RawSample, State$1 as State, TapListener, Target };
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* A point in reference units, as `renderer.viewport.toReference` answers it.
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* ```ts
|
|
933
|
+
* const point: Point = { x: 540, y: 960 };
|
|
934
|
+
* ```
|
|
935
|
+
*/
|
|
936
|
+
type Point$1 = {
|
|
937
|
+
x: number;
|
|
938
|
+
y: number;
|
|
833
939
|
};
|
|
834
940
|
/**
|
|
835
|
-
*
|
|
941
|
+
* The dominant axis of a swipe.
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* ```ts
|
|
945
|
+
* const direction: Direction = "left";
|
|
946
|
+
* ```
|
|
836
947
|
*/
|
|
837
|
-
type
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
948
|
+
type Direction = "up" | "down" | "left" | "right";
|
|
949
|
+
/**
|
|
950
|
+
* What `app.input.*` is pointed at: the projection key of a live view, or an entity. A key never
|
|
951
|
+
* addresses a view in the despawn queue.
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```ts
|
|
955
|
+
* const target: Target = { projection: "board.items", key: "i5" };
|
|
956
|
+
* ```
|
|
957
|
+
*/
|
|
958
|
+
type Target = {
|
|
959
|
+
projection: string;
|
|
960
|
+
key: string;
|
|
961
|
+
} | Entity;
|
|
962
|
+
/**
|
|
963
|
+
* One pointer event as the DOM handlers queue it: no hit test, no reference units, no decision.
|
|
964
|
+
* `cancel` is a `pointercancel`, `lost` a lost pointer capture, `leave` the pointer leaving the
|
|
965
|
+
* canvas. `pointerType` is the device: only a mouse or a pen hovers.
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```ts
|
|
969
|
+
* const sample: RawSample = {
|
|
970
|
+
* kind: "down", pointerType: "touch", pointerId: 1, clientX: 320, clientY: 640
|
|
971
|
+
* };
|
|
972
|
+
* ```
|
|
973
|
+
*/
|
|
974
|
+
type RawSample = {
|
|
975
|
+
kind: "down" | "move" | "up" | "cancel" | "lost" | "leave";
|
|
976
|
+
pointerType: "mouse" | "touch" | "pen";
|
|
977
|
+
pointerId: number;
|
|
978
|
+
clientX: number;
|
|
979
|
+
clientY: number;
|
|
841
980
|
};
|
|
842
981
|
/**
|
|
843
|
-
*
|
|
844
|
-
* place a locale is read and a sentence becomes parts.
|
|
982
|
+
* Where the one active pointer stands in the gesture machine.
|
|
845
983
|
*
|
|
846
984
|
* @example
|
|
847
985
|
* ```ts
|
|
848
|
-
*
|
|
849
|
-
* app.i18n.locales(); // ["en", "ru"]
|
|
850
|
-
* await app.i18n.setLocale("en");
|
|
851
|
-
* app.i18n.plain(tr("hud.orders", { n: 3 })); // "3 orders"
|
|
986
|
+
* const phase: GesturePhase = "dragging";
|
|
852
987
|
* ```
|
|
853
988
|
*/
|
|
854
|
-
type
|
|
855
|
-
/**
|
|
856
|
-
* The locale every `format` without a second argument reads.
|
|
857
|
-
*
|
|
858
|
-
* @returns The current locale.
|
|
859
|
-
* @example
|
|
860
|
-
* ```ts
|
|
861
|
-
* app.i18n.locale(); // "ru", the pluginConfigs.i18n.locale the game started with
|
|
862
|
-
* ```
|
|
863
|
-
*/
|
|
864
|
-
locale(): string;
|
|
865
|
-
/**
|
|
866
|
-
* Switches the current locale. A lazy module is imported once, then `i18n:locale-changed` is
|
|
867
|
-
* emitted; `text` re-resolves every message on it. A call for the locale already current
|
|
868
|
-
* resolves at once and emits nothing.
|
|
869
|
-
*
|
|
870
|
-
* @param locale - The locale to switch to.
|
|
871
|
-
* @returns A promise that settles when the messages of that locale are loaded.
|
|
872
|
-
* @throws {Error} When no feature and no config brought a module for the locale.
|
|
873
|
-
* @example
|
|
874
|
-
* ```ts
|
|
875
|
-
* // features/settings/nodes.ts: the language button of the settings screen.
|
|
876
|
-
* await app.i18n.setLocale("en"); // loads the compiled module, then emits i18n:locale-changed
|
|
877
|
-
* app.i18n.locale(); // "en"
|
|
878
|
-
* ```
|
|
879
|
-
*/
|
|
880
|
-
setLocale(locale: string): Promise<void>;
|
|
881
|
-
/**
|
|
882
|
-
* Formats a message in the current locale. Resolution order is the current locale, then
|
|
883
|
-
* `config.fallback`, then missing: a missing key gives one text part `⟦key⟧` and one warning.
|
|
884
|
-
*
|
|
885
|
-
* @param message - What `tr` returned.
|
|
886
|
-
* @returns The parts of the sentence, adjacent text merged.
|
|
887
|
-
* @example
|
|
888
|
-
* ```ts
|
|
889
|
-
* // text resolves the `content` of a Text component every time the locale changes.
|
|
890
|
-
* app.i18n.format(tr("hud.orders", { n: 3 })); // [{ kind: "text", text: "3 заказа" }]
|
|
891
|
-
* ```
|
|
892
|
-
*/
|
|
893
|
-
format(message: Message): readonly Part[];
|
|
894
|
-
/**
|
|
895
|
-
* Formats a message in another registered locale, without switching. `ui.lint` measures a
|
|
896
|
-
* label in every locale this way.
|
|
897
|
-
*
|
|
898
|
-
* @param message - What `tr` returned.
|
|
899
|
-
* @param locale - The registered locale to read.
|
|
900
|
-
* @returns The parts of the sentence in that locale.
|
|
901
|
-
* @throws {Error} When the locale is not registered, or is registered but not loaded yet.
|
|
902
|
-
* @example
|
|
903
|
-
* ```ts
|
|
904
|
-
* // ui.lint does this for every entry of app.i18n.locales() and keeps the widest result.
|
|
905
|
-
* app.i18n.format(tr("hud.orders", { n: 3 }), "en"); // [{ kind: "text", text: "3 orders" }]
|
|
906
|
-
* app.i18n.locale(); // "ru": reading another locale never switches
|
|
907
|
-
* ```
|
|
908
|
-
*/
|
|
909
|
-
format(message: Message, locale: string): readonly Part[];
|
|
910
|
-
/**
|
|
911
|
-
* The parts joined into one string, elements dropped. For logs, tests and `flow.describe()`.
|
|
912
|
-
*
|
|
913
|
-
* @param message - What `tr` returned.
|
|
914
|
-
* @returns The sentence as text.
|
|
915
|
-
* @example
|
|
916
|
-
* ```ts
|
|
917
|
-
* // A test asserts what the HUD says without touching the renderer.
|
|
918
|
-
* app.i18n.plain(tr("hud.coins", { n: 25, icon: coinIcon })); // "25": the icon is dropped
|
|
919
|
-
* ```
|
|
920
|
-
*/
|
|
921
|
-
plain(message: Message): string;
|
|
922
|
-
/**
|
|
923
|
-
* Tells whether a key exists in the current locale or in the fallback.
|
|
924
|
-
*
|
|
925
|
-
* @param key - The message key.
|
|
926
|
-
* @returns True when `format` would find a message for it.
|
|
927
|
-
* @example
|
|
928
|
-
* ```ts
|
|
929
|
-
* // A projection hides the label of an order that has no description yet.
|
|
930
|
-
* app.i18n.has("orders.hint"); // false: no locale brought that key
|
|
931
|
-
* ```
|
|
932
|
-
*/
|
|
933
|
-
has(key: string): boolean;
|
|
934
|
-
/**
|
|
935
|
-
* Formats milliseconds in the current locale through `Intl.DurationFormat`. The value is rounded
|
|
936
|
-
* up to whole seconds, so a countdown never reads zero while time is left; a negative or
|
|
937
|
-
* non-finite value reads as zero; the seconds are always shown. `text` formats a field bound
|
|
938
|
-
* with `format: "duration"` through this call.
|
|
939
|
-
*
|
|
940
|
-
* @param ms - The duration in milliseconds.
|
|
941
|
-
* @param style - How the duration reads. Default `"short"`.
|
|
942
|
-
* @returns The duration as text.
|
|
943
|
-
* @throws {Error} When the runtime has no `Intl.DurationFormat`.
|
|
944
|
-
* @example
|
|
945
|
-
* ```ts
|
|
946
|
-
* // The out-of-energy popup shows when the next refill comes, locale "en".
|
|
947
|
-
* app.i18n.duration(95_000); // "1 min, 35 sec"
|
|
948
|
-
* app.i18n.duration(95_000, "digital"); // "0:01:35"
|
|
949
|
-
* app.i18n.duration(500); // "1 sec": rounded up
|
|
950
|
-
* ```
|
|
951
|
-
*/
|
|
952
|
-
duration(ms: number, style?: DurationStyle): string;
|
|
953
|
-
/**
|
|
954
|
-
* Every locale at least one feature or the config brought a module for, sorted.
|
|
955
|
-
*
|
|
956
|
-
* @returns The registered locales.
|
|
957
|
-
* @example
|
|
958
|
-
* ```ts
|
|
959
|
-
* // The language menu is built from this list, and ui.lint measures the widest locale.
|
|
960
|
-
* app.i18n.locales(); // ["en", "ru"]
|
|
961
|
-
* ```
|
|
962
|
-
*/
|
|
963
|
-
locales(): readonly string[];
|
|
964
|
-
/**
|
|
965
|
-
* Swaps the compiled module of one feature in a loaded locale, for the dev hot swap. The one
|
|
966
|
-
* registered module whose keys overlap `messages` takes them, the locale is merged again and
|
|
967
|
-
* every key may warn once more. When the locale is the current one or the fallback,
|
|
968
|
-
* `i18n:locale-changed` is emitted with the current locale, so `text` re-resolves every label.
|
|
969
|
-
* A locale not loaded yet is left alone: its loader imports the new module later.
|
|
970
|
-
*
|
|
971
|
-
* @param locale - The locale of the saved `generated/strings.<locale>.ts`.
|
|
972
|
-
* @param messages - The `default` export of the new module.
|
|
973
|
-
* @throws {Error} When no registered module, or more than one, shares a key with `messages`.
|
|
974
|
-
* @example
|
|
975
|
-
* ```ts
|
|
976
|
-
* // ui's hot swap: generated/strings.ru.ts was written again with "hud.orders" as "3 заказа!".
|
|
977
|
-
* ctx.require(i18nPlugin).replace("ru", next.default); // emits i18n:locale-changed
|
|
978
|
-
* app.i18n.plain(tr("hud.orders", { n: 3 })); // "3 заказа!": text redraws the label
|
|
979
|
-
* ```
|
|
980
|
-
*/
|
|
981
|
-
replace(locale: string, messages: CompiledMessages): void;
|
|
982
|
-
};
|
|
983
|
-
/**
|
|
984
|
-
* What the kernel context offers this plugin.
|
|
985
|
-
*
|
|
986
|
-
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
987
|
-
* row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
|
|
988
|
-
* kernel's own, i18n-typed one. No member of the context is cast.
|
|
989
|
-
*/
|
|
990
|
-
type I18nCtx = PluginCtx<Config$1, State$1, Events> & {
|
|
991
|
-
readonly global: object;
|
|
992
|
-
readonly log: Log.LogApi;
|
|
993
|
-
readonly require: Require;
|
|
994
|
-
};
|
|
989
|
+
type GesturePhase = "idle" | "pressed" | "longPressed" | "dragging" | "tracing";
|
|
995
990
|
/**
|
|
996
|
-
*
|
|
997
|
-
* A key with no parameters carries `Record<never, never>`.
|
|
991
|
+
* What `onTap` hands a listener: the view the finger let go on, inside the tap slop.
|
|
998
992
|
*
|
|
999
993
|
* @example
|
|
1000
994
|
* ```ts
|
|
1001
|
-
* const
|
|
995
|
+
* const tapped: Entity[] = [];
|
|
996
|
+
* const listener: TapListener = entity => tapped.push(entity);
|
|
1002
997
|
* ```
|
|
1003
998
|
*/
|
|
1004
|
-
type
|
|
999
|
+
type TapListener = (entity: Entity) => void;
|
|
1005
1000
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1001
|
+
* What `onPointer` registers: called with the raw sample inside the DOM listener, for the kinds
|
|
1002
|
+
* `down`, `up` and `cancel`, right after the sample is queued.
|
|
1008
1003
|
*
|
|
1009
1004
|
* @example
|
|
1010
1005
|
* ```ts
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1006
|
+
* const lifts: RawSample[] = [];
|
|
1007
|
+
* const listener: PointerListener = sample => {
|
|
1008
|
+
* if (sample.kind === "up") lifts.push(sample);
|
|
1009
|
+
* };
|
|
1013
1010
|
* ```
|
|
1014
1011
|
*/
|
|
1015
|
-
type
|
|
1012
|
+
type PointerListener = (sample: RawSample) => void;
|
|
1016
1013
|
/**
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1014
|
+
* One key press as `onKey` hands it to a listener: the DOM `KeyboardEvent.key` and whether Shift
|
|
1015
|
+
* was held.
|
|
1019
1016
|
*
|
|
1020
1017
|
* @example
|
|
1021
1018
|
* ```ts
|
|
1022
|
-
* const
|
|
1023
|
-
* tr("hud.orders", { n: 3 }); // { key: "hud.orders", params: { n: 3 } }
|
|
1019
|
+
* const backTab: KeyInput = { key: "Tab", shift: true };
|
|
1024
1020
|
* ```
|
|
1025
1021
|
*/
|
|
1026
|
-
type
|
|
1027
|
-
|
|
1028
|
-
|
|
1022
|
+
type KeyInput = {
|
|
1023
|
+
key: string;
|
|
1024
|
+
shift: boolean;
|
|
1029
1025
|
};
|
|
1030
1026
|
/**
|
|
1031
|
-
* What `
|
|
1027
|
+
* What `onKey` registers. Returning `true` marks the key handled: input then calls
|
|
1028
|
+
* `preventDefault()` on the DOM event, and `app.input.pressKey` answers `true`.
|
|
1032
1029
|
*
|
|
1033
1030
|
* @example
|
|
1034
1031
|
* ```ts
|
|
1035
|
-
* const
|
|
1036
|
-
* kit.tr("home.play"); // { key: "home.play" }
|
|
1032
|
+
* const closeOnEscape: KeyListener = key => key.key === "Escape";
|
|
1037
1033
|
* ```
|
|
1038
1034
|
*/
|
|
1039
|
-
type
|
|
1040
|
-
tr: TypedTr<Table>;
|
|
1041
|
-
};
|
|
1042
|
-
declare namespace types_d_exports {
|
|
1043
|
-
export { AdvanceTable, BindField, BindOptions, BundleLoaded, BundleUnloaded, Config, CountdownValue, Deps, DrawnLabel, IconRun, KernelSlice, LayoutOptions, Line, LocaleChanged, Point, Run, SeenText, Size, State, TextAlign, TextApi, TextBind, TextCtx, TextFormat, TextLayout, TextRun, TextShadow, TextStyle, TextStyleInput, TextStyles, TextValue, TextWrap, Warn };
|
|
1044
|
-
}
|
|
1035
|
+
type KeyListener = (key: KeyInput) => boolean | void;
|
|
1045
1036
|
/**
|
|
1046
|
-
*
|
|
1037
|
+
* input plugin config. Every distance is in reference px, every duration in milliseconds of
|
|
1038
|
+
* `time`, so a time scale and a pause apply to the gestures too.
|
|
1047
1039
|
*
|
|
1048
1040
|
* @example
|
|
1049
1041
|
* ```ts
|
|
1050
|
-
*
|
|
1042
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { longPressMs: 300, swipeMinPx: 64 } } });
|
|
1043
|
+
* // A merge game carries the item in the hand 8% bigger: a board item resting at scale 0.5 on
|
|
1044
|
+
* // screen is drawn at 0.54 from the grab to the drop, even after a press squashed it, then
|
|
1045
|
+
* // flies home at 0.5.
|
|
1046
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { heldScale: 1.08 } } });
|
|
1047
|
+
* // A game with its own cursors: the config merges shallowly, so both values are given.
|
|
1048
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { cursor: { control: "grab", idle: "default" } } } });
|
|
1049
|
+
* // A word grid with 40 px cells: the finger's path is hit-tested every 16 px, below the
|
|
1050
|
+
* // shortest cell span, so a fast finger skips no letter.
|
|
1051
|
+
* createApp({ plugins: [...screen], pluginConfigs: { input: { traceStepPx: 16 } } });
|
|
1051
1052
|
* ```
|
|
1052
1053
|
*/
|
|
1053
|
-
type
|
|
1054
|
-
|
|
1055
|
-
|
|
1054
|
+
type Config$1 = {
|
|
1055
|
+
/** Largest move, in reference px, that still counts as a tap or a long press. */tapSlopPx: number; /** Hold time, in ms of `time`, after which a `Pressable` answers. */
|
|
1056
|
+
longPressMs: number; /** Move, in reference px, that turns a press on a `Draggable` into a drag. */
|
|
1057
|
+
dragStartPx: number; /** Shortest swipe, in reference px. */
|
|
1058
|
+
swipeMinPx: number; /** Longest swipe, in ms of `time`, from pointer down to pointer up. */
|
|
1059
|
+
swipeMaxMs: number;
|
|
1060
|
+
/**
|
|
1061
|
+
* How much bigger the view in the hand is drawn: its rest scale in root space times this, from
|
|
1062
|
+
* the grab to the release, so a squash a press left on it does not shrink the lift. A view with
|
|
1063
|
+
* no recorded rest starts from its scale at the grab. The drop, the cancel and the abort write
|
|
1064
|
+
* the rest scale back, so the way home starts at the view's own size. `1` changes nothing.
|
|
1065
|
+
*/
|
|
1066
|
+
heldScale: number;
|
|
1067
|
+
/**
|
|
1068
|
+
* CSS cursors of the canvas. `control` shows while a mouse or a pen rests on a view that
|
|
1069
|
+
* carries `Tappable`, `Draggable`, `Pressable`, `Swipeable` or a component another plugin
|
|
1070
|
+
* registered through `controls.add`, such as ui's `LocalWrite`; `idle` shows everywhere else.
|
|
1071
|
+
* `""` hands the cursor back to the page's own style.
|
|
1072
|
+
*/
|
|
1073
|
+
cursor: {
|
|
1074
|
+
control: string;
|
|
1075
|
+
idle: string;
|
|
1076
|
+
};
|
|
1077
|
+
/**
|
|
1078
|
+
* Reference px between two hit tests along the finger's path inside one frame of a trace. Keep
|
|
1079
|
+
* it below the shortest cell span: a longer step jumps over a cell.
|
|
1080
|
+
*/
|
|
1081
|
+
traceStepPx: number;
|
|
1082
|
+
/**
|
|
1083
|
+
* The hit circle of a trace cell: its radius is this times the short side of the cell's hit
|
|
1084
|
+
* box, around the box centre. A diagonal then crosses no corner of a neighbour.
|
|
1085
|
+
*/
|
|
1086
|
+
traceInset: number;
|
|
1056
1087
|
};
|
|
1057
1088
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
* @example
|
|
1061
|
-
* ```ts
|
|
1062
|
-
* const size: Size = { width: 57.6, height: 38.4 };
|
|
1063
|
-
* ```
|
|
1089
|
+
* One view a drag carries on top of the held one: hung under the held view from the grab to the
|
|
1090
|
+
* release.
|
|
1064
1091
|
*/
|
|
1065
|
-
type
|
|
1066
|
-
|
|
1067
|
-
|
|
1092
|
+
type Carried = {
|
|
1093
|
+
/** The carried view. */entity: Entity; /** The `Parent` it had at the grab, hung back under it on the release. */
|
|
1094
|
+
parent: Entity | undefined; /** The remover `world.projection.mute` returned for its root pose. */
|
|
1095
|
+
unmute: () => void;
|
|
1068
1096
|
};
|
|
1069
1097
|
/**
|
|
1070
|
-
*
|
|
1071
|
-
* show whole seconds, rounded up, so a timer never reads zero while time is left.
|
|
1072
|
-
*
|
|
1073
|
-
* @example
|
|
1074
|
-
* ```ts
|
|
1075
|
-
* const format: TextFormat = "mm:ss"; // 95_000 ms shows "01:35"
|
|
1076
|
-
* ```
|
|
1077
|
-
*/
|
|
1078
|
-
type TextFormat = "int" | "mm:ss" | "h:mm:ss" | "duration";
|
|
1079
|
-
/** The brand only `bind()` puts on a `TextBind`. Type-only: nothing is emitted for it. */
|
|
1080
|
-
declare const textBind: unique symbol;
|
|
1081
|
-
/**
|
|
1082
|
-
* Which numeric component field a label shows, and how. Plain data in the component, so a
|
|
1083
|
-
* snapshot lists it; nominal, so only `bind()` makes one and a hand-written literal does not
|
|
1084
|
-
* compile.
|
|
1085
|
-
*
|
|
1086
|
-
* @example
|
|
1087
|
-
* ```ts
|
|
1088
|
-
* const Counter = component("Counter", { value: 0 });
|
|
1089
|
-
* const coins: TextBind = bind(Counter, "value");
|
|
1090
|
-
* // { component: "Counter", field: "value", format: "int" }
|
|
1091
|
-
* ```
|
|
1098
|
+
* input plugin state: the raw queue the DOM handlers write, and the one gesture in progress.
|
|
1092
1099
|
*/
|
|
1093
|
-
type
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
*/
|
|
1108
|
-
|
|
1109
|
-
/**
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
*/
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
type CountdownValue = {
|
|
1131
|
-
until: number;
|
|
1132
|
-
left: number;
|
|
1133
|
-
};
|
|
1134
|
-
/**
|
|
1135
|
-
* What the `Text` component holds. `resolved` is engine-owned: a game writes `content`, `style`,
|
|
1136
|
-
* `bind`, `anchor` and `alpha`, and reads `resolved`. `alpha` fades every run, shadow and icon of
|
|
1137
|
-
* the block together, 1 by default; a change is written in place and a tween can drive it.
|
|
1138
|
-
*
|
|
1139
|
-
* @example
|
|
1140
|
-
* ```ts
|
|
1141
|
-
* const value: TextValue = {
|
|
1142
|
-
* content: "+5", style: "body", bind: undefined, anchor: { x: 0.5, y: 0.5 }, alpha: 1,
|
|
1143
|
-
* resolved: "+5"
|
|
1144
|
-
* };
|
|
1145
|
-
* // The hint of an empty name field, at half alpha.
|
|
1146
|
-
* const hint: TextValue = { ...value, content: "Your name", alpha: 0.5, resolved: "Your name" };
|
|
1147
|
-
* ```
|
|
1148
|
-
*/
|
|
1149
|
-
type TextValue = {
|
|
1150
|
-
content: string | Message;
|
|
1151
|
-
style: string;
|
|
1152
|
-
bind: TextBind | undefined;
|
|
1153
|
-
anchor: Point;
|
|
1154
|
-
alpha: number;
|
|
1155
|
-
resolved: string;
|
|
1156
|
-
};
|
|
1157
|
-
/**
|
|
1158
|
-
* Where the lines of a block sit inside the widest of them.
|
|
1159
|
-
*
|
|
1160
|
-
* @example
|
|
1161
|
-
* ```ts
|
|
1162
|
-
* const align: TextAlign = "center";
|
|
1163
|
-
* ```
|
|
1164
|
-
*/
|
|
1165
|
-
type TextAlign = "left" | "center" | "right";
|
|
1166
|
-
/**
|
|
1167
|
-
* The wrap width of a style in reference pixels, or `"none"` for one line per `\n` segment.
|
|
1168
|
-
*
|
|
1169
|
-
* @example
|
|
1170
|
-
* ```ts
|
|
1171
|
-
* const wrap: TextWrap = 480;
|
|
1172
|
-
* ```
|
|
1173
|
-
*/
|
|
1174
|
-
type TextWrap = number | "none";
|
|
1175
|
-
/**
|
|
1176
|
-
* The drop shadow of a style as the plugin stores it: a copy of every glyph run in `color`, at
|
|
1177
|
-
* `alpha`, moved by `dx` and `dy` reference pixels.
|
|
1178
|
-
*
|
|
1179
|
-
* @example
|
|
1180
|
-
* ```ts
|
|
1181
|
-
* const shadow: TextShadow = { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 };
|
|
1182
|
-
* ```
|
|
1183
|
-
*/
|
|
1184
|
-
type TextShadow = {
|
|
1185
|
-
color: number;
|
|
1186
|
-
dx: number;
|
|
1187
|
-
dy: number;
|
|
1188
|
-
alpha: number;
|
|
1100
|
+
type State$1 = {
|
|
1101
|
+
/** Written by the DOM handlers, drained by the frame step. */samples: RawSample[];
|
|
1102
|
+
phase: GesturePhase; /** The one active pointer. A sample of any other pointer is dropped while this is set. */
|
|
1103
|
+
pointerId: number | undefined; /** The pressed or held entity. */
|
|
1104
|
+
entity: Entity | undefined; /** The projection key of `entity`, read once at press time. */
|
|
1105
|
+
key: {
|
|
1106
|
+
projection: string;
|
|
1107
|
+
key: string;
|
|
1108
|
+
} | undefined; /** Reference px at pointer down. */
|
|
1109
|
+
start: Point$1; /** `Transform` minus the pointer at the moment of the grab. */
|
|
1110
|
+
grabOffset: Point$1; /** Sum of `time.delta` since pointer down. No device clock is ever read. */
|
|
1111
|
+
pressedMs: number; /** The drop target that carries `Hovered` during a drag. */
|
|
1112
|
+
hovered: Entity | undefined; /** The view that carries `PointerOver`: a press would take it, and a mouse or a pen is over it. */
|
|
1113
|
+
pointerOver: Entity | undefined; /** The `Parent` the held view had at the grab. It is hung back under it on the release. */
|
|
1114
|
+
parent: Entity | undefined; /** The rest scale of the held view in root space: the base of `heldScale`, set back on drop. */
|
|
1115
|
+
restScale: number | undefined;
|
|
1116
|
+
/**
|
|
1117
|
+
* The root pose of the held view, written at the grab and at every move. A carried view is
|
|
1118
|
+
* placed from it when the held view despawns before the release and takes its `Transform`.
|
|
1119
|
+
*/
|
|
1120
|
+
heldPose: TransformValue | undefined; /** The remover `world.projection.mute` returned, while a drag runs. */
|
|
1121
|
+
unmute: (() => void) | undefined; /** The views a drag carries on top of the held one, in `carry` order; empty outside a drag. */
|
|
1122
|
+
carried: Carried[]; /** The cells of the trace in progress, in path order; empty outside a trace. */
|
|
1123
|
+
path: Entity[]; /** The intent of the first cell of the trace; a cell of another intent closes the path. */
|
|
1124
|
+
traceIntent: string | undefined; /** True once a cell of another intent was crossed: the path neither grows nor pops. */
|
|
1125
|
+
traceClosed: boolean; /** Where the finger was at the last frame of the trace: the segment starts there. */
|
|
1126
|
+
lastPoint: Point$1 | undefined;
|
|
1127
|
+
canvas: HTMLCanvasElement | undefined;
|
|
1128
|
+
offFrame: (() => void) | undefined;
|
|
1129
|
+
detach: (() => void) | undefined; /** Registered through `onTap`, called in this order on every tap. */
|
|
1130
|
+
tapListeners: TapListener[]; /** Registered through `onKey`, called in this order on every key. */
|
|
1131
|
+
keyListeners: KeyListener[]; /** Registered through `onPointer`, called in this order inside the DOM listener. */
|
|
1132
|
+
pointerListeners: PointerListener[]; /** Takes the one `keydown` listener off `window`; set while a canvas is attached. */
|
|
1133
|
+
detachKeys: (() => void) | undefined; /** `time.wake`, bound in `onInit`: every pointer sample leaves the idle frame rate. */
|
|
1134
|
+
wake: (() => void) | undefined; /** The cursor last written on the attached canvas; `undefined` while nothing was written. */
|
|
1135
|
+
cursor: string | undefined; /** Component types other plugins registered through `controls.add`, once per registration. */
|
|
1136
|
+
controls: AnyComponentType[];
|
|
1189
1137
|
};
|
|
1190
1138
|
/**
|
|
1191
|
-
*
|
|
1139
|
+
* input plugin API, `app.input`: the same door for the finger, for a test and for an agent. Each
|
|
1140
|
+
* method builds the Answer the gesture would build and hands it to `flow.gate.answer`. Nothing
|
|
1141
|
+
* moves: no coordinates, no frames, no `Held`, no `settle`.
|
|
1192
1142
|
*
|
|
1193
1143
|
* @example
|
|
1194
1144
|
* ```ts
|
|
1195
|
-
* // A
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1198
|
-
* };
|
|
1199
|
-
*
|
|
1200
|
-
* defineTextStyles({ "popup.title": title }).map["popup.title"]?.shadow;
|
|
1201
|
-
* // { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 }
|
|
1145
|
+
* // A headless test plays the board without a screen.
|
|
1146
|
+
* app.input.tap({ projection: "board.generators", key: "g1" }); // true: the generator spat an item
|
|
1147
|
+
* app.input.drag({ projection: "board.items", key: "i5" }, { projection: "board.items", key: "i7" });
|
|
1202
1148
|
* ```
|
|
1203
1149
|
*/
|
|
1204
|
-
type
|
|
1205
|
-
font: string;
|
|
1206
|
-
bold?: string;
|
|
1207
|
-
italic?: string;
|
|
1208
|
-
size: number;
|
|
1209
|
-
fill: number; /** The outline colour. Drawn only when `strokeWidth` is above 0. */
|
|
1210
|
-
stroke?: number;
|
|
1150
|
+
type InputApi = {
|
|
1211
1151
|
/**
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1152
|
+
* Reads the `Tappable` of a view and answers its intent.
|
|
1153
|
+
*
|
|
1154
|
+
* @param target - The projection key of a live view, or an entity.
|
|
1155
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
1156
|
+
* @example
|
|
1157
|
+
* ```ts
|
|
1158
|
+
* // A hidden-object test finds one object without touching a pixel.
|
|
1159
|
+
* app.input.tap({ projection: "scene.objects", key: "lamp" });
|
|
1160
|
+
* // true: answers { intent: "found", payload: { id: "lamp" } }
|
|
1161
|
+
* ```
|
|
1214
1162
|
*/
|
|
1215
|
-
|
|
1216
|
-
letterSpacing?: number;
|
|
1217
|
-
align?: TextAlign;
|
|
1218
|
-
wrap?: TextWrap;
|
|
1219
|
-
digits?: boolean;
|
|
1163
|
+
tap(target: Target): boolean;
|
|
1220
1164
|
/**
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1165
|
+
* Reads the `Pressable` of a view and answers its intent, the way a long press would.
|
|
1166
|
+
*
|
|
1167
|
+
* @param target - The projection key of a live view, or an entity.
|
|
1168
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
1169
|
+
* @example
|
|
1170
|
+
* ```ts
|
|
1171
|
+
* // An agent opens the info card of an item without holding a finger for 450 ms.
|
|
1172
|
+
* app.input.press({ projection: "board.items", key: "i5" });
|
|
1173
|
+
* // true: answers { intent: "info", payload: { id: "i5" } }
|
|
1174
|
+
* ```
|
|
1223
1175
|
*/
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1176
|
+
press(target: Target): boolean;
|
|
1177
|
+
/**
|
|
1178
|
+
* Reads the `Draggable` of the source and the `DropTarget` of the destination and answers the
|
|
1179
|
+
* destination's intent with both payloads merged. The destination wins a key both carry.
|
|
1180
|
+
*
|
|
1181
|
+
* @param from - The view that is carried.
|
|
1182
|
+
* @param to - The view it is dropped on; this one names the intent.
|
|
1183
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
1184
|
+
* @example
|
|
1185
|
+
* ```ts
|
|
1186
|
+
* // The merge test of the fixture game: two items of level 1 become one of level 2.
|
|
1187
|
+
* app.input.drag({ projection: "board.items", key: "i5" }, { projection: "board.items", key: "i7" });
|
|
1188
|
+
* // true: answers { intent: "merge", payload: { from: "c2", to: "c3" } }
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
1191
|
+
drag(from: Target, to: Target): boolean;
|
|
1192
|
+
/**
|
|
1193
|
+
* Reads the `Swipeable` of a view and answers its intent with the direction in the payload.
|
|
1194
|
+
*
|
|
1195
|
+
* @param target - The projection key of a live view, or an entity.
|
|
1196
|
+
* @param direction - Which way the finger would have gone.
|
|
1197
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
1198
|
+
* @example
|
|
1199
|
+
* ```ts
|
|
1200
|
+
* // A match-3 test swaps the cell c2 with its right neighbour.
|
|
1201
|
+
* app.input.swipe({ projection: "board.cells", key: "c2" }, "right");
|
|
1202
|
+
* // true: answers { intent: "swap", payload: { cell: "c2", direction: "right" } }
|
|
1203
|
+
* ```
|
|
1204
|
+
*/
|
|
1205
|
+
swipe(target: Target, direction: Direction): boolean;
|
|
1206
|
+
/**
|
|
1207
|
+
* Reads the `Traceable` of every cell and answers one path, the way a finger drawn through the
|
|
1208
|
+
* cells would: the intent of the cells and `{ path }`, the cell payloads in order. Every cell
|
|
1209
|
+
* must name the same intent. An empty list, a cell with no `Traceable` and two intents warn and
|
|
1210
|
+
* answer `false` without calling the gate. Nothing is tagged `Traced`.
|
|
1211
|
+
*
|
|
1212
|
+
* @param path - The cells in the order the finger goes through them.
|
|
1213
|
+
* @returns What `flow.gate.answer` returned: true when the gate took the answer.
|
|
1214
|
+
* @example
|
|
1215
|
+
* ```ts
|
|
1216
|
+
* // A word-game test spells a word without a finger.
|
|
1217
|
+
* app.input.trace([
|
|
1218
|
+
* { projection: "board.cells", key: "b3" },
|
|
1219
|
+
* { projection: "board.cells", key: "c3" },
|
|
1220
|
+
* { projection: "board.cells", key: "d3" }
|
|
1221
|
+
* ]);
|
|
1222
|
+
* // true: answers { intent: "word", payload: { path: [{ cell: "b3" }, { cell: "c3" }, { cell: "d3" }] } }
|
|
1223
|
+
* ```
|
|
1224
|
+
*/
|
|
1225
|
+
trace(path: readonly Target[]): boolean;
|
|
1226
|
+
/**
|
|
1227
|
+
* Registers a listener called on every tap — a press and a release inside `tapSlopPx` — on the
|
|
1228
|
+
* topmost view the hit test accepted, before the `Tappable` answer. A view that carries only
|
|
1229
|
+
* `Touchable` reaches the listeners and answers nothing. A listener that throws is logged with
|
|
1230
|
+
* its entity, and the listeners after it still run.
|
|
1231
|
+
*
|
|
1232
|
+
* @param fn - What to run with the tapped entity.
|
|
1233
|
+
* @returns The remover; call it to stop listening.
|
|
1234
|
+
* @example
|
|
1235
|
+
* ```ts
|
|
1236
|
+
* // ui applies the local patch of the button the finger tapped.
|
|
1237
|
+
* const off = ctx.require(inputPlugin).onTap(entity => {
|
|
1238
|
+
* const write = ctx.require(worldPlugin).ecs.get(entity, LocalWrite);
|
|
1239
|
+
*
|
|
1240
|
+
* if (write !== undefined) applyLocal(entity, write.patch);
|
|
1241
|
+
* });
|
|
1242
|
+
* off(); // in onStop
|
|
1243
|
+
* ```
|
|
1244
|
+
*/
|
|
1245
|
+
onTap(fn: TapListener): () => void;
|
|
1246
|
+
/**
|
|
1247
|
+
* Registers a listener called synchronously inside the DOM pointer listener, right after the
|
|
1248
|
+
* sample is queued, for `pointerdown`, `pointerup` and `pointercancel`. It is the one place a
|
|
1249
|
+
* browser call that needs the user gesture can run, such as `focus()` on a text field: in the
|
|
1250
|
+
* next frame iOS shows no keyboard. Never called for a move, a leave or a lost capture, and never
|
|
1251
|
+
* by `tap`, `press`, `drag` or `swipe`, which have no DOM moment. The sample is raw: client px,
|
|
1252
|
+
* no hit test. Listeners run in registration order; a listener that throws is logged with the
|
|
1253
|
+
* sample kind, and the listeners after it still run.
|
|
1254
|
+
*
|
|
1255
|
+
* @param fn - What to run with the raw sample.
|
|
1256
|
+
* @returns The remover; call it to stop listening.
|
|
1257
|
+
* @example
|
|
1258
|
+
* ```ts
|
|
1259
|
+
* // ui opens the keyboard the moment the finger lifts on a text field, inside the DOM listener.
|
|
1260
|
+
* const off = ctx.require(inputPlugin).onPointer(sample => {
|
|
1261
|
+
* if (sample.kind === "up" && fieldAt(sample.clientX, sample.clientY) !== undefined) element.focus({ preventScroll: true });
|
|
1262
|
+
* if (sample.kind === "down" && fieldAt(sample.clientX, sample.clientY) === undefined) element.blur();
|
|
1263
|
+
* });
|
|
1264
|
+
* // a tap on the Rename field on an iPhone: the keyboard shows; a tap on the parchment next to it: the keyboard hides
|
|
1265
|
+
* off(); // in onStop
|
|
1266
|
+
* ```
|
|
1267
|
+
*/
|
|
1268
|
+
onPointer(fn: PointerListener): () => void;
|
|
1269
|
+
/**
|
|
1270
|
+
* Registers a listener called on every key pressed while the canvas is attached, and on every
|
|
1271
|
+
* `pressKey` call. A key typed into a text field is not handed over, except Enter and Escape,
|
|
1272
|
+
* and an Enter that commits an IME composition is dropped. Listeners run in registration order,
|
|
1273
|
+
* all of them, each time. A listener that
|
|
1274
|
+
* returns `true` marks the key handled, and input calls `preventDefault()` on the DOM event. A
|
|
1275
|
+
* listener that throws is logged with its key, and the listeners after it still run.
|
|
1276
|
+
*
|
|
1277
|
+
* @param fn - What to run with the key; return `true` when it handled it.
|
|
1278
|
+
* @returns The remover; call it to stop listening.
|
|
1279
|
+
* @example
|
|
1280
|
+
* ```ts
|
|
1281
|
+
* // ui moves the keyboard focus on Tab and keeps the browser from leaving the canvas.
|
|
1282
|
+
* const off = ctx.require(inputPlugin).onKey(key => {
|
|
1283
|
+
* if (key.key !== "Tab") return false;
|
|
1284
|
+
* moveFocus(key.shift ? -1 : 1);
|
|
1285
|
+
*
|
|
1286
|
+
* return true;
|
|
1287
|
+
* });
|
|
1288
|
+
* off(); // in onStop
|
|
1289
|
+
* ```
|
|
1290
|
+
*/
|
|
1291
|
+
onKey(fn: KeyListener): () => void;
|
|
1292
|
+
/**
|
|
1293
|
+
* Presses a key without a keyboard: runs the `onKey` listeners exactly as a DOM `keydown` does.
|
|
1294
|
+
*
|
|
1295
|
+
* @param key - The DOM `KeyboardEvent.key`, such as `"Tab"`, `"Enter"`, `" "` or `"Escape"`.
|
|
1296
|
+
* @param options - How the key is pressed.
|
|
1297
|
+
* @param options.shift - True presses it with Shift held; the default is `false`.
|
|
1298
|
+
* @returns True when a listener handled the key.
|
|
1299
|
+
* @example
|
|
1300
|
+
* ```ts
|
|
1301
|
+
* // A headless test walks the settings popup backwards and closes it.
|
|
1302
|
+
* app.input.pressKey("Tab", { shift: true }); // true: ui moved the focus to the previous control
|
|
1303
|
+
* app.input.pressKey("Escape"); // true: ui tapped the popup's close button
|
|
1304
|
+
* app.input.pressKey("q"); // false: no listener handles it
|
|
1305
|
+
* ```
|
|
1306
|
+
*/
|
|
1307
|
+
pressKey(key: string, options?: {
|
|
1308
|
+
shift?: boolean;
|
|
1309
|
+
}): boolean;
|
|
1310
|
+
/**
|
|
1311
|
+
* The CSS cursor input set on the canvas: `config.cursor.control` while a mouse or a pen rests
|
|
1312
|
+
* on a control, `config.cursor.idle` otherwise and whenever nothing is attached.
|
|
1313
|
+
*
|
|
1314
|
+
* @returns The cursor value.
|
|
1315
|
+
* @example
|
|
1316
|
+
* ```ts
|
|
1317
|
+
* // An e2e check: the mouse rests on the Play button of the home screen.
|
|
1318
|
+
* app.input.cursor(); // "pointer"
|
|
1319
|
+
* // The mouse moves to the empty sky above it.
|
|
1320
|
+
* app.input.cursor(); // ""
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
cursor(): string;
|
|
1324
|
+
/**
|
|
1325
|
+
* The components that make a view a control besides input's own: the cursor shows
|
|
1326
|
+
* `config.cursor.control` over a view that carries one of them.
|
|
1327
|
+
*/
|
|
1328
|
+
controls: {
|
|
1329
|
+
/**
|
|
1330
|
+
* Registers a component type of another plugin as a control. A type registered twice stays
|
|
1331
|
+
* a control until both removers ran.
|
|
1332
|
+
*
|
|
1333
|
+
* @param component - The component or tag type that makes a view answer a press.
|
|
1334
|
+
* @returns The remover; call it to take the registration back.
|
|
1335
|
+
* @example
|
|
1336
|
+
* ```ts
|
|
1337
|
+
* // ui counts its local-state buttons as controls, so the mouse over a tab shows a hand.
|
|
1338
|
+
* const off = ctx.require(inputPlugin).controls.add(LocalWrite);
|
|
1339
|
+
* app.input.cursor(); // "pointer" while the mouse rests on the Audio tab
|
|
1340
|
+
* off(); // in onStop
|
|
1341
|
+
* ```
|
|
1342
|
+
*/
|
|
1343
|
+
add(component: AnyComponentType): () => void;
|
|
1229
1344
|
};
|
|
1230
1345
|
};
|
|
1231
1346
|
/**
|
|
1232
|
-
*
|
|
1233
|
-
* shipped those MSDF fonts, `shadow` only when the style has one.
|
|
1234
|
-
*
|
|
1235
|
-
* @example
|
|
1236
|
-
* ```ts
|
|
1237
|
-
* const style: TextStyle = {
|
|
1238
|
-
* font: "ui.font-body", bold: undefined, italic: undefined, size: 32, fill: 0xffffff,
|
|
1239
|
-
* stroke: 0x000000, strokeWidth: 0, letterSpacing: 0, align: "left", wrap: "none",
|
|
1240
|
-
* digits: false, shadow: undefined
|
|
1241
|
-
* };
|
|
1242
|
-
* ```
|
|
1243
|
-
*/
|
|
1244
|
-
type TextStyle = {
|
|
1245
|
-
font: string;
|
|
1246
|
-
bold: string | undefined;
|
|
1247
|
-
italic: string | undefined;
|
|
1248
|
-
size: number;
|
|
1249
|
-
fill: number;
|
|
1250
|
-
stroke: number;
|
|
1251
|
-
strokeWidth: number;
|
|
1252
|
-
letterSpacing: number;
|
|
1253
|
-
align: TextAlign;
|
|
1254
|
-
wrap: TextWrap;
|
|
1255
|
-
digits: boolean;
|
|
1256
|
-
shadow: TextShadow | undefined;
|
|
1257
|
-
};
|
|
1258
|
-
/**
|
|
1259
|
-
* What `defineTextStyles` returns and a feature registers under its `textStyles` key. Structural
|
|
1260
|
-
* on purpose: `flow` carries the map, `text` reads it.
|
|
1261
|
-
*
|
|
1262
|
-
* @example
|
|
1263
|
-
* ```ts
|
|
1264
|
-
* const styles: TextStyles = { kind: "textStyles", map: { "hud.title": bodyStyle } };
|
|
1265
|
-
* ```
|
|
1347
|
+
* Resolved dependency APIs.
|
|
1266
1348
|
*/
|
|
1267
|
-
type
|
|
1268
|
-
|
|
1269
|
-
|
|
1349
|
+
type Deps$1 = {
|
|
1350
|
+
time: Api;
|
|
1351
|
+
flow: Api$1;
|
|
1352
|
+
world: Api$2;
|
|
1353
|
+
renderer: Api$3;
|
|
1270
1354
|
};
|
|
1271
1355
|
/**
|
|
1272
|
-
*
|
|
1273
|
-
* `style.size / size`; kerning pairs are ignored.
|
|
1356
|
+
* What the kernel context offers before the deps are attached.
|
|
1274
1357
|
*
|
|
1275
|
-
*
|
|
1276
|
-
* ```ts
|
|
1277
|
-
* const table: AdvanceTable = { size: 32, lineHeight: 40, advances: new Map([["1", 18]]) };
|
|
1278
|
-
* ```
|
|
1358
|
+
* `input` owns no event, so `emit` is the kernel's and never called here.
|
|
1279
1359
|
*/
|
|
1280
|
-
type
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1360
|
+
type KernelSlice$1 = PluginCtx<Config$1, State$1> & {
|
|
1361
|
+
readonly global: object;
|
|
1362
|
+
readonly log: Log.LogApi;
|
|
1363
|
+
readonly require: Require;
|
|
1284
1364
|
};
|
|
1285
1365
|
/**
|
|
1286
|
-
*
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
* const run: TextRun = { kind: "text", text: "+5", bold: true, italic: false, color: 0xffe082 };
|
|
1291
|
-
* ```
|
|
1292
|
-
*/
|
|
1293
|
-
type TextRun = {
|
|
1294
|
-
kind: "text";
|
|
1295
|
-
text: string;
|
|
1296
|
-
bold: boolean;
|
|
1297
|
-
italic: boolean;
|
|
1298
|
-
color: number | undefined;
|
|
1366
|
+
* Domain context of the input plugin: the kernel slice plus the four resolved dependencies.
|
|
1367
|
+
*/
|
|
1368
|
+
type InputCtx = KernelSlice$1 & {
|
|
1369
|
+
readonly deps: Deps$1;
|
|
1299
1370
|
};
|
|
1371
|
+
//#endregion
|
|
1372
|
+
//#region src/plugins/ui/jsx/component.d.ts
|
|
1300
1373
|
/**
|
|
1301
|
-
*
|
|
1374
|
+
* Declares a component: a piece of interface with local state, outcomes, or both. The view runs
|
|
1375
|
+
* at every reconcile with the instance's own local state, never at build time.
|
|
1302
1376
|
*
|
|
1377
|
+
* @param name - Unique name across every feature of the game.
|
|
1378
|
+
* @param spec - The initial local state, the outcomes a popup resolves with, and the view.
|
|
1379
|
+
* @returns The component, ready to stand in a tag and in a feature's `ui` key.
|
|
1303
1380
|
* @example
|
|
1304
1381
|
* ```ts
|
|
1305
|
-
* const
|
|
1382
|
+
* const Panel = defineComponent("Panel", {
|
|
1383
|
+
* local: { tab: "audio" },
|
|
1384
|
+
* view: (props: { volume: number }, local) => ({ type: "column", props: {}, children: [] })
|
|
1385
|
+
* });
|
|
1386
|
+
* Panel.name; // "Panel"
|
|
1306
1387
|
* ```
|
|
1307
1388
|
*/
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
};
|
|
1389
|
+
declare function defineComponent<Properties extends object, Local extends object, Outcomes extends Record<string, unknown>>(name: string, spec: ComponentSpec<Properties, Local, Outcomes> & {
|
|
1390
|
+
readonly outcomes: Outcomes;
|
|
1391
|
+
}): ComponentDefinition<Properties, Local, Outcomes>;
|
|
1312
1392
|
/**
|
|
1313
|
-
*
|
|
1393
|
+
* Declares a component without outcomes: it may stand in a tree, never in a `popup`.
|
|
1314
1394
|
*
|
|
1395
|
+
* @param name - Unique name across every feature of the game.
|
|
1396
|
+
* @param spec - The initial local state and the view.
|
|
1397
|
+
* @returns The component, ready to stand in a tag.
|
|
1315
1398
|
* @example
|
|
1316
1399
|
* ```ts
|
|
1317
|
-
* const
|
|
1400
|
+
* const Row = defineComponent("Row", { view: () => ({ type: "row", props: {}, children: [] }) });
|
|
1401
|
+
* Row.outcomes; // undefined
|
|
1318
1402
|
* ```
|
|
1319
1403
|
*/
|
|
1320
|
-
|
|
1404
|
+
declare function defineComponent<Properties extends object, Local extends object>(name: string, spec: ComponentSpec<Properties, Local, never>): ComponentDefinition<Properties, Local, undefined>;
|
|
1405
|
+
declare namespace types_d_exports {
|
|
1406
|
+
export { AdvanceTable, BindField, BindOptions, BundleLoaded, BundleUnloaded, Config, CountdownValue, Deps, DrawnLabel, IconRun, KernelSlice, LayoutOptions, Line, LocaleChanged, Point, Run, SeenText, Size, State, TextAlign, TextApi, TextBind, TextCtx, TextFormat, TextLayout, TextRun, TextShadow, TextStyle, TextStyleInput, TextStyles, TextValue, TextWrap, Warn };
|
|
1407
|
+
}
|
|
1321
1408
|
/**
|
|
1322
|
-
*
|
|
1409
|
+
* A point in reference units: where a label sits, and which point of the block sits there.
|
|
1323
1410
|
*
|
|
1324
1411
|
* @example
|
|
1325
1412
|
* ```ts
|
|
1326
|
-
* const
|
|
1413
|
+
* const anchor: Point = { x: 0.5, y: 0.5 };
|
|
1327
1414
|
* ```
|
|
1328
1415
|
*/
|
|
1329
|
-
type
|
|
1330
|
-
|
|
1331
|
-
|
|
1416
|
+
type Point = {
|
|
1417
|
+
x: number;
|
|
1418
|
+
y: number;
|
|
1332
1419
|
};
|
|
1333
1420
|
/**
|
|
1334
|
-
*
|
|
1421
|
+
* The size of a measured block, in reference pixels.
|
|
1335
1422
|
*
|
|
1336
1423
|
* @example
|
|
1337
1424
|
* ```ts
|
|
1338
|
-
* const
|
|
1425
|
+
* const size: Size = { width: 57.6, height: 38.4 };
|
|
1339
1426
|
* ```
|
|
1340
1427
|
*/
|
|
1341
|
-
type
|
|
1342
|
-
lines: Line[];
|
|
1428
|
+
type Size = {
|
|
1343
1429
|
width: number;
|
|
1344
1430
|
height: number;
|
|
1345
1431
|
};
|
|
1346
1432
|
/**
|
|
1347
|
-
*
|
|
1348
|
-
*
|
|
1349
|
-
* text and positions into the objects already there.
|
|
1350
|
-
*/
|
|
1351
|
-
type DrawnLabel = {
|
|
1352
|
-
style: TextStyle;
|
|
1353
|
-
layout: TextLayout;
|
|
1354
|
-
};
|
|
1355
|
-
/**
|
|
1356
|
-
* A dev warning that is written once per key. Pure modules take it as an argument, so nothing
|
|
1357
|
-
* below the plugin context reaches the log itself.
|
|
1433
|
+
* How a bound number is shown. `"int"` rounds it; the three time formats take milliseconds and
|
|
1434
|
+
* show whole seconds, rounded up, so a timer never reads zero while time is left.
|
|
1358
1435
|
*
|
|
1359
1436
|
* @example
|
|
1360
1437
|
* ```ts
|
|
1361
|
-
* const
|
|
1438
|
+
* const format: TextFormat = "mm:ss"; // 95_000 ms shows "01:35"
|
|
1362
1439
|
* ```
|
|
1363
1440
|
*/
|
|
1364
|
-
type
|
|
1441
|
+
type TextFormat = "int" | "mm:ss" | "h:mm:ss" | "duration";
|
|
1442
|
+
/** The brand only `bind()` puts on a `TextBind`. Type-only: nothing is emitted for it. */
|
|
1443
|
+
declare const textBind: unique symbol;
|
|
1365
1444
|
/**
|
|
1366
|
-
*
|
|
1445
|
+
* Which numeric component field a label shows, and how. Plain data in the component, so a
|
|
1446
|
+
* snapshot lists it; nominal, so only `bind()` makes one and a hand-written literal does not
|
|
1447
|
+
* compile.
|
|
1367
1448
|
*
|
|
1368
1449
|
* @example
|
|
1369
1450
|
* ```ts
|
|
1370
|
-
* const
|
|
1451
|
+
* const Counter = component("Counter", { value: 0 });
|
|
1452
|
+
* const coins: TextBind = bind(Counter, "value");
|
|
1453
|
+
* // { component: "Counter", field: "value", format: "int" }
|
|
1371
1454
|
* ```
|
|
1372
1455
|
*/
|
|
1373
|
-
type
|
|
1374
|
-
|
|
1456
|
+
type TextBind = {
|
|
1457
|
+
readonly component: string;
|
|
1458
|
+
readonly field: string;
|
|
1459
|
+
readonly format: TextFormat;
|
|
1460
|
+
} & {
|
|
1461
|
+
readonly [textBind]: true;
|
|
1375
1462
|
};
|
|
1376
1463
|
/**
|
|
1377
|
-
*
|
|
1378
|
-
* is what the shown string of a bound label was built from: the rounded value for `"int"`, whole
|
|
1379
|
-
* seconds for a time format.
|
|
1464
|
+
* The numeric fields of a component value, by name: what `bind()` takes as its field.
|
|
1380
1465
|
*
|
|
1381
1466
|
* @example
|
|
1382
1467
|
* ```ts
|
|
1383
|
-
*
|
|
1384
|
-
* content: "", style: "digits", bind: "Countdown.left:mm:ss", locale: "ru", unit: 95
|
|
1385
|
-
* };
|
|
1468
|
+
* type Fields = BindField<{ value: number; name: string }>; // "value"
|
|
1386
1469
|
* ```
|
|
1387
1470
|
*/
|
|
1388
|
-
type
|
|
1389
|
-
content: unknown;
|
|
1390
|
-
style: string;
|
|
1391
|
-
bind: string | undefined;
|
|
1392
|
-
locale: string;
|
|
1393
|
-
unit: number | undefined;
|
|
1394
|
-
};
|
|
1471
|
+
type BindField<Value extends object> = keyof NumericFields<Value> & string;
|
|
1395
1472
|
/**
|
|
1396
|
-
*
|
|
1473
|
+
* What `bind()` takes after the field. The format is `"int"` when left out.
|
|
1397
1474
|
*
|
|
1398
1475
|
* @example
|
|
1399
1476
|
* ```ts
|
|
1400
|
-
*
|
|
1477
|
+
* const options: BindOptions = { format: "mm:ss" };
|
|
1401
1478
|
* ```
|
|
1402
1479
|
*/
|
|
1403
|
-
type
|
|
1404
|
-
|
|
1405
|
-
body: string;
|
|
1406
|
-
digits: string;
|
|
1407
|
-
};
|
|
1408
|
-
};
|
|
1409
|
-
/**
|
|
1410
|
-
* text plugin state.
|
|
1411
|
-
*/
|
|
1412
|
-
type State = {
|
|
1413
|
-
/** 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. */
|
|
1414
|
-
styleOwner: Map<string, string>; /** The config fonts plus every `font`, `bold` and `italic` of every style. */
|
|
1415
|
-
fontKeys: Set<string>; /** The parsed advance table per font key, filled when `assets` has the font. */
|
|
1416
|
-
tables: Map<string, AdvanceTable>; /** The font keys handed to `renderer.sync.fonts.install`. */
|
|
1417
|
-
installed: Set<string>; /** What was resolved for an entity last. */
|
|
1418
|
-
seen: Map<Entity, SeenText>; /** `bind.component` to its world type; `undefined` after the one warning. */
|
|
1419
|
-
bindTypes: Map<string, AnyComponent | undefined>; /** The measured size of every live label, for `ui`. */
|
|
1420
|
-
measured: Map<Entity, Size>; /** Layout per `style + resolved`, oldest dropped first. */
|
|
1421
|
-
cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
|
|
1422
|
-
drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
|
|
1423
|
-
dirty: Set<Entity>; /** Warning keys already written, so a style, a tag, a glyph or a font warns once. */
|
|
1424
|
-
warned: Set<string>; /** The system, the two world hooks and the display adapter. */
|
|
1425
|
-
removers: Array<() => void>;
|
|
1480
|
+
type BindOptions = {
|
|
1481
|
+
format?: TextFormat;
|
|
1426
1482
|
};
|
|
1427
1483
|
/**
|
|
1428
|
-
*
|
|
1429
|
-
*
|
|
1430
|
-
* the frame.
|
|
1484
|
+
* A moment to count down to, and how much is left. A game writes `until`, a moment of `clock` in
|
|
1485
|
+
* epoch milliseconds; `left` is engine-owned and follows what the label shows.
|
|
1431
1486
|
*
|
|
1432
1487
|
* @example
|
|
1433
1488
|
* ```ts
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1489
|
+
* // clock.now() is 1_790_000_000_000: the chest opens in 95 s.
|
|
1490
|
+
* const chest: CountdownValue = { until: 1_790_000_095_000, left: 95_000 };
|
|
1436
1491
|
* ```
|
|
1437
1492
|
*/
|
|
1438
|
-
type
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
* A `Message` is formatted in the current locale first. Never touches a canvas, so the answer
|
|
1442
|
-
* is the same in a browser and in plain Bun. Cached per style and resolved string.
|
|
1443
|
-
*
|
|
1444
|
-
* @param content - A plain string, or what `tr` returned.
|
|
1445
|
-
* @param style - A registered style name. An unknown one warns once and measures as `body`.
|
|
1446
|
-
* @returns The width and height of the block.
|
|
1447
|
-
* @example
|
|
1448
|
-
* ```ts
|
|
1449
|
-
* // `ui.layout` asks once per invalidation, from the Yoga measure function of a text element.
|
|
1450
|
-
* const text = ctx.require(textPlugin);
|
|
1451
|
-
*
|
|
1452
|
-
* text.measure("120", "digits"); // { width: 57.6, height: 38.4 } on the 0.6 em fallback
|
|
1453
|
-
* ```
|
|
1454
|
-
*/
|
|
1455
|
-
measure(content: string | Message, style: string): Size;
|
|
1456
|
-
/**
|
|
1457
|
-
* Whether the font of a style has a glyph for one character, the lookup Pixi draws by. A
|
|
1458
|
-
* character its font lacks is drawn as nothing and `measure` counts it 0 wide. While the font is
|
|
1459
|
-
* not loaded every character counts, as the 0.6 em fallback measures it. An unknown style warns
|
|
1460
|
-
* once and looks up `body`.
|
|
1461
|
-
*
|
|
1462
|
-
* @param char - One character, a whole code point.
|
|
1463
|
-
* @param style - A registered style name.
|
|
1464
|
-
* @returns False only for a character a loaded font has no glyph for.
|
|
1465
|
-
* @example
|
|
1466
|
-
* ```ts
|
|
1467
|
-
* // `ui` measures the caret of a text field over the characters the font draws.
|
|
1468
|
-
* const text = ctx.require(textPlugin);
|
|
1469
|
-
*
|
|
1470
|
-
* text.hasGlyph("A", "body"); // true
|
|
1471
|
-
* text.hasGlyph("😀", "body"); // false once the body font is loaded: it has no emoji glyph
|
|
1472
|
-
* ```
|
|
1473
|
-
*/
|
|
1474
|
-
hasGlyph(char: string, style: string): boolean;
|
|
1475
|
-
/**
|
|
1476
|
-
* The registered style names: the two built-ins first, then the styles of every feature in
|
|
1477
|
-
* feature order.
|
|
1478
|
-
*
|
|
1479
|
-
* @returns A frozen list of style names.
|
|
1480
|
-
* @example
|
|
1481
|
-
* ```ts
|
|
1482
|
-
* // A dev overlay lists what a game may write in a `style` prop.
|
|
1483
|
-
* app.text.styles(); // ["body", "digits", "hud.digits"]
|
|
1484
|
-
* ```
|
|
1485
|
-
*/
|
|
1486
|
-
styles(): readonly string[];
|
|
1487
|
-
/**
|
|
1488
|
-
* The dev hot swap of a styles file: writes every style of the map over the table. A known
|
|
1489
|
-
* name keeps the feature that owns it, a new name is added and usable at once. The fonts the
|
|
1490
|
-
* styles name are read, the measured layouts are dropped, and every label is laid out again
|
|
1491
|
-
* on the next frame and redrawn with the new style, even when its text and its style name did
|
|
1492
|
-
* not change. The game loop is woken for that frame.
|
|
1493
|
-
*
|
|
1494
|
-
* @param styles - What `defineTextStyles` returned in the saved module.
|
|
1495
|
-
* @example
|
|
1496
|
-
* ```ts
|
|
1497
|
-
* // `ui` hot swaps a saved `hud/styles.ts`: the "hud.title" labels redraw at 64 px.
|
|
1498
|
-
* const uiStyles = defineTextStyles({
|
|
1499
|
-
* "hud.title": { font: "ui.font-body", size: 64, fill: 0xff0000 }
|
|
1500
|
-
* });
|
|
1501
|
-
*
|
|
1502
|
-
* ctx.require(textPlugin).replaceStyles(uiStyles);
|
|
1503
|
-
* ctx.require(textPlugin).measure("12", "hud.title").height; // 76.8 on the 0.6 em fallback
|
|
1504
|
-
* ```
|
|
1505
|
-
*/
|
|
1506
|
-
replaceStyles(styles: TextStyles): void;
|
|
1507
|
-
};
|
|
1508
|
-
/**
|
|
1509
|
-
* Resolved dependency APIs.
|
|
1510
|
-
*/
|
|
1511
|
-
type Deps = {
|
|
1512
|
-
time: Api;
|
|
1513
|
-
flow: Api$1;
|
|
1514
|
-
world: Api$3;
|
|
1515
|
-
renderer: Api$4;
|
|
1516
|
-
assets: Api$5;
|
|
1517
|
-
i18n: I18nApi;
|
|
1518
|
-
clock: Api$2;
|
|
1519
|
-
};
|
|
1520
|
-
/**
|
|
1521
|
-
* What the kernel context offers before the deps are attached.
|
|
1522
|
-
*
|
|
1523
|
-
* `text` owns no event, so `emit` is the kernel's and never called here.
|
|
1524
|
-
*/
|
|
1525
|
-
type KernelSlice = PluginCtx<Config, State> & {
|
|
1526
|
-
readonly global: object;
|
|
1527
|
-
readonly log: Log.LogApi;
|
|
1528
|
-
readonly require: Require;
|
|
1529
|
-
};
|
|
1530
|
-
/**
|
|
1531
|
-
* Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
|
|
1532
|
-
*/
|
|
1533
|
-
type TextCtx = KernelSlice & {
|
|
1534
|
-
readonly deps: Deps;
|
|
1493
|
+
type CountdownValue = {
|
|
1494
|
+
until: number;
|
|
1495
|
+
left: number;
|
|
1535
1496
|
};
|
|
1536
1497
|
/**
|
|
1537
|
-
*
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
/**
|
|
1541
|
-
* Payload of the `assets:bundle-unloaded` hook: `keys` names the assets that left.
|
|
1542
|
-
*/
|
|
1543
|
-
type BundleUnloaded = Events$1["assets:bundle-unloaded"];
|
|
1544
|
-
/**
|
|
1545
|
-
* Payload of the `i18n:locale-changed` hook: every message has to be resolved again.
|
|
1546
|
-
*/
|
|
1547
|
-
type LocaleChanged = Events["i18n:locale-changed"];
|
|
1548
|
-
//#endregion
|
|
1549
|
-
//#region src/plugins/ui/jsx/component.d.ts
|
|
1550
|
-
/**
|
|
1551
|
-
* Declares a component: a piece of interface with local state, outcomes, or both. The view runs
|
|
1552
|
-
* at every reconcile with the instance's own local state, never at build time.
|
|
1498
|
+
* What the `Text` component holds. `resolved` is engine-owned: a game writes `content`, `style`,
|
|
1499
|
+
* `bind`, `anchor` and `alpha`, and reads `resolved`. `alpha` fades every run, shadow and icon of
|
|
1500
|
+
* the block together, 1 by default; a change is written in place and a tween can drive it.
|
|
1553
1501
|
*
|
|
1554
|
-
* @param name - Unique name across every feature of the game.
|
|
1555
|
-
* @param spec - The initial local state, the outcomes a popup resolves with, and the view.
|
|
1556
|
-
* @returns The component, ready to stand in a tag and in a feature's `ui` key.
|
|
1557
1502
|
* @example
|
|
1558
1503
|
* ```ts
|
|
1559
|
-
* const
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
* }
|
|
1563
|
-
*
|
|
1504
|
+
* const value: TextValue = {
|
|
1505
|
+
* content: "+5", style: "body", bind: undefined, anchor: { x: 0.5, y: 0.5 }, alpha: 1,
|
|
1506
|
+
* resolved: "+5"
|
|
1507
|
+
* };
|
|
1508
|
+
* // The hint of an empty name field, at half alpha.
|
|
1509
|
+
* const hint: TextValue = { ...value, content: "Your name", alpha: 0.5, resolved: "Your name" };
|
|
1564
1510
|
* ```
|
|
1565
1511
|
*/
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1512
|
+
type TextValue = {
|
|
1513
|
+
content: string | Message;
|
|
1514
|
+
style: string;
|
|
1515
|
+
bind: TextBind | undefined;
|
|
1516
|
+
anchor: Point;
|
|
1517
|
+
alpha: number;
|
|
1518
|
+
resolved: string;
|
|
1519
|
+
};
|
|
1569
1520
|
/**
|
|
1570
|
-
*
|
|
1521
|
+
* Where the lines of a block sit inside the widest of them.
|
|
1571
1522
|
*
|
|
1572
|
-
* @param name - Unique name across every feature of the game.
|
|
1573
|
-
* @param spec - The initial local state and the view.
|
|
1574
|
-
* @returns The component, ready to stand in a tag.
|
|
1575
1523
|
* @example
|
|
1576
1524
|
* ```ts
|
|
1577
|
-
* const
|
|
1578
|
-
* Row.outcomes; // undefined
|
|
1525
|
+
* const align: TextAlign = "center";
|
|
1579
1526
|
* ```
|
|
1580
1527
|
*/
|
|
1581
|
-
|
|
1582
|
-
//#endregion
|
|
1583
|
-
//#region src/plugins/ui/styles/types.d.ts
|
|
1528
|
+
type TextAlign = "left" | "center" | "right";
|
|
1584
1529
|
/**
|
|
1585
|
-
*
|
|
1586
|
-
* matching `viewport.safeArea` value.
|
|
1530
|
+
* The wrap width of a style in reference pixels, or `"none"` for one line per `\n` segment.
|
|
1587
1531
|
*
|
|
1588
1532
|
* @example
|
|
1589
1533
|
* ```ts
|
|
1590
|
-
* const
|
|
1534
|
+
* const wrap: TextWrap = 480;
|
|
1591
1535
|
* ```
|
|
1592
1536
|
*/
|
|
1593
|
-
type
|
|
1537
|
+
type TextWrap = number | "none";
|
|
1594
1538
|
/**
|
|
1595
|
-
*
|
|
1539
|
+
* The drop shadow of a style as the plugin stores it: a copy of every glyph run in `color`, at
|
|
1540
|
+
* `alpha`, moved by `dx` and `dy` reference pixels.
|
|
1596
1541
|
*
|
|
1597
1542
|
* @example
|
|
1598
1543
|
* ```ts
|
|
1599
|
-
* const
|
|
1544
|
+
* const shadow: TextShadow = { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 };
|
|
1600
1545
|
* ```
|
|
1601
1546
|
*/
|
|
1602
|
-
type
|
|
1547
|
+
type TextShadow = {
|
|
1548
|
+
color: number;
|
|
1549
|
+
dx: number;
|
|
1550
|
+
dy: number;
|
|
1551
|
+
alpha: number;
|
|
1552
|
+
};
|
|
1603
1553
|
/**
|
|
1604
|
-
*
|
|
1554
|
+
* One style as a game writes it: the font and the size are required, the rest has defaults.
|
|
1605
1555
|
*
|
|
1606
1556
|
* @example
|
|
1607
1557
|
* ```ts
|
|
1608
|
-
*
|
|
1558
|
+
* // A popup title: cream display glyphs over a brown shadow 4 px down.
|
|
1559
|
+
* const title: TextStyleInput = {
|
|
1560
|
+
* font: "ui.font-display", size: 56, fill: 0xfff3d6, shadow: { color: 0x5b3a1e, dx: 0, dy: 4 }
|
|
1561
|
+
* };
|
|
1562
|
+
*
|
|
1563
|
+
* defineTextStyles({ "popup.title": title }).map["popup.title"]?.shadow;
|
|
1564
|
+
* // { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 }
|
|
1609
1565
|
* ```
|
|
1610
1566
|
*/
|
|
1611
|
-
type
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1567
|
+
type TextStyleInput = {
|
|
1568
|
+
font: string;
|
|
1569
|
+
bold?: string;
|
|
1570
|
+
italic?: string;
|
|
1571
|
+
size: number;
|
|
1572
|
+
fill: number; /** The outline colour. Drawn only when `strokeWidth` is above 0. */
|
|
1573
|
+
stroke?: number;
|
|
1574
|
+
/**
|
|
1575
|
+
* The outline width in reference px: 8 copies of each glyph run on a circle this wide (12 from 6
|
|
1576
|
+
* on), tinted `stroke`. 0 draws none. It never changes what `measure` answers.
|
|
1577
|
+
*/
|
|
1578
|
+
strokeWidth?: number;
|
|
1579
|
+
letterSpacing?: number;
|
|
1580
|
+
align?: TextAlign;
|
|
1581
|
+
wrap?: TextWrap;
|
|
1582
|
+
digits?: boolean;
|
|
1583
|
+
/**
|
|
1584
|
+
* A drop shadow under every glyph run, in reference px at the style size. `alpha` is 1 when
|
|
1585
|
+
* left out. It never changes what `measure` answers.
|
|
1586
|
+
*/
|
|
1587
|
+
shadow?: {
|
|
1588
|
+
color: number;
|
|
1589
|
+
dx: number;
|
|
1590
|
+
dy: number;
|
|
1591
|
+
alpha?: number;
|
|
1592
|
+
};
|
|
1616
1593
|
};
|
|
1617
1594
|
/**
|
|
1618
|
-
*
|
|
1595
|
+
* One style as the plugin stores it: every field filled, `bold` and `italic` only when the game
|
|
1596
|
+
* shipped those MSDF fonts, `shadow` only when the style has one.
|
|
1619
1597
|
*
|
|
1620
1598
|
* @example
|
|
1621
1599
|
* ```ts
|
|
1622
|
-
* const
|
|
1600
|
+
* const style: TextStyle = {
|
|
1601
|
+
* font: "ui.font-body", bold: undefined, italic: undefined, size: 32, fill: 0xffffff,
|
|
1602
|
+
* stroke: 0x000000, strokeWidth: 0, letterSpacing: 0, align: "left", wrap: "none",
|
|
1603
|
+
* digits: false, shadow: undefined
|
|
1604
|
+
* };
|
|
1623
1605
|
* ```
|
|
1624
1606
|
*/
|
|
1625
|
-
type
|
|
1607
|
+
type TextStyle = {
|
|
1608
|
+
font: string;
|
|
1609
|
+
bold: string | undefined;
|
|
1610
|
+
italic: string | undefined;
|
|
1611
|
+
size: number;
|
|
1612
|
+
fill: number;
|
|
1613
|
+
stroke: number;
|
|
1614
|
+
strokeWidth: number;
|
|
1615
|
+
letterSpacing: number;
|
|
1616
|
+
align: TextAlign;
|
|
1617
|
+
wrap: TextWrap;
|
|
1618
|
+
digits: boolean;
|
|
1619
|
+
shadow: TextShadow | undefined;
|
|
1620
|
+
};
|
|
1626
1621
|
/**
|
|
1627
|
-
*
|
|
1622
|
+
* What `defineTextStyles` returns and a feature registers under its `textStyles` key. Structural
|
|
1623
|
+
* on purpose: `flow` carries the map, `text` reads it.
|
|
1628
1624
|
*
|
|
1629
1625
|
* @example
|
|
1630
1626
|
* ```ts
|
|
1631
|
-
* const
|
|
1627
|
+
* const styles: TextStyles = { kind: "textStyles", map: { "hud.title": bodyStyle } };
|
|
1632
1628
|
* ```
|
|
1633
1629
|
*/
|
|
1634
|
-
type
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
justify?: "start" | "center" | "end" | "between" | "around" | "evenly";
|
|
1638
|
-
align?: "start" | "center" | "end" | "stretch";
|
|
1639
|
-
alignSelf?: "auto" | "start" | "center" | "end" | "stretch";
|
|
1640
|
-
gap?: number;
|
|
1641
|
-
grow?: number;
|
|
1642
|
-
shrink?: number;
|
|
1630
|
+
type TextStyles = {
|
|
1631
|
+
readonly kind: "textStyles";
|
|
1632
|
+
readonly map: Record<string, TextStyle>;
|
|
1643
1633
|
};
|
|
1644
1634
|
/**
|
|
1645
|
-
* The
|
|
1635
|
+
* The advances of one font at the size the `.fnt` was exported with. Widths scale by
|
|
1636
|
+
* `style.size / size`; kerning pairs are ignored.
|
|
1646
1637
|
*
|
|
1647
1638
|
* @example
|
|
1648
1639
|
* ```ts
|
|
1649
|
-
*
|
|
1650
|
-
* const box: BoxStyle = { width: 970, height: 970, fit: "contain" };
|
|
1640
|
+
* const table: AdvanceTable = { size: 32, lineHeight: 40, advances: new Map([["1", 18]]) };
|
|
1651
1641
|
* ```
|
|
1652
1642
|
*/
|
|
1653
|
-
type
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
height?: Extent;
|
|
1658
|
-
minWidth?: Extent;
|
|
1659
|
-
minHeight?: Extent;
|
|
1660
|
-
maxWidth?: Extent;
|
|
1661
|
-
maxHeight?: Extent;
|
|
1662
|
-
aspect?: number; /** `"hidden"` clips the children to the rect, as a scroll container does. */
|
|
1663
|
-
overflow?: "visible" | "hidden" | "scroll";
|
|
1664
|
-
/**
|
|
1665
|
-
* `"contain"`: the element keeps its own size and leaves the flow, then is scaled down to fit
|
|
1666
|
-
* the content box of its parent and centred in it. Its children keep their natural rects.
|
|
1667
|
-
*/
|
|
1668
|
-
fit?: "contain";
|
|
1643
|
+
type AdvanceTable = {
|
|
1644
|
+
size: number;
|
|
1645
|
+
lineHeight: number;
|
|
1646
|
+
advances: Map<string, number>;
|
|
1669
1647
|
};
|
|
1670
1648
|
/**
|
|
1671
|
-
*
|
|
1649
|
+
* One run of glyphs: the text and the flags every glyph in it shares.
|
|
1672
1650
|
*
|
|
1673
1651
|
* @example
|
|
1674
1652
|
* ```ts
|
|
1675
|
-
* const
|
|
1676
|
-
* // The order strip of the board drawn over the tray that follows it.
|
|
1677
|
-
* const strip: PositionStyle = { zIndex: 1 };
|
|
1653
|
+
* const run: TextRun = { kind: "text", text: "+5", bold: true, italic: false, color: 0xffe082 };
|
|
1678
1654
|
* ```
|
|
1679
1655
|
*/
|
|
1680
|
-
type
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
reason?: string;
|
|
1687
|
-
/**
|
|
1688
|
-
* The draw order among the siblings, an integer: a higher one draws over a lower one, and a
|
|
1689
|
-
* sibling without it draws at 0 in markup order. Ignored on a root element, which `lint()`
|
|
1690
|
-
* reports; a root draws at the order of its layer.
|
|
1691
|
-
*/
|
|
1692
|
-
zIndex?: number;
|
|
1656
|
+
type TextRun = {
|
|
1657
|
+
kind: "text";
|
|
1658
|
+
text: string;
|
|
1659
|
+
bold: boolean;
|
|
1660
|
+
italic: boolean;
|
|
1661
|
+
color: number | undefined;
|
|
1693
1662
|
};
|
|
1694
1663
|
/**
|
|
1695
|
-
*
|
|
1696
|
-
* here — the size of a text comes from its text style key.
|
|
1664
|
+
* One inline icon: an asset key drawn square at the line height.
|
|
1697
1665
|
*
|
|
1698
1666
|
* @example
|
|
1699
1667
|
* ```ts
|
|
1700
|
-
*
|
|
1701
|
-
* const visual: VisualStyle = { nineSlice: "ui.button-wood", alpha: 1, tint: 0xffffff };
|
|
1702
|
-
* // The popup board while its insets are checked: the slice lines drawn over it.
|
|
1703
|
-
* const checked: VisualStyle = { nineSlice: "ui.panel-signboard", debug: true };
|
|
1704
|
-
* // The play glyph of the Watch button, and a dashed ring around a slot.
|
|
1705
|
-
* const play: VisualStyle = { shape: "triangle", fill: 0xfffbe8 };
|
|
1706
|
-
* const slot: VisualStyle = { stroke: 0x3a2212, strokeWidth: 4, dash: 10, radius: 24 };
|
|
1668
|
+
* const run: IconRun = { kind: "icon", key: "hud.coin" };
|
|
1707
1669
|
* ```
|
|
1708
1670
|
*/
|
|
1709
|
-
type
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
strokeWidth?: number;
|
|
1713
|
-
radius?: number;
|
|
1714
|
-
alpha?: number;
|
|
1715
|
-
/**
|
|
1716
|
-
* The shape of the rectangle an element without a nine-slice is drawn with: `"rect"` (the
|
|
1717
|
-
* default) or `"triangle"`, which fills the box pointing right and ignores `radius`. Turn the
|
|
1718
|
-
* element with `rotation` for another direction.
|
|
1719
|
-
*/
|
|
1720
|
-
shape?: "rect" | "triangle"; /** The dash length of the stroke in reference units, with gaps of half a dash; 0 is solid. */
|
|
1721
|
-
dash?: number;
|
|
1722
|
-
/**
|
|
1723
|
-
* The asset key of a nine-slice drawn at the rect instead of the rounded rectangle. Any tag
|
|
1724
|
-
* but `image`, `icon` and `text` takes it; a clipping element (`scroll`, `overflow: "hidden"`)
|
|
1725
|
-
* keeps its rectangle, which carries the clip.
|
|
1726
|
-
*/
|
|
1727
|
-
nineSlice?: Asset; /** Multiplies the colour of a nine-slice or an image. */
|
|
1728
|
-
tint?: number;
|
|
1729
|
-
/**
|
|
1730
|
-
* Outlines the nine-slice of the style: its bounds and the four slice lines, cyan, red when
|
|
1731
|
-
* the corners overlap or the texture is missing. A look for checking art, never for a player.
|
|
1732
|
-
*/
|
|
1733
|
-
debug?: boolean;
|
|
1671
|
+
type IconRun = {
|
|
1672
|
+
kind: "icon";
|
|
1673
|
+
key: string;
|
|
1734
1674
|
};
|
|
1735
1675
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
* corner, or a point given in fractions of its box.
|
|
1676
|
+
* A piece of a line: glyphs or one inline icon.
|
|
1738
1677
|
*
|
|
1739
1678
|
* @example
|
|
1740
1679
|
* ```ts
|
|
1741
|
-
*
|
|
1742
|
-
* const origin: Origin = "top";
|
|
1680
|
+
* const runs: Run[] = [{ kind: "icon", key: "hud.coin" }];
|
|
1743
1681
|
* ```
|
|
1744
1682
|
*/
|
|
1745
|
-
type
|
|
1746
|
-
x: number;
|
|
1747
|
-
y: number;
|
|
1748
|
-
};
|
|
1683
|
+
type Run = TextRun | IconRun;
|
|
1749
1684
|
/**
|
|
1750
|
-
*
|
|
1751
|
-
* rest `Transform`, so a hover lift or a pressed sink never moves a sibling.
|
|
1685
|
+
* One laid-out line and how wide it is.
|
|
1752
1686
|
*
|
|
1753
1687
|
* @example
|
|
1754
1688
|
* ```ts
|
|
1755
|
-
*
|
|
1756
|
-
* const lift: TransformStyle = { offsetY: -6, scale: 1.05, origin: "center" };
|
|
1757
|
-
* // The title plaque of a popup, tilted by 1.5 degrees around the middle of its top edge.
|
|
1758
|
-
* const plaque: TransformStyle = { rotation: -0.026, origin: "top" };
|
|
1689
|
+
* const line: Line = { runs: [{ kind: "icon", key: "hud.coin" }], width: 38.4 };
|
|
1759
1690
|
* ```
|
|
1760
1691
|
*/
|
|
1761
|
-
type
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
scale?: number; /** Turns the drawn element around the origin, in radians, clockwise; 0 by default. */
|
|
1765
|
-
rotation?: number; /** The point the element scales and turns around; the centre by default. */
|
|
1766
|
-
origin?: Origin;
|
|
1692
|
+
type Line = {
|
|
1693
|
+
runs: Run[];
|
|
1694
|
+
width: number;
|
|
1767
1695
|
};
|
|
1768
1696
|
/**
|
|
1769
|
-
*
|
|
1697
|
+
* A laid-out block: its lines and the size `measure` answers with.
|
|
1770
1698
|
*
|
|
1771
1699
|
* @example
|
|
1772
1700
|
* ```ts
|
|
1773
|
-
* const
|
|
1701
|
+
* const layout: TextLayout = { lines: [], width: 0, height: 0 };
|
|
1774
1702
|
* ```
|
|
1775
1703
|
*/
|
|
1776
|
-
type
|
|
1704
|
+
type TextLayout = {
|
|
1705
|
+
lines: Line[];
|
|
1706
|
+
width: number;
|
|
1707
|
+
height: number;
|
|
1708
|
+
};
|
|
1777
1709
|
/**
|
|
1778
|
-
*
|
|
1779
|
-
*
|
|
1710
|
+
* What one label container was last filled from: the style object and the laid-out block. An
|
|
1711
|
+
* update with the same style object, the same anchor and the same lines and runs writes the new
|
|
1712
|
+
* text and positions into the objects already there.
|
|
1713
|
+
*/
|
|
1714
|
+
type DrawnLabel = {
|
|
1715
|
+
style: TextStyle;
|
|
1716
|
+
layout: TextLayout;
|
|
1717
|
+
};
|
|
1718
|
+
/**
|
|
1719
|
+
* A dev warning that is written once per key. Pure modules take it as an argument, so nothing
|
|
1720
|
+
* below the plugin context reaches the log itself.
|
|
1780
1721
|
*
|
|
1781
1722
|
* @example
|
|
1782
1723
|
* ```ts
|
|
1783
|
-
*
|
|
1784
|
-
* const variants: IsVariants = {
|
|
1785
|
-
* hover: { offsetY: -6, scale: 1.05 },
|
|
1786
|
-
* focus: { scale: 1.05 },
|
|
1787
|
-
* pressed: { offsetY: 4, scale: 0.95 },
|
|
1788
|
-
* disabled: { nineSlice: "ui.button-disabled" }
|
|
1789
|
-
* };
|
|
1724
|
+
* const warn: Warn = (key, message) => seen.has(key) || log.warn(message);
|
|
1790
1725
|
* ```
|
|
1791
1726
|
*/
|
|
1792
|
-
type
|
|
1793
|
-
pressed?: BaseStyle<Asset>;
|
|
1794
|
-
disabled?: BaseStyle<Asset>;
|
|
1795
|
-
active?: BaseStyle<Asset>;
|
|
1796
|
-
selected?: BaseStyle<Asset>; /** The mouse or pen is over the element. Touch never hovers. */
|
|
1797
|
-
hover?: BaseStyle<Asset>; /** The keyboard focus is on the element: Tab moved it there, and no pointer tapped since. */
|
|
1798
|
-
focus?: BaseStyle<Asset>; /** The popup the element belongs to is kept under another one. */
|
|
1799
|
-
covered?: BaseStyle<Asset>;
|
|
1800
|
-
};
|
|
1727
|
+
type Warn = (key: string, message: string, data?: Record<string, unknown>) => void;
|
|
1801
1728
|
/**
|
|
1802
|
-
*
|
|
1729
|
+
* What the pure layout needs next to the runs, the style and the tables.
|
|
1803
1730
|
*
|
|
1804
1731
|
* @example
|
|
1805
1732
|
* ```ts
|
|
1806
|
-
* const
|
|
1733
|
+
* const options: LayoutOptions = { warn: () => undefined };
|
|
1807
1734
|
* ```
|
|
1808
1735
|
*/
|
|
1809
|
-
type
|
|
1810
|
-
|
|
1811
|
-
landscape?: BaseStyle<Asset>;
|
|
1812
|
-
tall?: BaseStyle<Asset>;
|
|
1813
|
-
wide?: BaseStyle<Asset>;
|
|
1736
|
+
type LayoutOptions = {
|
|
1737
|
+
warn: Warn;
|
|
1814
1738
|
};
|
|
1815
1739
|
/**
|
|
1816
|
-
*
|
|
1817
|
-
*
|
|
1740
|
+
* What was resolved for one entity last, so the frame step knows whether anything moved. `unit`
|
|
1741
|
+
* is what the shown string of a bound label was built from: the rounded value for `"int"`, whole
|
|
1742
|
+
* seconds for a time format.
|
|
1818
1743
|
*
|
|
1819
1744
|
* @example
|
|
1820
1745
|
* ```ts
|
|
1821
|
-
* const
|
|
1746
|
+
* const seen: SeenText = {
|
|
1747
|
+
* content: "", style: "digits", bind: "Countdown.left:mm:ss", locale: "ru", unit: 95
|
|
1748
|
+
* };
|
|
1822
1749
|
* ```
|
|
1823
1750
|
*/
|
|
1824
|
-
type
|
|
1825
|
-
|
|
1826
|
-
|
|
1751
|
+
type SeenText = {
|
|
1752
|
+
content: unknown;
|
|
1753
|
+
style: string;
|
|
1754
|
+
bind: string | undefined;
|
|
1755
|
+
locale: string;
|
|
1756
|
+
unit: number | undefined;
|
|
1827
1757
|
};
|
|
1828
1758
|
/**
|
|
1829
|
-
*
|
|
1830
|
-
* number and every variant already merged in. Frozen.
|
|
1759
|
+
* text plugin config.
|
|
1831
1760
|
*
|
|
1832
1761
|
* @example
|
|
1833
1762
|
* ```ts
|
|
1834
|
-
*
|
|
1763
|
+
* createApp({ pluginConfigs: { text: { fonts: { body: "ui.body", digits: "ui.digits" } } } });
|
|
1835
1764
|
* ```
|
|
1836
1765
|
*/
|
|
1837
|
-
type
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
bottom?: number;
|
|
1842
|
-
left?: number;
|
|
1843
|
-
};
|
|
1844
|
-
margin?: number | {
|
|
1845
|
-
top?: number;
|
|
1846
|
-
right?: number;
|
|
1847
|
-
bottom?: number;
|
|
1848
|
-
left?: number;
|
|
1766
|
+
type Config = {
|
|
1767
|
+
/** The two fonts every game ships in its boot bundle. They back the built-in styles. */fonts: {
|
|
1768
|
+
body: string;
|
|
1769
|
+
digits: string;
|
|
1849
1770
|
};
|
|
1850
|
-
left?: number;
|
|
1851
|
-
top?: number;
|
|
1852
|
-
right?: number;
|
|
1853
|
-
bottom?: number;
|
|
1854
1771
|
};
|
|
1855
1772
|
/**
|
|
1856
|
-
*
|
|
1857
|
-
*
|
|
1858
|
-
* @example
|
|
1859
|
-
* ```ts
|
|
1860
|
-
* const flags: WhenFlags = { portrait: true, landscape: false, tall: true, wide: false };
|
|
1861
|
-
* ```
|
|
1773
|
+
* text plugin state.
|
|
1862
1774
|
*/
|
|
1863
|
-
type
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1775
|
+
type State = {
|
|
1776
|
+
/** 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. */
|
|
1777
|
+
styleOwner: Map<string, string>; /** The config fonts plus every `font`, `bold` and `italic` of every style. */
|
|
1778
|
+
fontKeys: Set<string>; /** The parsed advance table per font key, filled when `assets` has the font. */
|
|
1779
|
+
tables: Map<string, AdvanceTable>; /** The font keys handed to `renderer.sync.fonts.install`. */
|
|
1780
|
+
installed: Set<string>; /** What was resolved for an entity last. */
|
|
1781
|
+
seen: Map<Entity, SeenText>; /** `bind.component` to its world type; `undefined` after the one warning. */
|
|
1782
|
+
bindTypes: Map<string, AnyComponent | undefined>; /** The measured size of every live label, for `ui`. */
|
|
1783
|
+
measured: Map<Entity, Size>; /** Layout per `style + resolved`, oldest dropped first. */
|
|
1784
|
+
cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
|
|
1785
|
+
drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
|
|
1786
|
+
dirty: Set<Entity>; /** Warning keys already written, so a style, a tag, a glyph or a font warns once. */
|
|
1787
|
+
warned: Set<string>; /** The system, the two world hooks and the display adapter. */
|
|
1788
|
+
removers: Array<() => void>;
|
|
1868
1789
|
};
|
|
1869
1790
|
/**
|
|
1870
|
-
*
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1791
|
+
* text plugin API, `app.text`. Two questions: how big a piece of text is, and which styles the
|
|
1792
|
+
* game registered; and one order, the dev hot swap of a styles file. Everything else happens in
|
|
1793
|
+
* the frame.
|
|
1873
1794
|
*
|
|
1874
1795
|
* @example
|
|
1875
1796
|
* ```ts
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
* covered: false
|
|
1879
|
-
* };
|
|
1797
|
+
* app.text.measure("120", "digits"); // { width: 57.6, height: 38.4 }
|
|
1798
|
+
* app.text.styles(); // ["body", "digits"]
|
|
1880
1799
|
* ```
|
|
1881
1800
|
*/
|
|
1882
|
-
type
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1801
|
+
type TextApi = {
|
|
1802
|
+
/**
|
|
1803
|
+
* The size of a piece of text in reference pixels, from the advance table of the style's font.
|
|
1804
|
+
* A `Message` is formatted in the current locale first. Never touches a canvas, so the answer
|
|
1805
|
+
* is the same in a browser and in plain Bun. Cached per style and resolved string.
|
|
1806
|
+
*
|
|
1807
|
+
* @param content - A plain string, or what `tr` returned.
|
|
1808
|
+
* @param style - A registered style name. An unknown one warns once and measures as `body`.
|
|
1809
|
+
* @returns The width and height of the block.
|
|
1810
|
+
* @example
|
|
1811
|
+
* ```ts
|
|
1812
|
+
* // `ui.layout` asks once per invalidation, from the Yoga measure function of a text element.
|
|
1813
|
+
* const text = ctx.require(textPlugin);
|
|
1814
|
+
*
|
|
1815
|
+
* text.measure("120", "digits"); // { width: 57.6, height: 38.4 } on the 0.6 em fallback
|
|
1816
|
+
* ```
|
|
1817
|
+
*/
|
|
1818
|
+
measure(content: string | Message, style: string): Size;
|
|
1819
|
+
/**
|
|
1820
|
+
* Whether the font of a style has a glyph for one character, the lookup Pixi draws by. A
|
|
1821
|
+
* character its font lacks is drawn as nothing and `measure` counts it 0 wide. While the font is
|
|
1822
|
+
* not loaded every character counts, as the 0.6 em fallback measures it. An unknown style warns
|
|
1823
|
+
* once and looks up `body`.
|
|
1824
|
+
*
|
|
1825
|
+
* @param char - One character, a whole code point.
|
|
1826
|
+
* @param style - A registered style name.
|
|
1827
|
+
* @returns False only for a character a loaded font has no glyph for.
|
|
1828
|
+
* @example
|
|
1829
|
+
* ```ts
|
|
1830
|
+
* // `ui` measures the caret of a text field over the characters the font draws.
|
|
1831
|
+
* const text = ctx.require(textPlugin);
|
|
1832
|
+
*
|
|
1833
|
+
* text.hasGlyph("A", "body"); // true
|
|
1834
|
+
* text.hasGlyph("😀", "body"); // false once the body font is loaded: it has no emoji glyph
|
|
1835
|
+
* ```
|
|
1836
|
+
*/
|
|
1837
|
+
hasGlyph(char: string, style: string): boolean;
|
|
1838
|
+
/**
|
|
1839
|
+
* The registered style names: the two built-ins first, then the styles of every feature in
|
|
1840
|
+
* feature order.
|
|
1841
|
+
*
|
|
1842
|
+
* @returns A frozen list of style names.
|
|
1843
|
+
* @example
|
|
1844
|
+
* ```ts
|
|
1845
|
+
* // A dev overlay lists what a game may write in a `style` prop.
|
|
1846
|
+
* app.text.styles(); // ["body", "digits", "hud.digits"]
|
|
1847
|
+
* ```
|
|
1848
|
+
*/
|
|
1849
|
+
styles(): readonly string[];
|
|
1850
|
+
/**
|
|
1851
|
+
* The dev hot swap of a styles file: writes every style of the map over the table. A known
|
|
1852
|
+
* name keeps the feature that owns it, a new name is added and usable at once. The fonts the
|
|
1853
|
+
* styles name are read, the measured layouts are dropped, and every label is laid out again
|
|
1854
|
+
* on the next frame and redrawn with the new style, even when its text and its style name did
|
|
1855
|
+
* not change. The game loop is woken for that frame.
|
|
1856
|
+
*
|
|
1857
|
+
* @param styles - What `defineTextStyles` returned in the saved module.
|
|
1858
|
+
* @example
|
|
1859
|
+
* ```ts
|
|
1860
|
+
* // `ui` hot swaps a saved `hud/styles.ts`: the "hud.title" labels redraw at 64 px.
|
|
1861
|
+
* const uiStyles = defineTextStyles({
|
|
1862
|
+
* "hud.title": { font: "ui.font-body", size: 64, fill: 0xff0000 }
|
|
1863
|
+
* });
|
|
1864
|
+
*
|
|
1865
|
+
* ctx.require(textPlugin).replaceStyles(uiStyles);
|
|
1866
|
+
* ctx.require(textPlugin).measure("12", "hud.title").height; // 76.8 on the 0.6 em fallback
|
|
1867
|
+
* ```
|
|
1868
|
+
*/
|
|
1869
|
+
replaceStyles(styles: TextStyles): void;
|
|
1890
1870
|
};
|
|
1891
1871
|
/**
|
|
1892
|
-
*
|
|
1872
|
+
* Resolved dependency APIs.
|
|
1873
|
+
*/
|
|
1874
|
+
type Deps = {
|
|
1875
|
+
time: Api;
|
|
1876
|
+
flow: Api$1;
|
|
1877
|
+
world: Api$2;
|
|
1878
|
+
renderer: Api$3;
|
|
1879
|
+
assets: Api$4;
|
|
1880
|
+
i18n: I18nApi;
|
|
1881
|
+
clock: Api$5;
|
|
1882
|
+
};
|
|
1883
|
+
/**
|
|
1884
|
+
* What the kernel context offers before the deps are attached.
|
|
1893
1885
|
*
|
|
1894
|
-
*
|
|
1895
|
-
* ```ts
|
|
1896
|
-
* const tokens: Tokens = { space: { md: 16 }, color: { panel: 0x101018 } };
|
|
1897
|
-
* ```
|
|
1886
|
+
* `text` owns no event, so `emit` is the kernel's and never called here.
|
|
1898
1887
|
*/
|
|
1899
|
-
type
|
|
1900
|
-
readonly
|
|
1901
|
-
readonly
|
|
1902
|
-
readonly
|
|
1903
|
-
readonly font?: Readonly<Record<string, string>>;
|
|
1888
|
+
type KernelSlice = PluginCtx<Config, State> & {
|
|
1889
|
+
readonly global: object;
|
|
1890
|
+
readonly log: Log.LogApi;
|
|
1891
|
+
readonly require: Require;
|
|
1904
1892
|
};
|
|
1905
1893
|
/**
|
|
1906
|
-
*
|
|
1894
|
+
* Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
|
|
1907
1895
|
*/
|
|
1908
|
-
type
|
|
1909
|
-
|
|
1910
|
-
viewport: ViewportSize | undefined;
|
|
1896
|
+
type TextCtx = KernelSlice & {
|
|
1897
|
+
readonly deps: Deps;
|
|
1911
1898
|
};
|
|
1899
|
+
/**
|
|
1900
|
+
* Payload of the `assets:bundle-loaded` hook: a bundle landed, so a font may be readable now.
|
|
1901
|
+
*/
|
|
1902
|
+
type BundleLoaded = Events$1["assets:bundle-loaded"];
|
|
1903
|
+
/**
|
|
1904
|
+
* Payload of the `assets:bundle-unloaded` hook: `keys` names the assets that left.
|
|
1905
|
+
*/
|
|
1906
|
+
type BundleUnloaded = Events$1["assets:bundle-unloaded"];
|
|
1907
|
+
/**
|
|
1908
|
+
* Payload of the `i18n:locale-changed` hook: every message has to be resolved again.
|
|
1909
|
+
*/
|
|
1910
|
+
type LocaleChanged = Events["i18n:locale-changed"];
|
|
1912
1911
|
//#endregion
|
|
1913
1912
|
//#region src/plugins/ui/jsx/intrinsics.d.ts
|
|
1914
1913
|
/**
|
|
@@ -2778,4 +2777,4 @@ type CommonProps = {
|
|
|
2778
2777
|
components?: ElementComponents;
|
|
2779
2778
|
};
|
|
2780
2779
|
//#endregion
|
|
2781
|
-
export {
|
|
2780
|
+
export { ResolvedStyle as $, InputApi as A, SpriteOptions as B, TextFormat as C, types_d_exports as D, TextValue as E, NineSliceValue as F, Argument as G, Transform as H, Parent as I, Message as J, Config$2 as K, Shape as L, types_d_exports$1 as M, Display as N, defineComponent as O, NineSlice as P, IsFlags as Q, ShapeValue as R, TextBind as S, TextStyles as T, TransformValue as U, SpriteValue as V, sprite as W, TypedTr as X, State$2 as Y, types_d_exports$2 as Z, Config as _, JsxChild as a, State as b, UiNode as c, popup as d, Style as et, defineTokens as f, BindOptions as g, BindField as h, Finding as i, State$1 as j, Config$1 as k, Box as l, UiIntrinsicElements as m, ElementChange as n, WhenFlags as nt, JsxState as o, IntrinsicElementsFor as p, I18nApi as q, ElementMotion as r, PopupComponent as s, DescriptionNode as t, StylesState as tt, LocalWrite as u, CountdownValue as v, TextStyleInput as w, TextApi as x, Point as y, Sprite as z };
|