@simpludify/core 0.1.4 → 0.1.6

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.
@@ -1,11 +1,16 @@
1
1
  import type { ActionKind, ActionSet, ActionToken } from "./action-types.js";
2
+ import { type GamepadBinding } from "./gamepad-bindings.js";
2
3
  import { KeyCode } from "./key-code.js";
4
+ import { type MouseButton } from "./physical-input.js";
3
5
  export interface ButtonBinding {
4
6
  readonly keys?: readonly KeyCode[];
7
+ readonly mouse?: readonly MouseButton[];
5
8
  }
6
9
  export interface Axis1DBinding {
7
10
  readonly negative?: readonly KeyCode[];
8
11
  readonly positive?: readonly KeyCode[];
12
+ readonly mouseNegative?: readonly MouseButton[];
13
+ readonly mousePositive?: readonly MouseButton[];
9
14
  }
10
15
  export interface Axis2DBinding {
11
16
  readonly x?: Axis1DBinding;
@@ -14,16 +19,27 @@ export interface Axis2DBinding {
14
19
  export interface Axis3DBinding extends Axis2DBinding {
15
20
  readonly z?: Axis1DBinding;
16
21
  }
17
- export type ActionBinding<K extends ActionKind> = K extends "button" ? ButtonBinding : K extends "axis1d" ? Axis1DBinding : K extends "axis2d" ? Axis2DBinding : Axis3DBinding;
22
+ export type ActionBinding<K extends ActionKind> = (K extends "button" ? ButtonBinding : K extends "axis1d" ? Axis1DBinding : K extends "axis2d" ? Axis2DBinding : Axis3DBinding) & {
23
+ readonly gamepad?: GamepadBinding<K>;
24
+ };
18
25
  export type ActionBindings<A extends ActionSet> = {
19
26
  readonly [N in keyof A]?: ActionBinding<A[N]["kind"]>;
20
27
  };
21
- type Binding = ButtonBinding | Axis1DBinding | Axis2DBinding | Axis3DBinding;
28
+ type Binding = ActionBinding<ActionKind>;
22
29
  export declare function checkedBinding(action: ActionToken, value: unknown): Binding;
23
30
  export declare function keyLists(binding: Binding, path?: string): Array<{
24
31
  path: string;
25
32
  keys: readonly KeyCode[];
26
33
  }>;
34
+ export declare function mouseLists(binding: Binding, path?: string): Array<{
35
+ path: string;
36
+ buttons: readonly MouseButton[];
37
+ }>;
38
+ export declare function gamepadControls(binding: GamepadBinding<ActionKind>, path?: string): Array<{
39
+ kind: "button" | "axis";
40
+ index: number;
41
+ path: string;
42
+ }>;
27
43
  export declare function editBinding(current: Binding, patch: Binding, remove: boolean): Binding;
28
44
  /** Physical keyboard presets for any user-defined Axis2D Action. */
29
45
  export declare const KeyboardBindings: Readonly<{
@@ -10,11 +10,34 @@ import { type Scene } from "../scene/scene.js";
10
10
  import type { ActionInputPort, InputSystem } from "./input.js";
11
11
  import { OwnedInputSource } from "./input-source.js";
12
12
  import type { KeyCode } from "./key-code.js";
13
+ import type { MouseButton } from "./physical-input.js";
13
14
  type Token<A extends ActionSet> = A[keyof A];
14
15
  type ButtonToken<A extends ActionSet> = Extract<Token<A>, {
15
16
  kind: "button";
16
17
  }>;
17
18
  type Value = boolean | number | Axis2DValue | Axis3DValue;
19
+ type BindingUse<A extends ActionSet> = {
20
+ readonly action: Token<A>;
21
+ readonly path: string;
22
+ };
23
+ export interface KeyboardBindingConflict<A extends ActionSet> {
24
+ readonly key: KeyCode;
25
+ readonly bindings: readonly BindingUse<A>[];
26
+ }
27
+ export interface MouseBindingConflict<A extends ActionSet> {
28
+ readonly device: "mouse";
29
+ readonly button: MouseButton;
30
+ readonly bindings: readonly BindingUse<A>[];
31
+ }
32
+ export interface GamepadBindingConflict<A extends ActionSet> {
33
+ readonly device: "gamepad";
34
+ readonly kind: "button" | "axis";
35
+ readonly index: number;
36
+ readonly bindings: readonly BindingUse<A>[];
37
+ }
38
+ export type BindingConflict<A extends ActionSet> = (KeyboardBindingConflict<A> & {
39
+ readonly device: "keyboard";
40
+ }) | MouseBindingConflict<A> | GamepadBindingConflict<A>;
18
41
  export interface ButtonState {
19
42
  readonly held: boolean;
20
43
  readonly pressed: boolean;
@@ -44,6 +67,9 @@ export declare class ActionController<A extends ActionSet> implements LifetimeOw
44
67
  private readonly bus;
45
68
  private keyboardAllowed;
46
69
  private readonly blockedKeys;
70
+ private mouseAllowed;
71
+ private readonly blockedMouse;
72
+ private bindingGeneration;
47
73
  private pending;
48
74
  private transitions;
49
75
  constructor(actions: A, options: {
@@ -51,12 +77,17 @@ export declare class ActionController<A extends ActionSet> implements LifetimeOw
51
77
  input: InputSystem;
52
78
  bindings?: ActionBindings<A>;
53
79
  keyboard?: boolean;
80
+ mouse?: boolean;
54
81
  });
55
82
  get disposed(): boolean;
56
83
  get active(): boolean;
57
84
  get keyboardEnabled(): boolean;
58
85
  set keyboardEnabled(value: boolean);
59
86
  get sourceCount(): number;
87
+ get mouseEnabled(): boolean;
88
+ set mouseEnabled(value: boolean);
89
+ /** @internal Used by existing host adapters to follow common binding settings. */
90
+ get bindingRevision(): number;
60
91
  onDispose(callback: () => void): () => void;
61
92
  private entry;
62
93
  value<T extends Token<A>>(action: T): ActionValue<T["kind"]>;
@@ -70,28 +101,29 @@ export declare class ActionController<A extends ActionSet> implements LifetimeOw
70
101
  snapshot(): ActionSnapshot<A>;
71
102
  onPressed(action: ButtonToken<A>, handler: () => void): () => void;
72
103
  createSource(owner?: LifetimeOwner): ActionSource<A>;
73
- /** @internal */ write(source: ActionSource<A>, action: ActionToken, value: Value): void;
104
+ /** @internal */ write(source: ActionSource<A>, action: ActionToken, value: Value, edges?: boolean): void;
74
105
  /** @internal */ clearSource(source: ActionSource<A>, remove: boolean): void;
75
106
  private keyboard;
76
107
  private recompute;
77
108
  /** @internal */ keyChanged(): void;
78
109
  /** @internal */ accepts(code: KeyCode): boolean;
110
+ /** @internal */ acceptsMouse(button: MouseButton): boolean;
79
111
  /** @internal */ capture(override?: ActionFrame): void;
80
112
  /** @internal */ dispatch(): void;
81
113
  /** Clears held contributions and unconsumed edges; no synthetic button release. */
82
114
  reset(): void;
115
+ /** @internal Flush device transitions without destroying explicit program values. */
116
+ resetPhysical(): void;
83
117
  getBinding<T extends Token<A>>(action: T): ActionBinding<T["kind"]>;
84
118
  rebind<T extends Token<A>>(action: T, binding: NoInfer<ActionBinding<T["kind"]>>): void;
85
119
  addBindings<T extends Token<A>>(action: T, binding: NoInfer<ActionBinding<T["kind"]>>): void;
86
120
  removeBindings<T extends Token<A>>(action: T, binding: NoInfer<ActionBinding<T["kind"]>>): void;
87
121
  resetBindings(action?: Token<A>): void;
88
- conflicts(): ReadonlyArray<{
89
- readonly key: KeyCode;
90
- readonly bindings: ReadonlyArray<{
91
- readonly action: Token<A>;
92
- readonly path: string;
93
- }>;
94
- }>;
122
+ conflicts(): readonly KeyboardBindingConflict<A>[];
123
+ conflicts(device: "keyboard"): readonly KeyboardBindingConflict<A>[];
124
+ conflicts(device: "mouse"): readonly MouseBindingConflict<A>[];
125
+ conflicts(device: "gamepad"): readonly GamepadBindingConflict<A>[];
126
+ conflicts(device: "all"): readonly BindingConflict<A>[];
95
127
  exportBindings(): {
96
128
  version: 1;
97
129
  bindings: ActionBindings<A>;
@@ -110,5 +142,7 @@ export declare class ActionSource<A extends ActionSet> extends OwnedInputSource
110
142
  private readonly controller;
111
143
  /** @internal */ constructor(controller: ActionController<A>);
112
144
  set<T extends Token<A>>(action: T, value: NoInfer<ActionInputValue<T["kind"]>>): void;
145
+ /** @internal Reset one device binding without synthesizing logical edges. */
146
+ resetBinding(action: Token<A>): void;
113
147
  protected release(remove: boolean): void;
114
148
  }
@@ -0,0 +1,100 @@
1
+ import type { ActionKind, ActionSet, ActionToken } from "./action-types.js";
2
+ /** Standard mapping physical positions, not product-specific printed labels. */
3
+ export declare const GamepadButton: Readonly<{
4
+ South: 0;
5
+ East: 1;
6
+ West: 2;
7
+ North: 3;
8
+ LeftShoulder: 4;
9
+ RightShoulder: 5;
10
+ LeftTrigger: 6;
11
+ RightTrigger: 7;
12
+ Select: 8;
13
+ Start: 9;
14
+ LeftStick: 10;
15
+ RightStick: 11;
16
+ DPadUp: 12;
17
+ DPadDown: 13;
18
+ DPadLeft: 14;
19
+ DPadRight: 15;
20
+ Home: 16;
21
+ }>;
22
+ export type GamepadButton = (typeof GamepadButton)[keyof typeof GamepadButton];
23
+ export declare const GamepadAxis: Readonly<{
24
+ LeftX: 0;
25
+ LeftY: 1;
26
+ RightX: 2;
27
+ RightY: 3;
28
+ }>;
29
+ export type GamepadAxis = (typeof GamepadAxis)[keyof typeof GamepadAxis];
30
+ export interface GamepadButtonBinding {
31
+ readonly buttons?: readonly (number | {
32
+ readonly button: number;
33
+ readonly threshold?: number;
34
+ })[];
35
+ }
36
+ export interface GamepadAxisBinding {
37
+ readonly axis?: number;
38
+ readonly invert?: boolean;
39
+ readonly negative?: readonly number[];
40
+ readonly positive?: readonly number[];
41
+ readonly deadZone?: number;
42
+ }
43
+ export interface GamepadVectorBinding {
44
+ readonly x?: GamepadAxisBinding;
45
+ readonly y?: GamepadAxisBinding;
46
+ }
47
+ export interface GamepadVector3Binding extends GamepadVectorBinding {
48
+ readonly z?: GamepadAxisBinding;
49
+ }
50
+ export type GamepadBinding<K extends ActionKind> = K extends "button" ? GamepadButtonBinding : K extends "axis1d" ? GamepadAxisBinding : K extends "axis2d" ? GamepadVectorBinding : GamepadVector3Binding;
51
+ export type GamepadBindings<A extends ActionSet> = {
52
+ readonly [N in keyof A]?: GamepadBinding<A[N]["kind"]>;
53
+ };
54
+ /** Ready-to-use data; game Actions retain application-defined names. */
55
+ export declare const GamepadBindings: Readonly<{
56
+ LeftStick: Readonly<{
57
+ x: Readonly<{
58
+ axis: number;
59
+ }>;
60
+ y: Readonly<{
61
+ axis: number;
62
+ }>;
63
+ }>;
64
+ RightStick: Readonly<{
65
+ x: Readonly<{
66
+ axis: number;
67
+ }>;
68
+ y: Readonly<{
69
+ axis: number;
70
+ }>;
71
+ }>;
72
+ DPad: Readonly<{
73
+ x: Readonly<{
74
+ negative: readonly number[];
75
+ positive: readonly number[];
76
+ }>;
77
+ y: Readonly<{
78
+ negative: readonly number[];
79
+ positive: readonly number[];
80
+ }>;
81
+ }>;
82
+ Move: Readonly<{
83
+ x: Readonly<{
84
+ axis: 0;
85
+ negative: readonly number[];
86
+ positive: readonly number[];
87
+ }>;
88
+ y: Readonly<{
89
+ axis: 1;
90
+ negative: readonly number[];
91
+ positive: readonly number[];
92
+ }>;
93
+ }>;
94
+ }>;
95
+ type Binding = GamepadButtonBinding | GamepadAxisBinding | GamepadVector3Binding;
96
+ export type CheckedGamepadBinding = readonly [ActionToken, Binding];
97
+ export declare function gamepadIndex(value: unknown): number;
98
+ export declare function gamepadDeadZone(value: unknown): number;
99
+ export declare function checkedGamepadBindings(actions: ActionSet, value: unknown): readonly CheckedGamepadBinding[];
100
+ export {};
@@ -0,0 +1,38 @@
1
+ import { type LifetimeOwner } from "../foundation/lifetime.js";
2
+ import type { ActionBinding } from "./action-bindings.js";
3
+ import { InputDevice, type PhysicalInput } from "./physical-input.js";
4
+ export declare const InputCaptureState: Readonly<{
5
+ readonly Arming: "arming";
6
+ readonly Listening: "listening";
7
+ readonly Completed: "completed";
8
+ readonly Cancelled: "cancelled";
9
+ }>;
10
+ export type InputCaptureState = (typeof InputCaptureState)[keyof typeof InputCaptureState];
11
+ export interface InputCaptureOptions {
12
+ readonly devices?: readonly InputDevice[];
13
+ readonly gamepadIndex?: number;
14
+ readonly owner?: LifetimeOwner;
15
+ }
16
+ export type CapturedInput = PhysicalInput & {
17
+ readonly binding: ActionBinding<"button">;
18
+ };
19
+ /** A portable single-input session. Hosts supply neutral arming and physical events. */
20
+ export declare class InputCapture implements LifetimeOwner {
21
+ private readonly life;
22
+ private status;
23
+ readonly devices: readonly InputDevice[];
24
+ readonly gamepadIndex: number | undefined;
25
+ readonly result: Promise<CapturedInput | undefined>;
26
+ private settle;
27
+ constructor(options?: InputCaptureOptions);
28
+ get state(): InputCaptureState;
29
+ get disposed(): boolean;
30
+ onDispose(callback: () => void): () => void;
31
+ /** @internal Hosts call only after all eligible preheld buttons/keys are neutral. */
32
+ arm(): void;
33
+ /** @internal Offer one fresh physical down, never a repeat or opening gesture. */
34
+ accept(input: PhysicalInput): boolean;
35
+ private finish;
36
+ cancel(): void;
37
+ dispose(): void;
38
+ }
@@ -2,6 +2,7 @@ import { type LifetimeOwner } from "../foundation/lifetime.js";
2
2
  import type { ActionFrame } from "./action-frame.js";
3
3
  import { OwnedInputSource } from "./input-source.js";
4
4
  import { type KeyCode } from "./key-code.js";
5
+ import { type MouseButton } from "./physical-input.js";
5
6
  /** Host device adapter. Sampled before all Action frames; reset flushes held device state. */
6
7
  export interface InputAdapter {
7
8
  sample(): void;
@@ -13,10 +14,12 @@ export interface ActionInputPort {
13
14
  readonly active: boolean;
14
15
  readonly sourceCount: number;
15
16
  accepts(code: KeyCode): boolean;
17
+ acceptsMouse(button: MouseButton): boolean;
16
18
  keyChanged(): void;
17
19
  capture(frame?: ActionFrame): void;
18
20
  dispatch(): void;
19
21
  reset(): void;
22
+ resetPhysical(): void;
20
23
  dispose(): void;
21
24
  }
22
25
  /** A keyboard device contribution, not a game-facing state reader.
@@ -27,25 +30,41 @@ export declare class KeyboardSource extends OwnedInputSource {
27
30
  set(code: KeyCode, pressed: boolean): void;
28
31
  protected release(remove: boolean): void;
29
32
  }
33
+ /** Physical mouse contribution; game state is read through ActionController. */
34
+ export declare class MouseSource extends OwnedInputSource {
35
+ private readonly input;
36
+ /** @internal */ constructor(input: InputSystem);
37
+ set(button: MouseButton, pressed: boolean): void;
38
+ protected release(remove: boolean): void;
39
+ }
30
40
  /** Engine-owned routing and update boundary. No raw game-state/alternate movement API. */
31
41
  export declare class InputSystem {
32
42
  private readonly actions;
33
43
  private readonly adapters;
34
44
  private readonly keyboards;
45
+ private readonly mice;
46
+ private captureCancel;
35
47
  private dead;
36
48
  private playback;
37
49
  private frames;
50
+ /** @internal Device hosts may reject live gestures during Replay playback. */
51
+ get liveInput(): boolean;
38
52
  get sourceCount(): number;
39
53
  /** @internal */ registerActions(actions: ActionInputPort): () => void;
40
54
  /** Host integration boundary; ordinary game input uses ActionSource instead. */
41
55
  addAdapter(adapter: InputAdapter): () => void;
42
56
  acceptsKey(code: KeyCode): boolean;
57
+ acceptsMouse(button: MouseButton): boolean;
58
+ /** @internal */ mouseDown(button: MouseButton): boolean;
43
59
  private notify;
44
60
  /** @internal Device state is resolved only through Action bindings. */
45
61
  keyDown(code: KeyCode): boolean;
46
62
  /** @internal Freeze every controller before invoking any callbacks. */
47
63
  beginStep(captured?: () => void): void;
48
64
  createKeyboardSource(owner?: LifetimeOwner): KeyboardSource;
65
+ createMouseSource(owner?: LifetimeOwner): MouseSource;
66
+ /** @internal */ writeMouse(source: MouseSource, button: MouseButton, pressed: boolean): void;
67
+ /** @internal */ releaseMouse(source: MouseSource, remove: boolean): void;
49
68
  /** @internal */ writeKey(source: KeyboardSource, code: KeyCode, pressed: boolean): void;
50
69
  /** @internal */ releaseKeyboard(source: KeyboardSource, remove: boolean): void;
51
70
  /** @internal */ hasController(controller: object): boolean;
@@ -54,6 +73,9 @@ export declare class InputSystem {
54
73
  /** @internal */ lockPlayback(): void;
55
74
  /** @internal */ playbackFrames(frames: ReadonlyMap<object, ActionFrame>): void;
56
75
  /** @internal */ unlockPlayback(): void;
76
+ /** @internal Host capture gate. Program ActionSource values remain explicit. */
77
+ suspendLiveInput(cancel: () => void): () => void;
78
+ private resetPhysical;
57
79
  clear(): void;
58
80
  /** Device diagnostics only; game state is ActionController.snapshot(). */
59
81
  snapshot(): readonly KeyCode[];
@@ -0,0 +1,32 @@
1
+ import type { KeyCode } from "./key-code.js";
2
+ export declare const InputDevice: Readonly<{
3
+ readonly Keyboard: "keyboard";
4
+ readonly Mouse: "mouse";
5
+ readonly Gamepad: "gamepad";
6
+ }>;
7
+ export type InputDevice = (typeof InputDevice)[keyof typeof InputDevice];
8
+ /** Standard PointerEvent.button indices; these are not the buttons bit mask. */
9
+ export declare const MouseButton: Readonly<{
10
+ readonly Left: 0;
11
+ readonly Middle: 1;
12
+ readonly Right: 2;
13
+ readonly Back: 3;
14
+ readonly Forward: 4;
15
+ }>;
16
+ export type MouseButton = (typeof MouseButton)[keyof typeof MouseButton];
17
+ export declare function isMouseButton(value: unknown): value is MouseButton;
18
+ export declare function parseMouseButton(value: unknown): MouseButton;
19
+ /** One physical digital input, independent of an Action's gameplay meaning. */
20
+ export type PhysicalInput = {
21
+ readonly device: "keyboard";
22
+ readonly code: KeyCode;
23
+ } | {
24
+ readonly device: "mouse";
25
+ readonly button: MouseButton;
26
+ } | {
27
+ readonly device: "gamepad";
28
+ readonly index: number;
29
+ readonly id: string;
30
+ readonly mapping: string;
31
+ readonly button: number;
32
+ };
@@ -4,6 +4,7 @@ import type { ScreenPoint, WorldPoint } from "../graphics/coordinates.js";
4
4
  import type { Scene } from "../scene/scene.js";
5
5
  import type { CanvasNode } from "../ui/canvas-node.js";
6
6
  import type { CanvasUI } from "../ui/canvas-ui.js";
7
+ import type { PointerTarget, PointerTargetProvider } from "./pointer-target.js";
7
8
  /** Snapshot coordinates at DOM delivery. Screen is host CSS units; world includes the current Camera. */
8
9
  export interface PointerInfo {
9
10
  readonly pointerId: number;
@@ -30,6 +31,10 @@ export declare class ScenePointer implements LifetimeOwner {
30
31
  private readonly lifetime;
31
32
  readonly events: EventBus;
32
33
  private readonly removeScene;
34
+ private targets;
35
+ /** @internal Exactly one Scene virtual-input provider shares the existing router. */
36
+ attachTargets(provider: PointerTargetProvider): () => void;
37
+ /** @internal */ pickTarget(data: PointerInfo): PointerTarget | undefined;
33
38
  constructor(scene: Scene);
34
39
  get disposed(): boolean;
35
40
  onDispose(callback: () => void): () => void;
@@ -38,6 +43,7 @@ export declare class ScenePointer implements LifetimeOwner {
38
43
  /** @internal Device-independent routing state; DOM is only an adapter. */
39
44
  export declare class PointerRouting {
40
45
  private readonly released;
46
+ private readonly targetOwners;
41
47
  private readonly controlOwners;
42
48
  private readonly scrollOwners;
43
49
  private readonly suppressed;
@@ -60,6 +66,7 @@ export declare class PointerRouting {
60
66
  deliveries: number;
61
67
  scrollDrags: number;
62
68
  controlDrags: number;
69
+ virtualDrags: number;
63
70
  };
64
71
  setScene(channel: ScenePointer, ui?: CanvasUI): void;
65
72
  private hold;
@@ -72,6 +79,8 @@ export declare class PointerRouting {
72
79
  hasHover(id: number): boolean;
73
80
  has(id: number): boolean;
74
81
  owns(id: number): boolean;
82
+ /** @internal Mouse bindings observe world ownership without claiming a second gesture. */
83
+ worldOwns(id: number): boolean;
75
84
  move(data: PointerInfo): void;
76
85
  private sampleVelocity;
77
86
  private claimScroll;
@@ -86,6 +95,8 @@ export declare class PointerRouting {
86
95
  releaseKeys(node: CanvasNode): void;
87
96
  private prune;
88
97
  release(): void;
98
+ /** @internal Host focus changes interrupt virtual devices without changing Canvas editing rules. */
99
+ releaseVirtual(): void;
89
100
  dispose(): void;
90
101
  reset(): void;
91
102
  }
@@ -0,0 +1,14 @@
1
+ import type { LifetimeOwner } from "../foundation/lifetime.js";
2
+ import type { PointerInfo } from "./pointer-routing.js";
3
+ /** @internal Scene-scoped recipients behind Canvas UI; the existing router owns all IDs. */
4
+ export interface PointerTarget extends LifetimeOwner {
5
+ readonly available: boolean;
6
+ readonly revision: number;
7
+ subscribe(callback: () => void): () => void;
8
+ contains(data: PointerInfo): boolean;
9
+ deliver(type: "down" | "move" | "up" | "cancel", data: PointerInfo): void;
10
+ }
11
+ /** @internal */
12
+ export interface PointerTargetProvider extends LifetimeOwner {
13
+ pick(data: PointerInfo): PointerTarget | undefined;
14
+ }
@@ -0,0 +1,140 @@
1
+ import { type LifetimeOwner } from "../foundation/lifetime.js";
2
+ import type { Point, ScreenPoint } from "../graphics/coordinates.js";
3
+ import type { PointerInfo } from "./pointer-routing.js";
4
+ import type { PointerTarget } from "./pointer-target.js";
5
+ import { type VirtualPosition, type VirtualRegion } from "./virtual-regions.js";
6
+ export declare const VirtualStickMode: {
7
+ readonly Fixed: "fixed";
8
+ readonly Floating: "floating";
9
+ readonly Follow: "follow";
10
+ };
11
+ export type VirtualStickMode = (typeof VirtualStickMode)[keyof typeof VirtualStickMode];
12
+ export interface VirtualControlOptions {
13
+ readonly region: VirtualRegion;
14
+ readonly position?: VirtualPosition;
15
+ /** Host CSS pixels, independent of Camera, DPR and Canvas UI scale. */
16
+ readonly radius?: number;
17
+ /** Radial fraction, 0 <= deadZone < 1; the remaining magnitude is rescaled. */
18
+ readonly deadZone?: number;
19
+ readonly enabled?: boolean;
20
+ readonly visible?: boolean;
21
+ /** Presentation seconds to the one-shot onHold notification. Default 0.5. */
22
+ readonly holdDelay?: number;
23
+ readonly color?: number;
24
+ }
25
+ export interface VirtualStickOptions extends VirtualControlOptions {
26
+ readonly mode?: VirtualStickMode;
27
+ }
28
+ export interface VirtualButtonOptions extends VirtualControlOptions {
29
+ /** Direction is optional; false by default. The Action remains an ordinary Button. */
30
+ readonly direction?: boolean;
31
+ }
32
+ export interface VirtualGesture {
33
+ readonly control: VirtualControl;
34
+ readonly pointerId: number;
35
+ readonly pointerType: string;
36
+ readonly origin: ScreenPoint;
37
+ readonly position: ScreenPoint;
38
+ readonly center: ScreenPoint;
39
+ /** Final direction is retained in end/cancel payloads after the live Action is cleared. */
40
+ readonly direction: Point;
41
+ readonly inside: boolean;
42
+ readonly elapsed: number;
43
+ }
44
+ /** @internal */
45
+ export interface VirtualControlHost {
46
+ available(): boolean;
47
+ center(control: VirtualControl): Point;
48
+ contains(control: VirtualControl, data: PointerInfo): boolean;
49
+ region(control: VirtualControl, value: VirtualRegion): void;
50
+ remove(control: VirtualControl): void;
51
+ }
52
+ /** @internal */
53
+ export interface VirtualBinding extends LifetimeOwner {
54
+ write(direction: Point, pressed: boolean): void;
55
+ clear(): void;
56
+ dispose(): void;
57
+ active(): boolean;
58
+ }
59
+ /** @internal Validate before allocating ActionSources or registering a target. */
60
+ export declare function checkVirtualOptions(options: VirtualStickOptions & VirtualButtonOptions): void;
61
+ /** Portable gesture state. Routing, Action composition and rendering retain their existing owners. */
62
+ export declare abstract class VirtualControl implements PointerTarget {
63
+ readonly kind: "stick" | "button";
64
+ private readonly host;
65
+ private readonly binding;
66
+ private readonly life;
67
+ private readonly events;
68
+ private readonly observers;
69
+ private activation;
70
+ private activeFlag;
71
+ private shown;
72
+ private size;
73
+ private deadZoneValue;
74
+ private version;
75
+ private gesture;
76
+ private vector;
77
+ private dragMode;
78
+ readonly position: VirtualPosition | undefined;
79
+ readonly holdDelay: number;
80
+ readonly color: number;
81
+ readonly directional: boolean;
82
+ protected constructor(kind: "stick" | "button", options: VirtualStickOptions & VirtualButtonOptions, host: VirtualControlHost, binding: VirtualBinding);
83
+ get disposed(): boolean;
84
+ get available(): boolean;
85
+ get revision(): number;
86
+ get enabled(): boolean;
87
+ set enabled(value: boolean);
88
+ get visible(): boolean;
89
+ set visible(value: boolean);
90
+ get radius(): number;
91
+ set radius(value: number);
92
+ get deadZone(): number;
93
+ set deadZone(value: number);
94
+ get region(): VirtualRegion;
95
+ set region(value: VirtualRegion);
96
+ protected get stickMode(): VirtualStickMode;
97
+ protected set stickMode(value: VirtualStickMode);
98
+ get pointerId(): number | undefined;
99
+ get pressed(): boolean;
100
+ get elapsed(): number;
101
+ get direction(): Point;
102
+ get center(): ScreenPoint;
103
+ get knob(): ScreenPoint;
104
+ onDispose(callback: () => void): () => void;
105
+ /** @internal Geometry/availability invalidation, not a per-frame observer. */
106
+ subscribe(callback: () => void): () => void;
107
+ onStart(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
108
+ onChange(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
109
+ onEnd(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
110
+ onCancel(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
111
+ onHold(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
112
+ /** Release/cancel only; the captured physical ID stays suppressed until its release. */
113
+ cancel(): void;
114
+ /** @internal */
115
+ invalidate(): void;
116
+ private alive;
117
+ private interrupt;
118
+ contains(data: PointerInfo): boolean;
119
+ private snapshot;
120
+ private update;
121
+ deliver(type: "down" | "move" | "up" | "cancel", data: PointerInfo): void;
122
+ /** @internal Explicit presentation clock; no browser/OS timer in Core. */
123
+ advance(seconds: number): void;
124
+ private finish;
125
+ dispose(): void;
126
+ }
127
+ export declare class VirtualStick extends VirtualControl {
128
+ /** @internal Use VirtualInput.stick. */
129
+ constructor(options: VirtualStickOptions, host: VirtualControlHost, binding: VirtualBinding);
130
+ get value(): Point;
131
+ get mode(): VirtualStickMode;
132
+ set mode(value: VirtualStickMode);
133
+ }
134
+ export declare class VirtualButton extends VirtualControl {
135
+ /** @internal Use VirtualInput.button. */
136
+ constructor(options: VirtualButtonOptions, host: VirtualControlHost, binding: VirtualBinding);
137
+ get value(): boolean;
138
+ /** Confirmation on normal release, including a drag outside the activation region; never on cancel. */
139
+ onCommit(callback: (event: VirtualGesture) => void, owner?: LifetimeOwner): () => void;
140
+ }
@@ -0,0 +1,49 @@
1
+ import type { Viewport } from "../graphics/coordinates.js";
2
+ import type { ActionController, ActionSet } from "./actions.js";
3
+ import type { InputAdapter, InputSystem } from "./input.js";
4
+ import type { PointerInfo, ScenePointer } from "./pointer-routing.js";
5
+ import type { PointerTargetProvider } from "./pointer-target.js";
6
+ import { VirtualButton, type VirtualButtonOptions, type VirtualControl, VirtualStick, type VirtualStickOptions } from "./virtual-control.js";
7
+ /** Scene-scoped virtual devices; all gameplay values flow through existing ActionSources. */
8
+ export declare class VirtualInput implements InputAdapter, PointerTargetProvider {
9
+ readonly pointer: ScenePointer;
10
+ private readonly input;
11
+ private readonly life;
12
+ private list;
13
+ private regions;
14
+ private anchors;
15
+ private view;
16
+ private active;
17
+ private shown;
18
+ private dirty;
19
+ private readonly host;
20
+ constructor(pointer: ScenePointer, input: InputSystem);
21
+ get disposed(): boolean;
22
+ get controls(): readonly VirtualControl[];
23
+ get viewport(): Viewport;
24
+ get enabled(): boolean;
25
+ set enabled(value: boolean);
26
+ get visible(): boolean;
27
+ set visible(value: boolean);
28
+ onDispose(callback: () => void): () => void;
29
+ private flag;
30
+ private alive;
31
+ private validate;
32
+ private binding;
33
+ stick<A extends ActionSet>(controller: ActionController<A>, action: Extract<A[keyof A], {
34
+ kind: "axis2d";
35
+ }>, options: VirtualStickOptions): VirtualStick;
36
+ button<A extends ActionSet>(controller: ActionController<A>, action: Extract<A[keyof A], {
37
+ kind: "button";
38
+ }>, options: VirtualButtonOptions): VirtualButton;
39
+ private add;
40
+ private resolve;
41
+ private centers;
42
+ /** Resolve after Canvas layout and before routing/rendering. Layout changes cancel active gestures. */
43
+ refresh(viewport?: Viewport): void;
44
+ pick(data: PointerInfo): VirtualControl | undefined;
45
+ sample(): void;
46
+ advance(seconds: number): void;
47
+ reset(): void;
48
+ dispose(): void;
49
+ }