@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/CHANGELOG.md +117 -0
- package/README.md +1 -1
- package/dist/Controls.d.ts.map +1 -1
- package/dist/Controls.js +7 -3
- package/dist/Controls.js.map +1 -1
- package/dist/Keyboard.d.ts +23 -3
- package/dist/Keyboard.d.ts.map +1 -1
- package/dist/Keyboard.js +20 -3
- package/dist/Keyboard.js.map +1 -1
- package/dist/Touch.d.ts +108 -58
- package/dist/Touch.d.ts.map +1 -1
- package/dist/Touch.js +116 -56
- package/dist/Touch.js.map +1 -1
- package/package.json +2 -2
- package/src/Controls.ts +7 -2
- package/src/Keyboard.ts +38 -4
- package/src/Touch.ts +220 -113
package/dist/Touch.d.ts
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Touch controls over one element:
|
|
3
|
-
* aim by dragging: tap to use, drag to aim, release to fire), and an
|
|
4
|
-
* swipe area that reports how far a finger moved, for turning or
|
|
5
|
-
* control (Genshin Impact style: move on the left, swipe anywhere to
|
|
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
|
|
10
|
-
* the
|
|
11
|
-
* swipe area when it lands in its region.
|
|
12
|
-
* ignored, and a control keeps its finger
|
|
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:
|
|
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
|
-
* //
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
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 (
|
|
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
|
-
/**
|
|
65
|
-
export interface StickZone
|
|
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
|
-
/**
|
|
73
|
-
readonly vector?:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
108
|
-
|
|
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
|
|
134
|
-
* stick
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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
|
-
/**
|
|
181
|
+
/** Each stick's vector, and whether a thumb is on it. */
|
|
144
182
|
readonly vectors: {
|
|
145
|
-
readonly [K in V]:
|
|
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
|
|
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
|
-
/**
|
|
158
|
-
readonly
|
|
159
|
-
readonly
|
|
160
|
-
|
|
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
|
|
220
|
-
readonly
|
|
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
|
|
225
|
-
* `Touch.idle({ buttons: ["
|
|
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
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
245
|
-
readonly
|
|
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
|
|
266
|
-
*
|
|
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
|
package/dist/Touch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Touch.d.ts","sourceRoot":"","sources":["../src/Touch.ts"],"names":[],"mappings":"AAAA
|
|
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 = (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
swipe,
|
|
7
|
-
|
|
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
|
|
14
|
-
* `Touch.idle({ buttons: ["
|
|
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.
|
|
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
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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
|
|
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
|
|
222
|
-
*
|
|
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
|
-
|
|
290
|
+
/** Each held stick's vector. */
|
|
291
|
+
const sticks = new Map();
|
|
241
292
|
let frame = 0;
|
|
242
293
|
const layout = {
|
|
243
|
-
|
|
244
|
-
buttons:
|
|
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
|
-
|
|
258
|
-
|
|
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(
|
|
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
|
-
|
|
278
|
-
buttons:
|
|
337
|
+
sticks: byName(stickNames, () => null),
|
|
338
|
+
buttons: byName(buttons, (name) => {
|
|
279
339
|
const track = tracks.get(name);
|
|
280
|
-
return
|
|
281
|
-
})
|
|
340
|
+
return { held: track.held, aim: track.held ? track.aim : null };
|
|
341
|
+
})
|
|
282
342
|
};
|
|
283
343
|
},
|
|
284
344
|
dispose() { },
|