@flare-engine/input 0.1.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 +28 -0
- package/dist/index.d.mts +344 -0
- package/dist/index.d.ts +344 -0
- package/dist/index.js +595 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +541 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
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 });
|
|
9
|
+
};
|
|
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
|
|
121
|
+
};
|
|
122
|
+
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 };
|
|
129
|
+
}
|
|
130
|
+
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));
|
|
135
|
+
}
|
|
136
|
+
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;
|
|
184
|
+
}
|
|
185
|
+
function resetGamepadFrame(state) {
|
|
186
|
+
clearFrameFlags(state);
|
|
187
|
+
}
|
|
188
|
+
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
|
+
}
|
|
195
|
+
}
|
|
196
|
+
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;
|
|
201
|
+
}
|
|
202
|
+
function isButtonDown(state, button) {
|
|
203
|
+
return state.buttons[button]?.pressed ?? false;
|
|
204
|
+
}
|
|
205
|
+
function wasButtonPressed(state, button) {
|
|
206
|
+
return state.buttons[button]?.pressedThisFrame ?? false;
|
|
207
|
+
}
|
|
208
|
+
function wasButtonReleased(state, button) {
|
|
209
|
+
return state.buttons[button]?.releasedThisFrame ?? false;
|
|
210
|
+
}
|
|
211
|
+
function getAxis(state, axis) {
|
|
212
|
+
return state.axes[axis] ?? 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/remap.ts
|
|
216
|
+
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 };
|
|
222
|
+
}
|
|
223
|
+
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 };
|
|
237
|
+
}
|
|
238
|
+
async function saveRemap(store, key, table) {
|
|
239
|
+
await store.set(key, JSON.stringify(table));
|
|
240
|
+
}
|
|
241
|
+
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
|
+
}
|
|
251
|
+
}
|
|
252
|
+
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;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/input-manager.ts
|
|
268
|
+
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
|
+
}
|
|
349
|
+
};
|
|
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;
|
|
498
|
+
function createJoystickState() {
|
|
499
|
+
return {
|
|
500
|
+
active: false,
|
|
501
|
+
direction: { x: 0, y: 0 },
|
|
502
|
+
magnitude: 0,
|
|
503
|
+
offset: { x: 0, y: 0 }
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
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;
|
|
530
|
+
}
|
|
531
|
+
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;
|
|
538
|
+
}
|
|
539
|
+
|
|
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
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/pointer.ts","../src/gamepad.ts","../src/remap.ts","../src/input-manager.ts","../src/gamepad-source.ts","../src/gestures.ts","../src/virtual-joystick.ts","../src/virtual-button.ts"],"sourcesContent":["export {\n type Pointer,\n type TouchPoint,\n createPointer,\n resetPointerFrame,\n pointerDown,\n pointerMove,\n pointerUp,\n} from './pointer.js';\n\nexport { InputManager } from './input-manager.js';\n\nexport {\n GamepadButton,\n GamepadAxis,\n type GamepadButtonId,\n type GamepadAxisId,\n type GamepadButtonState,\n type GamepadState,\n type GamepadSnapshot,\n createGamepadState,\n updateGamepadFromSnapshot,\n resetGamepadFrame,\n isButtonDown,\n wasButtonPressed,\n wasButtonReleased,\n getAxis,\n} from './gamepad.js';\n\nexport {\n type GamepadSource,\n createWebGamepadSource,\n} from './gamepad-source.js';\n\nexport {\n type AxisMap,\n type RemapTable,\n type RemapStore,\n createIdentityRemap,\n applyRemap,\n saveRemap,\n loadRemap,\n} from './remap.js';\n\nexport {\n type SwipeDirection,\n type GestureConfig,\n type SwipeEvent,\n type PinchEvent,\n GestureDetector,\n} from './gestures.js';\n\nexport {\n type VirtualJoystickConfig,\n type VirtualJoystickState,\n createJoystickState,\n updateJoystick,\n releaseJoystick,\n} from './virtual-joystick.js';\n\nexport {\n type VirtualButtonState,\n createButtonState,\n resetButtonFrame,\n pressButton,\n releaseButton,\n} from './virtual-button.js';\n","import type { Vec2 } from '@flare-engine/math';\n\n/** State of a single pointer (primary touch or mouse). */\nexport interface Pointer {\n /** Current position in screen coordinates. */\n position: Vec2;\n /** Position delta since last frame. */\n delta: Vec2;\n /** Whether the pointer is currently held down. */\n isDown: boolean;\n /** True only on the frame the pointer was pressed. */\n justPressed: boolean;\n /** True only on the frame the pointer was released. */\n justReleased: boolean;\n}\n\n/** State of a single touch point in a multitouch scenario. */\nexport interface TouchPoint {\n /** Unique touch identifier. */\n id: number;\n /** Current position in screen coordinates. */\n position: Vec2;\n /** Whether this touch is currently active. */\n isDown: boolean;\n}\n\n/** Create a default pointer state. */\nexport function createPointer(): Pointer {\n return {\n position: { x: 0, y: 0 },\n delta: { x: 0, y: 0 },\n isDown: false,\n justPressed: false,\n justReleased: false,\n };\n}\n\n/**\n * Update pointer state for a new frame.\n * Call this at the start of each frame to clear per-frame flags.\n */\nexport function resetPointerFrame(pointer: Pointer): void {\n pointer.delta.x = 0;\n pointer.delta.y = 0;\n pointer.justPressed = false;\n pointer.justReleased = false;\n}\n\n/** Feed a pointer-down event into the pointer state. */\nexport function pointerDown(pointer: Pointer, x: number, y: number): void {\n const wasDown = pointer.isDown;\n pointer.isDown = true;\n if (!wasDown) {\n pointer.justPressed = true;\n }\n pointer.delta.x = x - pointer.position.x;\n pointer.delta.y = y - pointer.position.y;\n pointer.position.x = x;\n pointer.position.y = y;\n}\n\n/** Feed a pointer-move event into the pointer state. */\nexport function pointerMove(pointer: Pointer, x: number, y: number): void {\n pointer.delta.x = x - pointer.position.x;\n pointer.delta.y = y - pointer.position.y;\n pointer.position.x = x;\n pointer.position.y = y;\n}\n\n/** Feed a pointer-up event into the pointer state. */\nexport function pointerUp(pointer: Pointer, x: number, y: number): void {\n pointer.isDown = false;\n pointer.justReleased = true;\n pointer.delta.x = x - pointer.position.x;\n pointer.delta.y = y - pointer.position.y;\n pointer.position.x = x;\n pointer.position.y = y;\n}\n","/**\n * Gamepad — poll-based state for a single connected controller.\n *\n * Buttons and axes are indexed against the W3C \"Standard Gamepad\" mapping by\n * default. Callers can translate raw indices via `remap.ts` before feeding\n * snapshots into `updateGamepadFromSnapshot`.\n */\n\n/** W3C Standard Gamepad button indices. */\nexport const GamepadButton = {\n A: 0,\n B: 1,\n X: 2,\n Y: 3,\n LB: 4,\n RB: 5,\n LT: 6,\n RT: 7,\n Back: 8,\n Start: 9,\n LS: 10,\n RS: 11,\n DpadUp: 12,\n DpadDown: 13,\n DpadLeft: 14,\n DpadRight: 15,\n Home: 16,\n} as const;\nexport type GamepadButtonId = (typeof GamepadButton)[keyof typeof GamepadButton];\n\n/** W3C Standard Gamepad axes. */\nexport const GamepadAxis = {\n LeftX: 0,\n LeftY: 1,\n RightX: 2,\n RightY: 3,\n} as const;\nexport type GamepadAxisId = (typeof GamepadAxis)[keyof typeof GamepadAxis];\n\nexport interface GamepadButtonState {\n pressed: boolean;\n value: number;\n /** True for exactly one frame after a press. */\n pressedThisFrame: boolean;\n /** True for exactly one frame after a release. */\n releasedThisFrame: boolean;\n}\n\nexport interface GamepadState {\n connected: boolean;\n id: string;\n index: number;\n buttons: GamepadButtonState[];\n axes: number[];\n /** Monotonically increasing timestamp from the source (ms). */\n timestamp: number;\n}\n\n/** Raw snapshot from a `GamepadSource` — values only, no derived state. */\nexport interface GamepadSnapshot {\n id: string;\n index: number;\n buttons: { pressed: boolean; value: number }[];\n axes: number[];\n timestamp: number;\n}\n\nexport function createGamepadState(index = 0, buttonCount = 17, axisCount = 4): GamepadState {\n const buttons: GamepadButtonState[] = new Array(buttonCount);\n for (let i = 0; i < buttonCount; i++) {\n buttons[i] = { pressed: false, value: 0, pressedThisFrame: false, releasedThisFrame: false };\n }\n const axes = new Array<number>(axisCount).fill(0);\n return { connected: false, id: '', index, buttons, axes, timestamp: 0 };\n}\n\nfunction applyDeadZone(value: number, deadZone: number): number {\n const abs = Math.abs(value);\n if (abs <= deadZone) return 0;\n const sign = value < 0 ? -1 : 1;\n // Rescale [deadZone, 1] -> [0, 1] so inputs remain smooth past the dead zone.\n return sign * ((abs - deadZone) / (1 - deadZone));\n}\n\n/**\n * Apply a fresh snapshot to an existing `GamepadState` in place. Derived\n * `pressedThisFrame` / `releasedThisFrame` flags are recomputed from the\n * previous `pressed` value.\n *\n * `deadZone` in [0, 1). Values at or below are treated as 0.\n */\nexport function updateGamepadFromSnapshot(\n state: GamepadState,\n snapshot: GamepadSnapshot | null,\n deadZone = 0,\n): void {\n if (snapshot === null) {\n if (!state.connected && !anyButtonDown(state)) {\n clearFrameFlags(state);\n return;\n }\n // Disconnect: clear edge flags, keep sticky pressed=false for released-edge.\n for (let i = 0; i < state.buttons.length; i++) {\n const b = state.buttons[i];\n if (!b) continue;\n b.pressedThisFrame = false;\n b.releasedThisFrame = b.pressed;\n b.pressed = false;\n b.value = 0;\n }\n for (let i = 0; i < state.axes.length; i++) state.axes[i] = 0;\n state.connected = false;\n state.id = '';\n return;\n }\n\n state.connected = true;\n state.id = snapshot.id;\n state.index = snapshot.index;\n state.timestamp = snapshot.timestamp;\n\n const srcButtons = snapshot.buttons;\n const bLen = Math.min(state.buttons.length, srcButtons.length);\n for (let i = 0; i < bLen; i++) {\n const prev = state.buttons[i];\n const src = srcButtons[i];\n if (!prev || !src) continue;\n const wasPressed = prev.pressed;\n prev.pressed = src.pressed;\n prev.value = src.value;\n prev.pressedThisFrame = src.pressed && !wasPressed;\n prev.releasedThisFrame = !src.pressed && wasPressed;\n }\n // Buttons beyond the snapshot length: clear edges, keep prior pressed false.\n for (let i = bLen; i < state.buttons.length; i++) {\n const b = state.buttons[i];\n if (!b) continue;\n b.pressedThisFrame = false;\n b.releasedThisFrame = b.pressed;\n b.pressed = false;\n b.value = 0;\n }\n\n const aLen = Math.min(state.axes.length, snapshot.axes.length);\n for (let i = 0; i < aLen; i++) {\n state.axes[i] = applyDeadZone(snapshot.axes[i] ?? 0, deadZone);\n }\n for (let i = aLen; i < state.axes.length; i++) state.axes[i] = 0;\n}\n\n/** Reset just the per-frame edge flags. Call when advancing a frame without a fresh snapshot. */\nexport function resetGamepadFrame(state: GamepadState): void {\n clearFrameFlags(state);\n}\n\nfunction clearFrameFlags(state: GamepadState): void {\n for (let i = 0; i < state.buttons.length; i++) {\n const b = state.buttons[i];\n if (!b) continue;\n b.pressedThisFrame = false;\n b.releasedThisFrame = false;\n }\n}\n\nfunction anyButtonDown(state: GamepadState): boolean {\n for (let i = 0; i < state.buttons.length; i++) {\n if (state.buttons[i]?.pressed) return true;\n }\n return false;\n}\n\nexport function isButtonDown(state: GamepadState, button: number): boolean {\n return state.buttons[button]?.pressed ?? false;\n}\n\nexport function wasButtonPressed(state: GamepadState, button: number): boolean {\n return state.buttons[button]?.pressedThisFrame ?? false;\n}\n\nexport function wasButtonReleased(state: GamepadState, button: number): boolean {\n return state.buttons[button]?.releasedThisFrame ?? false;\n}\n\nexport function getAxis(state: GamepadState, axis: number): number {\n return state.axes[axis] ?? 0;\n}\n","import type { GamepadSnapshot } from './gamepad.js';\n\n/**\n * Button / axis remap table.\n *\n * `buttons[logicalId] = physicalIndex` — to poll A, read\n * `snapshot.buttons[table.buttons[GamepadButton.A]]`.\n *\n * `axes` maps logical axis id → `{ index, invert, scale }`. Most games invert\n * Y on some controllers; `scale` supports half-range triggers.\n */\nexport interface AxisMap {\n index: number;\n invert: boolean;\n scale: number;\n}\n\nexport interface RemapTable {\n buttons: number[];\n axes: AxisMap[];\n}\n\nexport function createIdentityRemap(buttonCount = 17, axisCount = 4): RemapTable {\n const buttons = new Array<number>(buttonCount);\n for (let i = 0; i < buttonCount; i++) buttons[i] = i;\n const axes = new Array<AxisMap>(axisCount);\n for (let i = 0; i < axisCount; i++) axes[i] = { index: i, invert: false, scale: 1 };\n return { buttons, axes };\n}\n\n/** Apply a remap table to a raw snapshot, returning a new snapshot with logical ordering. */\nexport function applyRemap(snapshot: GamepadSnapshot, table: RemapTable): GamepadSnapshot {\n const buttons = new Array<{ pressed: boolean; value: number }>(table.buttons.length);\n for (let logical = 0; logical < table.buttons.length; logical++) {\n const physical = table.buttons[logical] ?? logical;\n const src = snapshot.buttons[physical];\n buttons[logical] = { pressed: src?.pressed ?? false, value: src?.value ?? 0 };\n }\n const axes = new Array<number>(table.axes.length);\n for (let logical = 0; logical < table.axes.length; logical++) {\n const map = table.axes[logical] ?? { index: logical, invert: false, scale: 1 };\n const raw = snapshot.axes[map.index] ?? 0;\n axes[logical] = raw * map.scale * (map.invert ? -1 : 1);\n }\n return { id: snapshot.id, index: snapshot.index, timestamp: snapshot.timestamp, buttons, axes };\n}\n\n/**\n * Minimal storage contract — matches `@flare-engine/storage` `StorageAdapter`,\n * but declared locally so this package does not take a hard dep on storage.\n */\nexport interface RemapStore {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n}\n\n/** Persist a remap table under the given key. Throws on invalid table shape. */\nexport async function saveRemap(store: RemapStore, key: string, table: RemapTable): Promise<void> {\n await store.set(key, JSON.stringify(table));\n}\n\n/** Load a remap table. Returns `null` if absent or malformed. */\nexport async function loadRemap(store: RemapStore, key: string): Promise<RemapTable | null> {\n const raw = await store.get(key);\n if (raw === null) return null;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isRemapTable(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction isRemapTable(value: unknown): value is RemapTable {\n if (typeof value !== 'object' || value === null) return false;\n const obj = value as { buttons?: unknown; axes?: unknown };\n if (!Array.isArray(obj.buttons) || !Array.isArray(obj.axes)) return false;\n for (const b of obj.buttons) if (typeof b !== 'number') return false;\n for (const a of obj.axes) {\n if (typeof a !== 'object' || a === null) return false;\n const ax = a as { index?: unknown; invert?: unknown; scale?: unknown };\n if (\n typeof ax.index !== 'number' ||\n typeof ax.invert !== 'boolean' ||\n typeof ax.scale !== 'number'\n ) {\n return false;\n }\n }\n return true;\n}\n","import type { GamepadSource } from './gamepad-source.js';\nimport {\n type GamepadState,\n createGamepadState,\n resetGamepadFrame,\n updateGamepadFromSnapshot,\n} from './gamepad.js';\nimport {\n type Pointer,\n type TouchPoint,\n createPointer,\n pointerDown,\n pointerMove,\n pointerUp,\n resetPointerFrame,\n} from './pointer.js';\nimport type { RemapTable } from './remap.js';\nimport { applyRemap } from './remap.js';\n\n/**\n * InputManager provides a polling-based input model.\n *\n * Call `update()` once per frame (before game logic) to snapshot input state.\n * Feed events from the platform (RN touch/gesture handlers) via the on* methods.\n */\nexport class InputManager {\n /** Primary pointer (first touch or mouse). */\n readonly pointer: Pointer = createPointer();\n\n /** All active touch points. */\n readonly touches: TouchPoint[] = [];\n\n private _touchMap = new Map<number, TouchPoint>();\n private _gamepads: GamepadState[] = [];\n private _gamepadDeadZone = 0.15;\n private _gamepadRemaps = new Map<number, RemapTable>();\n\n /** Call once per frame to reset per-frame flags. */\n update(): void {\n resetPointerFrame(this.pointer);\n for (let i = 0; i < this._gamepads.length; i++) {\n const pad = this._gamepads[i];\n if (pad) resetGamepadFrame(pad);\n }\n }\n\n /** Feed a touch/pointer begin event. */\n onPointerDown(id: number, x: number, y: number): void {\n pointerDown(this.pointer, x, y);\n this._addOrUpdateTouch(id, x, y, true);\n }\n\n /** Feed a touch/pointer move event. */\n onPointerMove(id: number, x: number, y: number): void {\n pointerMove(this.pointer, x, y);\n this._addOrUpdateTouch(id, x, y, true);\n }\n\n /** Feed a touch/pointer end event. */\n onPointerUp(id: number, x: number, y: number): void {\n pointerUp(this.pointer, x, y);\n this._removeTouch(id);\n }\n\n /** Access the gamepad state at `index`. Allocates on first access. */\n gamepad(index: number): GamepadState {\n let pad = this._gamepads[index];\n if (!pad) {\n pad = createGamepadState(index);\n this._gamepads[index] = pad;\n }\n return pad;\n }\n\n /** Dead zone in [0, 1). Axes at or below this magnitude read as 0. */\n setGamepadDeadZone(deadZone: number): void {\n this._gamepadDeadZone = Math.max(0, Math.min(0.95, deadZone));\n }\n\n /** Install a remap table for a specific gamepad index. Pass `null` to clear. */\n setGamepadRemap(index: number, table: RemapTable | null): void {\n if (table === null) this._gamepadRemaps.delete(index);\n else this._gamepadRemaps.set(index, table);\n }\n\n /** Poll all gamepads from the given source, applying remap + dead zones. */\n pollGamepads(source: GamepadSource): void {\n const snapshots = source.poll();\n for (let i = 0; i < snapshots.length; i++) {\n const pad = this.gamepad(i);\n const snap = snapshots[i] ?? null;\n const remapped =\n snap !== null\n ? this._gamepadRemaps.get(i)\n ? applyRemap(snap, this._gamepadRemaps.get(i) as RemapTable)\n : snap\n : null;\n updateGamepadFromSnapshot(pad, remapped, this._gamepadDeadZone);\n }\n }\n\n private _addOrUpdateTouch(id: number, x: number, y: number, isDown: boolean): void {\n let touch = this._touchMap.get(id);\n if (!touch) {\n touch = { id, position: { x, y }, isDown };\n this._touchMap.set(id, touch);\n this.touches.push(touch);\n } else {\n touch.position.x = x;\n touch.position.y = y;\n touch.isDown = isDown;\n }\n }\n\n private _removeTouch(id: number): void {\n const touch = this._touchMap.get(id);\n if (!touch) return;\n this._touchMap.delete(id);\n const idx = this.touches.indexOf(touch);\n if (idx !== -1) {\n this.touches.splice(idx, 1);\n }\n }\n}\n","import type { GamepadSnapshot } from './gamepad.js';\n\n/**\n * Platform abstraction for sampling connected gamepads.\n *\n * `poll()` returns one snapshot per slot (typically up to 4). Unconnected\n * slots return `null`. Implementations:\n *\n * - `createWebGamepadSource()` wraps the W3C `navigator.getGamepads` API.\n * - Native implementations (e.g., `react-native-gamepad`) inject their own\n * via the `GamepadSource` interface — the package itself ships no native deps.\n */\nexport interface GamepadSource {\n poll(): (GamepadSnapshot | null)[];\n}\n\ninterface NavigatorLike {\n getGamepads?: () => (WebGamepad | null)[];\n}\n\ninterface WebGamepad {\n id: string;\n index: number;\n connected: boolean;\n timestamp: number;\n buttons: readonly { pressed: boolean; value: number }[];\n axes: readonly number[];\n}\n\n/**\n * Wrap `navigator.getGamepads()` (W3C Gamepad API). If `nav` is not supplied,\n * uses `globalThis.navigator`. Returns an empty array when the API is absent.\n */\nexport function createWebGamepadSource(nav?: NavigatorLike): GamepadSource {\n const source: NavigatorLike | undefined =\n nav ??\n (typeof globalThis === 'undefined'\n ? undefined\n : (globalThis as { navigator?: NavigatorLike }).navigator);\n\n return {\n poll(): (GamepadSnapshot | null)[] {\n if (!source?.getGamepads) return [];\n const pads = source.getGamepads();\n const out: (GamepadSnapshot | null)[] = new Array(pads.length);\n for (let i = 0; i < pads.length; i++) {\n const p = pads[i];\n if (!p || !p.connected) {\n out[i] = null;\n continue;\n }\n out[i] = webGamepadToSnapshot(p);\n }\n return out;\n },\n };\n}\n\nfunction webGamepadToSnapshot(p: WebGamepad): GamepadSnapshot {\n const buttons = new Array<{ pressed: boolean; value: number }>(p.buttons.length);\n for (let i = 0; i < p.buttons.length; i++) {\n const b = p.buttons[i];\n buttons[i] = { pressed: b?.pressed ?? false, value: b?.value ?? 0 };\n }\n const axes = new Array<number>(p.axes.length);\n for (let i = 0; i < p.axes.length; i++) axes[i] = p.axes[i] ?? 0;\n return { id: p.id, index: p.index, timestamp: p.timestamp, buttons, axes };\n}\n","import { Signal } from '@flare-engine/events';\nimport type { Vec2 } from '@flare-engine/math';\n\n/** Swipe direction. */\nexport type SwipeDirection = 'left' | 'right' | 'up' | 'down';\n\n/** Gesture detection configuration. */\nexport interface GestureConfig {\n /** Max time (seconds) for a tap (default 0.3). */\n tapMaxDuration?: number;\n /** Max distance (px) for a tap (default 20). */\n tapMaxDistance?: number;\n /** Min time (seconds) for a long press (default 0.5). */\n longPressMinDuration?: number;\n /** Min distance (px) for a swipe (default 50). */\n swipeMinDistance?: number;\n /** Max time (seconds) for a swipe (default 0.4). */\n swipeMaxDuration?: number;\n}\n\n/** Swipe event data. */\nexport interface SwipeEvent {\n direction: SwipeDirection;\n distance: number;\n duration: number;\n}\n\n/** Pinch event data. */\nexport interface PinchEvent {\n /** Scale factor (>1 = spread, <1 = pinch). */\n scale: number;\n /** Center point between the two touches. */\n center: Vec2;\n}\n\nconst DEFAULT_TAP_MAX_DURATION = 0.3;\nconst DEFAULT_TAP_MAX_DISTANCE = 20;\nconst DEFAULT_LONG_PRESS_MIN_DURATION = 0.5;\nconst DEFAULT_SWIPE_MIN_DISTANCE = 50;\nconst DEFAULT_SWIPE_MAX_DURATION = 0.4;\n\n/**\n * GestureDetector recognizes tap, long press, swipe, and pinch gestures\n * from raw pointer events.\n */\nexport class GestureDetector {\n readonly onTap = new Signal<Vec2>();\n readonly onLongPress = new Signal<Vec2>();\n readonly onSwipe = new Signal<SwipeEvent>();\n readonly onPinch = new Signal<PinchEvent>();\n\n private tapMaxDuration: number;\n private tapMaxDistance: number;\n private longPressMinDuration: number;\n private swipeMinDistance: number;\n private swipeMaxDuration: number;\n\n // Single-touch tracking\n private _startPos: Vec2 = { x: 0, y: 0 };\n private _startTime = 0;\n private _isTracking = false;\n private _longPressFired = false;\n\n // Pinch tracking\n private _pinchStartDistance = 0;\n private _pinchTouches: [Vec2, Vec2] | null = null;\n\n constructor(config?: GestureConfig) {\n this.tapMaxDuration = config?.tapMaxDuration ?? DEFAULT_TAP_MAX_DURATION;\n this.tapMaxDistance = config?.tapMaxDistance ?? DEFAULT_TAP_MAX_DISTANCE;\n this.longPressMinDuration = config?.longPressMinDuration ?? DEFAULT_LONG_PRESS_MIN_DURATION;\n this.swipeMinDistance = config?.swipeMinDistance ?? DEFAULT_SWIPE_MIN_DISTANCE;\n this.swipeMaxDuration = config?.swipeMaxDuration ?? DEFAULT_SWIPE_MAX_DURATION;\n }\n\n /** Feed a pointer-down event. */\n pointerDown(x: number, y: number): void {\n this._startPos = { x, y };\n this._startTime = 0;\n this._isTracking = true;\n this._longPressFired = false;\n }\n\n /** Feed a pointer-up event. */\n pointerUp(x: number, y: number, elapsedSinceDown: number): void {\n if (!this._isTracking) return;\n this._isTracking = false;\n\n const dx = x - this._startPos.x;\n const dy = y - this._startPos.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n const duration = elapsedSinceDown;\n\n // Check swipe first (requires min distance)\n if (dist >= this.swipeMinDistance && duration <= this.swipeMaxDuration) {\n const absDx = Math.abs(dx);\n const absDy = Math.abs(dy);\n let direction: SwipeDirection;\n if (absDx > absDy) {\n direction = dx > 0 ? 'right' : 'left';\n } else {\n direction = dy > 0 ? 'down' : 'up';\n }\n this.onSwipe.emit({ direction, distance: dist, duration });\n return;\n }\n\n // Check tap (short press, small movement)\n if (dist <= this.tapMaxDistance && duration <= this.tapMaxDuration) {\n this.onTap.emit({ x, y });\n return;\n }\n }\n\n /**\n * Update gesture tracking. Call each frame with dt.\n * @param dt Delta time in seconds.\n * @param currentX Current pointer X.\n * @param currentY Current pointer Y.\n */\n update(dt: number, currentX: number, currentY: number): void {\n if (!this._isTracking) return;\n this._startTime += dt;\n\n // Long press detection\n if (!this._longPressFired && this._startTime >= this.longPressMinDuration) {\n const dx = currentX - this._startPos.x;\n const dy = currentY - this._startPos.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n if (dist <= this.tapMaxDistance) {\n this._longPressFired = true;\n this.onLongPress.emit({ x: currentX, y: currentY });\n }\n }\n }\n\n /**\n * Feed a two-finger pinch update. Call when two touches are active.\n * @param t1 First touch position.\n * @param t2 Second touch position.\n */\n pinchUpdate(t1: Vec2, t2: Vec2): void {\n const dx = t2.x - t1.x;\n const dy = t2.y - t1.y;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n if (this._pinchTouches === null) {\n // Start pinch\n this._pinchStartDistance = dist;\n this._pinchTouches = [{ ...t1 }, { ...t2 }];\n return;\n }\n\n if (this._pinchStartDistance > 0) {\n const scale = dist / this._pinchStartDistance;\n const center: Vec2 = {\n x: (t1.x + t2.x) / 2,\n y: (t1.y + t2.y) / 2,\n };\n this.onPinch.emit({ scale, center });\n }\n }\n\n /** Call when pinch ends (fewer than 2 touches). */\n pinchEnd(): void {\n this._pinchTouches = null;\n this._pinchStartDistance = 0;\n }\n}\n","import type { Vec2 } from '@flare-engine/math';\n\n/** Virtual joystick configuration. */\nexport interface VirtualJoystickConfig {\n /** Dead zone radius in pixels (default 10). */\n deadZone?: number;\n /** Maximum radius in pixels (default 50). */\n maxRadius?: number;\n}\n\n/** Virtual joystick state — pure data, no UI. */\nexport interface VirtualJoystickState {\n /** Whether the joystick is currently being touched. */\n active: boolean;\n /** Normalized direction vector (-1 to 1 per axis). */\n direction: Vec2;\n /** Magnitude 0-1 (0 = center/dead zone, 1 = max radius). */\n magnitude: number;\n /** Raw offset from center in pixels. */\n offset: Vec2;\n}\n\nconst DEFAULT_DEAD_ZONE = 10;\nconst DEFAULT_MAX_RADIUS = 50;\n\n/** Create a default joystick state. */\nexport function createJoystickState(): VirtualJoystickState {\n return {\n active: false,\n direction: { x: 0, y: 0 },\n magnitude: 0,\n offset: { x: 0, y: 0 },\n };\n}\n\n/**\n * Update joystick state from a touch event.\n *\n * @param state The joystick state to update.\n * @param centerX The joystick center X in screen pixels.\n * @param centerY The joystick center Y in screen pixels.\n * @param touchX The current touch X in screen pixels.\n * @param touchY The current touch Y in screen pixels.\n * @param config Joystick configuration.\n */\nexport function updateJoystick(\n state: VirtualJoystickState,\n centerX: number,\n centerY: number,\n touchX: number,\n touchY: number,\n config?: VirtualJoystickConfig,\n): void {\n const deadZone = config?.deadZone ?? DEFAULT_DEAD_ZONE;\n const maxRadius = config?.maxRadius ?? DEFAULT_MAX_RADIUS;\n\n const dx = touchX - centerX;\n const dy = touchY - centerY;\n const dist = Math.sqrt(dx * dx + dy * dy);\n\n state.active = true;\n\n if (dist < deadZone) {\n state.direction.x = 0;\n state.direction.y = 0;\n state.magnitude = 0;\n state.offset.x = dx;\n state.offset.y = dy;\n return;\n }\n\n // Clamp to max radius\n const clampedDist = Math.min(dist, maxRadius);\n const normalizedDist = (clampedDist - deadZone) / (maxRadius - deadZone);\n\n // Direction is always the full normalized direction\n const invDist = 1 / dist;\n state.direction.x = dx * invDist;\n state.direction.y = dy * invDist;\n state.magnitude = Math.min(normalizedDist, 1);\n\n // Offset clamped to max radius\n const scale = clampedDist / dist;\n state.offset.x = dx * scale;\n state.offset.y = dy * scale;\n}\n\n/** Reset joystick state when touch is released. */\nexport function releaseJoystick(state: VirtualJoystickState): void {\n state.active = false;\n state.direction.x = 0;\n state.direction.y = 0;\n state.magnitude = 0;\n state.offset.x = 0;\n state.offset.y = 0;\n}\n","/** Virtual button state — pure data, no UI. */\nexport interface VirtualButtonState {\n /** Whether the button is currently pressed. */\n isPressed: boolean;\n /** Whether the button was just pressed this frame. */\n justPressed: boolean;\n /** Whether the button was just released this frame. */\n justReleased: boolean;\n}\n\n/** Create a default button state. */\nexport function createButtonState(): VirtualButtonState {\n return {\n isPressed: false,\n justPressed: false,\n justReleased: false,\n };\n}\n\n/** Call at the start of each frame to reset per-frame flags. */\nexport function resetButtonFrame(state: VirtualButtonState): void {\n state.justPressed = false;\n state.justReleased = false;\n}\n\n/** Call when the button is pressed. */\nexport function pressButton(state: VirtualButtonState): void {\n if (!state.isPressed) {\n state.isPressed = true;\n state.justPressed = true;\n }\n}\n\n/** Call when the button is released. */\nexport function releaseButton(state: VirtualButtonState): void {\n if (state.isPressed) {\n state.isPressed = false;\n state.justReleased = true;\n }\n}\n"],"mappings":"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","names":[]}
|