@typeonce/ritmo-browser 0.12.0 → 0.14.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/dist/Touch.d.ts CHANGED
@@ -1,17 +1,32 @@
1
1
  /**
2
- * Touch controls over one element: a floating stick, buttons (some of which
3
- * aim by dragging: tap to use, drag to aim, release to fire), and an optional
4
- * swipe area that reports how far a finger moved, for turning or camera
5
- * control (Genshin Impact style: move on the left, swipe anywhere to look).
2
+ * Touch controls over one element: named floating sticks, buttons (some of
3
+ * which aim by dragging: tap to use, drag to aim, release to fire), and an
4
+ * optional swipe area that reports how far a finger moved, for turning or
5
+ * camera control (Genshin Impact style: move on the left, swipe anywhere to
6
+ * look).
7
+ *
8
+ * A stick's key is the vector it drives and a button's key is the action it
9
+ * reports, so a twin-stick shooter declares `move` and `aim` sticks and
10
+ * reads `vectors.move` and `vectors.aim`. Use a stick for a direction the
11
+ * player holds and changes continuously (walking, aiming a stream of shots);
12
+ * use a button with `aimRadius` for a direction picked once per use (aim a
13
+ * throw, release to fire), which reports the aim on the release.
6
14
  *
7
15
  * The layout is a function of the element's size, so controls stay anchored
8
16
  * to corners on any screen. Each finger belongs to the first control it lands
9
- * on: a button when it lands inside one, otherwise the stick when it lands in
10
- * the stick's region (the stick then appears under the thumb), otherwise the
11
- * swipe area when it lands in its region. Fingers that land elsewhere are
12
- * ignored, and a control keeps its finger until it lifts.
17
+ * on: a button when it lands inside one, otherwise the first free stick (in
18
+ * the layout's key order) whose region contains it (the stick then appears
19
+ * under the thumb), otherwise the swipe area when it lands in its region.
20
+ * Fingers that land elsewhere are ignored, and a control keeps its finger
21
+ * until it lifts, so two thumbs drive two sticks at once.
22
+ *
23
+ * A control the game hides (a stick for walking while the player drives) is
24
+ * `null` in the layout. The layout is read again on every snapshot and every
25
+ * new touch, so a control turning `null` under a held finger releases at
26
+ * once, as a cancel, and that finger does nothing until it lifts: a thumb
27
+ * resting on the walking stick does not keep walking a car.
13
28
  *
14
- * Games read one snapshot per update, like keyboard actions: the stick's
29
+ * Games read one snapshot per update, like keyboard actions: each stick's
15
30
  * vector, and per button `held`/`pressed`/`released` edges that survive taps
16
31
  * shorter than a frame, plus the drag `aim`, which is still reported on the
17
32
  * release. A touch the browser cancels (a system gesture, the window losing
@@ -29,18 +44,19 @@
29
44
  * @example
30
45
  * ```ts
31
46
  * const touch = Touch.actions(canvas, window, ({ width, height }) => ({
32
- * // `vector` names the action the stick drives, so it merges into `Controls`.
33
- * stick: { region: { left: 0, top: 0, width: width / 2, height }, radius: 60, vector: "move" },
34
- * buttons: {
35
- * attack: { center: { x: width - 90, y: height - 90 }, radius: 55, aimRadius: 80 },
36
- * dash: { center: { x: width - 200, y: height - 60 }, radius: 36 }
37
- * }
47
+ * // Each stick drives the vector named like its key, so it merges into `Controls`.
48
+ * sticks: {
49
+ * move: { region: { left: 0, top: 0, width: width / 2, height }, radius: 56 },
50
+ * aim: { region: { left: width / 2, top: 0, width: width / 2, height }, radius: 56 }
51
+ * },
52
+ * buttons: { dash: { center: { x: width - 84, y: height - 150 }, radius: 36 } }
38
53
  * }))
39
54
  *
40
55
  * // Once per update:
41
56
  * const { vectors, actions } = touch.snapshot()
42
57
  * move(vectors.move)
43
- * if (actions.attack.released && !actions.attack.cancelled) fire(actions.attack.aim)
58
+ * if (vectors.aim.active) shoot(vectors.aim)
59
+ * if (actions.dash.pressed) dash()
44
60
  * ```
45
61
  *
46
62
  * For tests and headless runs, `scripted(...)` plays a timeline of stick
@@ -61,17 +77,24 @@ export interface Rect {
61
77
  readonly width: number;
62
78
  readonly height: number;
63
79
  }
64
- /** The floating stick of a {@link Layout}. */
65
- export interface StickZone<V extends string = never> {
80
+ /** One floating stick of a {@link Layout}; it drives the vector named like its key in `sticks`. */
81
+ export interface StickZone {
66
82
  /** Where a touch starts the stick. The stick is centered where the thumb lands. */
67
83
  readonly region: Rect;
68
84
  /** Drag distance in CSS pixels for full tilt. */
69
85
  readonly radius: number;
70
86
  /** Fraction of `radius` that reads as zero. Defaults to 0.15. */
71
87
  readonly deadZone?: number;
72
- /** The vector action the stick drives (`"move"`), reported in the snapshot's `vectors`. */
73
- readonly vector?: V;
88
+ /** Not a setting: a stick drives the vector named by its key in `Layout.sticks`. */
89
+ readonly vector?: "a stick drives the vector named by its key in sticks: remove vector";
74
90
  }
91
+ /**
92
+ * The zone of the stick named `K` in a {@link Layout}: a {@link StickZone}
93
+ * (`null` while the stick is hidden), or an error naming the cause when a
94
+ * button has the same name (a name is either a vector a stick drives or an
95
+ * action a button reports).
96
+ */
97
+ export type StickNamed<K extends string, B extends string> = K extends B ? `${K} names both a stick and a button: give them different names` : StickZone | null;
75
98
  /** A round on-screen button of a {@link Layout}; it reports the action named like its key. */
76
99
  export interface ButtonZone {
77
100
  readonly center: Position;
@@ -84,17 +107,31 @@ export interface ButtonZone {
84
107
  }
85
108
  /** The swipe area of a {@link Layout}. */
86
109
  export interface SwipeZone {
87
- /** Where a touch that is not on a button or the stick starts a swipe. */
110
+ /** Where a touch that is not on a button or a stick starts a swipe. */
88
111
  readonly region: Rect;
89
112
  }
90
113
  /**
91
114
  * The touch controls, returned by the layout function {@link actions} calls
92
- * with the element's size. The button names are fixed by the first layout.
115
+ * with the element's size. The stick and button names are fixed by the
116
+ * first layout; a control the game hides keeps its name with a `null` zone.
93
117
  */
94
118
  export interface Layout<B extends string, V extends string = never> {
95
- readonly stick: StickZone<V>;
119
+ /**
120
+ * The floating sticks, each keyed by the vector it drives (`move`, `aim`);
121
+ * `{}` for buttons only. A finger that is not on a button goes to the
122
+ * first stick, in key order, that is free and whose region contains it,
123
+ * so where regions overlap the earlier key wins. `null` hides a stick:
124
+ * it takes no finger and reports a resting vector.
125
+ */
126
+ readonly sticks: {
127
+ readonly [K in V]: StickNamed<K, B>;
128
+ };
129
+ /**
130
+ * The buttons, each keyed by the action it reports. `null` hides a
131
+ * button: it takes no finger and reports nothing held.
132
+ */
96
133
  readonly buttons: {
97
- readonly [K in B]: ButtonZone;
134
+ readonly [K in B]: ButtonZone | null;
98
135
  };
99
136
  /** Optional: fingers here report their movement, for turning or looking around. */
100
137
  readonly swipe?: SwipeZone;
@@ -104,12 +141,14 @@ export interface Size {
104
141
  readonly width: number;
105
142
  readonly height: number;
106
143
  }
107
- /** The stick's state in a snapshot. */
108
- export interface Stick {
144
+ /**
145
+ * One stick's state: its vector (tilt with the dead zone removed, zero at
146
+ * rest, length 1 at full tilt), plus whether a thumb is on it. It is a
147
+ * `Controls.Vector`, so `Controls` merges it like any vector.
148
+ */
149
+ export interface StickState extends Vector {
109
150
  /** Whether a thumb is on the stick. */
110
151
  readonly active: boolean;
111
- /** Tilt with the dead zone removed: zero at rest, length 1 at full tilt. */
112
- readonly vector: Vector;
113
152
  }
114
153
  /** One button's state: its action's edges, plus the drag aim and whether the release was cancelled. */
115
154
  export interface ButtonState extends ActionState {
@@ -130,35 +169,38 @@ export interface Swipe {
130
169
  readonly delta: Vector;
131
170
  }
132
171
  /**
133
- * The controls' state. Each button is an action named like it, and the
134
- * stick drives the vector action its layout names, so the snapshot has the
135
- * same core as the other devices' (`actions`, `vectors`, and `used`) and
136
- * `Controls` can merge it.
172
+ * The controls' state. Each button is an action named like it and each
173
+ * stick a vector named like it, so the snapshot has the same core as the
174
+ * other devices' (`actions`, `vectors`, and `used`) and `Controls` can
175
+ * merge it.
137
176
  */
138
177
  export interface Snapshot<B extends string, V extends string = never> {
139
- readonly stick: Stick;
140
178
  readonly actions: {
141
179
  readonly [K in B]: ButtonState;
142
180
  };
143
- /** The vector action the stick drives, when the layout names one: the stick's vector. */
181
+ /** Each stick's vector, and whether a thumb is on it. */
144
182
  readonly vectors: {
145
- readonly [K in V]: Vector;
183
+ readonly [K in V]: StickState;
146
184
  };
147
185
  readonly swipe: Swipe;
148
186
  /**
149
- * A button was pressed in this window or is held, or a finger is on the
187
+ * A button was pressed in this window or is held, or a finger is on a
150
188
  * stick or the swipe area: the player is using the touch controls.
151
189
  */
152
190
  readonly used: boolean;
153
191
  }
192
+ /** Where a held stick was centered and where its knob is drawn (on the rim at most). */
193
+ export interface StickView {
194
+ readonly origin: Position;
195
+ readonly knob: Position;
196
+ }
154
197
  /** Live control positions for drawing, in CSS pixels. Reading it consumes nothing. */
155
198
  export interface View<B extends string, V extends string = never> {
156
199
  readonly layout: Layout<B, V>;
157
- /** Where the stick was centered and where the thumb is, while it is held. */
158
- readonly stick: {
159
- readonly origin: Position;
160
- readonly knob: Position;
161
- } | null;
200
+ /** Each stick while a thumb holds it, `null` otherwise. */
201
+ readonly sticks: {
202
+ readonly [K in V]: StickView | null;
203
+ };
162
204
  readonly buttons: {
163
205
  readonly [K in B]: {
164
206
  readonly held: boolean;
@@ -215,34 +257,42 @@ export interface ActionsOptions {
215
257
  }
216
258
  /** The names a layout's controls report, for {@link idle} and {@link scripted}. */
217
259
  export interface Names<B extends string, V extends string = never> {
260
+ /** The layout's button keys: the actions they report. */
218
261
  readonly buttons: ReadonlyArray<B>;
219
- /** The vector action the stick drives, as the layout's `stick.vector`. */
220
- readonly vector?: V;
262
+ /** The layout's stick keys: the vectors they drive (`[]` for buttons only). */
263
+ readonly sticks: ReadonlyArray<V>;
221
264
  }
265
+ /** Rejects a name given both as a stick and as a button. */
266
+ export type DistinctNames<B extends string, V extends string> = [B & V] extends [never] ? unknown : `${B & V} names both a stick and a button: give them different names`;
222
267
  /**
223
268
  * A snapshot with nothing held, for initializing an input resource before
224
- * the first capture: pass the layout's button names and stick vector, as
225
- * `Touch.idle({ buttons: ["attack", "dash"], vector: "move" })`.
269
+ * the first capture: pass the layout's button and stick names, as
270
+ * `Touch.idle({ buttons: ["dash"], sticks: ["move", "aim"] })`.
226
271
  */
227
- export declare const idle: <const B extends string, const V extends string = never>(names: Names<B, V>) => Snapshot<B, V>;
272
+ export declare const idle: <const B extends string, const V extends string = never>(names: Names<B, V> & DistinctNames<B, V>) => Snapshot<B, V>;
228
273
  /**
229
274
  * Starts tracking touches over `target` with the controls `layout` returns
230
- * for the element's current size (it is read again on every new touch, so a
231
- * resize takes effect at the next touch). `blurHost` (usually `window`)
232
- * cancels held controls when the page loses focus. Use `Pointer.actions`
233
- * for mouse and pen input.
275
+ * for the element's current size. The layout is read again on every
276
+ * snapshot and every new touch, so a resize or a control the game hides
277
+ * (`null`) takes effect at once; keep it cheap, since it runs every update.
278
+ * A held control that turns `null` releases with `cancelled: true`, and its
279
+ * finger does nothing until it lifts. `blurHost` (usually `window`) cancels
280
+ * held controls when the page loses focus. Use `Pointer.actions` for mouse
281
+ * and pen input.
234
282
  */
235
283
  export declare const actions: <const B extends string, const V extends string = never>(target: TouchTarget, blurHost: BlurHost, layout: (size: Size) => Layout<B, V>, options?: ActionsOptions) => Actions<B, V>;
236
284
  /**
237
- * Control changes applied just before one snapshot, in this order: the stick
285
+ * Control changes applied just before one snapshot, in this order: the sticks
238
286
  * and swipe, presses, aims, releases. `frame` is the snapshot's index, counting from 0 at
239
287
  * the first `snapshot()` call. A button pressed and released in one frame is a
240
288
  * tap shorter than a frame.
241
289
  */
242
- export interface TimelineEntry<B extends string> {
290
+ export interface TimelineEntry<B extends string, V extends string = never> {
243
291
  readonly frame: number;
244
- /** The stick's vector from this frame on; `null` lifts the thumb. */
245
- readonly stick?: Vector | null;
292
+ /** Stick vectors from this frame on, by stick name; `null` lifts the thumb. */
293
+ readonly sticks?: {
294
+ readonly [K in V]?: Vector | null;
295
+ };
246
296
  /** A swipe of this many CSS pixels reported by this frame's snapshot only. */
247
297
  readonly swipe?: Vector;
248
298
  readonly press?: ReadonlyArray<B>;
@@ -255,16 +305,16 @@ export interface TimelineEntry<B extends string> {
255
305
  readonly cancel?: ReadonlyArray<B>;
256
306
  }
257
307
  /** Control changes by snapshot index. Plain data, so it round-trips through JSON. */
258
- export type Timeline<B extends string> = ReadonlyArray<TimelineEntry<B>>;
308
+ export type Timeline<B extends string, V extends string = never> = ReadonlyArray<TimelineEntry<B, V>>;
259
309
  /** Touch controls driven by a timeline instead of an element. */
260
310
  export interface Scripted<B extends string, V extends string = never> extends Actions<B, V> {
261
311
  /** Number of snapshots taken so far, which is the next frame index. */
262
312
  readonly frames: () => number;
263
313
  }
264
314
  /**
265
- * Plays `timeline` over controls with `names` (the buttons, and the vector
266
- * action the stick drives): before the snapshot at index `frame`, every
315
+ * Plays `timeline` over controls with `names` (the buttons, and the sticks
316
+ * by the vectors they drive): before the snapshot at index `frame`, every
267
317
  * entry for that frame is applied. Vectors longer than 1 are clamped.
268
318
  */
269
- export declare const scripted: <const B extends string, const V extends string = never>(names: Names<B, V>, timeline: Timeline<NoInfer<B>>) => Scripted<B, V>;
319
+ export declare const scripted: <const B extends string, const V extends string = never>(names: Names<B, V> & DistinctNames<B, V>, timeline: Timeline<NoInfer<B>, NoInfer<V>>) => Scripted<B, V>;
270
320
  //# sourceMappingURL=Touch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Touch.d.ts","sourceRoot":"","sources":["../src/Touch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,wBAAwB,CAAA;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD,0EAA0E;AAC1E,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK;IACjD,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;IACrB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;CACpB;AAED,8FAA8F;AAC9F,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;IACzB,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,6FAA6F;IAC7F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,0CAA0C;AAC1C,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK;IAChE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,UAAU;KAAE,CAAA;IACnD,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAC3B;AAED,uEAAuE;AACvE,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,uCAAuC;AACvC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,uGAAuG;AACvG,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED,4CAA4C;AAC5C,MAAM,WAAW,KAAK;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK;IAClE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,WAAW;KAAE,CAAA;IACpD,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,MAAM;KAAE,CAAA;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACvB;AAeD,sFAAsF;AACtF,MAAM,WAAW,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAA;IAC7E,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG;YAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE;KAAE,CAAA;CACjG;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,CAAE,SAAQ,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;IACtG,yEAAyE;IACzE,QAAQ,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC1B,kCAAkC;IAClC,IAAI,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAClB,6EAA6E;IAC7E,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,cAAc,IAAI,IAAI,CAAA;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,WAAW,GAAG,eAAe,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAA;IACtI,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,WAAW,GAAG,eAAe,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI,CAAA;IACzI,qBAAqB,IAAI;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACzH,iBAAiB,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC;AAED,4FAA4F;AAC5F,MAAM,WAAW,QAAQ;IACvB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC1D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC9D;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC9C;AAMD,mFAAmF;AACnF,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK;IAC/D,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IAClC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,CAM5G,CAAA;AAiEH;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,UACpE,WAAW,YACT,QAAQ,UACV,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,YAC3B,cAAc,KACtB,OAAO,CAAC,CAAC,EAAE,CAAC,CAqJd,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACjC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;KAAE,CAAA;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACnC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CACnC;AAED,qFAAqF;AACrF,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;AAExE,iEAAiE;AACjE,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,KAAK,CAAE,SAAQ,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IACzF,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAA;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,YAAY,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,CA2DlJ,CAAA"}
1
+ {"version":3,"file":"Touch.d.ts","sourceRoot":"","sources":["../src/Touch.ts"],"names":[],"mappings":"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"}
package/dist/Touch.js CHANGED
@@ -1,19 +1,20 @@
1
1
  import { anyUsed } from "./internal/used.js";
2
- const snapshotOf = (vector, stick, actions, swipe) => ({
3
- stick,
4
- actions,
5
- vectors: (vector === undefined ? {} : { [vector]: stick.vector }),
6
- swipe,
7
- used: stick.active || swipe.active || anyUsed(actions)
8
- });
2
+ const snapshotOf = (vectors, actions, swipe) => {
3
+ let held = false;
4
+ for (const name in vectors)
5
+ held ||= vectors[name].active;
6
+ return { actions, vectors, swipe, used: held || swipe.active || anyUsed(actions) };
7
+ };
9
8
  const zero = { x: 0, y: 0 };
9
+ const resting = { x: 0, y: 0, active: false };
10
10
  const released = () => ({ held: false, pressed: false, released: false, aim: null, cancelled: false });
11
+ const byName = (names, value) => Object.fromEntries(names.map((name) => [name, value(name)]));
11
12
  /**
12
13
  * A snapshot with nothing held, for initializing an input resource before
13
- * the first capture: pass the layout's button names and stick vector, as
14
- * `Touch.idle({ buttons: ["attack", "dash"], vector: "move" })`.
14
+ * the first capture: pass the layout's button and stick names, as
15
+ * `Touch.idle({ buttons: ["dash"], sticks: ["move", "aim"] })`.
15
16
  */
16
- export const idle = (names) => snapshotOf(names.vector, { active: false, vector: zero }, Object.fromEntries(names.buttons.map((button) => [button, released()])), { active: false, delta: zero });
17
+ export const idle = (names) => snapshotOf(byName(names.sticks, () => resting), byName(names.buttons, released), { active: false, delta: zero });
17
18
  /** `offset / radius`, clamped to length 1, with the inner `deadZone` fraction mapped to zero. */
18
19
  const tilt = (offset, radius, deadZone) => {
19
20
  const length = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
@@ -64,10 +65,13 @@ const lift = (track, cancelled) => {
64
65
  };
65
66
  /**
66
67
  * Starts tracking touches over `target` with the controls `layout` returns
67
- * for the element's current size (it is read again on every new touch, so a
68
- * resize takes effect at the next touch). `blurHost` (usually `window`)
69
- * cancels held controls when the page loses focus. Use `Pointer.actions`
70
- * for mouse and pen input.
68
+ * for the element's current size. The layout is read again on every
69
+ * snapshot and every new touch, so a resize or a control the game hides
70
+ * (`null`) takes effect at once; keep it cheap, since it runs every update.
71
+ * A held control that turns `null` releases with `cancelled: true`, and its
72
+ * finger does nothing until it lifts. `blurHost` (usually `window`) cancels
73
+ * held controls when the page loses focus. Use `Pointer.actions` for mouse
74
+ * and pen input.
71
75
  */
72
76
  export const actions = (target, blurHost, layout, options = {}) => {
73
77
  const pointerTypes = options.pointerTypes ?? ["touch"];
@@ -76,9 +80,13 @@ export const actions = (target, blurHost, layout, options = {}) => {
76
80
  return { width: rect.width, height: rect.height };
77
81
  };
78
82
  let current = layout(sizeOf());
83
+ /** The stick zones of the current layout; names that clash with a button do not compile. */
84
+ const zoneOf = (name) => current.sticks[name];
79
85
  const names = Object.keys(current.buttons);
86
+ const stickNames = Object.keys(current.sticks);
80
87
  const buttons = makeButtonTracks(names);
81
- let stick;
88
+ /** The finger and positions of each held stick. */
89
+ const sticks = new Map();
82
90
  /** Which button each finger holds. */
83
91
  const fingers = new Map();
84
92
  let swipe;
@@ -89,6 +97,31 @@ export const actions = (target, blurHost, layout, options = {}) => {
89
97
  return { x: event.clientX - rect.left, y: event.clientY - rect.top };
90
98
  };
91
99
  const tracked = (event) => pointerTypes.includes(event.pointerType);
100
+ const stickOf = (pointerId) => {
101
+ for (const [name, stick] of sticks)
102
+ if (stick.pointerId === pointerId)
103
+ return name;
104
+ return undefined;
105
+ };
106
+ /**
107
+ * Reads the layout again and lets go of the controls it hides: a held
108
+ * button releases as a cancel, and its finger, like a hidden stick's or
109
+ * swipe's, is forgotten, so it does nothing until it lifts.
110
+ */
111
+ const relayout = () => {
112
+ current = layout(sizeOf());
113
+ for (const [pointerId, name] of fingers) {
114
+ if (current.buttons[name] !== null)
115
+ continue;
116
+ fingers.delete(pointerId);
117
+ lift(buttons.get(name), true);
118
+ }
119
+ for (const name of sticks.keys())
120
+ if (zoneOf(name) === null)
121
+ sticks.delete(name);
122
+ if (current.swipe === undefined)
123
+ swipe = undefined;
124
+ };
92
125
  const onDown = (event) => {
93
126
  if (!tracked(event))
94
127
  return;
@@ -96,11 +129,13 @@ export const actions = (target, blurHost, layout, options = {}) => {
96
129
  // A pointer id that comes down again without an up in between (the up
97
130
  // was lost) releases what it held first, as a cancel.
98
131
  end(event, true);
99
- current = layout(sizeOf());
132
+ relayout();
100
133
  const at = locate(event);
101
134
  for (const name of names) {
102
135
  const zone = current.buttons[name];
103
136
  const button = buttons.get(name);
137
+ if (zone === null)
138
+ continue;
104
139
  const dx = at.x - zone.center.x;
105
140
  const dy = at.y - zone.center.y;
106
141
  if (button.held || dx * dx + dy * dy > zone.radius * zone.radius)
@@ -110,8 +145,11 @@ export const actions = (target, blurHost, layout, options = {}) => {
110
145
  target.setPointerCapture?.(event.pointerId);
111
146
  return;
112
147
  }
113
- if (stick === undefined && inside(current.stick.region, at)) {
114
- stick = { pointerId: event.pointerId, origin: at, knob: at };
148
+ for (const name of stickNames) {
149
+ const zone = zoneOf(name);
150
+ if (zone === null || sticks.has(name) || !inside(zone.region, at))
151
+ continue;
152
+ sticks.set(name, { pointerId: event.pointerId, origin: at, knob: at });
115
153
  target.setPointerCapture?.(event.pointerId);
116
154
  return;
117
155
  }
@@ -124,8 +162,9 @@ export const actions = (target, blurHost, layout, options = {}) => {
124
162
  if (!tracked(event))
125
163
  return;
126
164
  const at = locate(event);
127
- if (stick !== undefined && stick.pointerId === event.pointerId) {
128
- stick.knob = at;
165
+ const stick = stickOf(event.pointerId);
166
+ if (stick !== undefined) {
167
+ sticks.get(stick).knob = at;
129
168
  return;
130
169
  }
131
170
  if (swipe !== undefined && swipe.pointerId === event.pointerId) {
@@ -134,14 +173,16 @@ export const actions = (target, blurHost, layout, options = {}) => {
134
173
  return;
135
174
  }
136
175
  const name = fingers.get(event.pointerId);
137
- if (name !== undefined)
138
- buttons.get(name).aim = aimOf(current.buttons[name], at);
176
+ const zone = name === undefined ? null : current.buttons[name];
177
+ if (name !== undefined && zone !== null)
178
+ buttons.get(name).aim = aimOf(zone, at);
139
179
  };
140
180
  const end = (event, cancelled) => {
141
181
  if (!tracked(event))
142
182
  return;
143
- if (stick !== undefined && stick.pointerId === event.pointerId) {
144
- stick = undefined;
183
+ const stick = stickOf(event.pointerId);
184
+ if (stick !== undefined) {
185
+ sticks.delete(stick);
145
186
  return;
146
187
  }
147
188
  if (swipe !== undefined && swipe.pointerId === event.pointerId) {
@@ -153,14 +194,15 @@ export const actions = (target, blurHost, layout, options = {}) => {
153
194
  return;
154
195
  fingers.delete(event.pointerId);
155
196
  const button = buttons.get(name);
156
- if (!cancelled)
157
- button.aim = aimOf(current.buttons[name], locate(event));
197
+ const zone = current.buttons[name];
198
+ if (!cancelled && zone !== null)
199
+ button.aim = aimOf(zone, locate(event));
158
200
  lift(button, cancelled);
159
201
  };
160
202
  const onUp = (event) => end(event, false);
161
203
  const onCancel = (event) => end(event, true);
162
204
  const onBlur = () => {
163
- stick = undefined;
205
+ sticks.clear();
164
206
  swipe = undefined;
165
207
  fingers.clear();
166
208
  for (const button of buttons.values())
@@ -174,36 +216,43 @@ export const actions = (target, blurHost, layout, options = {}) => {
174
216
  target.addEventListener("pointerup", onUp);
175
217
  target.addEventListener("pointercancel", onCancel);
176
218
  blurHost.addEventListener("blur", onBlur);
177
- const stickState = () => {
178
- if (stick === undefined)
179
- return { active: false, vector: zero };
219
+ const stickState = (name) => {
220
+ const stick = sticks.get(name);
221
+ const zone = zoneOf(name);
222
+ if (stick === undefined || zone === null)
223
+ return resting;
180
224
  const offset = { x: stick.knob.x - stick.origin.x, y: stick.knob.y - stick.origin.y };
181
- return { active: true, vector: tilt(offset, current.stick.radius, current.stick.deadZone ?? 0.15) };
225
+ const vector = tilt(offset, zone.radius, zone.deadZone ?? 0.15);
226
+ return { x: vector.x, y: vector.y, active: true };
227
+ };
228
+ const stickView = (name) => {
229
+ const stick = sticks.get(name);
230
+ const zone = zoneOf(name);
231
+ if (stick === undefined || zone === null)
232
+ return null;
233
+ const radius = zone.radius;
234
+ const offset = { x: stick.knob.x - stick.origin.x, y: stick.knob.y - stick.origin.y };
235
+ const length = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
236
+ const scale = length > radius ? radius / length : 1;
237
+ return { origin: stick.origin, knob: { x: stick.origin.x + offset.x * scale, y: stick.origin.y + offset.y * scale } };
182
238
  };
183
239
  return {
184
240
  snapshot() {
185
- const snapshot = snapshotOf(current.stick.vector, stickState(), Object.fromEntries(names.map((name) => [name, buttonSnapshot(buttons.get(name))])), { active: swipe !== undefined, delta: swiped });
241
+ relayout();
242
+ const snapshot = snapshotOf(byName(stickNames, stickState), byName(names, (name) => buttonSnapshot(buttons.get(name))), { active: swipe !== undefined, delta: swiped });
186
243
  for (const button of buttons.values())
187
244
  startWindow(button);
188
245
  swiped = zero;
189
246
  return snapshot;
190
247
  },
191
248
  view() {
192
- const radius = current.stick.radius;
193
249
  return {
194
250
  layout: current,
195
- stick: stick === undefined
196
- ? null
197
- : (() => {
198
- const offset = { x: stick.knob.x - stick.origin.x, y: stick.knob.y - stick.origin.y };
199
- const length = Math.sqrt(offset.x * offset.x + offset.y * offset.y);
200
- const scale = length > radius ? radius / length : 1;
201
- return { origin: stick.origin, knob: { x: stick.origin.x + offset.x * scale, y: stick.origin.y + offset.y * scale } };
202
- })(),
203
- buttons: Object.fromEntries(names.map((name) => {
251
+ sticks: byName(stickNames, stickView),
252
+ buttons: byName(names, (name) => {
204
253
  const button = buttons.get(name);
205
- return [name, { held: button.held, aim: button.held ? button.aim : null }];
206
- }))
254
+ return { held: button.held, aim: button.held ? button.aim : null };
255
+ })
207
256
  };
208
257
  },
209
258
  dispose() {
@@ -218,12 +267,13 @@ export const actions = (target, blurHost, layout, options = {}) => {
218
267
  };
219
268
  };
220
269
  /**
221
- * Plays `timeline` over controls with `names` (the buttons, and the vector
222
- * action the stick drives): before the snapshot at index `frame`, every
270
+ * Plays `timeline` over controls with `names` (the buttons, and the sticks
271
+ * by the vectors they drive): before the snapshot at index `frame`, every
223
272
  * entry for that frame is applied. Vectors longer than 1 are clamped.
224
273
  */
225
274
  export const scripted = (names, timeline) => {
226
275
  const buttons = names.buttons;
276
+ const stickNames = names.sticks;
227
277
  const byFrame = new Map();
228
278
  for (const entry of timeline) {
229
279
  const entries = byFrame.get(entry.frame);
@@ -237,11 +287,12 @@ export const scripted = (names, timeline) => {
237
287
  return length <= 1 ? vector : { x: vector.x / length, y: vector.y / length };
238
288
  };
239
289
  const tracks = makeButtonTracks(buttons);
240
- let stick = null;
290
+ /** Each held stick's vector. */
291
+ const sticks = new Map();
241
292
  let frame = 0;
242
293
  const layout = {
243
- stick: { region: { left: 0, top: 0, width: 0, height: 0 }, radius: 1, ...(names.vector === undefined ? {} : { vector: names.vector }) },
244
- buttons: Object.fromEntries(buttons.map((button) => [button, { center: zero, radius: 0 }]))
294
+ sticks: byName(stickNames, () => ({ region: { left: 0, top: 0, width: 0, height: 0 }, radius: 1 })),
295
+ buttons: byName(buttons, () => ({ center: zero, radius: 0 }))
245
296
  };
246
297
  return {
247
298
  snapshot() {
@@ -254,8 +305,14 @@ export const scripted = (names, timeline) => {
254
305
  swipe.x += entry.swipe.x;
255
306
  swipe.y += entry.swipe.y;
256
307
  }
257
- if (entry.stick !== undefined)
258
- stick = entry.stick === null ? null : clamp(entry.stick);
308
+ for (const [name, vector] of Object.entries(entry.sticks ?? {})) {
309
+ if (vector === undefined || !stickNames.includes(name))
310
+ continue;
311
+ if (vector === null)
312
+ sticks.delete(name);
313
+ else
314
+ sticks.set(name, clamp(vector));
315
+ }
259
316
  for (const name of entry.press ?? [])
260
317
  press(tracks.get(name), null);
261
318
  for (const [name, aim] of Object.entries(entry.aim ?? {})) {
@@ -269,16 +326,19 @@ export const scripted = (names, timeline) => {
269
326
  lift(tracks.get(name), true);
270
327
  }
271
328
  frame += 1;
272
- return snapshotOf(names.vector, { active: stick !== null, vector: stick ?? zero }, Object.fromEntries(buttons.map((name) => [name, buttonSnapshot(tracks.get(name))])), { active: swipe.active, delta: { x: swipe.x, y: swipe.y } });
329
+ return snapshotOf(byName(stickNames, (name) => {
330
+ const vector = sticks.get(name);
331
+ return vector === undefined ? resting : { x: vector.x, y: vector.y, active: true };
332
+ }), byName(buttons, (name) => buttonSnapshot(tracks.get(name))), { active: swipe.active, delta: { x: swipe.x, y: swipe.y } });
273
333
  },
274
334
  view() {
275
335
  return {
276
336
  layout,
277
- stick: null,
278
- buttons: Object.fromEntries(buttons.map((name) => {
337
+ sticks: byName(stickNames, () => null),
338
+ buttons: byName(buttons, (name) => {
279
339
  const track = tracks.get(name);
280
- return [name, { held: track.held, aim: track.held ? track.aim : null }];
281
- }))
340
+ return { held: track.held, aim: track.held ? track.aim : null };
341
+ })
282
342
  };
283
343
  },
284
344
  dispose() { },