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