@skewedaspect/sage 0.3.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/LICENSE +21 -0
- package/Readme.md +53 -0
- package/dist/classes/bindings/toggle.d.ts +122 -0
- package/dist/classes/bindings/trigger.d.ts +79 -0
- package/dist/classes/bindings/value.d.ts +104 -0
- package/dist/classes/entity.d.ts +83 -0
- package/dist/classes/eventBus.d.ts +94 -0
- package/dist/classes/gameEngine.d.ts +57 -0
- package/dist/classes/input/gamepad.d.ts +94 -0
- package/dist/classes/input/keyboard.d.ts +66 -0
- package/dist/classes/input/mouse.d.ts +80 -0
- package/dist/classes/input/readers/gamepad.d.ts +77 -0
- package/dist/classes/input/readers/keyboard.d.ts +60 -0
- package/dist/classes/input/readers/mouse.d.ts +45 -0
- package/dist/classes/loggers/consoleBackend.d.ts +29 -0
- package/dist/classes/loggers/nullBackend.d.ts +14 -0
- package/dist/engines/scene.d.ts +11 -0
- package/dist/interfaces/action.d.ts +20 -0
- package/dist/interfaces/binding.d.ts +144 -0
- package/dist/interfaces/entity.d.ts +9 -0
- package/dist/interfaces/game.d.ts +26 -0
- package/dist/interfaces/input.d.ts +181 -0
- package/dist/interfaces/logger.d.ts +88 -0
- package/dist/managers/binding.d.ts +185 -0
- package/dist/managers/entity.d.ts +70 -0
- package/dist/managers/game.d.ts +20 -0
- package/dist/managers/input.d.ts +56 -0
- package/dist/managers/level.d.ts +55 -0
- package/dist/sage.d.ts +20 -0
- package/dist/sage.es.js +2208 -0
- package/dist/sage.es.js.map +1 -0
- package/dist/sage.umd.js +2 -0
- package/dist/sage.umd.js.map +1 -0
- package/dist/utils/capabilities.d.ts +2 -0
- package/dist/utils/graphics.d.ts +10 -0
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/physics.d.ts +2 -0
- package/dist/utils/version.d.ts +5 -0
- package/docs/architecture.md +129 -0
- package/docs/behaviors.md +706 -0
- package/docs/binding_system.md +820 -0
- package/docs/design/input.md +86 -0
- package/docs/entity_system.md +538 -0
- package/docs/eventbus.md +225 -0
- package/docs/getting_started.md +264 -0
- package/docs/images/sage_logo.png +0 -0
- package/docs/images/sage_logo_shape.png +0 -0
- package/docs/overview.md +38 -0
- package/docs/physics_system.md +686 -0
- package/docs/scene_system.md +513 -0
- package/package.json +69 -0
- package/src/classes/bindings/toggle.ts +261 -0
- package/src/classes/bindings/trigger.ts +211 -0
- package/src/classes/bindings/value.ts +227 -0
- package/src/classes/entity.ts +256 -0
- package/src/classes/eventBus.ts +259 -0
- package/src/classes/gameEngine.ts +125 -0
- package/src/classes/input/gamepad.ts +388 -0
- package/src/classes/input/keyboard.ts +189 -0
- package/src/classes/input/mouse.ts +276 -0
- package/src/classes/input/readers/gamepad.ts +179 -0
- package/src/classes/input/readers/keyboard.ts +123 -0
- package/src/classes/input/readers/mouse.ts +133 -0
- package/src/classes/loggers/consoleBackend.ts +135 -0
- package/src/classes/loggers/nullBackend.ts +51 -0
- package/src/engines/scene.ts +112 -0
- package/src/images/sage_logo.svg +172 -0
- package/src/images/sage_logo_shape.svg +146 -0
- package/src/interfaces/action.ts +30 -0
- package/src/interfaces/binding.ts +191 -0
- package/src/interfaces/entity.ts +21 -0
- package/src/interfaces/game.ts +44 -0
- package/src/interfaces/input.ts +221 -0
- package/src/interfaces/logger.ts +118 -0
- package/src/managers/binding.ts +729 -0
- package/src/managers/entity.ts +252 -0
- package/src/managers/game.ts +111 -0
- package/src/managers/input.ts +233 -0
- package/src/managers/level.ts +261 -0
- package/src/sage.ts +119 -0
- package/src/types/global.d.ts +11 -0
- package/src/utils/capabilities.ts +16 -0
- package/src/utils/graphics.ts +148 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/physics.ts +16 -0
- package/src/utils/version.ts +11 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Gamepad Input
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
// Interfaces
|
|
6
|
+
import type { ButtonState, GamepadDevice, GamepadInputState } from '../../interfaces/input.ts';
|
|
7
|
+
|
|
8
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Callback type for gamepad device connection events
|
|
12
|
+
*/
|
|
13
|
+
export type GamepadDeviceCallback = (device : GamepadDevice) => void;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callback type for gamepad input events
|
|
17
|
+
*/
|
|
18
|
+
export type GamepadInputCallback = (device : GamepadDevice, state : GamepadInputState) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Responsible for tracking gamepad state and notifying subscribers of changes.
|
|
22
|
+
*/
|
|
23
|
+
export class GamepadInputPlugin
|
|
24
|
+
{
|
|
25
|
+
private _gamepadDevices : Record<number, GamepadDevice> = {};
|
|
26
|
+
private _buttonStates : Record<number, Record<string, ButtonState>> = {};
|
|
27
|
+
private _axesStates : Record<number, Record<string, number>> = {};
|
|
28
|
+
|
|
29
|
+
// Callbacks
|
|
30
|
+
private _onDeviceConnected ?: GamepadDeviceCallback;
|
|
31
|
+
private _onDeviceDisconnected ?: GamepadDeviceCallback;
|
|
32
|
+
private _onInputChanged ?: GamepadInputCallback;
|
|
33
|
+
|
|
34
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new GamepadResourceAccess
|
|
38
|
+
*/
|
|
39
|
+
constructor()
|
|
40
|
+
{
|
|
41
|
+
// Set up event listeners for gamepad connection and disconnection
|
|
42
|
+
this._setupGamepadEvents();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
46
|
+
// Public API
|
|
47
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a callback for device connected events
|
|
51
|
+
*
|
|
52
|
+
* @param callback - The callback to register
|
|
53
|
+
*/
|
|
54
|
+
public onDeviceConnected(callback : GamepadDeviceCallback) : void
|
|
55
|
+
{
|
|
56
|
+
this._onDeviceConnected = callback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a callback for device disconnected events
|
|
61
|
+
*
|
|
62
|
+
* @param callback - The callback to register
|
|
63
|
+
*/
|
|
64
|
+
public onDeviceDisconnected(callback : GamepadDeviceCallback) : void
|
|
65
|
+
{
|
|
66
|
+
this._onDeviceDisconnected = callback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Register a callback for input changed events
|
|
71
|
+
*
|
|
72
|
+
* @param callback - The callback to register
|
|
73
|
+
*/
|
|
74
|
+
public onInputChanged(callback : GamepadInputCallback) : void
|
|
75
|
+
{
|
|
76
|
+
this._onInputChanged = callback;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get all connected gamepad devices
|
|
81
|
+
*/
|
|
82
|
+
public getDevices() : GamepadDevice[]
|
|
83
|
+
{
|
|
84
|
+
return Object.values(this._gamepadDevices).map((device) => ({ ...device }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get all connected gamepad states
|
|
89
|
+
*/
|
|
90
|
+
public getStates() : Record<number, GamepadInputState>
|
|
91
|
+
{
|
|
92
|
+
const states : Record<number, GamepadInputState> = {};
|
|
93
|
+
|
|
94
|
+
Object.keys(this._buttonStates).forEach((indexStr) =>
|
|
95
|
+
{
|
|
96
|
+
const index = Number(indexStr);
|
|
97
|
+
const buttons = this._buttonStates[index] || {};
|
|
98
|
+
const axes = this._axesStates[index] || {};
|
|
99
|
+
|
|
100
|
+
states[index] = {
|
|
101
|
+
type: 'gamepad',
|
|
102
|
+
buttons: { ...buttons },
|
|
103
|
+
axes: { ...axes },
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return states;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a specific gamepad device by index
|
|
112
|
+
*
|
|
113
|
+
* @param index - The index of the gamepad to get
|
|
114
|
+
*/
|
|
115
|
+
public getDevice(index : number) : GamepadDevice | null
|
|
116
|
+
{
|
|
117
|
+
const device = this._gamepadDevices[index];
|
|
118
|
+
return device ? { ...device } : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get a specific gamepad state by index
|
|
123
|
+
*
|
|
124
|
+
* @param index - The index of the gamepad state to get
|
|
125
|
+
*/
|
|
126
|
+
public getState(index : number) : GamepadInputState | null
|
|
127
|
+
{
|
|
128
|
+
const buttons = this._buttonStates[index];
|
|
129
|
+
const axes = this._axesStates[index];
|
|
130
|
+
|
|
131
|
+
if(!buttons && !axes) { return null; }
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
type: 'gamepad',
|
|
135
|
+
buttons: buttons ? { ...buttons } : {},
|
|
136
|
+
axes: axes ? { ...axes } : {},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Poll for gamepad state updates - call this in your game loop
|
|
142
|
+
*/
|
|
143
|
+
public pollGamepads() : void
|
|
144
|
+
{
|
|
145
|
+
/* eslint-disable no-continue */
|
|
146
|
+
|
|
147
|
+
if(!navigator.getGamepads) { return; }
|
|
148
|
+
|
|
149
|
+
const gamepads = navigator.getGamepads();
|
|
150
|
+
for(const gamepad of gamepads)
|
|
151
|
+
{
|
|
152
|
+
if(!gamepad) { continue; }
|
|
153
|
+
|
|
154
|
+
const index = gamepad.index;
|
|
155
|
+
|
|
156
|
+
if(!this._gamepadDevices[index])
|
|
157
|
+
{
|
|
158
|
+
this._handleGamepadConnected(gamepad);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Get device
|
|
163
|
+
const device = this._gamepadDevices[index];
|
|
164
|
+
if(!device) { continue; }
|
|
165
|
+
|
|
166
|
+
// Get current button and axis states or initialize them
|
|
167
|
+
const currentButtons = this._buttonStates[index] || {};
|
|
168
|
+
const currentAxes = this._axesStates[index] || {};
|
|
169
|
+
|
|
170
|
+
// Build new button state object
|
|
171
|
+
const newButtons : Record<string, ButtonState> = {};
|
|
172
|
+
let buttonsChanged = false;
|
|
173
|
+
|
|
174
|
+
gamepad.buttons.forEach((btn, i) =>
|
|
175
|
+
{
|
|
176
|
+
const buttonKey = `button-${ i }`;
|
|
177
|
+
const buttonState : ButtonState = {
|
|
178
|
+
pressed: btn.pressed,
|
|
179
|
+
touched: btn.touched,
|
|
180
|
+
value: btn.value,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
newButtons[buttonKey] = buttonState;
|
|
184
|
+
|
|
185
|
+
const prevButton = currentButtons[buttonKey];
|
|
186
|
+
if(!prevButton
|
|
187
|
+
|| prevButton.pressed !== buttonState.pressed
|
|
188
|
+
|| prevButton.touched !== buttonState.touched
|
|
189
|
+
|| prevButton.value !== buttonState.value)
|
|
190
|
+
{
|
|
191
|
+
buttonsChanged = true;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Build new axis state object
|
|
196
|
+
const newAxes : Record<string, number> = {};
|
|
197
|
+
let axesChanged = false;
|
|
198
|
+
|
|
199
|
+
gamepad.axes.forEach((axisValue, i) =>
|
|
200
|
+
{
|
|
201
|
+
const axisKey = `axis-${ i }`;
|
|
202
|
+
newAxes[axisKey] = axisValue;
|
|
203
|
+
|
|
204
|
+
const prevAxis = currentAxes[axisKey];
|
|
205
|
+
if(prevAxis !== axisValue)
|
|
206
|
+
{
|
|
207
|
+
axesChanged = true;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Update state objects
|
|
212
|
+
this._buttonStates[index] = newButtons;
|
|
213
|
+
this._axesStates[index] = newAxes;
|
|
214
|
+
|
|
215
|
+
// Notify if state changed
|
|
216
|
+
if(buttonsChanged || axesChanged)
|
|
217
|
+
{
|
|
218
|
+
const state : GamepadInputState = {
|
|
219
|
+
type: 'gamepad',
|
|
220
|
+
buttons: { ...newButtons },
|
|
221
|
+
axes: { ...newAxes },
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
this._notifyInputChanged(device, state);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* eslint-enable no-continue */
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Destroy the gamepad resource access and clean up event listeners
|
|
233
|
+
*/
|
|
234
|
+
public destroy() : void
|
|
235
|
+
{
|
|
236
|
+
// Remove gamepad event listeners
|
|
237
|
+
window.removeEventListener('gamepadconnected', this._handleGamepadConnected);
|
|
238
|
+
window.removeEventListener('gamepaddisconnected', this._handleGamepadDisconnected);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
242
|
+
// Private Methods
|
|
243
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Set up gamepad event listeners
|
|
247
|
+
*/
|
|
248
|
+
private _setupGamepadEvents() : void
|
|
249
|
+
{
|
|
250
|
+
// Bind handlers to this instance
|
|
251
|
+
this._handleGamepadConnected = this._handleGamepadConnected.bind(this);
|
|
252
|
+
this._handleGamepadDisconnected = this._handleGamepadDisconnected.bind(this);
|
|
253
|
+
|
|
254
|
+
// Add event listeners
|
|
255
|
+
window.addEventListener('gamepadconnected', this._handleGamepadConnected);
|
|
256
|
+
window.addEventListener('gamepaddisconnected', this._handleGamepadDisconnected);
|
|
257
|
+
|
|
258
|
+
// Check for already connected gamepads (browsers sometimes miss the connected event)
|
|
259
|
+
if(navigator.getGamepads)
|
|
260
|
+
{
|
|
261
|
+
const gamepads = navigator.getGamepads();
|
|
262
|
+
for(const gamepad of gamepads)
|
|
263
|
+
{
|
|
264
|
+
if(gamepad)
|
|
265
|
+
{
|
|
266
|
+
this._handleGamepadConnected(gamepad);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Handle gamepad connected event
|
|
274
|
+
*/
|
|
275
|
+
private _handleGamepadConnected(event : GamepadEvent | Gamepad) : void
|
|
276
|
+
{
|
|
277
|
+
const gamepad = event instanceof GamepadEvent ? event.gamepad : event;
|
|
278
|
+
const index = gamepad.index;
|
|
279
|
+
|
|
280
|
+
// Create gamepad device object
|
|
281
|
+
const gamepadDevice : GamepadDevice = {
|
|
282
|
+
id: `gamepad-${ index }`,
|
|
283
|
+
name: gamepad.id,
|
|
284
|
+
type: 'gamepad',
|
|
285
|
+
connected: true,
|
|
286
|
+
index,
|
|
287
|
+
mapping: gamepad.mapping,
|
|
288
|
+
axes: Array.from(gamepad.axes),
|
|
289
|
+
buttons: Array.from(gamepad.buttons),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Store the device
|
|
293
|
+
this._gamepadDevices[index] = gamepadDevice;
|
|
294
|
+
|
|
295
|
+
// Initialize button state object
|
|
296
|
+
const buttonStateObj : Record<string, ButtonState> = {};
|
|
297
|
+
Array.from(gamepad.buttons).forEach((btn, i) =>
|
|
298
|
+
{
|
|
299
|
+
buttonStateObj[`button-${ i }`] = {
|
|
300
|
+
pressed: btn.pressed,
|
|
301
|
+
touched: btn.touched,
|
|
302
|
+
value: btn.value,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
this._buttonStates[index] = buttonStateObj;
|
|
306
|
+
|
|
307
|
+
// Initialize axis state object
|
|
308
|
+
const axesStateObj : Record<string, number> = {};
|
|
309
|
+
Array.from(gamepad.axes).forEach((axisValue, i) =>
|
|
310
|
+
{
|
|
311
|
+
axesStateObj[`axis-${ i }`] = axisValue;
|
|
312
|
+
});
|
|
313
|
+
this._axesStates[index] = axesStateObj;
|
|
314
|
+
|
|
315
|
+
// Notify device connected with initial state
|
|
316
|
+
this._notifyDeviceConnected(gamepadDevice);
|
|
317
|
+
|
|
318
|
+
// Emit input changed event with initial state
|
|
319
|
+
this._notifyInputChanged(gamepadDevice, {
|
|
320
|
+
type: 'gamepad',
|
|
321
|
+
buttons: { ...buttonStateObj },
|
|
322
|
+
axes: { ...axesStateObj },
|
|
323
|
+
event: event instanceof GamepadEvent ? event : undefined,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Handle gamepad disconnected event
|
|
329
|
+
*/
|
|
330
|
+
private _handleGamepadDisconnected(event : GamepadEvent) : void
|
|
331
|
+
{
|
|
332
|
+
const gamepad = event.gamepad;
|
|
333
|
+
const index = gamepad.index;
|
|
334
|
+
|
|
335
|
+
// Get the device
|
|
336
|
+
const gamepadDevice = this._gamepadDevices[index];
|
|
337
|
+
|
|
338
|
+
if(gamepadDevice)
|
|
339
|
+
{
|
|
340
|
+
// Update connection status
|
|
341
|
+
gamepadDevice.connected = false;
|
|
342
|
+
|
|
343
|
+
// Notify device disconnected
|
|
344
|
+
this._notifyDeviceDisconnected(gamepadDevice);
|
|
345
|
+
|
|
346
|
+
// Remove from objects
|
|
347
|
+
// Using object property assignment with undefined instead of delete
|
|
348
|
+
this._gamepadDevices[index] = undefined as unknown as GamepadDevice;
|
|
349
|
+
this._buttonStates[index] = undefined as unknown as Record<string, ButtonState>;
|
|
350
|
+
this._axesStates[index] = undefined as unknown as Record<string, number>;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Notify subscribers of device connected event
|
|
356
|
+
*/
|
|
357
|
+
private _notifyDeviceConnected(device : GamepadDevice) : void
|
|
358
|
+
{
|
|
359
|
+
if(this._onDeviceConnected)
|
|
360
|
+
{
|
|
361
|
+
this._onDeviceConnected(device);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Notify subscribers of device disconnected event
|
|
367
|
+
*/
|
|
368
|
+
private _notifyDeviceDisconnected(device : GamepadDevice) : void
|
|
369
|
+
{
|
|
370
|
+
if(this._onDeviceDisconnected)
|
|
371
|
+
{
|
|
372
|
+
this._onDeviceDisconnected(device);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Notify subscribers of input changed event
|
|
378
|
+
*/
|
|
379
|
+
private _notifyInputChanged(device : GamepadDevice, state : GamepadInputState) : void
|
|
380
|
+
{
|
|
381
|
+
if(this._onInputChanged)
|
|
382
|
+
{
|
|
383
|
+
this._onInputChanged(device, state);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// Keyboard Input Resource Access
|
|
3
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
// Interfaces
|
|
6
|
+
import type { KeyboardDevice, KeyboardInputState } from '../../interfaces/input.ts';
|
|
7
|
+
|
|
8
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Callback type for keyboard device connection events
|
|
12
|
+
*/
|
|
13
|
+
export type KeyboardDeviceCallback = (device : KeyboardDevice) => void;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Callback type for keyboard input events
|
|
17
|
+
*/
|
|
18
|
+
export type KeyboardInputCallback = (device : KeyboardDevice, state : KeyboardInputState) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Responsible for tracking keyboard state and notifying subscribers of changes.
|
|
22
|
+
*/
|
|
23
|
+
export class KeyboardInputPlugin
|
|
24
|
+
{
|
|
25
|
+
private _keyboardDevice : KeyboardDevice;
|
|
26
|
+
private _keysState : Record<string, boolean> = {};
|
|
27
|
+
|
|
28
|
+
// Callbacks
|
|
29
|
+
private _onDeviceConnected ?: KeyboardDeviceCallback;
|
|
30
|
+
private _onInputChanged ?: KeyboardInputCallback;
|
|
31
|
+
|
|
32
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a new KeyboardResourceAccess
|
|
36
|
+
*/
|
|
37
|
+
constructor()
|
|
38
|
+
{
|
|
39
|
+
// Initialize keyboard device
|
|
40
|
+
this._keyboardDevice = {
|
|
41
|
+
id: 'keyboard-0',
|
|
42
|
+
name: 'Keyboard',
|
|
43
|
+
type: 'keyboard',
|
|
44
|
+
connected: true,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Set up event listeners
|
|
48
|
+
this._setupKeyboardEvents();
|
|
49
|
+
|
|
50
|
+
// Notify that device is connected (on next tick to allow callback registration)
|
|
51
|
+
setTimeout(() => this._notifyDeviceConnected(), 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
55
|
+
// Public API
|
|
56
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Register a callback for device connected events
|
|
60
|
+
*
|
|
61
|
+
* @param callback - The callback to register
|
|
62
|
+
*/
|
|
63
|
+
public onDeviceConnected(callback : KeyboardDeviceCallback) : void
|
|
64
|
+
{
|
|
65
|
+
this._onDeviceConnected = callback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register a callback for input changed events
|
|
70
|
+
*
|
|
71
|
+
* @param callback - The callback to register
|
|
72
|
+
*/
|
|
73
|
+
public onInputChanged(callback : KeyboardInputCallback) : void
|
|
74
|
+
{
|
|
75
|
+
this._onInputChanged = callback;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the current keyboard state
|
|
80
|
+
*/
|
|
81
|
+
public getState() : KeyboardInputState
|
|
82
|
+
{
|
|
83
|
+
return {
|
|
84
|
+
type: 'keyboard',
|
|
85
|
+
keys: { ...this._keysState },
|
|
86
|
+
delta: {},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the keyboard device
|
|
92
|
+
*/
|
|
93
|
+
public getDevice() : KeyboardDevice
|
|
94
|
+
{
|
|
95
|
+
return { ...this._keyboardDevice };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Destroy the keyboard resource access and clean up event listeners
|
|
100
|
+
*/
|
|
101
|
+
public destroy() : void
|
|
102
|
+
{
|
|
103
|
+
// Remove keyboard event listeners
|
|
104
|
+
window.removeEventListener('keydown', this._handleKeyDown);
|
|
105
|
+
window.removeEventListener('keyup', this._handleKeyUp);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
109
|
+
// Private Methods
|
|
110
|
+
//------------------------------------------------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Set up keyboard event listeners
|
|
114
|
+
*/
|
|
115
|
+
private _setupKeyboardEvents() : void
|
|
116
|
+
{
|
|
117
|
+
// Bind handlers to this instance
|
|
118
|
+
this._handleKeyDown = this._handleKeyDown.bind(this);
|
|
119
|
+
this._handleKeyUp = this._handleKeyUp.bind(this);
|
|
120
|
+
|
|
121
|
+
// Add event listeners
|
|
122
|
+
window.addEventListener('keydown', this._handleKeyDown);
|
|
123
|
+
window.addEventListener('keyup', this._handleKeyUp);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle keyboard key down events
|
|
128
|
+
*/
|
|
129
|
+
private _handleKeyDown(event : KeyboardEvent) : void
|
|
130
|
+
{
|
|
131
|
+
this._keysState[event.code] = true;
|
|
132
|
+
|
|
133
|
+
const delta : Record<string, boolean> = {};
|
|
134
|
+
delta[event.code] = true;
|
|
135
|
+
|
|
136
|
+
const state : KeyboardInputState = {
|
|
137
|
+
type: 'keyboard',
|
|
138
|
+
keys: { ...this._keysState },
|
|
139
|
+
delta,
|
|
140
|
+
event,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
this._notifyInputChanged(state);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle keyboard key up events
|
|
148
|
+
*/
|
|
149
|
+
private _handleKeyUp(event : KeyboardEvent) : void
|
|
150
|
+
{
|
|
151
|
+
this._keysState[event.code] = false;
|
|
152
|
+
|
|
153
|
+
const delta : Record<string, boolean> = {};
|
|
154
|
+
delta[event.code] = false;
|
|
155
|
+
|
|
156
|
+
const state : KeyboardInputState = {
|
|
157
|
+
type: 'keyboard',
|
|
158
|
+
keys: { ...this._keysState },
|
|
159
|
+
delta,
|
|
160
|
+
event,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
this._notifyInputChanged(state);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Notify subscribers of device connected event
|
|
168
|
+
*/
|
|
169
|
+
private _notifyDeviceConnected() : void
|
|
170
|
+
{
|
|
171
|
+
if(this._onDeviceConnected)
|
|
172
|
+
{
|
|
173
|
+
this._onDeviceConnected(this._keyboardDevice);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Notify subscribers of input changed event
|
|
179
|
+
*/
|
|
180
|
+
private _notifyInputChanged(state : KeyboardInputState) : void
|
|
181
|
+
{
|
|
182
|
+
if(this._onInputChanged)
|
|
183
|
+
{
|
|
184
|
+
this._onInputChanged(this._keyboardDevice, state);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//----------------------------------------------------------------------------------------------------------------------
|