@flare-engine/input 0.2.1 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,595 +1,771 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _flare_engine_events = require("@flare-engine/events");
3
+ //#region src/gamepad.ts
4
+ /**
5
+ * Gamepad — poll-based state for a single connected controller.
6
+ *
7
+ * Buttons and axes are indexed against the W3C "Standard Gamepad" mapping by
8
+ * default. Callers can translate raw indices via `remap.ts` before feeding
9
+ * snapshots into `updateGamepadFromSnapshot`.
10
+ */
11
+ /** W3C Standard Gamepad button indices. */
12
+ const GamepadButton = {
13
+ A: 0,
14
+ B: 1,
15
+ X: 2,
16
+ Y: 3,
17
+ LB: 4,
18
+ RB: 5,
19
+ LT: 6,
20
+ RT: 7,
21
+ Back: 8,
22
+ Start: 9,
23
+ LS: 10,
24
+ RS: 11,
25
+ DpadUp: 12,
26
+ DpadDown: 13,
27
+ DpadLeft: 14,
28
+ DpadRight: 15,
29
+ Home: 16
9
30
  };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- GamepadAxis: () => GamepadAxis,
24
- GamepadButton: () => GamepadButton,
25
- GestureDetector: () => GestureDetector,
26
- InputManager: () => InputManager,
27
- applyRemap: () => applyRemap,
28
- createButtonState: () => createButtonState,
29
- createGamepadState: () => createGamepadState,
30
- createIdentityRemap: () => createIdentityRemap,
31
- createJoystickState: () => createJoystickState,
32
- createPointer: () => createPointer,
33
- createWebGamepadSource: () => createWebGamepadSource,
34
- getAxis: () => getAxis,
35
- isButtonDown: () => isButtonDown,
36
- loadRemap: () => loadRemap,
37
- pointerDown: () => pointerDown,
38
- pointerMove: () => pointerMove,
39
- pointerUp: () => pointerUp,
40
- pressButton: () => pressButton,
41
- releaseButton: () => releaseButton,
42
- releaseJoystick: () => releaseJoystick,
43
- resetButtonFrame: () => resetButtonFrame,
44
- resetGamepadFrame: () => resetGamepadFrame,
45
- resetPointerFrame: () => resetPointerFrame,
46
- saveRemap: () => saveRemap,
47
- updateGamepadFromSnapshot: () => updateGamepadFromSnapshot,
48
- updateJoystick: () => updateJoystick,
49
- wasButtonPressed: () => wasButtonPressed,
50
- wasButtonReleased: () => wasButtonReleased
51
- });
52
- module.exports = __toCommonJS(index_exports);
53
-
54
- // src/pointer.ts
55
- function createPointer() {
56
- return {
57
- position: { x: 0, y: 0 },
58
- delta: { x: 0, y: 0 },
59
- isDown: false,
60
- justPressed: false,
61
- justReleased: false
62
- };
63
- }
64
- function resetPointerFrame(pointer) {
65
- pointer.delta.x = 0;
66
- pointer.delta.y = 0;
67
- pointer.justPressed = false;
68
- pointer.justReleased = false;
69
- }
70
- function pointerDown(pointer, x, y) {
71
- const wasDown = pointer.isDown;
72
- pointer.isDown = true;
73
- if (!wasDown) {
74
- pointer.justPressed = true;
75
- }
76
- pointer.delta.x = x - pointer.position.x;
77
- pointer.delta.y = y - pointer.position.y;
78
- pointer.position.x = x;
79
- pointer.position.y = y;
80
- }
81
- function pointerMove(pointer, x, y) {
82
- pointer.delta.x = x - pointer.position.x;
83
- pointer.delta.y = y - pointer.position.y;
84
- pointer.position.x = x;
85
- pointer.position.y = y;
86
- }
87
- function pointerUp(pointer, x, y) {
88
- pointer.isDown = false;
89
- pointer.justReleased = true;
90
- pointer.delta.x = x - pointer.position.x;
91
- pointer.delta.y = y - pointer.position.y;
92
- pointer.position.x = x;
93
- pointer.position.y = y;
94
- }
95
-
96
- // src/gamepad.ts
97
- var GamepadButton = {
98
- A: 0,
99
- B: 1,
100
- X: 2,
101
- Y: 3,
102
- LB: 4,
103
- RB: 5,
104
- LT: 6,
105
- RT: 7,
106
- Back: 8,
107
- Start: 9,
108
- LS: 10,
109
- RS: 11,
110
- DpadUp: 12,
111
- DpadDown: 13,
112
- DpadLeft: 14,
113
- DpadRight: 15,
114
- Home: 16
115
- };
116
- var GamepadAxis = {
117
- LeftX: 0,
118
- LeftY: 1,
119
- RightX: 2,
120
- RightY: 3
31
+ /** W3C Standard Gamepad axes. */
32
+ const GamepadAxis = {
33
+ LeftX: 0,
34
+ LeftY: 1,
35
+ RightX: 2,
36
+ RightY: 3
121
37
  };
122
38
  function createGamepadState(index = 0, buttonCount = 17, axisCount = 4) {
123
- const buttons = new Array(buttonCount);
124
- for (let i = 0; i < buttonCount; i++) {
125
- buttons[i] = { pressed: false, value: 0, pressedThisFrame: false, releasedThisFrame: false };
126
- }
127
- const axes = new Array(axisCount).fill(0);
128
- return { connected: false, id: "", index, buttons, axes, timestamp: 0 };
39
+ const buttons = new Array(buttonCount);
40
+ for (let i = 0; i < buttonCount; i++) buttons[i] = {
41
+ pressed: false,
42
+ value: 0,
43
+ pressedThisFrame: false,
44
+ releasedThisFrame: false
45
+ };
46
+ return {
47
+ connected: false,
48
+ id: "",
49
+ index,
50
+ buttons,
51
+ axes: new Array(axisCount).fill(0),
52
+ timestamp: 0
53
+ };
129
54
  }
130
55
  function applyDeadZone(value, deadZone) {
131
- const abs = Math.abs(value);
132
- if (abs <= deadZone) return 0;
133
- const sign = value < 0 ? -1 : 1;
134
- return sign * ((abs - deadZone) / (1 - deadZone));
56
+ const abs = Math.abs(value);
57
+ if (abs <= deadZone) return 0;
58
+ return (value < 0 ? -1 : 1) * ((abs - deadZone) / (1 - deadZone));
135
59
  }
60
+ /**
61
+ * Apply a fresh snapshot to an existing `GamepadState` in place. Derived
62
+ * `pressedThisFrame` / `releasedThisFrame` flags are recomputed from the
63
+ * previous `pressed` value.
64
+ *
65
+ * `deadZone` in [0, 1). Values at or below are treated as 0.
66
+ */
136
67
  function updateGamepadFromSnapshot(state, snapshot, deadZone = 0) {
137
- if (snapshot === null) {
138
- if (!state.connected && !anyButtonDown(state)) {
139
- clearFrameFlags(state);
140
- return;
141
- }
142
- for (let i = 0; i < state.buttons.length; i++) {
143
- const b = state.buttons[i];
144
- if (!b) continue;
145
- b.pressedThisFrame = false;
146
- b.releasedThisFrame = b.pressed;
147
- b.pressed = false;
148
- b.value = 0;
149
- }
150
- for (let i = 0; i < state.axes.length; i++) state.axes[i] = 0;
151
- state.connected = false;
152
- state.id = "";
153
- return;
154
- }
155
- state.connected = true;
156
- state.id = snapshot.id;
157
- state.index = snapshot.index;
158
- state.timestamp = snapshot.timestamp;
159
- const srcButtons = snapshot.buttons;
160
- const bLen = Math.min(state.buttons.length, srcButtons.length);
161
- for (let i = 0; i < bLen; i++) {
162
- const prev = state.buttons[i];
163
- const src = srcButtons[i];
164
- if (!prev || !src) continue;
165
- const wasPressed = prev.pressed;
166
- prev.pressed = src.pressed;
167
- prev.value = src.value;
168
- prev.pressedThisFrame = src.pressed && !wasPressed;
169
- prev.releasedThisFrame = !src.pressed && wasPressed;
170
- }
171
- for (let i = bLen; i < state.buttons.length; i++) {
172
- const b = state.buttons[i];
173
- if (!b) continue;
174
- b.pressedThisFrame = false;
175
- b.releasedThisFrame = b.pressed;
176
- b.pressed = false;
177
- b.value = 0;
178
- }
179
- const aLen = Math.min(state.axes.length, snapshot.axes.length);
180
- for (let i = 0; i < aLen; i++) {
181
- state.axes[i] = applyDeadZone(snapshot.axes[i] ?? 0, deadZone);
182
- }
183
- for (let i = aLen; i < state.axes.length; i++) state.axes[i] = 0;
68
+ if (snapshot === null) {
69
+ if (!state.connected && !anyButtonDown(state)) {
70
+ clearFrameFlags(state);
71
+ return;
72
+ }
73
+ for (let i = 0; i < state.buttons.length; i++) {
74
+ const b = state.buttons[i];
75
+ if (!b) continue;
76
+ b.pressedThisFrame = false;
77
+ b.releasedThisFrame = b.pressed;
78
+ b.pressed = false;
79
+ b.value = 0;
80
+ }
81
+ for (let i = 0; i < state.axes.length; i++) state.axes[i] = 0;
82
+ state.connected = false;
83
+ state.id = "";
84
+ return;
85
+ }
86
+ state.connected = true;
87
+ state.id = snapshot.id;
88
+ state.index = snapshot.index;
89
+ state.timestamp = snapshot.timestamp;
90
+ const srcButtons = snapshot.buttons;
91
+ const bLen = Math.min(state.buttons.length, srcButtons.length);
92
+ for (let i = 0; i < bLen; i++) {
93
+ const prev = state.buttons[i];
94
+ const src = srcButtons[i];
95
+ if (!prev || !src) continue;
96
+ const wasPressed = prev.pressed;
97
+ prev.pressed = src.pressed;
98
+ prev.value = src.value;
99
+ prev.pressedThisFrame = src.pressed && !wasPressed;
100
+ prev.releasedThisFrame = !src.pressed && wasPressed;
101
+ }
102
+ for (let i = bLen; i < state.buttons.length; i++) {
103
+ const b = state.buttons[i];
104
+ if (!b) continue;
105
+ b.pressedThisFrame = false;
106
+ b.releasedThisFrame = b.pressed;
107
+ b.pressed = false;
108
+ b.value = 0;
109
+ }
110
+ const aLen = Math.min(state.axes.length, snapshot.axes.length);
111
+ for (let i = 0; i < aLen; i++) state.axes[i] = applyDeadZone(snapshot.axes[i] ?? 0, deadZone);
112
+ for (let i = aLen; i < state.axes.length; i++) state.axes[i] = 0;
184
113
  }
114
+ /** Reset just the per-frame edge flags. Call when advancing a frame without a fresh snapshot. */
185
115
  function resetGamepadFrame(state) {
186
- clearFrameFlags(state);
116
+ clearFrameFlags(state);
187
117
  }
188
118
  function clearFrameFlags(state) {
189
- for (let i = 0; i < state.buttons.length; i++) {
190
- const b = state.buttons[i];
191
- if (!b) continue;
192
- b.pressedThisFrame = false;
193
- b.releasedThisFrame = false;
194
- }
119
+ for (let i = 0; i < state.buttons.length; i++) {
120
+ const b = state.buttons[i];
121
+ if (!b) continue;
122
+ b.pressedThisFrame = false;
123
+ b.releasedThisFrame = false;
124
+ }
195
125
  }
196
126
  function anyButtonDown(state) {
197
- for (let i = 0; i < state.buttons.length; i++) {
198
- if (state.buttons[i]?.pressed) return true;
199
- }
200
- return false;
127
+ for (let i = 0; i < state.buttons.length; i++) if (state.buttons[i]?.pressed) return true;
128
+ return false;
201
129
  }
202
130
  function isButtonDown(state, button) {
203
- return state.buttons[button]?.pressed ?? false;
131
+ return state.buttons[button]?.pressed ?? false;
204
132
  }
205
133
  function wasButtonPressed(state, button) {
206
- return state.buttons[button]?.pressedThisFrame ?? false;
134
+ return state.buttons[button]?.pressedThisFrame ?? false;
207
135
  }
208
136
  function wasButtonReleased(state, button) {
209
- return state.buttons[button]?.releasedThisFrame ?? false;
137
+ return state.buttons[button]?.releasedThisFrame ?? false;
210
138
  }
211
139
  function getAxis(state, axis) {
212
- return state.axes[axis] ?? 0;
140
+ return state.axes[axis] ?? 0;
213
141
  }
214
-
215
- // src/remap.ts
142
+ //#endregion
143
+ //#region src/gamepad-source.ts
144
+ /**
145
+ * Wrap `navigator.getGamepads()` (W3C Gamepad API). If `nav` is not supplied,
146
+ * uses `globalThis.navigator`. Returns an empty array when the API is absent.
147
+ */
148
+ function createWebGamepadSource(nav) {
149
+ const source = nav ?? (typeof globalThis === "undefined" ? void 0 : globalThis.navigator);
150
+ return { poll() {
151
+ if (!source?.getGamepads) return [];
152
+ const pads = source.getGamepads();
153
+ const out = new Array(pads.length);
154
+ for (let i = 0; i < pads.length; i++) {
155
+ const p = pads[i];
156
+ if (!p?.connected) {
157
+ out[i] = null;
158
+ continue;
159
+ }
160
+ out[i] = webGamepadToSnapshot(p);
161
+ }
162
+ return out;
163
+ } };
164
+ }
165
+ function webGamepadToSnapshot(p) {
166
+ const buttons = new Array(p.buttons.length);
167
+ for (let i = 0; i < p.buttons.length; i++) {
168
+ const b = p.buttons[i];
169
+ buttons[i] = {
170
+ pressed: b?.pressed ?? false,
171
+ value: b?.value ?? 0
172
+ };
173
+ }
174
+ const axes = new Array(p.axes.length);
175
+ for (let i = 0; i < p.axes.length; i++) axes[i] = p.axes[i] ?? 0;
176
+ return {
177
+ id: p.id,
178
+ index: p.index,
179
+ timestamp: p.timestamp,
180
+ buttons,
181
+ axes
182
+ };
183
+ }
184
+ //#endregion
185
+ //#region src/gestures.ts
186
+ const DEFAULT_TAP_MAX_DURATION = .3;
187
+ const DEFAULT_TAP_MAX_DISTANCE = 20;
188
+ const DEFAULT_LONG_PRESS_MIN_DURATION = .5;
189
+ const DEFAULT_SWIPE_MIN_DISTANCE = 50;
190
+ const DEFAULT_SWIPE_MAX_DURATION = .4;
191
+ /**
192
+ * GestureDetector recognizes tap, long press, swipe, and pinch gestures
193
+ * from raw pointer events.
194
+ */
195
+ var GestureDetector = class {
196
+ onTap = new _flare_engine_events.Signal();
197
+ onLongPress = new _flare_engine_events.Signal();
198
+ onSwipe = new _flare_engine_events.Signal();
199
+ onPinch = new _flare_engine_events.Signal();
200
+ tapMaxDuration;
201
+ tapMaxDistance;
202
+ longPressMinDuration;
203
+ swipeMinDistance;
204
+ swipeMaxDuration;
205
+ _startPos = {
206
+ x: 0,
207
+ y: 0
208
+ };
209
+ _startTime = 0;
210
+ _isTracking = false;
211
+ _longPressFired = false;
212
+ _pinchStartDistance = 0;
213
+ _pinchTouches = null;
214
+ constructor(config) {
215
+ this.tapMaxDuration = config?.tapMaxDuration ?? DEFAULT_TAP_MAX_DURATION;
216
+ this.tapMaxDistance = config?.tapMaxDistance ?? DEFAULT_TAP_MAX_DISTANCE;
217
+ this.longPressMinDuration = config?.longPressMinDuration ?? DEFAULT_LONG_PRESS_MIN_DURATION;
218
+ this.swipeMinDistance = config?.swipeMinDistance ?? DEFAULT_SWIPE_MIN_DISTANCE;
219
+ this.swipeMaxDuration = config?.swipeMaxDuration ?? DEFAULT_SWIPE_MAX_DURATION;
220
+ }
221
+ /** Feed a pointer-down event. */
222
+ pointerDown(x, y) {
223
+ this._startPos = {
224
+ x,
225
+ y
226
+ };
227
+ this._startTime = 0;
228
+ this._isTracking = true;
229
+ this._longPressFired = false;
230
+ }
231
+ /** Feed a pointer-up event. */
232
+ pointerUp(x, y, elapsedSinceDown) {
233
+ if (!this._isTracking) return;
234
+ this._isTracking = false;
235
+ const dx = x - this._startPos.x;
236
+ const dy = y - this._startPos.y;
237
+ const dist = Math.sqrt(dx * dx + dy * dy);
238
+ const duration = elapsedSinceDown;
239
+ if (dist >= this.swipeMinDistance && duration <= this.swipeMaxDuration) {
240
+ const absDx = Math.abs(dx);
241
+ const absDy = Math.abs(dy);
242
+ let direction;
243
+ if (absDx > absDy) direction = dx > 0 ? "right" : "left";
244
+ else direction = dy > 0 ? "down" : "up";
245
+ this.onSwipe.emit({
246
+ direction,
247
+ distance: dist,
248
+ duration
249
+ });
250
+ return;
251
+ }
252
+ if (dist <= this.tapMaxDistance && duration <= this.tapMaxDuration) {
253
+ this.onTap.emit({
254
+ x,
255
+ y
256
+ });
257
+ return;
258
+ }
259
+ }
260
+ /**
261
+ * Update gesture tracking. Call each frame with dt.
262
+ * @param dt Delta time in seconds.
263
+ * @param currentX Current pointer X.
264
+ * @param currentY Current pointer Y.
265
+ */
266
+ update(dt, currentX, currentY) {
267
+ if (!this._isTracking) return;
268
+ this._startTime += dt;
269
+ if (!this._longPressFired && this._startTime >= this.longPressMinDuration) {
270
+ const dx = currentX - this._startPos.x;
271
+ const dy = currentY - this._startPos.y;
272
+ if (Math.sqrt(dx * dx + dy * dy) <= this.tapMaxDistance) {
273
+ this._longPressFired = true;
274
+ this.onLongPress.emit({
275
+ x: currentX,
276
+ y: currentY
277
+ });
278
+ }
279
+ }
280
+ }
281
+ /**
282
+ * Feed a two-finger pinch update. Call when two touches are active.
283
+ * @param t1 First touch position.
284
+ * @param t2 Second touch position.
285
+ */
286
+ pinchUpdate(t1, t2) {
287
+ const dx = t2.x - t1.x;
288
+ const dy = t2.y - t1.y;
289
+ const dist = Math.sqrt(dx * dx + dy * dy);
290
+ if (this._pinchTouches === null) {
291
+ this._pinchStartDistance = dist;
292
+ this._pinchTouches = [{ ...t1 }, { ...t2 }];
293
+ return;
294
+ }
295
+ if (this._pinchStartDistance > 0) {
296
+ const scale = dist / this._pinchStartDistance;
297
+ const center = {
298
+ x: (t1.x + t2.x) / 2,
299
+ y: (t1.y + t2.y) / 2
300
+ };
301
+ this.onPinch.emit({
302
+ scale,
303
+ center
304
+ });
305
+ }
306
+ }
307
+ /** Call when pinch ends (fewer than 2 touches). */
308
+ pinchEnd() {
309
+ this._pinchTouches = null;
310
+ this._pinchStartDistance = 0;
311
+ }
312
+ };
313
+ //#endregion
314
+ //#region src/virtual-button.ts
315
+ /** Create a default button state. */
316
+ function createButtonState() {
317
+ return {
318
+ isPressed: false,
319
+ justPressed: false,
320
+ justReleased: false
321
+ };
322
+ }
323
+ /** Call at the start of each frame to reset per-frame flags. */
324
+ function resetButtonFrame(state) {
325
+ state.justPressed = false;
326
+ state.justReleased = false;
327
+ }
328
+ /** Call when the button is pressed. */
329
+ function pressButton(state) {
330
+ if (!state.isPressed) {
331
+ state.isPressed = true;
332
+ state.justPressed = true;
333
+ }
334
+ }
335
+ /** Call when the button is released. */
336
+ function releaseButton(state) {
337
+ if (state.isPressed) {
338
+ state.isPressed = false;
339
+ state.justReleased = true;
340
+ }
341
+ }
342
+ //#endregion
343
+ //#region src/keyboard.ts
344
+ /** Create a fresh keyboard state. */
345
+ function createKeyboard() {
346
+ const states = [];
347
+ const codeIndex = /* @__PURE__ */ new Map();
348
+ return {
349
+ isDown(code) {
350
+ const i = codeIndex.get(code);
351
+ if (i === void 0) return false;
352
+ return states[i]?.isPressed ?? false;
353
+ },
354
+ wasPressed(code) {
355
+ const i = codeIndex.get(code);
356
+ if (i === void 0) return false;
357
+ return states[i]?.justPressed ?? false;
358
+ },
359
+ wasReleased(code) {
360
+ const i = codeIndex.get(code);
361
+ if (i === void 0) return false;
362
+ return states[i]?.justReleased ?? false;
363
+ },
364
+ keyDown(code) {
365
+ let i = codeIndex.get(code);
366
+ if (i === void 0) {
367
+ i = states.length;
368
+ states.push(createButtonState());
369
+ codeIndex.set(code, i);
370
+ }
371
+ pressButton(states[i]);
372
+ },
373
+ keyUp(code) {
374
+ const i = codeIndex.get(code);
375
+ if (i === void 0) return;
376
+ releaseButton(states[i]);
377
+ },
378
+ reset() {
379
+ for (let i = 0; i < states.length; i++) resetButtonFrame(states[i]);
380
+ },
381
+ clear() {
382
+ for (let i = 0; i < states.length; i++) releaseButton(states[i]);
383
+ }
384
+ };
385
+ }
386
+ /** Free-function parity with `resetPointerFrame`. Delegates to `kb.reset()`. */
387
+ function resetKeyboardFrame(kb) {
388
+ kb.reset();
389
+ }
390
+ /**
391
+ * Web turnkey adapter: registers `keydown`, `keyup`, and `blur` listeners on
392
+ * `target`. `blur` calls `kb.clear()` (stuck-key guard). Returns a detach fn.
393
+ *
394
+ * `target` is structural `{ addEventListener, removeEventListener }` — no DOM
395
+ * types dep. On web, pass `window`; for mobile/RN, feed `keyDown`/`keyUp`
396
+ * directly from a hardware-keyboard native module or an on-screen source.
397
+ *
398
+ * Does NOT call `preventDefault` — bare use lets Space/arrows scroll and Tab
399
+ * move DOM focus. For game-mode input, attach your own listener that calls
400
+ * `preventDefault` for game keys and forwards `kb.keyDown(ev.code)`.
401
+ */
402
+ function attachKeyboardListeners(kb, target) {
403
+ const onKeyDown = (ev) => {
404
+ kb.keyDown(ev.code);
405
+ };
406
+ const onKeyUp = (ev) => {
407
+ kb.keyUp(ev.code);
408
+ };
409
+ const onBlur = (_ev) => {
410
+ kb.clear();
411
+ };
412
+ target.addEventListener("keydown", onKeyDown);
413
+ target.addEventListener("keyup", onKeyUp);
414
+ target.addEventListener("blur", onBlur);
415
+ return () => {
416
+ target.removeEventListener("keydown", onKeyDown);
417
+ target.removeEventListener("keyup", onKeyUp);
418
+ target.removeEventListener("blur", onBlur);
419
+ };
420
+ }
421
+ /** True if any bound code for `action` is currently held. */
422
+ function isActionDown(kb, bindings, action) {
423
+ const codes = bindings[action];
424
+ if (!codes) return false;
425
+ for (let i = 0; i < codes.length; i++) if (kb.isDown(codes[i])) return true;
426
+ return false;
427
+ }
428
+ /** True if any bound code for `action` was pressed this frame. */
429
+ function wasActionPressed(kb, bindings, action) {
430
+ const codes = bindings[action];
431
+ if (!codes) return false;
432
+ for (let i = 0; i < codes.length; i++) if (kb.wasPressed(codes[i])) return true;
433
+ return false;
434
+ }
435
+ /**
436
+ * True if any bound code for `action` was released this frame AND no bound
437
+ * code is still held. Release fires only when the last held bound key lifts.
438
+ */
439
+ function wasActionReleased(kb, bindings, action) {
440
+ const codes = bindings[action];
441
+ if (!codes) return false;
442
+ let anyReleased = false;
443
+ for (let i = 0; i < codes.length; i++) {
444
+ if (kb.isDown(codes[i])) return false;
445
+ if (kb.wasReleased(codes[i])) anyReleased = true;
446
+ }
447
+ return anyReleased;
448
+ }
449
+ //#endregion
450
+ //#region src/pointer.ts
451
+ /** Create a default pointer state. */
452
+ function createPointer() {
453
+ return {
454
+ position: {
455
+ x: 0,
456
+ y: 0
457
+ },
458
+ delta: {
459
+ x: 0,
460
+ y: 0
461
+ },
462
+ isDown: false,
463
+ justPressed: false,
464
+ justReleased: false
465
+ };
466
+ }
467
+ /**
468
+ * Update pointer state for a new frame.
469
+ * Call this at the start of each frame to clear per-frame flags.
470
+ */
471
+ function resetPointerFrame(pointer) {
472
+ pointer.delta.x = 0;
473
+ pointer.delta.y = 0;
474
+ pointer.justPressed = false;
475
+ pointer.justReleased = false;
476
+ }
477
+ /** Feed a pointer-down event into the pointer state. */
478
+ function pointerDown(pointer, x, y) {
479
+ const wasDown = pointer.isDown;
480
+ pointer.isDown = true;
481
+ if (!wasDown) pointer.justPressed = true;
482
+ pointer.delta.x += x - pointer.position.x;
483
+ pointer.delta.y += y - pointer.position.y;
484
+ pointer.position.x = x;
485
+ pointer.position.y = y;
486
+ }
487
+ /** Feed a pointer-move event into the pointer state. */
488
+ function pointerMove(pointer, x, y) {
489
+ pointer.delta.x += x - pointer.position.x;
490
+ pointer.delta.y += y - pointer.position.y;
491
+ pointer.position.x = x;
492
+ pointer.position.y = y;
493
+ }
494
+ /** Feed a pointer-up event into the pointer state. */
495
+ function pointerUp(pointer, x, y) {
496
+ pointer.isDown = false;
497
+ pointer.justReleased = true;
498
+ pointer.delta.x += x - pointer.position.x;
499
+ pointer.delta.y += y - pointer.position.y;
500
+ pointer.position.x = x;
501
+ pointer.position.y = y;
502
+ }
503
+ //#endregion
504
+ //#region src/remap.ts
216
505
  function createIdentityRemap(buttonCount = 17, axisCount = 4) {
217
- const buttons = new Array(buttonCount);
218
- for (let i = 0; i < buttonCount; i++) buttons[i] = i;
219
- const axes = new Array(axisCount);
220
- for (let i = 0; i < axisCount; i++) axes[i] = { index: i, invert: false, scale: 1 };
221
- return { buttons, axes };
506
+ const buttons = new Array(buttonCount);
507
+ for (let i = 0; i < buttonCount; i++) buttons[i] = i;
508
+ const axes = new Array(axisCount);
509
+ for (let i = 0; i < axisCount; i++) axes[i] = {
510
+ index: i,
511
+ invert: false,
512
+ scale: 1
513
+ };
514
+ return {
515
+ buttons,
516
+ axes
517
+ };
222
518
  }
519
+ /** Apply a remap table to a raw snapshot, returning a new snapshot with logical ordering. */
223
520
  function applyRemap(snapshot, table) {
224
- const buttons = new Array(table.buttons.length);
225
- for (let logical = 0; logical < table.buttons.length; logical++) {
226
- const physical = table.buttons[logical] ?? logical;
227
- const src = snapshot.buttons[physical];
228
- buttons[logical] = { pressed: src?.pressed ?? false, value: src?.value ?? 0 };
229
- }
230
- const axes = new Array(table.axes.length);
231
- for (let logical = 0; logical < table.axes.length; logical++) {
232
- const map = table.axes[logical] ?? { index: logical, invert: false, scale: 1 };
233
- const raw = snapshot.axes[map.index] ?? 0;
234
- axes[logical] = raw * map.scale * (map.invert ? -1 : 1);
235
- }
236
- return { id: snapshot.id, index: snapshot.index, timestamp: snapshot.timestamp, buttons, axes };
521
+ const buttons = new Array(table.buttons.length);
522
+ for (let logical = 0; logical < table.buttons.length; logical++) {
523
+ const physical = table.buttons[logical] ?? logical;
524
+ const src = snapshot.buttons[physical];
525
+ buttons[logical] = {
526
+ pressed: src?.pressed ?? false,
527
+ value: src?.value ?? 0
528
+ };
529
+ }
530
+ const axes = new Array(table.axes.length);
531
+ for (let logical = 0; logical < table.axes.length; logical++) {
532
+ const map = table.axes[logical] ?? {
533
+ index: logical,
534
+ invert: false,
535
+ scale: 1
536
+ };
537
+ axes[logical] = (snapshot.axes[map.index] ?? 0) * map.scale * (map.invert ? -1 : 1);
538
+ }
539
+ return {
540
+ id: snapshot.id,
541
+ index: snapshot.index,
542
+ timestamp: snapshot.timestamp,
543
+ buttons,
544
+ axes
545
+ };
237
546
  }
547
+ /** Persist a remap table under the given key. Throws on invalid table shape. */
238
548
  async function saveRemap(store, key, table) {
239
- await store.set(key, JSON.stringify(table));
549
+ await store.set(key, JSON.stringify(table));
240
550
  }
551
+ /** Load a remap table. Returns `null` if absent or malformed. */
241
552
  async function loadRemap(store, key) {
242
- const raw = await store.get(key);
243
- if (raw === null) return null;
244
- try {
245
- const parsed = JSON.parse(raw);
246
- if (!isRemapTable(parsed)) return null;
247
- return parsed;
248
- } catch {
249
- return null;
250
- }
553
+ const raw = await store.get(key);
554
+ if (raw === null) return null;
555
+ try {
556
+ const parsed = JSON.parse(raw);
557
+ if (!isRemapTable(parsed)) return null;
558
+ return parsed;
559
+ } catch {
560
+ return null;
561
+ }
251
562
  }
252
563
  function isRemapTable(value) {
253
- if (typeof value !== "object" || value === null) return false;
254
- const obj = value;
255
- if (!Array.isArray(obj.buttons) || !Array.isArray(obj.axes)) return false;
256
- for (const b of obj.buttons) if (typeof b !== "number") return false;
257
- for (const a of obj.axes) {
258
- if (typeof a !== "object" || a === null) return false;
259
- const ax = a;
260
- if (typeof ax.index !== "number" || typeof ax.invert !== "boolean" || typeof ax.scale !== "number") {
261
- return false;
262
- }
263
- }
264
- return true;
564
+ if (typeof value !== "object" || value === null) return false;
565
+ const obj = value;
566
+ if (!Array.isArray(obj.buttons) || !Array.isArray(obj.axes)) return false;
567
+ for (const b of obj.buttons) if (typeof b !== "number") return false;
568
+ for (const a of obj.axes) {
569
+ if (typeof a !== "object" || a === null) return false;
570
+ const ax = a;
571
+ if (typeof ax.index !== "number" || typeof ax.invert !== "boolean" || typeof ax.scale !== "number") return false;
572
+ }
573
+ return true;
265
574
  }
266
-
267
- // src/input-manager.ts
575
+ //#endregion
576
+ //#region src/input-manager.ts
577
+ /**
578
+ * InputManager provides a polling-based input model.
579
+ *
580
+ * Call `update()` once per frame (before game logic) to snapshot input state.
581
+ * Feed events from the platform (RN touch/gesture handlers) via the on* methods.
582
+ */
268
583
  var InputManager = class {
269
- /** Primary pointer (first touch or mouse). */
270
- pointer = createPointer();
271
- /** All active touch points. */
272
- touches = [];
273
- _touchMap = /* @__PURE__ */ new Map();
274
- _gamepads = [];
275
- _gamepadDeadZone = 0.15;
276
- _gamepadRemaps = /* @__PURE__ */ new Map();
277
- /** Call once per frame to reset per-frame flags. */
278
- update() {
279
- resetPointerFrame(this.pointer);
280
- for (let i = 0; i < this._gamepads.length; i++) {
281
- const pad = this._gamepads[i];
282
- if (pad) resetGamepadFrame(pad);
283
- }
284
- }
285
- /** Feed a touch/pointer begin event. */
286
- onPointerDown(id, x, y) {
287
- pointerDown(this.pointer, x, y);
288
- this._addOrUpdateTouch(id, x, y, true);
289
- }
290
- /** Feed a touch/pointer move event. */
291
- onPointerMove(id, x, y) {
292
- pointerMove(this.pointer, x, y);
293
- this._addOrUpdateTouch(id, x, y, true);
294
- }
295
- /** Feed a touch/pointer end event. */
296
- onPointerUp(id, x, y) {
297
- pointerUp(this.pointer, x, y);
298
- this._removeTouch(id);
299
- }
300
- /** Access the gamepad state at `index`. Allocates on first access. */
301
- gamepad(index) {
302
- let pad = this._gamepads[index];
303
- if (!pad) {
304
- pad = createGamepadState(index);
305
- this._gamepads[index] = pad;
306
- }
307
- return pad;
308
- }
309
- /** Dead zone in [0, 1). Axes at or below this magnitude read as 0. */
310
- setGamepadDeadZone(deadZone) {
311
- this._gamepadDeadZone = Math.max(0, Math.min(0.95, deadZone));
312
- }
313
- /** Install a remap table for a specific gamepad index. Pass `null` to clear. */
314
- setGamepadRemap(index, table) {
315
- if (table === null) this._gamepadRemaps.delete(index);
316
- else this._gamepadRemaps.set(index, table);
317
- }
318
- /** Poll all gamepads from the given source, applying remap + dead zones. */
319
- pollGamepads(source) {
320
- const snapshots = source.poll();
321
- for (let i = 0; i < snapshots.length; i++) {
322
- const pad = this.gamepad(i);
323
- const snap = snapshots[i] ?? null;
324
- const remapped = snap !== null ? this._gamepadRemaps.get(i) ? applyRemap(snap, this._gamepadRemaps.get(i)) : snap : null;
325
- updateGamepadFromSnapshot(pad, remapped, this._gamepadDeadZone);
326
- }
327
- }
328
- _addOrUpdateTouch(id, x, y, isDown) {
329
- let touch = this._touchMap.get(id);
330
- if (!touch) {
331
- touch = { id, position: { x, y }, isDown };
332
- this._touchMap.set(id, touch);
333
- this.touches.push(touch);
334
- } else {
335
- touch.position.x = x;
336
- touch.position.y = y;
337
- touch.isDown = isDown;
338
- }
339
- }
340
- _removeTouch(id) {
341
- const touch = this._touchMap.get(id);
342
- if (!touch) return;
343
- this._touchMap.delete(id);
344
- const idx = this.touches.indexOf(touch);
345
- if (idx !== -1) {
346
- this.touches.splice(idx, 1);
347
- }
348
- }
584
+ /** Primary pointer (first touch or mouse). */
585
+ pointer = createPointer();
586
+ /** Buffered keyboard state. Feed via `attachKeyboardListeners` (web) or `keyboard.keyDown`/`keyUp` (mobile/RN). */
587
+ keyboard = createKeyboard();
588
+ /** All active touch points. */
589
+ touches = [];
590
+ _touchMap = /* @__PURE__ */ new Map();
591
+ _gamepads = [];
592
+ _gamepadDeadZone = .15;
593
+ _gamepadRemaps = /* @__PURE__ */ new Map();
594
+ /** Call once per frame to reset per-frame flags. */
595
+ update() {
596
+ resetPointerFrame(this.pointer);
597
+ resetKeyboardFrame(this.keyboard);
598
+ for (let i = 0; i < this._gamepads.length; i++) {
599
+ const pad = this._gamepads[i];
600
+ if (pad) resetGamepadFrame(pad);
601
+ }
602
+ }
603
+ /** Feed a touch/pointer begin event. */
604
+ onPointerDown(id, x, y) {
605
+ pointerDown(this.pointer, x, y);
606
+ this._addOrUpdateTouch(id, x, y, true);
607
+ }
608
+ /** Feed a touch/pointer move event. */
609
+ onPointerMove(id, x, y) {
610
+ pointerMove(this.pointer, x, y);
611
+ this._addOrUpdateTouch(id, x, y, true);
612
+ }
613
+ /** Feed a touch/pointer end event. */
614
+ onPointerUp(id, x, y) {
615
+ pointerUp(this.pointer, x, y);
616
+ this._removeTouch(id);
617
+ }
618
+ /** Access the gamepad state at `index`. Allocates on first access. */
619
+ gamepad(index) {
620
+ let pad = this._gamepads[index];
621
+ if (!pad) {
622
+ pad = createGamepadState(index);
623
+ this._gamepads[index] = pad;
624
+ }
625
+ return pad;
626
+ }
627
+ /** Dead zone in [0, 1). Axes at or below this magnitude read as 0. */
628
+ setGamepadDeadZone(deadZone) {
629
+ this._gamepadDeadZone = Math.max(0, Math.min(.95, deadZone));
630
+ }
631
+ /** Install a remap table for a specific gamepad index. Pass `null` to clear. */
632
+ setGamepadRemap(index, table) {
633
+ if (table === null) this._gamepadRemaps.delete(index);
634
+ else this._gamepadRemaps.set(index, table);
635
+ }
636
+ /** Poll all gamepads from the given source, applying remap + dead zones. */
637
+ pollGamepads(source) {
638
+ const snapshots = source.poll();
639
+ for (let i = 0; i < snapshots.length; i++) {
640
+ const pad = this.gamepad(i);
641
+ const snap = snapshots[i] ?? null;
642
+ updateGamepadFromSnapshot(pad, snap !== null ? this._gamepadRemaps.get(i) ? applyRemap(snap, this._gamepadRemaps.get(i)) : snap : null, this._gamepadDeadZone);
643
+ }
644
+ }
645
+ _addOrUpdateTouch(id, x, y, isDown) {
646
+ let touch = this._touchMap.get(id);
647
+ if (!touch) {
648
+ touch = {
649
+ id,
650
+ position: {
651
+ x,
652
+ y
653
+ },
654
+ isDown
655
+ };
656
+ this._touchMap.set(id, touch);
657
+ this.touches.push(touch);
658
+ } else {
659
+ touch.position.x = x;
660
+ touch.position.y = y;
661
+ touch.isDown = isDown;
662
+ }
663
+ }
664
+ _removeTouch(id) {
665
+ const touch = this._touchMap.get(id);
666
+ if (!touch) return;
667
+ this._touchMap.delete(id);
668
+ const idx = this.touches.indexOf(touch);
669
+ if (idx !== -1) this.touches.splice(idx, 1);
670
+ }
349
671
  };
350
-
351
- // src/gamepad-source.ts
352
- function createWebGamepadSource(nav) {
353
- const source = nav ?? (typeof globalThis === "undefined" ? void 0 : globalThis.navigator);
354
- return {
355
- poll() {
356
- if (!source?.getGamepads) return [];
357
- const pads = source.getGamepads();
358
- const out = new Array(pads.length);
359
- for (let i = 0; i < pads.length; i++) {
360
- const p = pads[i];
361
- if (!p || !p.connected) {
362
- out[i] = null;
363
- continue;
364
- }
365
- out[i] = webGamepadToSnapshot(p);
366
- }
367
- return out;
368
- }
369
- };
370
- }
371
- function webGamepadToSnapshot(p) {
372
- const buttons = new Array(p.buttons.length);
373
- for (let i = 0; i < p.buttons.length; i++) {
374
- const b = p.buttons[i];
375
- buttons[i] = { pressed: b?.pressed ?? false, value: b?.value ?? 0 };
376
- }
377
- const axes = new Array(p.axes.length);
378
- for (let i = 0; i < p.axes.length; i++) axes[i] = p.axes[i] ?? 0;
379
- return { id: p.id, index: p.index, timestamp: p.timestamp, buttons, axes };
380
- }
381
-
382
- // src/gestures.ts
383
- var import_events = require("@flare-engine/events");
384
- var DEFAULT_TAP_MAX_DURATION = 0.3;
385
- var DEFAULT_TAP_MAX_DISTANCE = 20;
386
- var DEFAULT_LONG_PRESS_MIN_DURATION = 0.5;
387
- var DEFAULT_SWIPE_MIN_DISTANCE = 50;
388
- var DEFAULT_SWIPE_MAX_DURATION = 0.4;
389
- var GestureDetector = class {
390
- onTap = new import_events.Signal();
391
- onLongPress = new import_events.Signal();
392
- onSwipe = new import_events.Signal();
393
- onPinch = new import_events.Signal();
394
- tapMaxDuration;
395
- tapMaxDistance;
396
- longPressMinDuration;
397
- swipeMinDistance;
398
- swipeMaxDuration;
399
- // Single-touch tracking
400
- _startPos = { x: 0, y: 0 };
401
- _startTime = 0;
402
- _isTracking = false;
403
- _longPressFired = false;
404
- // Pinch tracking
405
- _pinchStartDistance = 0;
406
- _pinchTouches = null;
407
- constructor(config) {
408
- this.tapMaxDuration = config?.tapMaxDuration ?? DEFAULT_TAP_MAX_DURATION;
409
- this.tapMaxDistance = config?.tapMaxDistance ?? DEFAULT_TAP_MAX_DISTANCE;
410
- this.longPressMinDuration = config?.longPressMinDuration ?? DEFAULT_LONG_PRESS_MIN_DURATION;
411
- this.swipeMinDistance = config?.swipeMinDistance ?? DEFAULT_SWIPE_MIN_DISTANCE;
412
- this.swipeMaxDuration = config?.swipeMaxDuration ?? DEFAULT_SWIPE_MAX_DURATION;
413
- }
414
- /** Feed a pointer-down event. */
415
- pointerDown(x, y) {
416
- this._startPos = { x, y };
417
- this._startTime = 0;
418
- this._isTracking = true;
419
- this._longPressFired = false;
420
- }
421
- /** Feed a pointer-up event. */
422
- pointerUp(x, y, elapsedSinceDown) {
423
- if (!this._isTracking) return;
424
- this._isTracking = false;
425
- const dx = x - this._startPos.x;
426
- const dy = y - this._startPos.y;
427
- const dist = Math.sqrt(dx * dx + dy * dy);
428
- const duration = elapsedSinceDown;
429
- if (dist >= this.swipeMinDistance && duration <= this.swipeMaxDuration) {
430
- const absDx = Math.abs(dx);
431
- const absDy = Math.abs(dy);
432
- let direction;
433
- if (absDx > absDy) {
434
- direction = dx > 0 ? "right" : "left";
435
- } else {
436
- direction = dy > 0 ? "down" : "up";
437
- }
438
- this.onSwipe.emit({ direction, distance: dist, duration });
439
- return;
440
- }
441
- if (dist <= this.tapMaxDistance && duration <= this.tapMaxDuration) {
442
- this.onTap.emit({ x, y });
443
- return;
444
- }
445
- }
446
- /**
447
- * Update gesture tracking. Call each frame with dt.
448
- * @param dt Delta time in seconds.
449
- * @param currentX Current pointer X.
450
- * @param currentY Current pointer Y.
451
- */
452
- update(dt, currentX, currentY) {
453
- if (!this._isTracking) return;
454
- this._startTime += dt;
455
- if (!this._longPressFired && this._startTime >= this.longPressMinDuration) {
456
- const dx = currentX - this._startPos.x;
457
- const dy = currentY - this._startPos.y;
458
- const dist = Math.sqrt(dx * dx + dy * dy);
459
- if (dist <= this.tapMaxDistance) {
460
- this._longPressFired = true;
461
- this.onLongPress.emit({ x: currentX, y: currentY });
462
- }
463
- }
464
- }
465
- /**
466
- * Feed a two-finger pinch update. Call when two touches are active.
467
- * @param t1 First touch position.
468
- * @param t2 Second touch position.
469
- */
470
- pinchUpdate(t1, t2) {
471
- const dx = t2.x - t1.x;
472
- const dy = t2.y - t1.y;
473
- const dist = Math.sqrt(dx * dx + dy * dy);
474
- if (this._pinchTouches === null) {
475
- this._pinchStartDistance = dist;
476
- this._pinchTouches = [{ ...t1 }, { ...t2 }];
477
- return;
478
- }
479
- if (this._pinchStartDistance > 0) {
480
- const scale = dist / this._pinchStartDistance;
481
- const center = {
482
- x: (t1.x + t2.x) / 2,
483
- y: (t1.y + t2.y) / 2
484
- };
485
- this.onPinch.emit({ scale, center });
486
- }
487
- }
488
- /** Call when pinch ends (fewer than 2 touches). */
489
- pinchEnd() {
490
- this._pinchTouches = null;
491
- this._pinchStartDistance = 0;
492
- }
493
- };
494
-
495
- // src/virtual-joystick.ts
496
- var DEFAULT_DEAD_ZONE = 10;
497
- var DEFAULT_MAX_RADIUS = 50;
672
+ //#endregion
673
+ //#region src/virtual-joystick.ts
674
+ const DEFAULT_DEAD_ZONE = 10;
675
+ const DEFAULT_MAX_RADIUS = 50;
676
+ /** Create a default joystick state. */
498
677
  function createJoystickState() {
499
- return {
500
- active: false,
501
- direction: { x: 0, y: 0 },
502
- magnitude: 0,
503
- offset: { x: 0, y: 0 }
504
- };
678
+ return {
679
+ active: false,
680
+ direction: {
681
+ x: 0,
682
+ y: 0
683
+ },
684
+ magnitude: 0,
685
+ offset: {
686
+ x: 0,
687
+ y: 0
688
+ }
689
+ };
505
690
  }
691
+ /**
692
+ * Update joystick state from a touch event.
693
+ *
694
+ * @param state The joystick state to update.
695
+ * @param centerX The joystick center X in screen pixels.
696
+ * @param centerY The joystick center Y in screen pixels.
697
+ * @param touchX The current touch X in screen pixels.
698
+ * @param touchY The current touch Y in screen pixels.
699
+ * @param config Joystick configuration.
700
+ */
506
701
  function updateJoystick(state, centerX, centerY, touchX, touchY, config) {
507
- const deadZone = config?.deadZone ?? DEFAULT_DEAD_ZONE;
508
- const maxRadius = config?.maxRadius ?? DEFAULT_MAX_RADIUS;
509
- const dx = touchX - centerX;
510
- const dy = touchY - centerY;
511
- const dist = Math.sqrt(dx * dx + dy * dy);
512
- state.active = true;
513
- if (dist < deadZone) {
514
- state.direction.x = 0;
515
- state.direction.y = 0;
516
- state.magnitude = 0;
517
- state.offset.x = dx;
518
- state.offset.y = dy;
519
- return;
520
- }
521
- const clampedDist = Math.min(dist, maxRadius);
522
- const normalizedDist = (clampedDist - deadZone) / (maxRadius - deadZone);
523
- const invDist = 1 / dist;
524
- state.direction.x = dx * invDist;
525
- state.direction.y = dy * invDist;
526
- state.magnitude = Math.min(normalizedDist, 1);
527
- const scale = clampedDist / dist;
528
- state.offset.x = dx * scale;
529
- state.offset.y = dy * scale;
702
+ const deadZone = config?.deadZone ?? DEFAULT_DEAD_ZONE;
703
+ const maxRadius = config?.maxRadius ?? DEFAULT_MAX_RADIUS;
704
+ const dx = touchX - centerX;
705
+ const dy = touchY - centerY;
706
+ const dist = Math.sqrt(dx * dx + dy * dy);
707
+ state.active = true;
708
+ if (dist < deadZone) {
709
+ state.direction.x = 0;
710
+ state.direction.y = 0;
711
+ state.magnitude = 0;
712
+ state.offset.x = dx;
713
+ state.offset.y = dy;
714
+ return;
715
+ }
716
+ const clampedDist = Math.min(dist, maxRadius);
717
+ const normalizedDist = (clampedDist - deadZone) / (maxRadius - deadZone);
718
+ const invDist = 1 / dist;
719
+ state.direction.x = dx * invDist;
720
+ state.direction.y = dy * invDist;
721
+ state.magnitude = Math.min(normalizedDist, 1);
722
+ const scale = clampedDist / dist;
723
+ state.offset.x = dx * scale;
724
+ state.offset.y = dy * scale;
530
725
  }
726
+ /** Reset joystick state when touch is released. */
531
727
  function releaseJoystick(state) {
532
- state.active = false;
533
- state.direction.x = 0;
534
- state.direction.y = 0;
535
- state.magnitude = 0;
536
- state.offset.x = 0;
537
- state.offset.y = 0;
728
+ state.active = false;
729
+ state.direction.x = 0;
730
+ state.direction.y = 0;
731
+ state.magnitude = 0;
732
+ state.offset.x = 0;
733
+ state.offset.y = 0;
538
734
  }
735
+ //#endregion
736
+ exports.GamepadAxis = GamepadAxis;
737
+ exports.GamepadButton = GamepadButton;
738
+ exports.GestureDetector = GestureDetector;
739
+ exports.InputManager = InputManager;
740
+ exports.applyRemap = applyRemap;
741
+ exports.attachKeyboardListeners = attachKeyboardListeners;
742
+ exports.createButtonState = createButtonState;
743
+ exports.createGamepadState = createGamepadState;
744
+ exports.createIdentityRemap = createIdentityRemap;
745
+ exports.createJoystickState = createJoystickState;
746
+ exports.createKeyboard = createKeyboard;
747
+ exports.createPointer = createPointer;
748
+ exports.createWebGamepadSource = createWebGamepadSource;
749
+ exports.getAxis = getAxis;
750
+ exports.isActionDown = isActionDown;
751
+ exports.isButtonDown = isButtonDown;
752
+ exports.loadRemap = loadRemap;
753
+ exports.pointerDown = pointerDown;
754
+ exports.pointerMove = pointerMove;
755
+ exports.pointerUp = pointerUp;
756
+ exports.pressButton = pressButton;
757
+ exports.releaseButton = releaseButton;
758
+ exports.releaseJoystick = releaseJoystick;
759
+ exports.resetButtonFrame = resetButtonFrame;
760
+ exports.resetGamepadFrame = resetGamepadFrame;
761
+ exports.resetKeyboardFrame = resetKeyboardFrame;
762
+ exports.resetPointerFrame = resetPointerFrame;
763
+ exports.saveRemap = saveRemap;
764
+ exports.updateGamepadFromSnapshot = updateGamepadFromSnapshot;
765
+ exports.updateJoystick = updateJoystick;
766
+ exports.wasActionPressed = wasActionPressed;
767
+ exports.wasActionReleased = wasActionReleased;
768
+ exports.wasButtonPressed = wasButtonPressed;
769
+ exports.wasButtonReleased = wasButtonReleased;
539
770
 
540
- // src/virtual-button.ts
541
- function createButtonState() {
542
- return {
543
- isPressed: false,
544
- justPressed: false,
545
- justReleased: false
546
- };
547
- }
548
- function resetButtonFrame(state) {
549
- state.justPressed = false;
550
- state.justReleased = false;
551
- }
552
- function pressButton(state) {
553
- if (!state.isPressed) {
554
- state.isPressed = true;
555
- state.justPressed = true;
556
- }
557
- }
558
- function releaseButton(state) {
559
- if (state.isPressed) {
560
- state.isPressed = false;
561
- state.justReleased = true;
562
- }
563
- }
564
- // Annotate the CommonJS export names for ESM import in node:
565
- 0 && (module.exports = {
566
- GamepadAxis,
567
- GamepadButton,
568
- GestureDetector,
569
- InputManager,
570
- applyRemap,
571
- createButtonState,
572
- createGamepadState,
573
- createIdentityRemap,
574
- createJoystickState,
575
- createPointer,
576
- createWebGamepadSource,
577
- getAxis,
578
- isButtonDown,
579
- loadRemap,
580
- pointerDown,
581
- pointerMove,
582
- pointerUp,
583
- pressButton,
584
- releaseButton,
585
- releaseJoystick,
586
- resetButtonFrame,
587
- resetGamepadFrame,
588
- resetPointerFrame,
589
- saveRemap,
590
- updateGamepadFromSnapshot,
591
- updateJoystick,
592
- wasButtonPressed,
593
- wasButtonReleased
594
- });
595
771
  //# sourceMappingURL=index.js.map