canvasengine 2.0.0-beta.4 → 2.0.0-beta.41

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.
Files changed (142) hide show
  1. package/dist/DebugRenderer-BxfW34YG.js +172 -0
  2. package/dist/DebugRenderer-BxfW34YG.js.map +1 -0
  3. package/dist/components/Button.d.ts +183 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +18 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/DOMElement.d.ts +44 -0
  8. package/dist/components/DOMElement.d.ts.map +1 -0
  9. package/dist/components/Graphic.d.ts +65 -0
  10. package/dist/components/Graphic.d.ts.map +1 -0
  11. package/dist/components/Joystick.d.ts +36 -0
  12. package/dist/components/Joystick.d.ts.map +1 -0
  13. package/dist/components/NineSliceSprite.d.ts +17 -0
  14. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  15. package/dist/components/ParticleEmitter.d.ts +5 -0
  16. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  17. package/dist/components/Scene.d.ts +2 -0
  18. package/dist/components/Scene.d.ts.map +1 -0
  19. package/dist/components/Text.d.ts +26 -0
  20. package/dist/components/Text.d.ts.map +1 -0
  21. package/dist/components/TilingSprite.d.ts +18 -0
  22. package/dist/components/TilingSprite.d.ts.map +1 -0
  23. package/dist/components/Video.d.ts +15 -0
  24. package/dist/components/Video.d.ts.map +1 -0
  25. package/dist/components/index.d.ts +18 -0
  26. package/dist/components/index.d.ts.map +1 -0
  27. package/dist/components/types/DisplayObject.d.ts +110 -0
  28. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  29. package/dist/components/types/MouseEvent.d.ts +4 -0
  30. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  31. package/dist/components/types/Spritesheet.d.ts +248 -0
  32. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  33. package/dist/components/types/index.d.ts +5 -0
  34. package/dist/components/types/index.d.ts.map +1 -0
  35. package/dist/directives/Controls.d.ts +113 -0
  36. package/dist/directives/Controls.d.ts.map +1 -0
  37. package/dist/directives/ControlsBase.d.ts +198 -0
  38. package/dist/directives/ControlsBase.d.ts.map +1 -0
  39. package/dist/directives/Drag.d.ts +70 -0
  40. package/dist/directives/Drag.d.ts.map +1 -0
  41. package/dist/directives/Flash.d.ts +117 -0
  42. package/dist/directives/Flash.d.ts.map +1 -0
  43. package/dist/directives/GamepadControls.d.ts +225 -0
  44. package/dist/directives/GamepadControls.d.ts.map +1 -0
  45. package/dist/directives/JoystickControls.d.ts +172 -0
  46. package/dist/directives/JoystickControls.d.ts.map +1 -0
  47. package/dist/directives/KeyboardControls.d.ts +219 -0
  48. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  49. package/dist/directives/Scheduler.d.ts +36 -0
  50. package/dist/directives/Scheduler.d.ts.map +1 -0
  51. package/dist/directives/Shake.d.ts +98 -0
  52. package/dist/directives/Shake.d.ts.map +1 -0
  53. package/dist/directives/Sound.d.ts +26 -0
  54. package/dist/directives/Sound.d.ts.map +1 -0
  55. package/dist/directives/Transition.d.ts +11 -0
  56. package/dist/directives/Transition.d.ts.map +1 -0
  57. package/dist/directives/ViewportCull.d.ts +12 -0
  58. package/dist/directives/ViewportCull.d.ts.map +1 -0
  59. package/dist/directives/ViewportFollow.d.ts +19 -0
  60. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  61. package/dist/directives/index.d.ts +13 -0
  62. package/dist/directives/index.d.ts.map +1 -0
  63. package/dist/engine/animation.d.ts +73 -0
  64. package/dist/engine/animation.d.ts.map +1 -0
  65. package/dist/engine/bootstrap.d.ts +16 -0
  66. package/dist/engine/bootstrap.d.ts.map +1 -0
  67. package/dist/engine/directive.d.ts +14 -0
  68. package/dist/engine/directive.d.ts.map +1 -0
  69. package/dist/engine/reactive.d.ts +105 -0
  70. package/dist/engine/reactive.d.ts.map +1 -0
  71. package/dist/engine/signal.d.ts +72 -0
  72. package/dist/engine/signal.d.ts.map +1 -0
  73. package/dist/engine/trigger.d.ts +54 -0
  74. package/dist/engine/trigger.d.ts.map +1 -0
  75. package/dist/engine/utils.d.ts +90 -0
  76. package/dist/engine/utils.d.ts.map +1 -0
  77. package/dist/hooks/addContext.d.ts +2 -0
  78. package/dist/hooks/addContext.d.ts.map +1 -0
  79. package/dist/hooks/useProps.d.ts +42 -0
  80. package/dist/hooks/useProps.d.ts.map +1 -0
  81. package/dist/hooks/useRef.d.ts +5 -0
  82. package/dist/hooks/useRef.d.ts.map +1 -0
  83. package/dist/index-BnuKipxl.js +12568 -0
  84. package/dist/index-BnuKipxl.js.map +1 -0
  85. package/dist/index.d.ts +15 -1083
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.global.js +29 -0
  88. package/dist/index.global.js.map +1 -0
  89. package/dist/index.js +81 -3041
  90. package/dist/index.js.map +1 -1
  91. package/dist/utils/Ease.d.ts +17 -0
  92. package/dist/utils/Ease.d.ts.map +1 -0
  93. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  94. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  95. package/dist/utils/RadialGradient.d.ts +58 -0
  96. package/dist/utils/RadialGradient.d.ts.map +1 -0
  97. package/dist/utils/functions.d.ts +2 -0
  98. package/dist/utils/functions.d.ts.map +1 -0
  99. package/package.json +13 -7
  100. package/src/components/Button.ts +396 -0
  101. package/src/components/Canvas.ts +61 -45
  102. package/src/components/Container.ts +21 -2
  103. package/src/components/DOMContainer.ts +123 -0
  104. package/src/components/DOMElement.ts +421 -0
  105. package/src/components/DisplayObject.ts +350 -197
  106. package/src/components/Graphic.ts +200 -34
  107. package/src/components/Joystick.ts +363 -0
  108. package/src/components/Mesh.ts +222 -0
  109. package/src/components/NineSliceSprite.ts +4 -1
  110. package/src/components/ParticleEmitter.ts +12 -8
  111. package/src/components/Sprite.ts +306 -30
  112. package/src/components/Text.ts +125 -18
  113. package/src/components/Video.ts +110 -0
  114. package/src/components/Viewport.ts +59 -43
  115. package/src/components/index.ts +8 -2
  116. package/src/components/types/DisplayObject.ts +34 -0
  117. package/src/components/types/Spritesheet.ts +0 -118
  118. package/src/directives/Controls.ts +254 -0
  119. package/src/directives/ControlsBase.ts +266 -0
  120. package/src/directives/Drag.ts +357 -52
  121. package/src/directives/Flash.ts +419 -0
  122. package/src/directives/GamepadControls.ts +537 -0
  123. package/src/directives/JoystickControls.ts +396 -0
  124. package/src/directives/KeyboardControls.ts +66 -424
  125. package/src/directives/Shake.ts +295 -0
  126. package/src/directives/Sound.ts +94 -31
  127. package/src/directives/ViewportFollow.ts +35 -7
  128. package/src/directives/index.ts +12 -6
  129. package/src/engine/animation.ts +175 -21
  130. package/src/engine/bootstrap.ts +23 -3
  131. package/src/engine/directive.ts +2 -2
  132. package/src/engine/reactive.ts +780 -177
  133. package/src/engine/signal.ts +35 -4
  134. package/src/engine/trigger.ts +34 -7
  135. package/src/engine/utils.ts +19 -3
  136. package/src/hooks/useProps.ts +1 -1
  137. package/src/index.ts +4 -2
  138. package/src/utils/GlobalAssetLoader.ts +257 -0
  139. package/src/utils/functions.ts +7 -0
  140. package/testing/index.ts +12 -0
  141. package/tsconfig.json +17 -0
  142. package/vite.config.ts +39 -0
@@ -0,0 +1,537 @@
1
+ import { ControlsBase, Controls } from "./ControlsBase";
2
+ import { WritableSignal } from "@signe/reactive";
3
+ import 'joypad.js'
4
+
5
+ /**
6
+ * Gamepad configuration interface
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const gamepadConfig: GamepadConfig = {
11
+ * enabled: true,
12
+ * buttonMapping: {
13
+ * 'button_0': 'action',
14
+ * 'button_1': 'back'
15
+ * },
16
+ * axisMapping: {
17
+ * 'top': 'up',
18
+ * 'bottom': 'down',
19
+ * 'left': 'left',
20
+ * 'right': 'right'
21
+ * },
22
+ * moveInterval: 400,
23
+ * onConnect: () => console.log('Gamepad connected!'),
24
+ * onDisconnect: () => console.log('Gamepad disconnected!')
25
+ * };
26
+ * ```
27
+ */
28
+ export interface GamepadConfig {
29
+ /** Whether gamepad is enabled (default: true) */
30
+ enabled?: boolean;
31
+ /** Mapping of gamepad button names to control names */
32
+ buttonMapping?: {
33
+ [buttonName: string]: string; // e.g., 'button_0' -> 'action'
34
+ };
35
+ /** Mapping of axis directions to control directions */
36
+ axisMapping?: {
37
+ [axisDirection: string]: string; // e.g., 'top' -> 'up'
38
+ };
39
+ /** Threshold for axis movement detection (default: 0.5) */
40
+ axisThreshold?: number;
41
+ /** Interval in milliseconds for repeating movement actions (default: 400) */
42
+ moveInterval?: number;
43
+ /** Callback called when a gamepad is connected */
44
+ onConnect?: () => void;
45
+ /** Callback called when a gamepad is disconnected */
46
+ onDisconnect?: () => void;
47
+ /** Signal that tracks gamepad connection status (optional) */
48
+ gamepadConnected?: WritableSignal<boolean>;
49
+ }
50
+
51
+ /**
52
+ * Default button mapping
53
+ */
54
+ const DEFAULT_BUTTON_MAPPING: { [buttonName: string]: string } = {
55
+ 'button_0': 'action',
56
+ 'button_1': 'back',
57
+ 'button_9': 'back'
58
+ };
59
+
60
+ /**
61
+ * Default axis mapping
62
+ */
63
+ const DEFAULT_AXIS_MAPPING: { [axisDirection: string]: string } = {
64
+ 'top': 'up',
65
+ 'bottom': 'down',
66
+ 'left': 'left',
67
+ 'right': 'right'
68
+ };
69
+
70
+ /**
71
+ * Gamepad input controls implementation
72
+ *
73
+ * Handles gamepad input events using joypad.js library and maps them to control actions.
74
+ * Supports button presses and analog stick movement with configurable mappings.
75
+ *
76
+ * The gamepad controls are automatically activated when joypad.js is available and enabled.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const gamepadControls = new GamepadControls();
81
+ * gamepadControls.setInputs({
82
+ * up: {
83
+ * repeat: true,
84
+ * bind: 'up',
85
+ * keyDown() {
86
+ * console.log('Up pressed');
87
+ * }
88
+ * }
89
+ * });
90
+ * gamepadControls.updateGamepadConfig({
91
+ * enabled: true,
92
+ * buttonMapping: {
93
+ * 'button_0': 'action'
94
+ * }
95
+ * });
96
+ * gamepadControls.start();
97
+ * ```
98
+ */
99
+ export class GamepadControls extends ControlsBase {
100
+ private gamepadEnabled: boolean = true;
101
+ private gamepadConfig: GamepadConfig = {
102
+ enabled: true,
103
+ buttonMapping: DEFAULT_BUTTON_MAPPING,
104
+ axisMapping: DEFAULT_AXIS_MAPPING,
105
+ axisThreshold: 0.5,
106
+ moveInterval: 400
107
+ };
108
+ private gamepadMoving: boolean = false;
109
+ private gamepadDirections: { [direction: string]: boolean } = {};
110
+ private gamepadAxisDate: number = 0;
111
+ private gamepadMoveInterval: any = null;
112
+ private joypad: any = null;
113
+ private connectCallbacks: Array<() => void> = [];
114
+ private disconnectCallbacks: Array<() => void> = [];
115
+ private currentPower: number = 0;
116
+
117
+ /**
118
+ * Setup gamepad event listeners
119
+ * Initializes joypad.js if available
120
+ */
121
+ protected setupListeners(): void {
122
+ this.initGamepad();
123
+ }
124
+
125
+ /**
126
+ * Cleanup gamepad event listeners and intervals
127
+ */
128
+ protected cleanup(): void {
129
+ if (this.gamepadMoveInterval) {
130
+ clearInterval(this.gamepadMoveInterval);
131
+ this.gamepadMoveInterval = null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Initialize joypad.js library if available
137
+ */
138
+ private initGamepad(): void {
139
+ if (typeof window === 'undefined') return;
140
+
141
+ const joypadLib = (window as any)['joypad'];
142
+
143
+ if (!joypadLib) {
144
+ // joypad.js not available
145
+ // Set initial state if signal is provided
146
+ if (this.gamepadConfig.gamepadConnected) {
147
+ this.gamepadConfig.gamepadConnected.set(false);
148
+ }
149
+ return;
150
+ }
151
+
152
+ this.joypad = joypadLib;
153
+
154
+ // Setup event listeners
155
+ this.joypad.on('connect', () => this.handleGamepadConnect());
156
+ this.joypad.on('disconnect', () => this.handleGamepadDisconnect());
157
+ this.joypad.on('button_press', (e: any) => this.handleGamepadButtonPress(e));
158
+ this.joypad.on('axis_move', (e: any) => this.handleGamepadAxisMove(e));
159
+ }
160
+
161
+ /**
162
+ * Handle gamepad connection event
163
+ */
164
+ private handleGamepadConnect(): void {
165
+ if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
166
+
167
+ // Start movement processing interval
168
+ this.gamepadMoveInterval = setInterval(() => {
169
+ this.processGamepadMovement();
170
+ }, this.gamepadConfig.moveInterval || 400);
171
+
172
+ // Update gamepadConnected signal if provided
173
+ if (this.gamepadConfig.gamepadConnected) {
174
+ this.gamepadConfig.gamepadConnected.set(true);
175
+ }
176
+
177
+ // Call all registered connect callbacks
178
+ this.connectCallbacks.forEach(callback => callback());
179
+ }
180
+
181
+ /**
182
+ * Handle gamepad disconnection event
183
+ */
184
+ private handleGamepadDisconnect(): void {
185
+ // Stop movement interval
186
+ if (this.gamepadMoveInterval) {
187
+ clearInterval(this.gamepadMoveInterval);
188
+ this.gamepadMoveInterval = null;
189
+ }
190
+
191
+ // Reset states
192
+ this.gamepadMoving = false;
193
+ this.gamepadDirections = {};
194
+ this.gamepadAxisDate = 0;
195
+ this.currentPower = 0;
196
+
197
+ // Update gamepadConnected signal if provided
198
+ if (this.gamepadConfig.gamepadConnected) {
199
+ this.gamepadConfig.gamepadConnected.set(false);
200
+ }
201
+
202
+ // Call all registered disconnect callbacks
203
+ this.disconnectCallbacks.forEach(callback => callback());
204
+ }
205
+
206
+ /**
207
+ * Handle gamepad button press event
208
+ *
209
+ * @param e - Button press event from joypad.js
210
+ */
211
+ private handleGamepadButtonPress(e: any): void {
212
+ if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
213
+ if (this.stop) return;
214
+
215
+ const { buttonName } = e.detail;
216
+ const buttonMapping = this.gamepadConfig.buttonMapping || DEFAULT_BUTTON_MAPPING;
217
+ const controlName = buttonMapping[buttonName];
218
+
219
+ if (controlName) {
220
+ // Apply the control action (press and release)
221
+ this.applyControl(controlName).catch(() => {
222
+ // Ignore errors
223
+ });
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Handle gamepad axis movement event
229
+ *
230
+ * @param e - Axis move event from joypad.js
231
+ */
232
+ private handleGamepadAxisMove(e: any): void {
233
+ if (!this.gamepadEnabled || !this.gamepadConfig.enabled) return;
234
+ if (this.stop) return;
235
+
236
+ this.gamepadMoving = true;
237
+ this.gamepadAxisDate = Date.now();
238
+
239
+ let direction = e.detail.directionOfMovement;
240
+ const axisMapping = this.gamepadConfig.axisMapping || DEFAULT_AXIS_MAPPING;
241
+
242
+ // Map joypad direction to control direction
243
+ if (direction === 'top') direction = axisMapping['top'] || 'up';
244
+ else if (direction === 'bottom') direction = axisMapping['bottom'] || 'down';
245
+ else if (direction === 'left') direction = axisMapping['left'] || 'left';
246
+ else if (direction === 'right') direction = axisMapping['right'] || 'right';
247
+
248
+ // Calculate power/intensity from axis values
249
+ // Get the first connected gamepad instance
250
+ if (this.joypad && this.joypad.instances) {
251
+ const gamepadInstances = Object.values(this.joypad.instances) as any[];
252
+ if (gamepadInstances.length > 0) {
253
+ const gamepad = gamepadInstances[0];
254
+ // Get axes values (axes 0-1 for left stick, 2-3 for right stick)
255
+ // We'll use the left stick by default (axes 0 and 1)
256
+ const axisX = gamepad.axes?.[0] || 0;
257
+ const axisY = gamepad.axes?.[1] || 0;
258
+ // Calculate power as magnitude of the vector
259
+ this.currentPower = Math.min(1, Math.sqrt(axisX * axisX + axisY * axisY));
260
+ }
261
+ }
262
+
263
+ // Update active directions
264
+ this.gamepadDirections = {
265
+ [direction]: true
266
+ };
267
+
268
+ // Release other directions
269
+ const allDirections = ['up', 'down', 'left', 'right'];
270
+ for (const dir of allDirections) {
271
+ if (!this.gamepadDirections[dir]) {
272
+ this.applyControl(dir, false).catch(() => {
273
+ // Ignore errors
274
+ });
275
+ }
276
+ }
277
+
278
+ // Trigger movement with power
279
+ this.processGamepadMovement();
280
+ }
281
+
282
+ /**
283
+ * Process continuous gamepad movement
284
+ * Called at intervals to repeat movement actions while axes are active
285
+ */
286
+ private processGamepadMovement(): void {
287
+ if (!this.gamepadMoving) return;
288
+ if (this.stop) return;
289
+
290
+ // Update current power from gamepad axes if available
291
+ if (this.joypad && this.joypad.instances) {
292
+ const gamepadInstances = Object.values(this.joypad.instances) as any[];
293
+ if (gamepadInstances.length > 0) {
294
+ const gamepad = gamepadInstances[0];
295
+ const axisX = gamepad.axes?.[0] || 0;
296
+ const axisY = gamepad.axes?.[1] || 0;
297
+ this.currentPower = Math.min(1, Math.sqrt(axisX * axisX + axisY * axisY));
298
+ }
299
+ }
300
+
301
+ for (const direction in this.gamepadDirections) {
302
+ if (this.gamepadDirections[direction]) {
303
+ this.applyControl(direction, true, { power: this.currentPower }).catch(() => {
304
+ // Ignore errors
305
+ });
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Process gamepad inputs each step
312
+ * Handles timeout for stopping movements after axis inactivity
313
+ */
314
+ protected preStep(): void {
315
+ if (this.stop) return;
316
+
317
+ // Stop movements if no axis input for 100ms
318
+ const now = Date.now();
319
+ if (now - this.gamepadAxisDate > 100 && this.gamepadMoving) {
320
+ const allDirections = ['up', 'down', 'left', 'right'];
321
+ for (const dir of allDirections) {
322
+ this.gamepadDirections = {};
323
+ this.gamepadMoving = false;
324
+ this.applyControl(dir, false).catch(() => {
325
+ // Ignore errors
326
+ });
327
+ }
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Update gamepad configuration
333
+ * Merges provided config with defaults
334
+ * Automatically registers callbacks from config
335
+ *
336
+ * @param config - Partial gamepad configuration
337
+ */
338
+ updateGamepadConfig(config: Partial<GamepadConfig>): void {
339
+ // Remove old callbacks if they were registered
340
+ if (this.gamepadConfig.onConnect) {
341
+ this.offConnect(this.gamepadConfig.onConnect);
342
+ }
343
+ if (this.gamepadConfig.onDisconnect) {
344
+ this.offDisconnect(this.gamepadConfig.onDisconnect);
345
+ }
346
+
347
+ this.gamepadConfig = {
348
+ enabled: config.enabled !== undefined ? config.enabled : true,
349
+ buttonMapping: config.buttonMapping || DEFAULT_BUTTON_MAPPING,
350
+ axisMapping: config.axisMapping || DEFAULT_AXIS_MAPPING,
351
+ axisThreshold: config.axisThreshold || 0.5,
352
+ moveInterval: config.moveInterval || 400,
353
+ onConnect: config.onConnect,
354
+ onDisconnect: config.onDisconnect,
355
+ gamepadConnected: config.gamepadConnected
356
+ };
357
+
358
+ // Register new callbacks if provided
359
+ if (this.gamepadConfig.onConnect) {
360
+ this.onConnect(this.gamepadConfig.onConnect);
361
+ }
362
+ if (this.gamepadConfig.onDisconnect) {
363
+ this.onDisconnect(this.gamepadConfig.onDisconnect);
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Extract gamepad config from controls configuration and update
369
+ * Note: Callbacks are stored but not automatically registered, they should be registered in mount()
370
+ *
371
+ * @param inputs - Controls configuration that may contain a 'gamepad' property
372
+ */
373
+ extractGamepadConfig(inputs: Controls & { gamepad?: GamepadConfig }): void {
374
+ if (inputs.gamepad) {
375
+ this.updateGamepadConfig(inputs.gamepad);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Get the current gamepad configuration
381
+ *
382
+ * @returns The gamepad configuration object
383
+ */
384
+ getGamepadConfig(): GamepadConfig {
385
+ return this.gamepadConfig;
386
+ }
387
+
388
+ /**
389
+ * Apply a control action programmatically
390
+ * Uses the bound controls to trigger actions
391
+ *
392
+ * @param controlName - Name of the control
393
+ * @param isDown - Whether the control is pressed (true) or released (false)
394
+ * @param payload - Optional payload to pass to keyDown/keyUp callbacks (e.g., { power: 0.8 })
395
+ * @returns Promise that resolves when the action is complete
396
+ */
397
+ async applyControl(controlName: string | number, isDown?: boolean, payload?: any): Promise<void> {
398
+ const control = this._controlsOptions[controlName];
399
+ if (!control) return;
400
+
401
+ // Find the bound key for this control
402
+ const boundKeys = Object.keys(this.boundKeys);
403
+ for (const keyName of boundKeys) {
404
+ const boundKey = this.boundKeys[keyName];
405
+ if (boundKey.actionName === String(controlName)) {
406
+ // Execute the control callback
407
+ if (isDown === undefined) {
408
+ // Press and release (simulate button press)
409
+ if (boundKey.options.keyDown) {
410
+ let parameters = payload ?? boundKey.parameters;
411
+ if (typeof parameters === "function") {
412
+ parameters = parameters();
413
+ }
414
+ boundKey.options.keyDown(boundKey, parameters);
415
+ }
416
+ // Release after a short delay (similar to keyboard)
417
+ return new Promise((resolve) => {
418
+ setTimeout(() => {
419
+ if (boundKey.options.keyUp) {
420
+ let parameters = payload ?? boundKey.parameters;
421
+ if (typeof parameters === "function") {
422
+ parameters = parameters();
423
+ }
424
+ boundKey.options.keyUp(boundKey, parameters);
425
+ }
426
+ resolve();
427
+ }, 200);
428
+ });
429
+ } else if (isDown) {
430
+ if (boundKey.options.keyDown) {
431
+ let parameters = payload ?? boundKey.parameters;
432
+ if (typeof parameters === "function") {
433
+ parameters = parameters();
434
+ }
435
+ boundKey.options.keyDown(boundKey, parameters);
436
+ }
437
+ } else {
438
+ if (boundKey.options.keyUp) {
439
+ let parameters = payload ?? boundKey.parameters;
440
+ if (typeof parameters === "function") {
441
+ parameters = parameters();
442
+ }
443
+ boundKey.options.keyUp(boundKey, parameters);
444
+ }
445
+ }
446
+ break;
447
+ }
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Override setInputs to extract gamepad config
453
+ */
454
+ setInputs(inputs: Controls & { gamepad?: GamepadConfig }): void {
455
+ super.setInputs(inputs);
456
+ this.extractGamepadConfig(inputs);
457
+ }
458
+
459
+ /**
460
+ * Register a callback to be called when a gamepad is connected
461
+ *
462
+ * @param callback - Function to call when gamepad connects
463
+ * @example
464
+ * ```ts
465
+ * gamepadControls.onConnect(() => {
466
+ * console.log('Gamepad connected!');
467
+ * });
468
+ * ```
469
+ */
470
+ onConnect(callback: () => void): void {
471
+ this.connectCallbacks.push(callback);
472
+ }
473
+
474
+ /**
475
+ * Register a callback to be called when a gamepad is disconnected
476
+ *
477
+ * @param callback - Function to call when gamepad disconnects
478
+ * @example
479
+ * ```ts
480
+ * gamepadControls.onDisconnect(() => {
481
+ * console.log('Gamepad disconnected!');
482
+ * });
483
+ * ```
484
+ */
485
+ onDisconnect(callback: () => void): void {
486
+ this.disconnectCallbacks.push(callback);
487
+ }
488
+
489
+ /**
490
+ * Remove a connect callback
491
+ *
492
+ * @param callback - Callback to remove
493
+ */
494
+ offConnect(callback: () => void): void {
495
+ const index = this.connectCallbacks.indexOf(callback);
496
+ if (index > -1) {
497
+ this.connectCallbacks.splice(index, 1);
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Remove a disconnect callback
503
+ *
504
+ * @param callback - Callback to remove
505
+ */
506
+ offDisconnect(callback: () => void): void {
507
+ const index = this.disconnectCallbacks.indexOf(callback);
508
+ if (index > -1) {
509
+ this.disconnectCallbacks.splice(index, 1);
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Check if gamepad is currently connected
515
+ *
516
+ * @returns true if gamepad is connected, false otherwise
517
+ */
518
+ isConnected(): boolean {
519
+ return this.gamepadMoveInterval !== null;
520
+ }
521
+
522
+ /**
523
+ * Reinitialize gamepad listeners
524
+ * Useful if joypad.js becomes available after initialization
525
+ *
526
+ * @example
527
+ * ```ts
528
+ * // If joypad.js loads later
529
+ * gamepadControls.reinit();
530
+ * ```
531
+ */
532
+ reinit(): void {
533
+ if (!this.joypad) {
534
+ this.initGamepad();
535
+ }
536
+ }
537
+ }