pixijs-input-devices 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,47 +1,98 @@
1
- # đŸ•šī¸ pixijs-input-devices  [![NPM version](https://img.shields.io/npm/v/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![Minzipped](https://badgen.net/bundlephobia/minzip/pixijs-input-devices@latest)](https://bundlephobia.com/package/pixijs-input-devices) [![Downloads per month](https://img.shields.io/npm/dm/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![Tests](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml/badge.svg)](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml) [![License](https://badgen.net/npm/license/pixijs-input-devices)](https://github.com/reececomo/pixijs-input-devices/blob/main/LICENSE)
1
+ # đŸ•šī¸ pixijs-input-devices  [![License](https://badgen.net/npm/license/pixijs-input-devices)](https://github.com/reececomo/pixijs-input-devices/blob/main/LICENSE) [![Tests](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml/badge.svg)](https://github.com/reececomo/pixijs-input-devices/actions/workflows/tests.yml) [![Downloads per month](https://img.shields.io/npm/dm/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices) [![NPM version](https://img.shields.io/npm/v/pixijs-input-devices.svg)](https://www.npmjs.com/package/pixijs-input-devices)
2
2
 
3
- 🚧 WIP - This API is a work in progress, and is subject to change.
3
+ ⚡ Powerful, high-performance input device management for PixiJS
4
4
 
5
- - Adds comprehensive support for âŒ¨ī¸ **Keyboard**, 🎮 **Gamepads**, and other human-interface devices
6
- - High-performance, easy-to-use, sensible defaults
7
- - Supports either real-time or event driven APIs
8
- - Built-in `Navigation` API to navigate pointer/mouse based menus _(optional)_
5
+ | | |
6
+ | ------ | ------ |
7
+ | 🎮 Handles [keyboards](#keyboarddevice), [gamepads](#gamepaddevice), and [more](#custom-devices)! | 🚀 Flexible [update](#real-time) and [event-driven](#keyboarddevice-events) APIs |
8
+ | ⚡ Optimized for [INP performance](https://web.dev/articles/inp) | đŸĒ„ Built-in [named binds](#named-binds) |
9
+ | 🔮 Highly configurable | 🌐 Built-in [international keyboard](#keyboard-layout---detection) support |
10
+ | ✅ Cross-platform &amp; mobile-friendly <sup>[[1]](https://caniuse.com/mdn-api_keyboardlayoutmap) [[2]](https://caniuse.com/mdn-api_gamepad_vibrationactuator) [[3]](https://chromestatus.com/feature/5989275208253440)</sup> | 🧭 Built-in [UI navigation](#navigation-api) _(optional)_ |
11
+ | 🍃 Zero dependencies & tree-shakeable | ✨ Supports PixiJS v8, v7, v6.3+ |
9
12
 
10
- <hr/>
11
13
 
12
- ## đŸ’ŋ Install
14
+ ## Sample Usage
15
+
16
+ *Handle device inputs with ease.*
17
+
18
+ ```ts
19
+ import { InputDevice } from "pixijs-input-devices";
20
+
21
+ // Iterative
22
+ let jump = false
23
+
24
+ for (const device of InputDevice.devices) {
25
+ if (device.type === "keyboard" && device.key.Space) jump = true
26
+ if (device.type === "gamepad" && device.button.A) jump = true
27
+ }
28
+
29
+ // Event-driven
30
+ const gamepad = InputDevice.gamepads[0]
31
+
32
+ gamepad?.on("LeftShoulder", (e) => {
33
+ e.device.playVibration({ duration: 100 })
34
+ });
35
+ ```
36
+
37
+ ## Getting Started with PixiJS Input Devices
38
+
39
+ *Everything you need to quickly integrate powerful device management.*
40
+
41
+ **PixiJS Input Devices** adds first-class support for input device management and input handling. It also provides an optional navigation manager
42
+ that can enable input devices to traverse pointer-based UIs.
43
+
44
+ The core concepts are:
45
+
46
+ 1. **Devices:** _Any human interface device_
47
+ 2. **Binds:** _Custom, named input actions that can be triggered by assigned keys or buttons_
48
+ 3. **UINavigation:** _A global controller that allows non-pointer devices to navigate UIs_
49
+
50
+ > [!NOTE]
51
+ > _See [UINavigation API](#uinavigation-api) for more information._
52
+
53
+
54
+ ## Installation
55
+
56
+ *Quick start guide.*
57
+
58
+ **1.** Install the latest `pixijs-input-devices` package:
13
59
 
14
60
  ```sh
15
- npm i pixijs-input-devices
61
+ # npm
62
+ npm install pixijs-input-devices -D
63
+
64
+ # yarn
65
+ yarn add pixijs-input-devices --dev
16
66
  ```
17
67
 
18
- ### Setup
68
+ **2.** Register the update loop:
19
69
 
20
70
  ```ts
21
- import { InputDevice } from "pixijs-input-devices"
71
+ import { Ticker } from 'pixi.js';
72
+ import { InputDevice } from 'pixijs-input-devices';
22
73
 
23
- Ticker.shared.add( () => InputDevice.update() )
74
+ Ticker.shared.add(ticker => InputDevice.update());
24
75
  ```
25
76
 
26
- _(Optional)_ Enable the Navigation API:
77
+ > [!TIP]
78
+ > **Input polling:** In the context of a video game, you may want to put the input update at the start of your game event loop instead.
79
+
80
+ **3.** (Optional) enable the UINavigation API
27
81
 
28
82
  ```ts
29
- import { Navigation } from "pixijs-input-devices"
83
+ import * as PIXI from 'pixi.js';
84
+ import { UINavigation, registerPixiJSNavigationMixin } from 'pixijs-input-devices';
30
85
 
31
- // set root node
32
- Navigation.stage = app.stage
86
+ const app = new PIXI.Application(/*â€Ļ*/)
33
87
 
34
- // register mixin
35
- registerPixiJSInputDeviceMixin( Container )
88
+ // enable the navigation API
89
+ UINavigation.configureWithRoot( app.stage )
90
+ registerPixiJSNavigationMixin( PIXI.Container )
36
91
  ```
37
92
 
38
- ## Overview
39
-
40
- There are a few very simple themes:
93
+ ✨ You are now ready to use inputs!
41
94
 
42
- - All devices are accessed through the `InputDevice` manager
43
- - There are three supported device types: âŒ¨ī¸ `"keyboard"`, 🎮 `"gamepad"` and đŸ‘ģ `"custom"`
44
- - Inputs can be accessed directly, or configured by [Named Groups](#named-input-groups)
95
+ ## Features
45
96
 
46
97
  ### InputDevice Manager
47
98
 
@@ -56,7 +107,7 @@ InputDevice.custom // Array<CustomDevice>
56
107
  You can access all **active/connected** devices using `.devices`:
57
108
 
58
109
  ```ts
59
- for ( const device of InputDevice.devices ) { // ...
110
+ for ( const device of InputDevice.devices ) { // â€Ļ
60
111
  ```
61
112
 
62
113
  #### InputDevice - properties
@@ -76,18 +127,18 @@ for ( const device of InputDevice.devices ) { // ...
76
127
  Access global events directly through the manager:
77
128
 
78
129
  ```ts
79
- InputDevice.on( "deviceconnected", ({ device }) => {
130
+ InputDevice.on( "deviceadded", ({ device }) => {
80
131
  // a device was connected
81
132
  // do additional setup here, show a dialog, etc.
82
133
  })
83
134
 
84
- InputDevice.off( "deviceconnected" ) // stop listening
135
+ InputDevice.off( "deviceadded" ) // stop listening
85
136
  ```
86
137
 
87
138
  | Event | Description | Payload |
88
139
  |---|---|---|
89
- | `"deviceconnected"` | `{device}` | A device has become available. |
90
- | `"devicedisconnected"` | `{device}` | A device has been removed. |
140
+ | `"deviceadded"` | `{device}` | A device has been added. |
141
+ | `"deviceremoved"` | `{device}` | A device has been removed. |
91
142
 
92
143
 
93
144
  ### KeyboardDevice
@@ -97,7 +148,7 @@ Unlike gamepads & custom devices, there is a single global keyboard device.
97
148
  ```ts
98
149
  let keyboard = InputDevice.keyboard
99
150
 
100
- if ( keyboard.key.ControlLeft ) { // ...
151
+ if ( keyboard.key.ControlLeft ) { // â€Ļ
101
152
  ```
102
153
 
103
154
  > [!NOTE]
@@ -141,12 +192,12 @@ InputDevice.keyboard.layoutSource // "manual"
141
192
  | Event | Description | Payload |
142
193
  |---|---|---|
143
194
  | `"layoutdetected"` | `{layout,layoutSource,device}` | The keyboard layout (`"QWERTY"`, `"QWERTZ"`, `"AZERTY"`, or `"JCUKEN"`) has been detected, either from the native API or from keypresses. |
144
- | `"group"` | `{groupName,event,keyCode,keyLabel,device}` | A **named input group** key was pressed. |
195
+ | `"bind"` | `{name,event,keyCode,keyLabel,device}` | A **named bind** key was pressed. |
145
196
  | **Key presses:** | | |
146
197
  | `"KeyA"` | `{event,keyCode,keyLabel,device}` | The `"KeyA"` was pressed. |
147
198
  | `"KeyB"` | `{event,keyCode,keyLabel,device}` | The `"KeyB"` was pressed. |
148
199
  | `"KeyC"` | `{event,keyCode,keyLabel,device}` | The `"KeyC"` was pressed. |
149
- | ... | ... | ... |
200
+ | â€Ļ | â€Ļ | â€Ļ |
150
201
 
151
202
 
152
203
  ### GamepadDevice
@@ -158,9 +209,9 @@ Gamepad accessors are modelled around the "Standard Controller Layout":
158
209
  ```ts
159
210
  let gamepad = InputDevice.gamepads[0]
160
211
 
161
- if ( gamepad.button.Start ) { // ...
162
- if ( gamepad.leftTrigger > 0.25 ) { // ...
163
- if ( gamepad.leftJoystick.x > 0.5 ) { // ...
212
+ if ( gamepad.button.Start ) { // â€Ļ
213
+ if ( gamepad.leftTrigger > 0.25 ) { // â€Ļ
214
+ if ( gamepad.leftJoystick.x > 0.5 ) { // â€Ļ
164
215
  ```
165
216
 
166
217
  > [!TIP]
@@ -172,12 +223,11 @@ if ( gamepad.leftJoystick.x > 0.5 ) { // ...
172
223
  Use the `playVibration()` method to play a haptic vibration, in supported browsers.
173
224
 
174
225
  ```ts
175
- gamepad.playVibration()
176
-
177
226
  gamepad.playVibration({
178
- duration: 150,
179
- weakMagnitude: 0.25,
180
- strongMagnitude: 0.65,
227
+ duration: 150,
228
+ weakMagnitude: 0.75,
229
+ strongMagnitude: 0.25,
230
+ // â€Ļ
181
231
  })
182
232
  ```
183
233
 
@@ -185,31 +235,40 @@ gamepad.playVibration({
185
235
 
186
236
  The gamepad buttons reference **Standard Controller Layout**:
187
237
 
188
- | Button # | ButtonCode | Standard | Nintendo* | Playstation | Xbox |
238
+ | Button | BindableCode | Standard | Nintendo <sup>[[1]](#gamepad---nintendo-layout-remapping)</sup> | Playstation | Xbox |
189
239
  |:---:|:---:|:---:|:---:|:---:|:---:|
190
- | `0` | `"A"` | **A** | A | Cross | A |
191
- | `1` | `"B"` | **B** | X | Circle | B |
192
- | `2` | `"X"` | **X** | B | Square | X |
193
- | `3` | `"Y"` | **Y** | Y | Triangle | Y |
240
+ | `0` | `"A"` | **A / FaceButton1** | A | Cross | A |
241
+ | `1` | `"B"` | **B / FaceButton2** | X | Circle | B |
242
+ | `2` | `"X"` | **X / FaceButton3** | B | Square | X |
243
+ | `3` | `"Y"` | **Y / FaceButton4** | Y | Triangle | Y |
194
244
  | `4` | `"LeftShoulder"` | **Left Shoulder** | L | L1 | LB |
195
245
  | `5` | `"RightShoulder"` | **Right Shoulder** | R | R1 | RB |
196
246
  | `6` | `"LeftTrigger"` | **Left Trigger** | L2 | ZL | LT |
197
247
  | `7` | `"RightTrigger"` | **Right Trigger** | R2 | ZR | RT |
198
248
  | `8` | `"Back"` | **Back** | Minus | Options | Back |
199
249
  | `9` | `"Start"` | **Start** | Plus | Select | Start |
200
- | `10` | `"LeftStick"` | **Left Stick (click)** | L3 | L3 | LSB |
201
- | `11` | `"RightStick"` | **Right Stick (click)** | R3 | R3 | RSB |
250
+ | `10` | `"LeftStickClick"` | **Left Stick Click** | L3 | L3 | LSB |
251
+ | `11` | `"RightStickClick"` | **Right Stick Click** | R3 | R3 | RSB |
202
252
  | `12` | `"DPadUp"` | **D-Pad Up** | âŦ†ī¸ | âŦ†ī¸ | âŦ†ī¸ |
203
253
  | `13` | `"DPadDown"` | **D-Pad Down** | âŦ‡ī¸ | âŦ‡ī¸ | âŦ‡ī¸ |
204
254
  | `14` | `"DPadLeft"` | **D-Pad Left** | âŦ…ī¸ | âŦ…ī¸ | âŦ…ī¸ |
205
255
  | `15` | `"DPadRight"` | **D-Pad Right** | âžĄī¸ | âžĄī¸ | âžĄī¸ |
206
256
 
207
- *See [Nintendo Layout Remapping](#gamepad---nintendo-layout-remapping) for more context
257
+ #### Gamepad Axis Codes
258
+
259
+ Bindable helpers are available for the joysticks.
260
+
261
+ | Axis # | AxisCode | Standard | Layout
262
+ |:---:|:---:|:---:|:---:|
263
+ | `0` | `"LeftStickLeft"`<br/>`"LeftStickRight"` | **Left Stick (Left/Right)** | âŦ…ī¸âžĄī¸ |
264
+ | `1` | `"LeftStickUp"`<br/>`"LeftStickDown"` | **Left Stick (Up/Down)** | âŦ†ī¸âŦ‡ī¸ |
265
+ | `2` | `"RightStickLeft"`<br/>`"RightStickRight"` | **Right Stick (Left/Right)** | âŦ…ī¸âžĄī¸ |
266
+ | `3` | `"RightStickUp"`<br/>`"RightStickDown"` | **Right Stick (Up/Down)** | âŦ†ī¸âŦ‡ī¸ |
208
267
 
209
268
  #### Gamepad Layouts
210
269
 
211
270
  ```ts
212
- gamepad.layout // "nintendo" | "xbox" | "playstation" | "logitech" | "steam" | "generic"
271
+ gamepad.layout // "nintendo" | "xbox" | "playstation" | "logitech" | "steam" | "standard"
213
272
  ```
214
273
 
215
274
  Layout detection is **highly non-standard** across major browsers, it should generally be used for aesthetic
@@ -224,10 +283,10 @@ only major brand controller that deviates from the standard.
224
283
  > ***Nintendo:** Both the labels and physical positions of the A,B,X,Y buttons are different
225
284
  > on Nintendo controllers.
226
285
  >
227
- > Set `GamepadDevice.defaultOptions.remapNintendoMode` to apply the remapping as required.
286
+ > Set `GamepadDevice.defaultOptions.nintendoRemapMode` to apply the remapping as required.
228
287
  >
229
- > - `"physical"` _**(default)**_ &ndash; The A,B,X,Y button codes will refer the physical layout of a standard controller (Left=X, Top=Y, Bottom=A, Right=B).
230
- > - `"accurate"` &ndash; The A,B,X,Y button codes will correspond to the exact Nintendo labels (Left=Y, Top=X, Bottom=B, Right=A).
288
+ > - `"physical"` _**(default)**_ &ndash; The A,B,X,Y button codes will refer the standard face button positions (Left=X, Top=Y, Bottom=A, Right=B).
289
+ > - `"accurate"` &ndash; The A,B,X,Y button codes will refer to the exact Nintendo labels (Left=Y, Top=X, Bottom=B, Right=A).
231
290
  > - `"none"` &ndash; The A,B,X,Y button codes mapping stay at the default indices (Left=Y, Top=B, Bottom=X, Right=A).
232
291
  >
233
292
  > ```
@@ -247,25 +306,28 @@ only major brand controller that deviates from the standard.
247
306
  You can manually override this per-gamepad, or for all gamepads:
248
307
 
249
308
  ```ts
250
- gamepad.options.remapNintendoMode = "none"
251
- GamepadDevice.defaultOptions.remapNintendoMode = "none"
309
+ // set default
310
+ GamepadDevice.defaultOptions.nintendoRemapMode = "none"
311
+
312
+ // set for a single gamepad
313
+ gamepad.options.nintendoRemapMode = "accurate"
252
314
  ```
253
315
 
254
316
  #### GamepadDevice Events
255
317
 
256
318
  | Event | Description | Payload |
257
319
  |---|---|---|
258
- | `"group"` | `{groupName,button,buttonCode,device}` | A **named input group** button was pressed. |
320
+ | `"bind"` | `{name,button,buttonCode,device}` | A **named bind** button was pressed. |
259
321
  | **Button presses:** | | |
260
322
  | `"A"` | `{button,buttonCode,device}` | Standard layout button `"A"` was pressed. Equivalent to `0`. |
261
323
  | `"B"` | `{button,buttonCode,device}` | Standard layout button `"B"` was pressed. Equivalent to `1`. |
262
324
  | `"X"` | `{button,buttonCode,device}` | Standard layout button `"X"` was pressed. Equivalent to `2`. |
263
- | ... | ... | ... |
325
+ | â€Ļ | â€Ļ | â€Ļ |
264
326
  | **Button presses (no label):** | | |
265
327
  | `0` or `Button.A` | `{button,buttonCode,device}` | Button at offset `0` was pressed. |
266
328
  | `1` or `Button.B` | `{button,buttonCode,device}` | Button at offset `1` was pressed. |
267
329
  | `2` or `Button.X` | `{button,buttonCode,device}` | Button at offset `2` was pressed. |
268
- | ... | ... | ... |
330
+ | â€Ļ | â€Ļ | â€Ļ |
269
331
 
270
332
  ### Custom Devices
271
333
 
@@ -287,26 +349,26 @@ export const myDevice: CustomDevice = {
287
349
  InputDevice.add( myDevice )
288
350
  ```
289
351
 
290
- ## Named Input Groups
352
+ ## Named Binds
291
353
 
292
- Use named "groups" to create named inputs that can be referenced.
354
+ Use _named binds_ to create mappings between abstract inputs and the keys/buttons that trigger those inputs.
293
355
 
294
356
  This allows you to change the keys/buttons later (e.g. allow users to override inputs).
295
357
 
296
358
  ```ts
297
359
  // keyboard:
298
- InputDevice.keyboard.options.namedGroups = {
360
+ InputDevice.keyboard.configureBinds({
299
361
  jump: [ "ArrowUp", "Space", "KeyW" ],
300
362
  crouch: [ "ArrowDown", "KeyS" ],
301
363
  toggleGraphics: [ "KeyB" ],
302
- }
364
+ })
303
365
 
304
366
  // all gamepads:
305
- GamepadDevice.defaultOptions.namedGroups = {
306
- jump: [ "A" ],
367
+ GamepadDevice.configureDefaultBinds({
368
+ jump: [ "A", "LeftStickUp" ],
307
369
  crouch: [ "B", "X", "RightTrigger" ],
308
- toggleGraphics: [ "RightStick" ],
309
- }
370
+ toggleGraphics: [ "RightStickUp", "RightStickDown" ],
371
+ })
310
372
  ```
311
373
 
312
374
  These can then be used with either the real-time and event-based APIs.
@@ -315,11 +377,11 @@ These can then be used with either the real-time and event-based APIs.
315
377
 
316
378
  ```ts
317
379
  // listen to all devices:
318
- InputDevice.onGroup( "toggleGraphics", ( e ) => toggleGraphics() )
380
+ InputDevice.onBind( "toggleGraphics", ( e ) => toggleGraphics() )
319
381
 
320
382
  // listen to specific devices:
321
- InputDevice.keyboard.onGroup( "jump", ( e ) => doJump() )
322
- InputDevice.gamepads[0].onGroup( "jump", ( e ) => doJump() )
383
+ InputDevice.keyboard.onBind( "jump", ( e ) => doJump() )
384
+ InputDevice.gamepads[0].onBind( "jump", ( e ) => doJump() )
323
385
  ```
324
386
 
325
387
  #### Real-time:
@@ -328,14 +390,14 @@ InputDevice.gamepads[0].onGroup( "jump", ( e ) => doJump() )
328
390
  let jump = false, crouch = false, moveX = 0
329
391
 
330
392
  const keyboard = InputDevice.keyboard
331
- if ( keyboard.groupPressed( "jump" ) ) jump = true
332
- if ( keyboard.groupPressed( "crouch" ) ) crouch = true
393
+ if ( keyboard.pressedBind( "jump" ) ) jump = true
394
+ if ( keyboard.pressedBind( "crouch" ) ) crouch = true
333
395
  if ( keyboard.key.ArrowLeft ) moveX = -1
334
396
  else if ( keyboard.key.ArrowRight ) moveX = 1
335
397
 
336
398
  for ( const gamepad of InputDevice.gamepads ) {
337
- if ( gamepad.groupPressed( "jump" ) ) jump = true
338
- if ( gamepad.groupPressed( "crouch" ) ) crouch = true
399
+ if ( gamepad.pressedBind( "jump" ) ) jump = true
400
+ if ( gamepad.pressedBind( "crouch" ) ) crouch = true
339
401
 
340
402
  // gamepads have additional analog inputs
341
403
  // we're going to apply these only if touched
@@ -344,123 +406,69 @@ for ( const gamepad of InputDevice.gamepads ) {
344
406
  }
345
407
  ```
346
408
 
347
- ## Navigation API
348
-
349
- Automatically traverse existing pointer/mouse based menus using the `Navigation` API.
350
-
351
- ```ts
352
- // set root container
353
- Navigation.stage = app.stage
354
-
355
- const button = new ButtonSprite()
356
- button.on( "mousedown", () => button.run( clickAnimation ) )
357
- button.on( "mouseout", () => button.run( resetAnimation ) )
358
- button.on( "mouseover", () => button.run( hoverAnimation ) )
359
-
360
- app.stage.addChild( button )
361
-
362
- button.isNavigatable // true
363
- ```
364
-
365
- > [!NOTE]
366
- > **isNavigatable:** By default, any element with `"mousedown"` or `"pointerdown"` handlers is navigatable.
409
+ ## UINavigation API
367
410
 
368
- > [!WARNING]
369
- > **Fallback Hover Effect:** If there is no `"pointerover"` or `"mouseover"` handler detected on a container, `Navigation`
370
- > will apply abasic alpha effect to the selected item to indicate which container is currently the navigation target. This
371
- > can be disabled by setting `Navigation.options.useFallbackHoverEffect` to `false`.
372
-
373
- ### Disable Navigation
374
-
375
- You can **disable** the navigation API - either permanently or temporarily - like so:
411
+ _Traverse a UI using input devices._
376
412
 
377
413
  ```ts
378
- Navigation.options.enabled = false
414
+ UINavigation.configureWithRoot( app.stage ) // (or any Container)
379
415
  ```
380
416
 
381
- ### NavigationResponders
382
-
383
- UIs can be complex! The Navigation API allows you to take over some - or all - of the navigation elements.
384
-
385
- You can create **NavigationResponder** controllers, which can be a `Container` that becomes the
386
- "root" node for navigation. It can also just be any object (like a custom manager class).
387
-
388
- It has a method called `handledNavigationIntent(): boolean` which can return a boolean saying whether
389
- the navigation event was handled. If you return false here, it is bubbled up to the next parent in the
390
- stack.
391
-
392
- To add a responder, just use `Navigation.pushResponder( responder )` - and then remove it with `Navigation.popResponder()`.
417
+ You can manually take control of navigation using:
393
418
 
394
419
  ```ts
395
- class MyVerticalMenu implements NavigationResponder
396
- {
397
- handledNavigationIntent( intent, device ): boolean {
398
- if ( intent === "navigateUp" ) this.moveCursorUp()
399
- else if ( intent === "navigateDown" ) this.moveCursorDown()
400
- else if ( intent === "navigateBack" ) this.loseFocus()
401
- else if ( intent === "trigger" ) this.clickCursorItem()
402
-
403
- // we are going to return false here, which will propagates unhandled
404
- // intents ("navigateLeft", "navigateRight") up to the next responder
405
- // in the stack - which could be a parent view, etc.
406
- return false
407
- }
420
+ // take control
421
+ UINavigation.pushResponder( myModalView )
408
422
 
409
- becameFirstResponder() {
410
- console.log( "I'm in charge now!" )
411
- }
423
+ // relinquish control
424
+ UINavigation.popResponder()
425
+ ```
412
426
 
413
- resignedAsFirstResponder() {
414
- console.log( "Nooo! My power is gone!" )
415
- }
416
- }
427
+ The Navigation API is centered around the **UINavigation** manager, which
428
+ receives navigation intents from devices and forwards it to the UI context.
417
429
 
418
- const myMenu = new MyVerticalMenu()
419
- Navigation.pushResponder( myMenu )
420
- ```
430
+ The **UINavigation** manager maintains a stack of responders, which can be a
431
+ `Container`, or any object that implements the `NavigationResponder` interface.
421
432
 
422
- In a game, you might use this to disable navigation outside of menus:
433
+ When a device sends a navigation intent, the **UINavigation** manager is
434
+ responsible for asking the **first responder** whether it can handle the intent.
423
435
 
424
- ```ts
425
- class GameScene implements NavigationResponder
426
- {
427
- handledNavigationIntent( intent, device ) {
428
- // ignore navigation intents, but allow other navigatable
429
- // views to be pushed on top of me - e.g. a dialog window:
430
- return true
431
- }
432
- }
433
- ```
436
+ If it returns `false`, any other responders are checked (if they exist),
437
+ otherwise the default global navigation behavior kicks in.
434
438
 
435
- ### Default Navigation Binds
439
+ ### Default Global Navigation Behaviors
436
440
 
437
- Keyboard and gamepad devices are configured with a few default binds for navigation.
441
+ When a navigation intent is **not** handled manually by a responder, it is handled in one of the following ways:
438
442
 
439
- The default binds are below:
443
+ | Intent | Behavior |
444
+ |---|---|
445
+ |`"navigate.back"`|<ul><li>No action.</li></ul>|
446
+ |`"navigate.left"`, `"navigate.right"`, `"navigate.up"`, `"navigate.down"`|<ul><li>Looks for the nearest `Container` where `container.isNavigatable` in the direction given, and if found, receives a `"deviceover"` event.</li><li>Additionally, if the newly focused container has registered an event handler for either `"pointerover"` or `"mouseover"` (in that order), it will fire that too.</li><li>If we were previously focused on a container, that previous container receives a `"deviceout"` event.</li><li>If the blurred container has register an event handler for either `"pointerout"` or `"mouseout"` (in that order), that event handler will be fired too.</li></ul>|
447
+ |`"navigate.trigger"`|<ul><li>Checks if we are currently focused on a container, and then issue a `"devicedown"` event.</li><li>If the focused container has registered an event handler for either `"pointerdown"` or `"mousedown"` (in that order), that event handler will be fired too.</li></ul>|
440
448
 
441
- Navigation Intent | Keyboard | Gamepad
442
- ------------------|------------------------|-----------------------------------
443
- `"navigateLeft"` | `ArrowLeft`, `KeyA` | Left Joystick (Left), `DPadLeft`
444
- `"navigateRight"` | `ArrowRight`, `KeyD` | Left Joystick (Right), `DPadRight`
445
- `"navigateUp"` | `ArrowUp`, `KeyW` | Left Joystick (Up), `DPadDown`
446
- `"navigateDown"` | `ArrowDown`, `KeyS` | Left Joystick (Down), `DPadUp`
447
- `"navigateBack"` | `Escape`, `Backspace` | `B`, `Back`
448
- `"trigger"` | `Enter,` `Space` | `A`
449
+ Container event | Description | Compatibility
450
+ -----------------|--------------------------------------------------------
451
+ `"devicedown"` | Target was triggered. | `"pointerdown"`, `"mousedown"`
452
+ `"deviceover"` | Target became focused. | `"pointerover"`, `"mouseover"`
453
+ `"deviceout"` | Target lost focus. | `"pointerout"`, `"mouseout"`
449
454
 
450
- These can be manually configured in `<device>.options.navigation.binds`.
455
+ ### Container Navigatability
451
456
 
452
- #### Container Mixin
457
+ Containers are extended with a few properties/accessors:
453
458
 
454
459
  Container properties | type | default | description
455
460
  ---------------------|------|---------|--------------
456
- `isNavigatable` | `boolean` | `false` | returns `true` if `navigationMode` is set to `"target"`, or is `"auto"` and a `"pointerdown"` or `"mousedown"` event handler is registered.
461
+ `isNavigatable` | `get(): boolean` | `false` | returns `true` if `navigationMode` is set to `"target"`, or is `"auto"` and a `"pointerdown"` or `"mousedown"` event handler is registered.
457
462
  `navigationMode` | `"auto"` \| `"disabled"` \| `"target"` | `"auto"` | When set to `"auto"`, a `Container` can be navigated to if it has a `"pointerdown"` or `"mousedown"` event handler registered.
458
463
  `navigationPriority` | `number` | `0` | The priority relative to other navigation items in this group.
459
464
 
460
- Container events | description
461
- ------------------|--------------------------------------------------------
462
- `focus` | Target became focused.
463
- `blur` | Target lost focus.
465
+ > [!NOTE]
466
+ > **isNavigatable:** By default, any element with `"pointerdown"` or `"mousedown"` handlers is navigatable.
467
+
468
+ > [!WARNING]
469
+ > **Fallback Hover Effect:** If there is no `"pointerover"` or `"mouseover"` handler detected on a container, `UINavigation`
470
+ > will apply abasic alpha effect to the selected item to indicate which container is currently the navigation target. This
471
+ > can be disabled by setting `UINavigation.options.useFallbackHoverEffect` to `false`.
464
472
 
465
473
 
466
474
  ## Advanced usage
@@ -519,7 +527,7 @@ InputDevice.remove( onscreen )
519
527
  You could set up multiple named inputs:
520
528
 
521
529
  ```ts
522
- InputDevice.keyboard.options.namedGroups = {
530
+ InputDevice.keyboard.configureBinds({
523
531
  jump: [ "ArrowUp", "KeyW" ],
524
532
  defend: [ "ArrowDown", "KeyS" ],
525
533
  left: [ "ArrowLeft", "KeyA" ],
@@ -534,31 +542,31 @@ InputDevice.keyboard.options.namedGroups = {
534
542
  p2_defend: [ "ArrowDown" ],
535
543
  p2_left: [ "ArrowLeft" ],
536
544
  p2_right: [ "ArrowRight" ],
537
- }
545
+ })
538
546
  ```
539
547
 
540
548
  and then switch groups depending on the mode:
541
549
 
542
550
  ```ts
543
- if ( gameMode === "2p" )
551
+ if ( gameMode === "multiplayer" )
544
552
  {
545
- // multiplayer
546
- player1.jump = device.pressedGroup( "p1_jump" )
547
- player1.defend = device.pressedGroup( "p1_defend" )
548
- player1.moveX += device.pressedGroup( "p1_left" ) ? -1 : 0
549
- player1.moveX += device.pressedGroup( "p1_right" ) ? 1 : 0
550
- player2.jump = device.pressedGroup( "p2_jump" )
551
- player2.defend = device.pressedGroup( "p2_defend" )
552
- player2.moveX += device.pressedGroup( "p2_left" ) ? -1 : 0
553
- player2.moveX += device.pressedGroup( "p2_right" ) ? 1 : 0
553
+ player1.jump = device.pressedBind( "p1_jump" )
554
+ player1.defend = device.pressedBind( "p1_defend" )
555
+ player1.moveX += device.pressedBind( "p1_left" ) ? -1 : 0
556
+ player1.moveX += device.pressedBind( "p1_right" ) ? 1 : 0
557
+
558
+ player2.jump = device.pressedBind( "p2_jump" )
559
+ player2.defend = device.pressedBind( "p2_defend" )
560
+ player2.moveX += device.pressedBind( "p2_left" ) ? -1 : 0
561
+ player2.moveX += device.pressedBind( "p2_right" ) ? 1 : 0
554
562
  }
555
563
  else
556
564
  {
557
- // single player
558
- player1.jump = device.pressedGroup( "jump" )
559
- player1.defend = device.pressedGroup( "defend" )
560
- player1.moveX += device.pressedGroup( "left" ) ? -1 : 0
561
- player1.moveX += device.pressedGroup( "right" ) ? 1 : 0
562
- player2.updateComputerPlayer()
565
+ player1.jump = device.pressedBind( "jump" )
566
+ player1.defend = device.pressedBind( "defend" )
567
+ player1.moveX += device.pressedBind( "left" ) ? -1 : 0
568
+ player1.moveX += device.pressedBind( "right" ) ? 1 : 0
569
+
570
+ updateComputerPlayerInput( player2 )
563
571
  }
564
572
  ```