@antha/input 0.0.1

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.
@@ -0,0 +1,332 @@
1
+ import { defineAnthaMod } from '@antha/engine';
2
+ import { check } from '@augment-vir/assert';
3
+ import { getObjectTypedEntries, getObjectTypedValues } from '@augment-vir/common';
4
+ import { convertDuration } from 'date-vir';
5
+ import { NavController, NavDirection, NavValue } from 'device-navigation';
6
+ import { PredefinedGamepadBrand } from 'gamepad-type';
7
+ import { InputDirection } from '../raw-inputs/raw-input.js';
8
+ import { AnyGamepad, } from './player-bindings.js';
9
+ export { nav, navAttribute, NavController } from 'device-navigation';
10
+ /**
11
+ * All supported menu navigation bindings. To ignore any, simply don't allow players to bind to
12
+ * them. Any menus that don't have sufficient nestings to support any specific binding simply won't
13
+ * do anything if they're active.
14
+ *
15
+ * @category Menu
16
+ */
17
+ export var MenuNavBinding;
18
+ (function (MenuNavBinding) {
19
+ MenuNavBinding["MenuUp"] = "menu-up";
20
+ MenuNavBinding["MenuDown"] = "menu-down";
21
+ MenuNavBinding["MenuLeft"] = "menu-left";
22
+ MenuNavBinding["MenuRight"] = "menu-right";
23
+ /**
24
+ * Enter into a sub-menu.
25
+ *
26
+ * For example, this is usually a click, the enter button, "A" on Xbox or Nintendo controllers,
27
+ * or "X" on Playstation controllers.
28
+ */
29
+ MenuNavBinding["MenuEnter"] = "menu-enter";
30
+ /**
31
+ * Exit out of a sub-menu.
32
+ *
33
+ * For example, this is usually the Escape key, "B" on Xbox or Nintendo controllers, or "△" on
34
+ * Playstation controllers.
35
+ */
36
+ MenuNavBinding["MenuExit"] = "menu-exit";
37
+ /** Navigate to the next section in a menu. */
38
+ MenuNavBinding["MenuSectionNext"] = "menu-section-next";
39
+ /** Navigate to the previous section in a menu. */
40
+ MenuNavBinding["MenuSectionPrevious"] = "menu-section-previous";
41
+ })(MenuNavBinding || (MenuNavBinding = {}));
42
+ /**
43
+ * Default menu nav bindings for {@link AnthaMenuNavMod}.
44
+ *
45
+ * @category Internal
46
+ */
47
+ export const defaultMenuNavBindings = {
48
+ [MenuNavBinding.MenuLeft]: [
49
+ {
50
+ deviceKey: AnyGamepad,
51
+ direction: InputDirection.Positive,
52
+ inputName: 'd-pad-left',
53
+ },
54
+ {
55
+ deviceKey: 'keyboard',
56
+ direction: InputDirection.Positive,
57
+ inputName: 'button-KeyA',
58
+ },
59
+ {
60
+ deviceKey: 'keyboard',
61
+ direction: InputDirection.Positive,
62
+ inputName: 'button-KeyJ',
63
+ },
64
+ {
65
+ deviceKey: 'keyboard',
66
+ direction: InputDirection.Positive,
67
+ inputName: 'button-ArrowLeft',
68
+ },
69
+ ],
70
+ [MenuNavBinding.MenuRight]: [
71
+ {
72
+ deviceKey: AnyGamepad,
73
+ direction: InputDirection.Positive,
74
+ inputName: 'd-pad-right',
75
+ },
76
+ {
77
+ deviceKey: 'keyboard',
78
+ direction: InputDirection.Positive,
79
+ inputName: 'button-KeyD',
80
+ },
81
+ {
82
+ deviceKey: 'keyboard',
83
+ direction: InputDirection.Positive,
84
+ inputName: 'button-KeyL',
85
+ },
86
+ {
87
+ deviceKey: 'keyboard',
88
+ direction: InputDirection.Positive,
89
+ inputName: 'button-ArrowRight',
90
+ },
91
+ ],
92
+ [MenuNavBinding.MenuUp]: [
93
+ {
94
+ deviceKey: AnyGamepad,
95
+ direction: InputDirection.Positive,
96
+ inputName: 'd-pad-up',
97
+ },
98
+ {
99
+ deviceKey: 'keyboard',
100
+ direction: InputDirection.Positive,
101
+ inputName: 'button-KeyW',
102
+ },
103
+ {
104
+ deviceKey: 'keyboard',
105
+ direction: InputDirection.Positive,
106
+ inputName: 'button-KeyI',
107
+ },
108
+ {
109
+ deviceKey: 'keyboard',
110
+ direction: InputDirection.Positive,
111
+ inputName: 'button-ArrowUp',
112
+ },
113
+ ],
114
+ [MenuNavBinding.MenuDown]: [
115
+ {
116
+ deviceKey: AnyGamepad,
117
+ direction: InputDirection.Positive,
118
+ inputName: 'd-pad-down',
119
+ },
120
+ {
121
+ deviceKey: 'keyboard',
122
+ direction: InputDirection.Positive,
123
+ inputName: 'button-KeyS',
124
+ },
125
+ {
126
+ deviceKey: 'keyboard',
127
+ direction: InputDirection.Positive,
128
+ inputName: 'button-KeyK',
129
+ },
130
+ {
131
+ deviceKey: 'keyboard',
132
+ direction: InputDirection.Positive,
133
+ inputName: 'button-ArrowDown',
134
+ },
135
+ ],
136
+ [MenuNavBinding.MenuEnter]: [
137
+ {
138
+ deviceKey: 'keyboard',
139
+ direction: InputDirection.Positive,
140
+ inputName: 'button-Space',
141
+ },
142
+ {
143
+ deviceKey: 'keyboard',
144
+ direction: InputDirection.Positive,
145
+ inputName: 'button-Enter',
146
+ },
147
+ {
148
+ deviceKey: 'keyboard',
149
+ direction: InputDirection.Positive,
150
+ inputName: 'button-NumpadEnter',
151
+ },
152
+ {
153
+ deviceKey: AnyGamepad,
154
+ direction: InputDirection.Positive,
155
+ gamepadBrand: PredefinedGamepadBrand.Sony,
156
+ inputName: 'X',
157
+ },
158
+ {
159
+ deviceKey: AnyGamepad,
160
+ direction: InputDirection.Positive,
161
+ inputName: 'A',
162
+ },
163
+ ],
164
+ [MenuNavBinding.MenuExit]: [
165
+ {
166
+ deviceKey: 'keyboard',
167
+ direction: InputDirection.Positive,
168
+ inputName: 'button-Escape',
169
+ },
170
+ {
171
+ deviceKey: AnyGamepad,
172
+ direction: InputDirection.Positive,
173
+ gamepadBrand: PredefinedGamepadBrand.Sony,
174
+ inputName: 'O',
175
+ },
176
+ {
177
+ deviceKey: AnyGamepad,
178
+ direction: InputDirection.Positive,
179
+ inputName: 'B',
180
+ },
181
+ ],
182
+ [MenuNavBinding.MenuSectionNext]: [
183
+ {
184
+ deviceKey: 'keyboard',
185
+ direction: InputDirection.Positive,
186
+ inputName: 'button-KeyE',
187
+ },
188
+ {
189
+ deviceKey: 'keyboard',
190
+ direction: InputDirection.Positive,
191
+ inputName: 'button-KeyO',
192
+ },
193
+ {
194
+ deviceKey: AnyGamepad,
195
+ direction: InputDirection.Positive,
196
+ inputName: 'R1',
197
+ },
198
+ ],
199
+ [MenuNavBinding.MenuSectionPrevious]: [
200
+ {
201
+ deviceKey: 'keyboard',
202
+ direction: InputDirection.Positive,
203
+ inputName: 'button-KeyQ',
204
+ },
205
+ {
206
+ deviceKey: 'keyboard',
207
+ direction: InputDirection.Positive,
208
+ inputName: 'button-KeyU',
209
+ },
210
+ {
211
+ deviceKey: AnyGamepad,
212
+ direction: InputDirection.Positive,
213
+ inputName: 'L1',
214
+ },
215
+ ],
216
+ };
217
+ /** @category Internal */
218
+ export const defaultMenuNavOptions = {
219
+ repeatThreshold: {
220
+ milliseconds: 500,
221
+ },
222
+ repeatInterval: {
223
+ milliseconds: 60,
224
+ },
225
+ allowWrapping: true,
226
+ };
227
+ /**
228
+ * A pre-built mod that enables menu navigation. Set `isInMenu` on your game state to true to
229
+ * activate it.
230
+ *
231
+ * @category Pre-Built Mods
232
+ */
233
+ export function createAnthaMenuNavMod(options = {}) {
234
+ return defineAnthaMod({
235
+ modName: 'menu-nav',
236
+ initState: {
237
+ menuNavOptions: {
238
+ ...defaultMenuNavOptions,
239
+ ...options,
240
+ },
241
+ },
242
+ execute({ state, hostElement }) {
243
+ if (!state.navController) {
244
+ state.navController = new NavController(hostElement, {
245
+ alwaysRequireFocused: true,
246
+ activateOnMouseUp: false,
247
+ ...options,
248
+ });
249
+ }
250
+ if (!state.isInMenu || !state.menuNavOptions || !state.activeBindings) {
251
+ return;
252
+ }
253
+ const repeatThreshold = convertDuration(state.menuNavOptions.repeatThreshold, {
254
+ milliseconds: true,
255
+ }).milliseconds;
256
+ const repeatInterval = convertDuration(state.menuNavOptions.repeatInterval, {
257
+ milliseconds: true,
258
+ }).milliseconds;
259
+ const bindingsToAct = {};
260
+ const activeMenuBindings = {};
261
+ getObjectTypedValues(state.activeBindings).forEach((playerActiveBindings) => {
262
+ getObjectTypedEntries(playerActiveBindings).forEach(([bindingName, activeBinding,]) => {
263
+ if (!check.isEnumValue(bindingName, MenuNavBinding)) {
264
+ return;
265
+ }
266
+ activeMenuBindings[bindingName] = true;
267
+ if (!activeBinding.actCount ||
268
+ (activeBinding.holdDuration.milliseconds >= repeatThreshold &&
269
+ activeBinding.holdDuration.milliseconds -
270
+ activeBinding.lastActDuration.milliseconds >
271
+ repeatInterval)) {
272
+ bindingsToAct[bindingName] = true;
273
+ activeBinding.actCount++;
274
+ activeBinding.lastActDuration = activeBinding.holdDuration;
275
+ }
276
+ });
277
+ });
278
+ if (bindingsToAct[MenuNavBinding.MenuEnter]) {
279
+ state.navController.enterInto({
280
+ fallbackToActivate: true,
281
+ });
282
+ return;
283
+ }
284
+ else if (bindingsToAct[MenuNavBinding.MenuExit]) {
285
+ state.navController.exitOutOf();
286
+ return;
287
+ }
288
+ if (!activeMenuBindings[MenuNavBinding.MenuEnter] &&
289
+ state.navController.currentNavEntry?.entry.navValue === NavValue.Active) {
290
+ state.navController.deactivate();
291
+ }
292
+ const sectionDirection = bindingsToAct[MenuNavBinding.MenuSectionNext] &&
293
+ !bindingsToAct[MenuNavBinding.MenuSectionPrevious]
294
+ ? NavDirection.Right
295
+ : !bindingsToAct[MenuNavBinding.MenuSectionNext] &&
296
+ bindingsToAct[MenuNavBinding.MenuSectionPrevious]
297
+ ? NavDirection.Left
298
+ : undefined;
299
+ if (sectionDirection) {
300
+ state.navController.navigatePibling({
301
+ allowWrapping: state.menuNavOptions.allowWrapping,
302
+ direction: sectionDirection,
303
+ });
304
+ return;
305
+ }
306
+ const vertical = bindingsToAct[MenuNavBinding.MenuUp] && !bindingsToAct[MenuNavBinding.MenuDown]
307
+ ? NavDirection.Up
308
+ : !bindingsToAct[MenuNavBinding.MenuUp] &&
309
+ bindingsToAct[MenuNavBinding.MenuDown]
310
+ ? NavDirection.Down
311
+ : undefined;
312
+ const horizontal = bindingsToAct[MenuNavBinding.MenuRight] && !bindingsToAct[MenuNavBinding.MenuLeft]
313
+ ? NavDirection.Right
314
+ : !bindingsToAct[MenuNavBinding.MenuRight] &&
315
+ bindingsToAct[MenuNavBinding.MenuLeft]
316
+ ? NavDirection.Left
317
+ : undefined;
318
+ if (vertical) {
319
+ state.navController.navigate({
320
+ allowWrapping: state.menuNavOptions.allowWrapping,
321
+ direction: vertical,
322
+ });
323
+ }
324
+ if (horizontal) {
325
+ state.navController.navigate({
326
+ allowWrapping: state.menuNavOptions.allowWrapping,
327
+ direction: horizontal,
328
+ });
329
+ }
330
+ },
331
+ });
332
+ }
@@ -0,0 +1,116 @@
1
+ import { type GamepadInputDeviceKey, type InputDeviceKey } from 'input-device-handler';
2
+ import { type InputDirection } from '../raw-inputs/raw-input.js';
3
+ /**
4
+ * A mapping of gamepad keys that simply allows them to be interpreted as different keys. This is
5
+ * useful for mapping a controller in any port to any other port. For example, mapping the
6
+ * controller in port 4 to port 1.
7
+ *
8
+ * @category Internal
9
+ */
10
+ export type GamepadKeyMap = Partial<Record<GamepadInputDeviceKey, GamepadInputDeviceKey>>;
11
+ /**
12
+ * A binding assignment device key that maps the input to _any_ connected controller.
13
+ *
14
+ * @category Internal
15
+ */
16
+ export declare const AnyGamepad: "any-gamepad";
17
+ /**
18
+ * A binding assignment device key that maps the input to _any_ connected controller.
19
+ *
20
+ * @category Internal
21
+ */
22
+ export type AnyGamepad = typeof AnyGamepad;
23
+ /**
24
+ * All supported device keys for input binding assignments.
25
+ *
26
+ * @category Internal
27
+ */
28
+ export type AnthaDeviceKey = InputDeviceKey | AnyGamepad;
29
+ /**
30
+ * An individual binding assignment. Used in `createAnthaReadBindingsMod` and
31
+ * {@link BindingAssignments}.
32
+ *
33
+ * @category Internal
34
+ */
35
+ export type BindingAssignment = {
36
+ deviceKey: AnthaDeviceKey;
37
+ /**
38
+ * The input key, button, or gamepad input name (like `'button-keyW'` for keyboard buttons or
39
+ * `'button-12'` or `'X'` for gamepads).
40
+ */
41
+ inputName: string;
42
+ /** Required gamepad brand. */
43
+ gamepadBrand?: string;
44
+ direction: InputDirection;
45
+ };
46
+ /**
47
+ * Starts at `'1'`.
48
+ *
49
+ * @category Internal
50
+ */
51
+ export type PlayerPosition = `${number}`;
52
+ /**
53
+ * A collection of bindings for a single player. Used in `createAnthaReadBindingsMod` and
54
+ * {@link PlayersBindingAssignments}.
55
+ *
56
+ * @category Internal
57
+ */
58
+ export type BindingAssignments<BindingNames extends string = string> = Partial<Record<BindingNames, BindingAssignment[]>>;
59
+ /**
60
+ * A collection of bindings for all players. Used in `createAnthaReadBindingsMod` and
61
+ * `AnthaInputBindingsState`.
62
+ *
63
+ * @category Internal
64
+ */
65
+ export type PlayersBindingAssignments<BindingNames extends string = string> = Record<PlayerPosition, BindingAssignments<BindingNames>>;
66
+ /**
67
+ * An individual active binding. Used in `createAnthaReadBindingsMod` and {@link ActiveBindings}.
68
+ *
69
+ * @category Internal
70
+ */
71
+ export type ActiveBinding = {
72
+ /**
73
+ * The full duration for which the current binding has been active in its current direction.
74
+ * When an active is first pressed, this will be 0 milliseconds.
75
+ *
76
+ * @default {milliseconds: 0}
77
+ */
78
+ holdDuration: {
79
+ milliseconds: number;
80
+ };
81
+ value: number;
82
+ /**
83
+ * The hold duration at which the last time this binding was acted upon. When the binding hasn't
84
+ * been acted on yet, this contain 0 milliseconds.
85
+ *
86
+ * This must be set by whatever process is acting on this binding, whenever it does so.
87
+ *
88
+ * @default {milliseconds: 0}
89
+ */
90
+ lastActDuration: {
91
+ milliseconds: number;
92
+ };
93
+ /**
94
+ * The number of times which this binding has been acted upon for the current hold. When a
95
+ * binding is first activated, this will be `0`.
96
+ *
97
+ * This must be incremented by whatever process is acting on this binding, whenever it does so.
98
+ *
99
+ * @default 0
100
+ */
101
+ actCount: number;
102
+ };
103
+ /**
104
+ * A collection of all active bindings for an individual player. Used in
105
+ * `createAnthaReadBindingsMod` and {@link PlayersActiveBindings}.
106
+ *
107
+ * @category Internal
108
+ */
109
+ export type ActiveBindings<BindingNames extends string = string> = Partial<Record<BindingNames, ActiveBinding>>;
110
+ /**
111
+ * A collection of all active bindings for all players. Used in `createAnthaReadBindingsMod` and
112
+ * `AnthaInputBindingsState`.
113
+ *
114
+ * @category Internal
115
+ */
116
+ export type PlayersActiveBindings<BindingNames extends string = string> = Record<PlayerPosition, ActiveBindings<BindingNames>>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A binding assignment device key that maps the input to _any_ connected controller.
3
+ *
4
+ * @category Internal
5
+ */
6
+ export const AnyGamepad = 'any-gamepad';
@@ -0,0 +1,92 @@
1
+ import { type PartialWithUndefined } from '@augment-vir/common';
2
+ import { type NavController } from 'device-navigation';
3
+ import { type RequireExactlyOne } from 'type-fest';
4
+ /**
5
+ * Keypress details emitted by the `keyPress` event from {@link AnthaKeyboard}.
6
+ *
7
+ * @category Internal
8
+ */
9
+ export type AnthaKeyboardKeyPress = RequireExactlyOne<{
10
+ typedCharacter: string;
11
+ special: AnthaKeyboardSpecialKey;
12
+ }>;
13
+ /** @category Internal */
14
+ export declare enum AnthaKeyboardSpecialKey {
15
+ Backspace = "backspace",
16
+ Enter = "enter",
17
+ Tab = "tab",
18
+ NavLeft = "nav-left",
19
+ NavRight = "nav-right",
20
+ Paste = "paste",
21
+ HideKeyboard = "hide-keyboard",
22
+ CapsLock = "caps-lock",
23
+ LeftShift = "left-shift",
24
+ RightShift = "right-shift",
25
+ ClearAll = "clear-all"
26
+ }
27
+ /** @category Internal */
28
+ export declare enum ToggleKey {
29
+ Shift = "shift",
30
+ CapsLock = "caps-lock"
31
+ }
32
+ type KeyboardToggleState = Partial<Record<ToggleKey, boolean>>;
33
+ /**
34
+ * An on-screen keyboard that works with `AnthaMenuNavMod` to allow navigation by controller.
35
+ *
36
+ * @category Pre-Build Mods
37
+ */
38
+ export declare const AnthaKeyboard: import("element-vir").DeclarativeElementDefinition<"antha-keyboard", {
39
+ navController: NavController;
40
+ } & PartialWithUndefined<{
41
+ /**
42
+ * If set to `true`, a "Hide" (keyboard hide) button is shown.
43
+ *
44
+ * @default false
45
+ */
46
+ showHideButton: boolean;
47
+ /**
48
+ * If `true`, a text box with the currently entered text is not rendered above the keyboard.
49
+ *
50
+ * @default false
51
+ */
52
+ hideText: boolean;
53
+ /**
54
+ * Max length of the text.
55
+ *
56
+ * @default 128
57
+ */
58
+ maxLength: number;
59
+ }>, {
60
+ value: string;
61
+ toggled: KeyboardToggleState;
62
+ cursorPosition: number;
63
+ beamElement: undefined | HTMLElement;
64
+ arrowRepeatDelay: undefined | ReturnType<typeof globalThis.setTimeout>;
65
+ arrowRepeatInterval: undefined | ReturnType<typeof globalThis.setInterval>;
66
+ }, {
67
+ keyPress: import("element-vir").DefineEvent<((Required<Pick<{
68
+ typedCharacter: string;
69
+ special: AnthaKeyboardSpecialKey;
70
+ }, "typedCharacter">> & Partial<Record<"special", never>>) | (Required<Pick<{
71
+ typedCharacter: string;
72
+ special: AnthaKeyboardSpecialKey;
73
+ }, "special">> & Partial<Record<"typedCharacter", never>>)) & Omit<{
74
+ typedCharacter: string;
75
+ special: AnthaKeyboardSpecialKey;
76
+ }, "typedCharacter" | "special">>;
77
+ valueChange: import("element-vir").DefineEvent<string>;
78
+ }, "antha-keyboard-", "antha-keyboard-", readonly [], readonly []>;
79
+ /**
80
+ * A helper for handling key press events from {@link AnthaKeyboard}. This is used internally in
81
+ * {@link AnthaKeyboard} but is useful if you want to hide the keyboard's built-in text pane and use
82
+ * your own processing.
83
+ *
84
+ * @category Internal
85
+ */
86
+ export declare function handleKeyPress({ currentValue, keyPress, cursorPosition, maxLength, }: Readonly<{
87
+ keyPress: Readonly<AnthaKeyboardKeyPress>;
88
+ currentValue: string;
89
+ cursorPosition: number;
90
+ maxLength: number;
91
+ }>): Promise<string>;
92
+ export {};