@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.10
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 +163 -0
- package/LICENSE +21 -21
- package/README.md +203 -139
- package/dist/auto.d.ts +8 -8
- package/dist/auto.js +3 -3
- package/dist/avatar-pixi.d.ts +165 -0
- package/dist/avatar-pixi.d.ts.map +1 -0
- package/dist/avatar-pixi.js +299 -0
- package/dist/avatar-pixi.js.map +1 -0
- package/dist/avatar-svg.d.ts +4 -0
- package/dist/avatar-svg.js +5 -0
- package/dist/avatar-three.d.ts +69 -0
- package/dist/avatar-three.d.ts.map +1 -0
- package/dist/avatar-three.js +49 -0
- package/dist/avatar-three.js.map +1 -0
- package/dist/avatar.d.ts +7 -0
- package/dist/avatar.js +500 -0
- package/dist/avatar.js.map +1 -0
- package/dist/daily.d.ts +2 -2
- package/dist/doctor/cli.mjs +119 -28
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +3 -3
- package/dist/input.d.ts +2 -2
- package/dist/input.js +2 -2
- package/dist/mp.d.ts +3 -3
- package/dist/race.d.ts +1 -1
- package/dist/rooms.d.ts +3 -3
- package/dist/rooms.js +1 -1
- package/dist/shared/animator.d.ts +104 -0
- package/dist/shared/animator.d.ts.map +1 -0
- package/dist/shared/art.js +1946 -0
- package/dist/shared/art.js.map +1 -0
- package/dist/shared/atlas.d.ts +138 -0
- package/dist/shared/atlas.d.ts.map +1 -0
- package/dist/shared/avatar.d.ts +123 -0
- package/dist/shared/avatar.d.ts.map +1 -0
- package/dist/shared/bridge.js +18 -4
- package/dist/shared/bridge.js.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.d.ts +145 -19
- package/dist/shared/daily.d.ts.map +1 -1
- package/dist/shared/daily.js +138 -25
- package/dist/shared/daily.js.map +1 -1
- package/dist/shared/defineProperty.js +39 -0
- package/dist/shared/describe.d.ts +32 -0
- package/dist/shared/describe.d.ts.map +1 -0
- package/dist/shared/describe.js +109 -0
- package/dist/shared/describe.js.map +1 -0
- package/dist/shared/globals.js +9 -42
- package/dist/shared/globals.js.map +1 -1
- package/dist/shared/index.d.ts +1122 -46
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/input.d.ts +74 -14
- package/dist/shared/input.d.ts.map +1 -1
- package/dist/shared/input.js +179 -25
- package/dist/shared/input.js.map +1 -1
- package/dist/shared/json-guards.js +48 -0
- package/dist/shared/json-guards.js.map +1 -0
- package/dist/shared/latest-state.js +1 -1
- package/dist/shared/live-canvas.js +62 -0
- package/dist/shared/live-canvas.js.map +1 -0
- package/dist/shared/mp.js +10 -2
- package/dist/shared/mp.js.map +1 -1
- package/dist/shared/multiplayer-types.d.ts +15 -2
- package/dist/shared/multiplayer-types.d.ts.map +1 -1
- package/dist/shared/overlays.js +800 -0
- package/dist/shared/overlays.js.map +1 -0
- package/dist/shared/page-stack.js +2 -1
- package/dist/shared/page-stack.js.map +1 -1
- package/dist/shared/pose-key.js +249 -0
- package/dist/shared/pose-key.js.map +1 -0
- package/dist/shared/race-types.d.ts +1 -1
- package/dist/shared/race.js +1 -1
- package/dist/shared/room-types.d.ts +28 -3
- package/dist/shared/room-types.d.ts.map +1 -1
- package/dist/shared/rooms.d.ts.map +1 -1
- package/dist/shared/rooms.js +23 -4
- package/dist/shared/rooms.js.map +1 -1
- package/dist/shared/runtime-opener.js +1 -1
- package/dist/shared/scene.d.ts +90 -0
- package/dist/shared/scene.d.ts.map +1 -0
- package/dist/shared/social.d.ts +90 -9
- package/dist/shared/social.d.ts.map +1 -1
- package/dist/shared/social.js +6 -1
- package/dist/shared/social.js.map +1 -1
- package/dist/shared/src.js +4563 -667
- package/dist/shared/src.js.map +1 -1
- package/dist/shared/svg-markup.js +2354 -0
- package/dist/shared/svg-markup.js.map +1 -0
- package/dist/shared/svg.js +27 -0
- package/dist/shared/svg.js.map +1 -0
- package/dist/shared/types.d.ts +174 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/wire-types.d.ts +6 -2
- package/dist/shared/wire-types.d.ts.map +1 -1
- package/dist/social.d.ts +5 -5
- package/dist/social.js +2 -2
- package/dist/turns-rules.d.ts +2 -0
- package/dist/turns-rules.js +1 -0
- package/dist/turns.d.ts +61 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +183 -0
- package/dist/turns.js.map +1 -0
- package/package.json +125 -89
- package/src/auto.ts +39 -39
- package/src/avatar/adapters/pixi-avatar.ts +73 -0
- package/src/avatar/adapters/pixi-crowd.ts +329 -0
- package/src/avatar/adapters/pixi-types.ts +84 -0
- package/src/avatar/adapters/three.ts +126 -0
- package/src/avatar/animator/animator.ts +285 -0
- package/src/avatar/animator/reactions.ts +44 -0
- package/src/avatar/animator/schedule.ts +73 -0
- package/src/avatar/art/beard.ts +49 -0
- package/src/avatar/art/glasses.ts +67 -0
- package/src/avatar/art/hair.ts +254 -0
- package/src/avatar/art/headwear.ts +75 -0
- package/src/avatar/art/index.ts +21 -0
- package/src/avatar/art/shape.ts +87 -0
- package/src/avatar/art-store.ts +36 -0
- package/src/avatar/atlas.ts +246 -0
- package/src/avatar/catalog-view.ts +14 -0
- package/src/avatar/colour.ts +109 -0
- package/src/avatar/describe.ts +79 -0
- package/src/avatar/face.ts +187 -0
- package/src/avatar/geom.ts +173 -0
- package/src/avatar/layout.ts +47 -0
- package/src/avatar/look-like.ts +52 -0
- package/src/avatar/outputs/bitmap.ts +75 -0
- package/src/avatar/outputs/canvas-paint.ts +177 -0
- package/src/avatar/outputs/canvas.ts +62 -0
- package/src/avatar/outputs/custom-element.ts +140 -0
- package/src/avatar/outputs/element.ts +58 -0
- package/src/avatar/outputs/live-canvas.ts +57 -0
- package/src/avatar/outputs/overlay-svg.ts +22 -0
- package/src/avatar/outputs/pose-key.ts +30 -0
- package/src/avatar/outputs/svg-markup.ts +113 -0
- package/src/avatar/outputs/svg.ts +41 -0
- package/src/avatar/overlays.ts +128 -0
- package/src/avatar/render/body.ts +116 -0
- package/src/avatar/render/colours.ts +81 -0
- package/src/avatar/render/compose.ts +320 -0
- package/src/avatar/render/context.ts +117 -0
- package/src/avatar/render/eyes.ts +78 -0
- package/src/avatar/render/eyewear.ts +118 -0
- package/src/avatar/render/frame.ts +73 -0
- package/src/avatar/render/head.ts +81 -0
- package/src/avatar/render/mouth.ts +86 -0
- package/src/avatar/render/props.ts +91 -0
- package/src/avatar/render/types.ts +93 -0
- package/src/avatar/render/walker-parts.ts +121 -0
- package/src/avatar/render/walker.ts +199 -0
- package/src/avatar/scene.ts +119 -0
- package/src/avatar-pixi.ts +20 -0
- package/src/avatar-svg.ts +21 -0
- package/src/avatar-three.ts +18 -0
- package/src/avatar.ts +63 -0
- package/src/core/avatar/avatar-cache.ts +95 -0
- package/src/core/avatar/avatar-ref.ts +34 -0
- package/src/core/avatar/avatar-types.ts +43 -0
- package/src/core/avatar/hub-avatar.ts +308 -0
- package/src/core/connection.ts +408 -299
- package/src/core/errors.ts +160 -147
- package/src/core/game-data.ts +40 -38
- package/src/core/hub-economy.ts +216 -179
- package/src/core/hub-progress.ts +269 -205
- package/src/core/hub.ts +352 -131
- package/src/core/menu-control.ts +61 -61
- package/src/core/offline/cloud-saves.ts +62 -0
- package/src/core/offline/events.ts +50 -50
- package/src/core/offline/favorites.ts +21 -21
- package/src/core/offline/hub-kinds/avatar-kind.ts +42 -0
- package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
- package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
- package/src/core/offline/hub-kinds/game-fields.ts +2 -0
- package/src/core/offline/hub-kinds/index.ts +52 -0
- package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
- package/src/core/offline/hub-kinds/progress-kinds.ts +165 -0
- package/src/core/offline/hub-kinds/resume-kind.ts +108 -0
- package/src/core/offline/hub-kinds/saved-kind.ts +93 -0
- package/src/core/offline/kind-adapters.ts +48 -0
- package/src/core/offline/local-inventory.ts +64 -0
- package/src/core/offline/local-store.ts +304 -261
- package/src/core/offline/offline-api.ts +65 -0
- package/src/core/offline/outbox-entry.ts +59 -0
- package/src/core/offline/outbox-kinds.ts +78 -0
- package/src/core/offline/outbox-merge.ts +39 -0
- package/src/core/offline/outbox-replayer.ts +141 -0
- package/src/core/offline/outbox-store.ts +85 -0
- package/src/core/offline/outbox.ts +232 -0
- package/src/core/offline/queue.ts +126 -171
- package/src/core/offline/replay-lease.ts +87 -0
- package/src/core/offline/replay-policy.ts +52 -0
- package/src/core/page-account.ts +121 -0
- package/src/core/page.ts +87 -74
- package/src/core/rest/endpoint-types.ts +22 -22
- package/src/core/rest/rest-client.ts +163 -152
- package/src/core/rest/routes.gen.ts +144 -88
- package/src/core/resume/hub-resume.ts +286 -0
- package/src/core/resume/local-resume.ts +130 -0
- package/src/core/resume/resume-list.ts +65 -0
- package/src/core/resume/resume-request.ts +52 -0
- package/src/core/resume/resume-types.ts +64 -0
- package/src/core/saved/daily-saves.ts +196 -0
- package/src/core/saved/daily-seed.ts +16 -0
- package/src/core/saved/hub-saved.ts +221 -0
- package/src/core/saved/local-saved.ts +122 -0
- package/src/core/saved/saved-list.ts +129 -0
- package/src/core/saved/saved-open.ts +104 -0
- package/src/core/saved/saved-order.ts +49 -0
- package/src/core/saved/saved-types.ts +24 -0
- package/src/core/storage/json-guards.ts +80 -74
- package/src/core/storage/key-value.ts +66 -66
- package/src/core/ticket.ts +43 -43
- package/src/core/turns/hub-turns.ts +161 -0
- package/src/core/turns/turn-game.ts +510 -0
- package/src/core/turns/your-turn-buzz.ts +69 -0
- package/src/core/types.ts +134 -134
- package/src/core/version/client-version.gen.ts +2 -2
- package/src/core/version/hub-version.ts +62 -62
- package/src/core/version/semver.ts +97 -97
- package/src/daily/daily-system.ts +276 -161
- package/src/daily/page-daily.ts +36 -36
- package/src/daily/types.ts +105 -67
- package/src/daily.ts +10 -10
- package/src/doctor/checks.ts +86 -76
- package/src/doctor/cli.ts +101 -106
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -12
- package/src/doctor/world-check.ts +61 -0
- package/src/globals.ts +115 -110
- package/src/index.ts +68 -50
- package/src/input/device-state.ts +146 -146
- package/src/input/edges.ts +55 -55
- package/src/input/focus-ring.ts +13 -0
- package/src/input/gamepad.ts +61 -61
- package/src/input/input-events.ts +32 -32
- package/src/input/input-registry.ts +171 -164
- package/src/input/input-system.ts +406 -393
- package/src/input/legacy-controls.ts +64 -64
- package/src/input/modality.ts +122 -0
- package/src/input/nav-state.ts +200 -191
- package/src/input/navigator.ts +182 -171
- package/src/input/on-screen-controls.ts +101 -101
- package/src/input/page-control.ts +43 -43
- package/src/input/page-input.ts +193 -193
- package/src/input/player-remaps.ts +77 -77
- package/src/input/source-values.ts +116 -116
- package/src/input/spatial.ts +47 -47
- package/src/input/types.ts +331 -331
- package/src/input/zero-config.ts +164 -177
- package/src/input.ts +26 -22
- package/src/mp.ts +10 -10
- package/src/overlay/controls-panel.ts +163 -163
- package/src/overlay/dom.ts +46 -46
- package/src/overlay/layout-clusters.ts +162 -162
- package/src/overlay/layout-geometry.ts +136 -136
- package/src/overlay/layout.ts +203 -203
- package/src/overlay/listener-scope.ts +23 -23
- package/src/overlay/on-screen-keyboard.ts +137 -137
- package/src/overlay/overlay.ts +234 -208
- package/src/overlay/styles.ts +13 -12
- package/src/overlay/virtual-controls.ts +262 -262
- package/src/race.ts +10 -10
- package/src/realtime/bridge.ts +126 -121
- package/src/realtime/cross-bundle.ts +29 -29
- package/src/realtime/fallback-notice.ts +48 -48
- package/src/realtime/hub-link.ts +65 -65
- package/src/realtime/latest-state.ts +33 -33
- package/src/realtime/multiplayer-forwarder.ts +40 -28
- package/src/realtime/multiplayer-types.ts +66 -52
- package/src/realtime/page-runtime.ts +60 -60
- package/src/realtime/race-forwarder.ts +59 -59
- package/src/realtime/race-types.ts +132 -132
- package/src/realtime/room-types.ts +151 -125
- package/src/realtime/rooms-forwarder.ts +104 -79
- package/src/realtime/runtime-api.ts +74 -49
- package/src/realtime/runtime-opener.ts +61 -61
- package/src/realtime/social-forwarder.ts +77 -72
- package/src/realtime/social-types.ts +86 -78
- package/src/realtime/types.ts +54 -53
- package/src/realtime/wire-types.ts +79 -72
- package/src/realtime/world-types.ts +20 -0
- package/src/rooms.ts +10 -10
- package/src/social.ts +19 -18
- package/src/turns-kit/check-rules.ts +281 -0
- package/src/turns-rules.ts +12 -0
- package/src/turns.ts +14 -0
- package/src/types.ts +13 -13
- package/src/uistack/page-stack.ts +45 -45
- package/src/uistack/play-suspension.ts +82 -82
- package/src/uistack/types.ts +76 -76
- package/src/uistack/ui-stack.ts +144 -144
package/src/input/navigator.ts
CHANGED
|
@@ -1,171 +1,182 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gamepad navigation the input system drives each frame: the hub menu while
|
|
3
|
-
* it is open, a game's own menus opted in with `autoNavigate`, and the
|
|
4
|
-
* navigable groups a game declares.
|
|
5
|
-
*/
|
|
6
|
-
import type { RepeatingEdges, RisingEdges } from './edges';
|
|
7
|
-
import { anyPadDirections, AXIS_DEADZONE, isAnyPadButtonPressed, PAD_BUTTON } from './gamepad';
|
|
8
|
-
import type { Direction } from './gamepad';
|
|
9
|
-
import { NavState } from './nav-state';
|
|
10
|
-
import type { NavHighlighter, NavListeners } from './nav-state';
|
|
11
|
-
import type { MenuBridge, NavigableDef, NavTarget } from './types';
|
|
12
|
-
|
|
13
|
-
/** What the navigator reads from the input system. */
|
|
14
|
-
export interface NavigatorContext {
|
|
15
|
-
readonly gamepads: readonly Gamepad[];
|
|
16
|
-
readonly repeats: RepeatingEdges;
|
|
17
|
-
readonly presses: RisingEdges;
|
|
18
|
-
readonly listeners: NavListeners;
|
|
19
|
-
/** The overlay, created on demand. */
|
|
20
|
-
highlighter(): NavHighlighter;
|
|
21
|
-
/** Whether an input is held (≥ 0.5). */
|
|
22
|
-
isPressed(inputName: string): boolean;
|
|
23
|
-
/** Whether an input went down this frame. */
|
|
24
|
-
wentDown(inputName: string): boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const DIRECTIONS: readonly Direction[] = ['up', 'down', 'left', 'right'];
|
|
28
|
-
|
|
29
|
-
/** The visible focusables of the hub menu. */
|
|
30
|
-
function menuFocusables(root: ShadowRoot | null): HTMLElement[] {
|
|
31
|
-
if (root === null) {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
return [...root.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input, [tabindex]')]
|
|
35
|
-
.filter((element) => element.getClientRects().length > 0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Visible elements matching a CSS selector. */
|
|
39
|
-
function visibleMatches(selector: string): HTMLElement[] {
|
|
40
|
-
return [...document.querySelectorAll<HTMLElement>(selector)].filter((element) => element.getClientRects().length > 0);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
interface
|
|
45
|
-
readonly
|
|
46
|
-
readonly
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
this.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
state.
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Gamepad navigation the input system drives each frame: the hub menu while
|
|
3
|
+
* it is open, a game's own menus opted in with `autoNavigate`, and the
|
|
4
|
+
* navigable groups a game declares.
|
|
5
|
+
*/
|
|
6
|
+
import type { RepeatingEdges, RisingEdges } from './edges';
|
|
7
|
+
import { anyPadDirections, AXIS_DEADZONE, isAnyPadButtonPressed, PAD_BUTTON } from './gamepad';
|
|
8
|
+
import type { Direction } from './gamepad';
|
|
9
|
+
import { NavState } from './nav-state';
|
|
10
|
+
import type { NavHighlighter, NavListeners } from './nav-state';
|
|
11
|
+
import type { MenuBridge, NavigableDef, NavTarget } from './types';
|
|
12
|
+
|
|
13
|
+
/** What the navigator reads from the input system. */
|
|
14
|
+
export interface NavigatorContext {
|
|
15
|
+
readonly gamepads: readonly Gamepad[];
|
|
16
|
+
readonly repeats: RepeatingEdges;
|
|
17
|
+
readonly presses: RisingEdges;
|
|
18
|
+
readonly listeners: NavListeners;
|
|
19
|
+
/** The overlay, created on demand. */
|
|
20
|
+
highlighter(): NavHighlighter;
|
|
21
|
+
/** Whether an input is held (≥ 0.5). */
|
|
22
|
+
isPressed(inputName: string): boolean;
|
|
23
|
+
/** Whether an input went down this frame. */
|
|
24
|
+
wentDown(inputName: string): boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DIRECTIONS: readonly Direction[] = ['up', 'down', 'left', 'right'];
|
|
28
|
+
|
|
29
|
+
/** The visible focusables of the hub menu. */
|
|
30
|
+
function menuFocusables(root: ShadowRoot | null): HTMLElement[] {
|
|
31
|
+
if (root === null) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return [...root.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input, [tabindex]')]
|
|
35
|
+
.filter((element) => element.getClientRects().length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Visible elements matching a CSS selector. */
|
|
39
|
+
function visibleMatches(selector: string): HTMLElement[] {
|
|
40
|
+
return [...document.querySelectorAll<HTMLElement>(selector)].filter((element) => element.getClientRects().length > 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What `autoNavigate` was last given: the targets' query and the back action. */
|
|
44
|
+
export interface AutoNavigateTarget {
|
|
45
|
+
readonly query: () => NavTarget[];
|
|
46
|
+
readonly back: (() => void) | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A navigable group with its highlight state. */
|
|
50
|
+
interface GroupNavigation {
|
|
51
|
+
readonly definition: NavigableDef;
|
|
52
|
+
readonly state: NavState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The menu, auto-navigate and group navigation of one input system. */
|
|
56
|
+
export class Navigator {
|
|
57
|
+
private menuState: NavState | null = null;
|
|
58
|
+
private autoNavigateQuery: (() => NavTarget[]) | null = null;
|
|
59
|
+
private autoNavigateState: NavState | null = null;
|
|
60
|
+
private autoNavigateBack: (() => void) | null = null;
|
|
61
|
+
private readonly groups = new Map<string, GroupNavigation>();
|
|
62
|
+
|
|
63
|
+
/** Forget the declared groups (a new `define`). */
|
|
64
|
+
clearGroups(): void {
|
|
65
|
+
this.groups.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Declare a navigable group. */
|
|
69
|
+
addGroup(group: string, definition: NavigableDef): void {
|
|
70
|
+
this.groups.set(group, { definition, state: new NavState(definition.elements, definition.wrap !== false) });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** A group was enabled: highlight its first element. */
|
|
74
|
+
refreshGroup(group: string, context: NavigatorContext): void {
|
|
75
|
+
this.groups.get(group)?.state.requery(context.highlighter(), context.listeners);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A group was disabled: drop its highlight. */
|
|
79
|
+
blurGroup(group: string, highlighter: NavHighlighter | null): void {
|
|
80
|
+
this.groups.get(group)?.state.blur(highlighter);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Opt a game's own menus into gamepad navigation. */
|
|
84
|
+
setAutoNavigate(query: () => NavTarget[], back: (() => void) | null): void {
|
|
85
|
+
this.autoNavigateQuery = query;
|
|
86
|
+
this.autoNavigateBack = back;
|
|
87
|
+
this.autoNavigateState = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The current auto-navigate target and back action, or `null` when none is set. */
|
|
91
|
+
autoNavigateTarget(): AutoNavigateTarget | null {
|
|
92
|
+
return this.autoNavigateQuery === null ? null : { query: this.autoNavigateQuery, back: this.autoNavigateBack };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Make a CSS selector's visible matches the auto-navigate targets. */
|
|
96
|
+
static queryFor(selector: string | (() => NavTarget[])): () => NavTarget[] {
|
|
97
|
+
return typeof selector === 'string' ? () => visibleMatches(selector) : selector;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Stop auto-navigating. */
|
|
101
|
+
clearAutoNavigate(highlighter: NavHighlighter | null): void {
|
|
102
|
+
this.autoNavigateQuery = null;
|
|
103
|
+
this.autoNavigateState?.blur(highlighter);
|
|
104
|
+
this.autoNavigateState = null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The hub menu is open: drive it with the highlight ring (gamepad only). */
|
|
108
|
+
driveMenu(menu: MenuBridge, isGamepadActive: boolean, context: NavigatorContext, highlighter: NavHighlighter | null): void {
|
|
109
|
+
if (!isGamepadActive) {
|
|
110
|
+
highlighter?.hideHighlight();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.menuState ??= new NavState(() => menuFocusables(menu.root));
|
|
114
|
+
this.driveList(this.menuState, 'm', context, () => {
|
|
115
|
+
menu.close();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The hub menu closed: drop its highlight. */
|
|
120
|
+
leaveMenu(highlighter: NavHighlighter | null): void {
|
|
121
|
+
this.menuState?.blur(highlighter);
|
|
122
|
+
this.menuState = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A game's own menus, when opted in, visible, and a gamepad is in use. */
|
|
126
|
+
driveAutoNavigate(isGamepadActive: boolean, context: NavigatorContext, highlighter: NavHighlighter | null): void {
|
|
127
|
+
const query = this.autoNavigateQuery;
|
|
128
|
+
if (query === null || !isGamepadActive || query().length === 0) {
|
|
129
|
+
this.autoNavigateState?.blur(highlighter);
|
|
130
|
+
this.autoNavigateState = null;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.autoNavigateState ??= new NavState(query);
|
|
134
|
+
this.driveList(this.autoNavigateState, 'an', context, this.autoNavigateBack);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The declared navigable groups that are enabled. */
|
|
138
|
+
driveGroups(isGroupEnabled: (group: string) => boolean, context: NavigatorContext): void {
|
|
139
|
+
for (const [group, { definition, state }] of this.groups) {
|
|
140
|
+
if (!isGroupEnabled(group)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const highlighter = context.highlighter();
|
|
144
|
+
const fires = (prefix: string, inputName: string | undefined): boolean => inputName !== undefined && context.repeats.fires(prefix + group, context.isPressed(inputName));
|
|
145
|
+
const shouldStepPrevious = fires('np:', definition.prev);
|
|
146
|
+
const shouldStepNext = fires('nn:', definition.next);
|
|
147
|
+
const shouldMoveUp = fires('nu:', definition.up);
|
|
148
|
+
const shouldMoveDown = fires('nd:', definition.down);
|
|
149
|
+
if (shouldStepNext) {
|
|
150
|
+
state.step(1, highlighter, context.listeners);
|
|
151
|
+
} else if (shouldStepPrevious) {
|
|
152
|
+
state.step(-1, highlighter, context.listeners);
|
|
153
|
+
} else if (shouldMoveUp) {
|
|
154
|
+
state.moveInDirection('up', highlighter, context.listeners);
|
|
155
|
+
} else if (shouldMoveDown) {
|
|
156
|
+
state.moveInDirection('down', highlighter, context.listeners);
|
|
157
|
+
}
|
|
158
|
+
if (definition.act !== '' && context.wentDown(definition.act)) {
|
|
159
|
+
state.act(highlighter, context.listeners);
|
|
160
|
+
}
|
|
161
|
+
state.position(highlighter);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Move with the stick or d-pad, A acts, B goes back. */
|
|
166
|
+
private driveList(state: NavState, keyPrefix: string, context: NavigatorContext, back: (() => void) | null): void {
|
|
167
|
+
const highlighter = context.highlighter();
|
|
168
|
+
state.requery(highlighter, context.listeners);
|
|
169
|
+
const held = anyPadDirections(context.gamepads, AXIS_DEADZONE);
|
|
170
|
+
const direction = DIRECTIONS.find((candidate) => context.repeats.fires(`${keyPrefix}:${candidate[0] ?? ''}`, held[candidate]));
|
|
171
|
+
if (direction !== undefined) {
|
|
172
|
+
state.moveInDirection(direction, highlighter, context.listeners);
|
|
173
|
+
}
|
|
174
|
+
if (context.presses.rose('act', isAnyPadButtonPressed(context.gamepads, PAD_BUTTON.A))) {
|
|
175
|
+
state.act(highlighter, context.listeners);
|
|
176
|
+
}
|
|
177
|
+
if (context.presses.rose('back', isAnyPadButtonPressed(context.gamepads, PAD_BUTTON.B))) {
|
|
178
|
+
back?.();
|
|
179
|
+
}
|
|
180
|
+
state.position(highlighter);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The input system's side of the overlay: creating it on demand, building
|
|
3
|
-
* the on-screen controls, and deciding when they show (the player's choice,
|
|
4
|
-
* else the game's default, else on touch devices without a gamepad; editing
|
|
5
|
-
* the layout shows them all).
|
|
6
|
-
*/
|
|
7
|
-
import { Overlay } from '../overlay/overlay';
|
|
8
|
-
import type { VirtualControlCallbacks } from '../overlay/virtual-controls';
|
|
9
|
-
import type { DeviceState } from './device-state';
|
|
10
|
-
import type { InputConfig, InputOverrides } from './types';
|
|
11
|
-
|
|
12
|
-
/** Whether this page counts as a touch device. */
|
|
13
|
-
export function detectTouchDevice(): boolean {
|
|
14
|
-
return typeof navigator !== 'undefined' && (navigator.maxTouchPoints > 0 || 'ontouchstart' in window);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** On-screen controls write into the device state; touching one makes touch the active source. */
|
|
18
|
-
export function feedDeviceState(devices: DeviceState, reactions: { onTouch(): void; onDragged: VirtualControlCallbacks['onDragged'] }): VirtualControlCallbacks {
|
|
19
|
-
return {
|
|
20
|
-
onButtonDown: (id) => {
|
|
21
|
-
devices.pressedVirtualButtons.add(id);
|
|
22
|
-
reactions.onTouch();
|
|
23
|
-
},
|
|
24
|
-
onButtonUp: (id) => {
|
|
25
|
-
devices.pressedVirtualButtons.delete(id);
|
|
26
|
-
},
|
|
27
|
-
onStick: (id, outX, outY) => {
|
|
28
|
-
devices.sticks.set(id, { outX, outY });
|
|
29
|
-
reactions.onTouch();
|
|
30
|
-
},
|
|
31
|
-
onTap: () => {
|
|
32
|
-
devices.pulse('tap');
|
|
33
|
-
reactions.onTouch();
|
|
34
|
-
},
|
|
35
|
-
onDragged: reactions.onDragged,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The overlay and the on-screen controls' visibility. */
|
|
40
|
-
export class OnScreenControls {
|
|
41
|
-
/** Whether this page counts as a touch device. */
|
|
42
|
-
readonly isTouchDevice: boolean = detectTouchDevice();
|
|
43
|
-
/** Whether a gamepad is connected (controls hide by default then). */
|
|
44
|
-
isGamepadConnected = false;
|
|
45
|
-
/** The player's explicit show/hide choice, or `null`. */
|
|
46
|
-
playerChoice: boolean | null = null;
|
|
47
|
-
private overlayInstance: Overlay | null = null;
|
|
48
|
-
private gameDefault: boolean | null = null;
|
|
49
|
-
private isEditing = false;
|
|
50
|
-
private readonly enabledGroups: () => ReadonlySet<string>;
|
|
51
|
-
|
|
52
|
-
constructor(enabledGroups: () => ReadonlySet<string>) {
|
|
53
|
-
this.enabledGroups = enabledGroups;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The overlay if it exists yet. */
|
|
57
|
-
get existingOverlay(): Overlay | null {
|
|
58
|
-
return this.overlayInstance;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The overlay, created on first use. */
|
|
62
|
-
overlay(): Overlay {
|
|
63
|
-
this.overlayInstance ??= new Overlay();
|
|
64
|
-
return this.overlayInstance;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** What the "Show on-screen controls" toggle reflects (excludes the editing force). */
|
|
68
|
-
get isShowing(): boolean {
|
|
69
|
-
return this.playerChoice ?? this.gameDefault ?? (this.isTouchDevice && !this.isGamepadConnected);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Set the game's default visibility (`null` = automatic). */
|
|
73
|
-
setGameDefault(isVisible: boolean | null): void {
|
|
74
|
-
this.gameDefault = isVisible;
|
|
75
|
-
this.sync();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Force every control visible while the player edits the layout. */
|
|
79
|
-
setEditing(isEditing: boolean): void {
|
|
80
|
-
this.isEditing = isEditing;
|
|
81
|
-
this.sync();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** (Re)build the controls from the config and overrides. */
|
|
85
|
-
build(config: InputConfig, overrides: InputOverrides, callbacks: VirtualControlCallbacks): void {
|
|
86
|
-
this.overlay().build(config, overrides, callbacks);
|
|
87
|
-
this.sync();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Apply the visibility rules to the built controls. */
|
|
91
|
-
sync(): void {
|
|
92
|
-
if (this.overlayInstance === null) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (this.isEditing) {
|
|
96
|
-
this.overlayInstance.setVirtualVisible(true, null);
|
|
97
|
-
} else {
|
|
98
|
-
this.overlayInstance.setVirtualVisible(this.isShowing, this.enabledGroups());
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The input system's side of the overlay: creating it on demand, building
|
|
3
|
+
* the on-screen controls, and deciding when they show (the player's choice,
|
|
4
|
+
* else the game's default, else on touch devices without a gamepad; editing
|
|
5
|
+
* the layout shows them all).
|
|
6
|
+
*/
|
|
7
|
+
import { Overlay } from '../overlay/overlay';
|
|
8
|
+
import type { VirtualControlCallbacks } from '../overlay/virtual-controls';
|
|
9
|
+
import type { DeviceState } from './device-state';
|
|
10
|
+
import type { InputConfig, InputOverrides } from './types';
|
|
11
|
+
|
|
12
|
+
/** Whether this page counts as a touch device. */
|
|
13
|
+
export function detectTouchDevice(): boolean {
|
|
14
|
+
return typeof navigator !== 'undefined' && (navigator.maxTouchPoints > 0 || 'ontouchstart' in window);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** On-screen controls write into the device state; touching one makes touch the active source. */
|
|
18
|
+
export function feedDeviceState(devices: DeviceState, reactions: { onTouch(): void; onDragged: VirtualControlCallbacks['onDragged'] }): VirtualControlCallbacks {
|
|
19
|
+
return {
|
|
20
|
+
onButtonDown: (id) => {
|
|
21
|
+
devices.pressedVirtualButtons.add(id);
|
|
22
|
+
reactions.onTouch();
|
|
23
|
+
},
|
|
24
|
+
onButtonUp: (id) => {
|
|
25
|
+
devices.pressedVirtualButtons.delete(id);
|
|
26
|
+
},
|
|
27
|
+
onStick: (id, outX, outY) => {
|
|
28
|
+
devices.sticks.set(id, { outX, outY });
|
|
29
|
+
reactions.onTouch();
|
|
30
|
+
},
|
|
31
|
+
onTap: () => {
|
|
32
|
+
devices.pulse('tap');
|
|
33
|
+
reactions.onTouch();
|
|
34
|
+
},
|
|
35
|
+
onDragged: reactions.onDragged,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The overlay and the on-screen controls' visibility. */
|
|
40
|
+
export class OnScreenControls {
|
|
41
|
+
/** Whether this page counts as a touch device. */
|
|
42
|
+
readonly isTouchDevice: boolean = detectTouchDevice();
|
|
43
|
+
/** Whether a gamepad is connected (controls hide by default then). */
|
|
44
|
+
isGamepadConnected = false;
|
|
45
|
+
/** The player's explicit show/hide choice, or `null`. */
|
|
46
|
+
playerChoice: boolean | null = null;
|
|
47
|
+
private overlayInstance: Overlay | null = null;
|
|
48
|
+
private gameDefault: boolean | null = null;
|
|
49
|
+
private isEditing = false;
|
|
50
|
+
private readonly enabledGroups: () => ReadonlySet<string>;
|
|
51
|
+
|
|
52
|
+
constructor(enabledGroups: () => ReadonlySet<string>) {
|
|
53
|
+
this.enabledGroups = enabledGroups;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The overlay if it exists yet. */
|
|
57
|
+
get existingOverlay(): Overlay | null {
|
|
58
|
+
return this.overlayInstance;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The overlay, created on first use. */
|
|
62
|
+
overlay(): Overlay {
|
|
63
|
+
this.overlayInstance ??= new Overlay();
|
|
64
|
+
return this.overlayInstance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What the "Show on-screen controls" toggle reflects (excludes the editing force). */
|
|
68
|
+
get isShowing(): boolean {
|
|
69
|
+
return this.playerChoice ?? this.gameDefault ?? (this.isTouchDevice && !this.isGamepadConnected);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Set the game's default visibility (`null` = automatic). */
|
|
73
|
+
setGameDefault(isVisible: boolean | null): void {
|
|
74
|
+
this.gameDefault = isVisible;
|
|
75
|
+
this.sync();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Force every control visible while the player edits the layout. */
|
|
79
|
+
setEditing(isEditing: boolean): void {
|
|
80
|
+
this.isEditing = isEditing;
|
|
81
|
+
this.sync();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** (Re)build the controls from the config and overrides. */
|
|
85
|
+
build(config: InputConfig, overrides: InputOverrides, callbacks: VirtualControlCallbacks): void {
|
|
86
|
+
this.overlay().build(config, overrides, callbacks);
|
|
87
|
+
this.sync();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Apply the visibility rules to the built controls. */
|
|
91
|
+
sync(): void {
|
|
92
|
+
if (this.overlayInstance === null) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this.isEditing) {
|
|
96
|
+
this.overlayInstance.setVirtualVisible(true, null);
|
|
97
|
+
} else {
|
|
98
|
+
this.overlayInstance.setVirtualVisible(this.isShowing, this.enabledGroups());
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|