@forgeax/engine-input 0.0.0-dev.8d955ade1c79
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 +202 -0
- package/README.md +312 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/browser-backend-dual-instance.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-dual-instance.test.d.ts.map +1 -0
- package/dist/__tests__/browser-backend-focus-loss.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-focus-loss.test.d.ts.map +1 -0
- package/dist/__tests__/browser-backend-lock-error.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-lock-error.test.d.ts.map +1 -0
- package/dist/__tests__/browser-backend-lock-gate.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-lock-gate.test.d.ts.map +1 -0
- package/dist/__tests__/browser-backend-lock-release.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-lock-release.test.d.ts.map +1 -0
- package/dist/__tests__/browser-backend-lock-state.test.d.ts +2 -0
- package/dist/__tests__/browser-backend-lock-state.test.d.ts.map +1 -0
- package/dist/__tests__/canvas-input-boundary.test.d.ts +2 -0
- package/dist/__tests__/canvas-input-boundary.test.d.ts.map +1 -0
- package/dist/__tests__/canvas-input-owner.test-d.d.ts +2 -0
- package/dist/__tests__/canvas-input-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/composite-backend.test.d.ts +2 -0
- package/dist/__tests__/composite-backend.test.d.ts.map +1 -0
- package/dist/__tests__/controller-platform-owner.test-d.d.ts +2 -0
- package/dist/__tests__/controller-platform-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/input-snapshot.test-d.d.ts +2 -0
- package/dist/__tests__/input-snapshot.test-d.d.ts.map +1 -0
- package/dist/__tests__/input-snapshot.test.d.ts +2 -0
- package/dist/__tests__/input-snapshot.test.d.ts.map +1 -0
- package/dist/__tests__/swipe-direction-owner.test-d.d.ts +2 -0
- package/dist/__tests__/swipe-direction-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/ui-input-matrix.test.d.ts +2 -0
- package/dist/__tests__/ui-input-matrix.test.d.ts.map +1 -0
- package/dist/__tests__/ui-ownership-edge.test.d.ts +2 -0
- package/dist/__tests__/ui-ownership-edge.test.d.ts.map +1 -0
- package/dist/__tests__/ui-ownership.test.d.ts +2 -0
- package/dist/__tests__/ui-ownership.test.d.ts.map +1 -0
- package/dist/__tests__/ui-reset-idempotence.test.d.ts +2 -0
- package/dist/__tests__/ui-reset-idempotence.test.d.ts.map +1 -0
- package/dist/__tests__/ui-reset.test.d.ts +2 -0
- package/dist/__tests__/ui-reset.test.d.ts.map +1 -0
- package/dist/action-state.d.ts +146 -0
- package/dist/action-state.d.ts.map +1 -0
- package/dist/browser-backend.d.ts +121 -0
- package/dist/browser-backend.d.ts.map +1 -0
- package/dist/canvas-input-boundary.d.ts +13 -0
- package/dist/canvas-input-boundary.d.ts.map +1 -0
- package/dist/composite-backend.d.ts +34 -0
- package/dist/composite-backend.d.ts.map +1 -0
- package/dist/controller-db-data.d.ts +5 -0
- package/dist/controller-db-data.d.ts.map +1 -0
- package/dist/controller-db-data.mjs +12 -0
- package/dist/controller-db-data.mjs.map +1 -0
- package/dist/controller-db.d.ts +70 -0
- package/dist/controller-db.d.ts.map +1 -0
- package/dist/controller-db.mjs +119 -0
- package/dist/controller-db.mjs.map +1 -0
- package/dist/frame-start-scan-system.d.ts +37 -0
- package/dist/frame-start-scan-system.d.ts.map +1 -0
- package/dist/gamepad-frame.d.ts +42 -0
- package/dist/gamepad-frame.d.ts.map +1 -0
- package/dist/gesture-recognizer.d.ts +145 -0
- package/dist/gesture-recognizer.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1502 -0
- package/dist/index.mjs.map +1 -0
- package/dist/input-snapshot.d.ts +420 -0
- package/dist/input-snapshot.d.ts.map +1 -0
- package/dist/plugin-service.d.ts +11 -0
- package/dist/plugin-service.d.ts.map +1 -0
- package/dist/ui-ownership.d.ts +17 -0
- package/dist/ui-ownership.d.ts.map +1 -0
- package/dist/virtual-joystick.d.ts +39 -0
- package/dist/virtual-joystick.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/__tests__/browser-backend-dual-instance.test.ts +379 -0
- package/src/__tests__/browser-backend-focus-loss.test.ts +239 -0
- package/src/__tests__/browser-backend-lock-error.test.ts +425 -0
- package/src/__tests__/browser-backend-lock-gate.test.ts +283 -0
- package/src/__tests__/browser-backend-lock-release.test.ts +641 -0
- package/src/__tests__/browser-backend-lock-state.test.ts +604 -0
- package/src/__tests__/canvas-input-boundary.test.ts +65 -0
- package/src/__tests__/canvas-input-owner.test-d.ts +39 -0
- package/src/__tests__/composite-backend.test.ts +299 -0
- package/src/__tests__/controller-platform-owner.test-d.ts +46 -0
- package/src/__tests__/input-snapshot.test-d.ts +78 -0
- package/src/__tests__/input-snapshot.test.ts +96 -0
- package/src/__tests__/swipe-direction-owner.test-d.ts +45 -0
- package/src/__tests__/ui-input-matrix.test.ts +31 -0
- package/src/__tests__/ui-ownership-edge.test.ts +33 -0
- package/src/__tests__/ui-ownership.test.ts +35 -0
- package/src/__tests__/ui-reset-idempotence.test.ts +17 -0
- package/src/__tests__/ui-reset.test.ts +25 -0
- package/src/action-state.ts +380 -0
- package/src/browser-backend.ts +884 -0
- package/src/canvas-input-boundary.ts +81 -0
- package/src/composite-backend.ts +189 -0
- package/src/controller-db-data.ts +25 -0
- package/src/controller-db.ts +218 -0
- package/src/frame-start-scan-system.ts +93 -0
- package/src/gamepad-frame.ts +259 -0
- package/src/gesture-recognizer.ts +495 -0
- package/src/index.ts +101 -0
- package/src/input-snapshot.ts +732 -0
- package/src/plugin-service.ts +31 -0
- package/src/ui-ownership.ts +61 -0
- package/src/virtual-joystick.ts +103 -0
- package/vendor/gamecontrollerdb.txt +2114 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Plugin } from '@forgeax/engine-plugin';
|
|
2
|
+
|
|
3
|
+
import type { InputBackend } from './input-snapshot';
|
|
4
|
+
|
|
5
|
+
declare module '@forgeax/engine-plugin' {
|
|
6
|
+
interface EngineContextServices {
|
|
7
|
+
input?: InputBackend;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function inputBackendPlugin(backend: InputBackend): Plugin {
|
|
12
|
+
return {
|
|
13
|
+
name: 'input-backend',
|
|
14
|
+
provide: 'input',
|
|
15
|
+
apply(ctx) {
|
|
16
|
+
ctx.provide('input', backend);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Provide an App-acquired input backend and release its Host listeners with the Fiber. */
|
|
22
|
+
export function ownedInputBackendPlugin(backend: InputBackend, dispose: () => void): Plugin {
|
|
23
|
+
return {
|
|
24
|
+
name: 'input-backend',
|
|
25
|
+
provide: 'input',
|
|
26
|
+
apply(ctx) {
|
|
27
|
+
ctx.provide('input', backend);
|
|
28
|
+
ctx.effect(() => dispose, 'input/backend');
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Event-path ownership and lifecycle reset primitives for browser hosts. */
|
|
2
|
+
|
|
3
|
+
export interface UiOwnershipResult {
|
|
4
|
+
readonly owned: boolean;
|
|
5
|
+
readonly source: 'composed-path' | 'target-fallback' | 'unknown';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface UiInputResetBoundaryOptions {
|
|
9
|
+
readonly clear: () => void;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UiInputResetBoundary {
|
|
14
|
+
readonly reset: () => void;
|
|
15
|
+
readonly dispose: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function contains(root: Node, value: EventTarget | null): boolean {
|
|
19
|
+
const NodeCtor = globalThis.Node;
|
|
20
|
+
return NodeCtor && value instanceof NodeCtor ? value === root || root.contains(value) : false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveUiOwnership(event: Event, host: Node): UiOwnershipResult {
|
|
24
|
+
const path = event.composedPath;
|
|
25
|
+
if (typeof path === 'function') {
|
|
26
|
+
const composed = path.call(event);
|
|
27
|
+
return {
|
|
28
|
+
owned:
|
|
29
|
+
composed.some((item) => contains(host, item)) ||
|
|
30
|
+
(composed.length === 0 && contains(host, event.target)),
|
|
31
|
+
source: composed.length === 0 ? 'target-fallback' : 'composed-path',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if ('target' in event) {
|
|
35
|
+
return { owned: contains(host, event.target), source: 'target-fallback' };
|
|
36
|
+
}
|
|
37
|
+
return { owned: false, source: 'unknown' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isUiOwnedEvent(event: Event, host: Node): boolean {
|
|
41
|
+
return resolveUiOwnership(event, host).owned;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createUiInputResetBoundary(
|
|
45
|
+
options: UiInputResetBoundaryOptions,
|
|
46
|
+
): UiInputResetBoundary {
|
|
47
|
+
let disposed = false;
|
|
48
|
+
const reset = (): void => {
|
|
49
|
+
if (!disposed) options.clear();
|
|
50
|
+
};
|
|
51
|
+
const onAbort = (): void => reset();
|
|
52
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
53
|
+
return {
|
|
54
|
+
reset,
|
|
55
|
+
dispose: () => {
|
|
56
|
+
if (disposed) return;
|
|
57
|
+
disposed = true;
|
|
58
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// virtual-joystick.ts -- pure-function virtual joystick derivation (M3).
|
|
2
|
+
//
|
|
3
|
+
// No DOM dependency; backend closure holds per-joystick bindState
|
|
4
|
+
// and calls deriveVirtualAxes each sample() frame.
|
|
5
|
+
//
|
|
6
|
+
// @internal -- only the browser backend calls these functions; the
|
|
7
|
+
// barrel does not re-export them.
|
|
8
|
+
|
|
9
|
+
import type { VirtualAxisSample, VirtualJoystickConfig } from './input-snapshot';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-joystick binding state. The backend closure owns a
|
|
13
|
+
* Map<string, BindState> keyed by joystick config name.
|
|
14
|
+
*/
|
|
15
|
+
export interface BindState {
|
|
16
|
+
/** bound pointerId, or undefined if unbound */
|
|
17
|
+
pointerId?: number | undefined;
|
|
18
|
+
/** origin in canvas pixels (anchor for fixed, first-touch for floating) */
|
|
19
|
+
originX: number;
|
|
20
|
+
originY: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function computeVec(
|
|
24
|
+
ptrX: number,
|
|
25
|
+
ptrY: number,
|
|
26
|
+
originX: number,
|
|
27
|
+
originY: number,
|
|
28
|
+
radius: number,
|
|
29
|
+
deadzone: number,
|
|
30
|
+
): { x: number; y: number } {
|
|
31
|
+
const rawX = (ptrX - originX) / radius;
|
|
32
|
+
const rawY = (ptrY - originY) / radius;
|
|
33
|
+
const mag = Math.sqrt(rawX * rawX + rawY * rawY);
|
|
34
|
+
if (mag < deadzone) return { x: 0, y: 0 };
|
|
35
|
+
if (mag > 1) {
|
|
36
|
+
// Clamp to unit vector: normalize direction, magnitude = 1.
|
|
37
|
+
return { x: rawX / mag, y: rawY / mag };
|
|
38
|
+
}
|
|
39
|
+
return { x: rawX, y: rawY };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Derive one frame of virtual axis outputs from live pointer state and
|
|
44
|
+
* per-joystick binding state.
|
|
45
|
+
*
|
|
46
|
+
* Pure function: no DOM access. The backend closure is the SSOT for
|
|
47
|
+
* per-joystick state; binding + origin selection happens in
|
|
48
|
+
* browser-backend.ts onPointerDown. This function only reads bindState
|
|
49
|
+
* and computes per-frame vectors.
|
|
50
|
+
*
|
|
51
|
+
* Unbound joysticks always produce the zero vector.
|
|
52
|
+
*
|
|
53
|
+
* @param configs - configured virtual joysticks
|
|
54
|
+
* @param pointerMap - live pointer positions keyed by pointerId
|
|
55
|
+
* @param bindState - per-joystick binding state (read-only)
|
|
56
|
+
* @returns one VirtualAxisSample per configured joystick
|
|
57
|
+
*/
|
|
58
|
+
export function deriveVirtualAxes(
|
|
59
|
+
configs: readonly VirtualJoystickConfig[],
|
|
60
|
+
pointerMap: Map<number, { readonly x: number; readonly y: number }>,
|
|
61
|
+
bindState: Map<string, BindState>,
|
|
62
|
+
): VirtualAxisSample[] {
|
|
63
|
+
const results: VirtualAxisSample[] = [];
|
|
64
|
+
|
|
65
|
+
for (const cfg of configs) {
|
|
66
|
+
const bs = bindState.get(cfg.name);
|
|
67
|
+
|
|
68
|
+
if (bs?.pointerId !== undefined) {
|
|
69
|
+
// Bound: compute vector from current pointer position.
|
|
70
|
+
const ptr = pointerMap.get(bs.pointerId);
|
|
71
|
+
if (ptr) {
|
|
72
|
+
const vec = computeVec(ptr.x, ptr.y, bs.originX, bs.originY, cfg.radius, cfg.deadzone);
|
|
73
|
+
results.push({ name: cfg.name, x: vec.x, y: vec.y });
|
|
74
|
+
} else {
|
|
75
|
+
// Pointer disappeared (up/cancel handled by backend unbind,
|
|
76
|
+
// but this guards against pointer removal without unbind).
|
|
77
|
+
bs.pointerId = undefined;
|
|
78
|
+
results.push({ name: cfg.name, x: 0, y: 0 });
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Unbound: zero vector. Binding is the backend's responsibility.
|
|
82
|
+
results.push({ name: cfg.name, x: 0, y: 0 });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Unbind a joystick when its bound pointer goes up or is cancelled.
|
|
91
|
+
* Sets pointerId to undefined; the next deriveVirtualAxes call will
|
|
92
|
+
* produce a zero vector for the unbound joystick.
|
|
93
|
+
*/
|
|
94
|
+
export function handleVirtualJoystickUnbind(
|
|
95
|
+
bindState: Map<string, BindState>,
|
|
96
|
+
pointerId: number,
|
|
97
|
+
): void {
|
|
98
|
+
for (const bs of bindState.values()) {
|
|
99
|
+
if (bs.pointerId === pointerId) {
|
|
100
|
+
bs.pointerId = undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|