UNPKG

@utsp/input

Version:

Comprehensive input management system for UTSP - keyboard, mouse, gamepad, and touch support with unified routing

1,360 lines (1,345 loc) 42.1 kB
import { Vector2, Vector3, IInputSystem, InputDeviceType, AxisBinding, ButtonBinding } from '@utsp/types'; export { GamepadInput, IInputSystem, InputDeviceType, KeyboardInput, MouseInput, TouchInput, Vector2, Vector3 } from '@utsp/types'; /** * Base interface for all input management systems * This provides a common API for keyboard, mouse, gamepad, and touch inputs */ interface IInputs { /** * Start listening to input events */ start(): void; /** * Stop listening to input events and cleanup */ stop(): void; /** * Reset all input states to default */ reset(): void; /** * Check if the input system is currently active/listening */ isListening(): boolean; /** * Cleanup and destroy the instance, removing all event listeners */ destroy(): void; /** * Reset delta values (call at the end of each frame/update) */ resetDelta(): void; /** * Set or update event callbacks */ setCallbacks(callbacks: unknown): void; /** * Remove all event callbacks */ clearCallbacks(): void; /** * Check if a key is currently pressed (Keyboard) */ isKeyPressed?(key: string): boolean; /** * Check if left mouse button is pressed (Mouse) */ isLeftMousePressed?(): boolean; /** * Get mouse position (Mouse) */ getMousePosition?(): Vector2 | null; /** * Get mouse movement delta (Mouse) */ getMouseDelta?(): Vector2 | null; /** * Get wheel delta (Mouse) */ getWheelDelta?(): Vector2 | null; /** * Check if a button is pressed (Gamepad, TVRemote) */ isButtonPressed?(button: string | number): boolean; /** * Get axis value (Gamepad, TVRemote) */ getAxis?(axis: number): number | null; /** * Get motion/gyroscope data (TVRemote, Mobile) */ getMotionData?(): MotionData | null; } /** * Motion/Gyroscope data interface */ interface MotionData extends Vector3 { timestamp?: number; } /** * Common button state interface */ interface ButtonState { pressed: boolean; justPressed: boolean; justReleased: boolean; timestamp?: number; } /** * Input event types enumeration */ declare enum InputEventType { KeyDown = "keydown", KeyUp = "keyup", MouseDown = "mousedown", MouseUp = "mouseup", MouseMove = "mousemove", MouseWheel = "mousewheel", TouchStart = "touchstart", TouchEnd = "touchend", TouchMove = "touchmove", GamepadConnected = "gamepadconnected", GamepadDisconnected = "gamepaddisconnected", GamepadButton = "gamepadbutton", GamepadAxis = "gamepadaxis" } /** * Generic input event interface */ interface InputEvent<T = unknown> { type: InputEventType; timestamp: number; data: T; originalEvent?: Event; } /** * Input device information */ interface InputDevice { type: number; id: string; name?: string; connected: boolean; timestamp?: number; } /** * Configuration options for input systems */ interface InputConfig { /** * Target element to attach listeners to (default: window) */ targetElement?: HTMLElement | Window; /** * Enable/disable specific input types */ enabled?: boolean; /** * Prevent default browser behaviors */ preventDefault?: boolean; /** * Stop event propagation */ stopPropagation?: boolean; /** * Enable debug logging */ debug?: boolean; } /** * Abstract base class for input systems * All input classes should extend this to maintain consistency */ declare abstract class BaseInputs implements IInputs { protected isActive: boolean; protected targetElement: HTMLElement | Window; protected config: InputConfig; protected callbacks: Record<string, ((...args: any[]) => void)[]>; constructor(targetElement?: HTMLElement | Window, config?: InputConfig); abstract start(): void; abstract stop(): void; abstract reset(): void; abstract resetDelta(): void; isListening(): boolean; destroy(): void; setCallbacks(callbacks: unknown): void; clearCallbacks(): void; /** * Get the target element */ getTargetElement(): HTMLElement | Window; /** * Enable the input system */ enable(): void; /** * Disable the input system */ disable(): void; /** * Check if the input system is enabled */ isEnabled(): boolean; /** * Emit an event to all registered callbacks */ protected emit(eventType: string, ...args: unknown[]): void; /** * Log debug messages if debug mode is enabled */ protected log(...args: unknown[]): void; } /** * Type guard to check if an object implements IInputs */ declare function isInputs(obj: unknown): obj is IInputs; /** * KeyboardInputs - Keyboard input management * Handles keyboard events with support for vanilla JS, React, and other frameworks */ /** * Keyboard button state with press/release detection */ interface KeyButtonState { pressed: boolean; justPressed: boolean; justReleased: boolean; } interface KeyState { [key: string]: KeyButtonState; } interface KeyboardCallbacks { onKeyDown?: (key: string, event: KeyboardEvent) => void; onKeyUp?: (key: string, event: KeyboardEvent) => void; } declare class KeyboardInputs extends BaseInputs { private keys; private keyboardCallbacks; private textInputsThisFrame; private boundHandlers; constructor(targetElement?: HTMLElement | Window, config?: InputConfig); start(): void; stop(): void; reset(): void; resetDelta(): void; /** * Poll - Reset transient states (justPressed/justReleased) * Should be called once per frame AFTER collecting input */ poll(): void; /** * Get text inputs captured this frame * Returns actual characters typed (handles keyboard layout, modifiers, etc.) * * @returns Array of character strings typed this frame * * @example * ```typescript * const textInputs = keyboard.getTextInputs(); * textInputs.forEach(char => chatBox.append(char)); * ``` */ getTextInputs(): string[]; /** * Check if a key is currently pressed * * @param key - Key code (e.g., 'Space', 'KeyA', 'ArrowUp') * @returns True if key is pressed, false otherwise * * @example * ```typescript * if (keyboard.isKeyPressed('Space')) { * player.jump(); * } * * if (keyboard.isKeyPressed('KeyW')) { * player.moveForward(); * } * ``` */ isKeyPressed(key: string): boolean; /** * Check if a key was just pressed this frame * * @param key - Key code (e.g., 'Space', 'KeyA', 'ArrowUp') * @returns True if key was just pressed (transition false→true), false otherwise * * @example * ```typescript * if (keyboard.isKeyJustPressed('Space')) { * player.jump(); // Jump once per press * } * ``` */ isKeyJustPressed(key: string): boolean; /** * Check if a key was just released this frame * * @param key - Key code (e.g., 'Space', 'KeyA', 'ArrowUp') * @returns True if key was just released (transition true→false), false otherwise * * @example * ```typescript * if (keyboard.isKeyJustReleased('Space')) { * releaseChargedShot(); * } * ``` */ isKeyJustReleased(key: string): boolean; /** * Get all currently pressed keys * * @returns Array of key codes currently pressed * * @example * ```typescript * const keys = keyboard.getKeysPressed(); * console.log('Pressed keys:', keys); // ['KeyW', 'Space'] * ``` */ getKeysPressed(): string[]; /** * Get all keys that were just pressed this frame */ getKeysJustPressed(): string[]; /** * Get all keys that were just released this frame */ getKeysJustReleased(): string[]; /** * Check if any key is currently pressed * * @returns True if at least one key is pressed, false otherwise * * @example * ```typescript * if (keyboard.isAnyKeyPressed()) { * console.log('Player is active'); * } * ``` */ isAnyKeyPressed(): boolean; /** * Set keyboard-specific callbacks * * @param callbacks - Object containing callback functions * * @example * ```typescript * keyboard.setKeyboardCallbacks({ * onKeyDown: (key, event) => { * console.log(`Key pressed: ${key}`); * }, * onKeyUp: (key, event) => { * console.log(`Key released: ${key}`); * }, * }); * ``` */ setKeyboardCallbacks(callbacks: KeyboardCallbacks): void; clearKeyboardCallbacks(): void; setCallbacks(callbacks: KeyboardCallbacks | unknown): void; private isKeyboardCallbacks; private handleKeyDown; private handleKeyUp; } /** * React hook for keyboard inputs */ declare function useKeyboardInputs(targetElement?: HTMLElement | Window, callbacks?: KeyboardCallbacks): KeyboardInputs; /** * MouseInputs - Mouse input management * Handles mouse events with support for vanilla JS, React, and other frameworks */ /** * Mouse button state with press/release detection */ interface MouseButtonState { pressed: boolean; justPressed: boolean; justReleased: boolean; } interface MouseButtons { left: MouseButtonState; middle: MouseButtonState; right: MouseButtonState; } interface MouseCallbacks { onMouseDown?: (button: string, position: Vector2, event: MouseEvent) => void; onMouseUp?: (button: string, position: Vector2, event: MouseEvent) => void; onMouseMove?: (position: Vector2, delta: Vector2, event: MouseEvent) => void; onMouseWheel?: (delta: number, event: WheelEvent) => void; onMouseEnter?: (event: MouseEvent) => void; onMouseLeave?: (event: MouseEvent) => void; } declare class MouseInputs extends BaseInputs { private mouseButtons; private mousePosition; private mouseDelta; private wheelDelta; private mouseCallbacks; private boundHandlers; constructor(targetElement?: HTMLElement | Window, config?: InputConfig); start(): void; stop(): void; reset(): void; /** * Poll - Reset transient states (justPressed/justReleased) * Should be called once per frame AFTER collecting input */ poll(): void; resetDelta(): void; /** * Check if left mouse button is pressed * * @returns True if left button is pressed, false otherwise * * @example * ```typescript * if (mouse.isLeftMousePressed()) { * console.log('Left click detected'); * } * ``` */ isLeftMousePressed(): boolean; /** * Check if left mouse button was just pressed this frame * * @returns True if button was just pressed (transition false→true), false otherwise * * @example * ```typescript * if (mouse.isLeftMouseJustPressed()) { * fireWeapon(); // Fires once per click * } * ``` */ isLeftMouseJustPressed(): boolean; /** * Check if left mouse button was just released this frame * * @returns True if button was just released (transition true→false), false otherwise * * @example * ```typescript * if (mouse.isLeftMouseJustReleased()) { * releaseChargedAttack(); * } * ``` */ isLeftMouseJustReleased(): boolean; /** * Check if right mouse button is pressed * * @returns True if right button is pressed, false otherwise * * @example * ```typescript * if (mouse.isRightMousePressed()) { * showContextMenu(); * } * ``` */ isRightMousePressed(): boolean; /** * Check if right mouse button was just pressed this frame */ isRightMouseJustPressed(): boolean; /** * Check if right mouse button was just released this frame */ isRightMouseJustReleased(): boolean; /** * Check if middle mouse button is pressed * * @returns True if middle button is pressed, false otherwise */ isMiddleMousePressed(): boolean; /** * Check if middle mouse button was just pressed this frame */ isMiddleMouseJustPressed(): boolean; /** * Check if middle mouse button was just released this frame */ isMiddleMouseJustReleased(): boolean; /** * Get current mouse position * * @returns Vector2 with x and y coordinates in pixels * * @example * ```typescript * const pos = mouse.getMousePosition(); * console.log(`Mouse at (${pos.x}, ${pos.y})`); * ``` */ getMousePosition(): Vector2; /** * Get mouse movement delta since last frame * * @returns Vector2 with dx and dy in pixels * * @example * ```typescript * const delta = mouse.getMouseDelta(); * camera.rotate(delta.x * sensitivity, delta.y * sensitivity); * ``` */ getMouseDelta(): Vector2; /** * Get mouse wheel scroll delta * * @returns Wheel delta (positive = scroll down, negative = scroll up) * * @example * ```typescript * const wheel = mouse.getWheelDelta(); * if (wheel !== 0) { * camera.zoom(wheel > 0 ? -1 : 1); * } * ``` */ getWheelDelta(): number; /** * Set mouse-specific callbacks * * @param callbacks - Object containing callback functions * * @example * ```typescript * mouse.setMouseCallbacks({ * onMouseDown: (button, position, event) => { * console.log(`${button} button pressed at`, position); * }, * onMouseMove: (position, delta, event) => { * console.log('Mouse moved by', delta); * }, * onMouseWheel: (delta, event) => { * console.log('Wheel scrolled:', delta); * }, * }); * ``` */ setMouseCallbacks(callbacks: MouseCallbacks): void; clearMouseCallbacks(): void; setCallbacks(callbacks: MouseCallbacks | unknown): void; private isMouseCallbacks; private handleMouseDown; private handleMouseUp; private handleMouseMove; private handleWheel; private handleMouseEnter; private handleMouseLeave; private handleContextMenu; } /** * React hook for mouse inputs */ declare function useMouseInputs(targetElement?: HTMLElement | Window, callbacks?: MouseCallbacks): MouseInputs; /** * MobileInputs - Touch and mobile device input management * Handles touch events with support for multi-touch, gestures, and orientation */ interface TouchPoint { id: number; nativeId: number; position: Vector2; startPosition: Vector2; active: boolean; justTouched: boolean; justReleased: boolean; } interface MobileCallbacks { onTouchStart?: (touches: TouchPoint[], event: TouchEvent) => void; onTouchEnd?: (touches: TouchPoint[], event: TouchEvent) => void; onTouchMove?: (touches: TouchPoint[], event: TouchEvent) => void; onTouchCancel?: (touches: TouchPoint[], event: TouchEvent) => void; onOrientationChange?: (orientation: number) => void; } interface MobileInputsConfig extends InputConfig { /** Prevent default browser behavior (scroll, zoom) - Default: true */ preventDefault?: boolean; /** Use passive event listeners for better performance - Default: false */ passive?: boolean; /** Maximum number of simultaneous touches - Default: 10 */ maxTouches?: number; } declare class MobileInputs extends BaseInputs { private touches; private nativeToInternal; private mobileCallbacks; private mobileConfig; private maxTouches; private lastLogTime; private boundHandlers; constructor(targetElement?: HTMLElement | Window, config?: MobileInputsConfig); start(): void; stop(): void; reset(): void; resetDelta(): void; /** * Poll - Reset transient states (justTouched/justReleased) * Should be called once per frame AFTER collecting input */ poll(): void; /** * Check if a touch is active (touchId 0-9) * * @param touchId - Touch identifier (0-9) * @returns True if touch is active, false otherwise * * @example * ```typescript * if (mobile.isTouchActive(0)) { * const pos = mobile.getTouchPosition(0); * console.log('First touch at', pos); * } * ``` */ isTouchActive(touchId: number): boolean; /** * Get the position of a touch (touchId 0-9) * * @param touchId - Touch identifier (0-9) * @returns Vector2 with current position, or null if touch is not active * * @example * ```typescript * const pos = mobile.getTouchPosition(0); * if (pos) { * player.moveTo(pos.x, pos.y); * } * ``` */ getTouchPosition(touchId: number): Vector2 | null; /** * Get the starting position of a touch (touchId 0-9) * * @param touchId - Touch identifier (0-9) * @returns Vector2 with start position, or null if touch is not active * * @example * ```typescript * const start = mobile.getTouchStartPosition(0); * const current = mobile.getTouchPosition(0); * if (start && current) { * const swipeDistance = current.subtract(start).length(); * if (swipeDistance > 100) { * console.log('Swipe detected!'); * } * } * ``` */ getTouchStartPosition(touchId: number): Vector2 | null; /** * Get the delta since the beginning of the touch * * @param touchId - Touch identifier (0-9) * @returns Vector2 with delta from start position, or null if touch is not active * * @example * ```typescript * const delta = mobile.getTouchDelta(0); * if (delta && delta.x > 50) { * console.log('Swiped right!'); * } * ``` */ getTouchDelta(touchId: number): Vector2 | null; /** * Get the number of active touches * * @returns Number of simultaneous touches (0-10) * * @example * ```typescript * const count = mobile.getTouchCount(); * if (count === 2) { * console.log('Pinch gesture possible'); * } * ``` */ getTouchCount(): number; /** * Check if a touch just started this frame * * @param touchId - Touch identifier (0-9) * @returns True if touch just started, false otherwise * * @example * ```typescript * if (mobile.isTouchJustStarted(0)) { * playSound('tap'); * } * ``` */ isTouchJustStarted(touchId: number): boolean; /** * Check if a touch just ended this frame * * @param touchId - Touch identifier (0-9) * @returns True if touch just ended, false otherwise * * @example * ```typescript * if (mobile.isTouchJustReleased(0)) { * releaseDragObject(); * } * ``` */ isTouchJustReleased(touchId: number): boolean; /** * Check if any touch just started this frame */ isAnyTouchJustStarted(): boolean; /** * Get all active touches * * @returns Array of all active TouchPoint objects * * @example * ```typescript * const touches = mobile.getAllTouches(); * touches.forEach(touch => { * console.log(`Touch ${touch.id} at`, touch.position); * }); * ``` */ getAllTouches(): TouchPoint[]; /** * Set mobile-specific callbacks * * @param callbacks - Object containing callback functions * * @example * ```typescript * mobile.setMobileCallbacks({ * onTouchStart: (touches, event) => { * console.log(`${touches.length} touches started`); * }, * onTouchMove: (touches, event) => { * touches.forEach(t => console.log(`Touch ${t.id}:`, t.position)); * }, * onOrientationChange: (orientation) => { * console.log('Orientation:', orientation); * }, * }); * ``` */ setMobileCallbacks(callbacks: MobileCallbacks): void; clearMobileCallbacks(): void; setCallbacks(callbacks: MobileCallbacks | unknown): void; private isMobileCallbacks; private handleTouchStart; private handleTouchEnd; private handleTouchMove; private handleTouchCancel; private handleOrientationChange; } /** * React hook for mobile inputs * * ⚠️ IMPORTANT: This function is NOT a proper React hook! * It creates a new MobileInputs instance on every render, causing memory leaks. * * To use correctly in React, wrap with useMemo and useEffect: * * @example * ```typescript * import { useMemo, useEffect } from 'react'; * * const inputs = useMemo(() => new MobileInputs(canvasRef.current, config), [config]); * * useEffect(() => { * if (callbacks) inputs.setCallbacks(callbacks); * inputs.start(); * return () => inputs.stop(); * }, [inputs, callbacks]); * ``` * * @deprecated Use the pattern above instead of this function */ declare function useMobileInputs(targetElement?: HTMLElement | Window, callbacks?: MobileCallbacks, config?: MobileInputsConfig): MobileInputs; /** * GamepadInputs - Gamepad/Controller input management * Handles gamepad events with support for multiple controllers, buttons, axes, and vibration */ /** * Gamepad button state with press detection */ interface GamepadButtonState { pressed: boolean; justPressed: boolean; justReleased: boolean; value: number; touched: boolean; } /** * Complete state of a single gamepad */ interface GamepadState { id: string; index: number; connected: boolean; buttons: GamepadButtonState[]; axes: number[]; timestamp: number; mapping: GamepadMappingType; hapticActuators?: GamepadHapticActuator[]; } /** * Callbacks for gamepad events */ interface GamepadCallbacks { onGamepadConnected?: (gamepad: GamepadState) => void; onGamepadDisconnected?: (index: number) => void; onButtonDown?: (gamepadIndex: number, buttonIndex: number, value: number) => void; onButtonUp?: (gamepadIndex: number, buttonIndex: number) => void; onAxisMove?: (gamepadIndex: number, axisIndex: number, value: number) => void; } /** * Configuration for GamepadInputs */ interface GamepadInputsConfig extends InputConfig { /** Maximum number of gamepads to support (default: 4) */ maxGamepads?: number; /** Deadzone for analog axes (default: 0.1) */ axisDeadzone?: number; /** Polling interval in milliseconds (default: 16 ~= 60fps) */ pollingInterval?: number; /** Enable automatic polling (default: true) */ autoPolling?: boolean; } declare class GamepadInputs extends BaseInputs { private gamepads; private previousGamepads; private gamepadCallbacks; private gamepadConfig; private pollingIntervalId; private rafId; private boundHandlers; constructor(config?: GamepadInputsConfig); start(): void; stop(): void; reset(): void; resetDelta(): void; /** * Start automatic polling of gamepad state */ startPolling(): void; /** * Stop automatic polling */ stopPolling(): void; /** * Manually poll gamepad state (call this in your game loop if autoPolling is false) * * @example * ```typescript * const gamepad = new GamepadInputs({ autoPolling: false }); * gamepad.start(); * * function gameLoop() { * gamepad.poll(); // Manual polling * // Check inputs... * requestAnimationFrame(gameLoop); * } * ``` */ poll(): void; /** * Get the number of connected gamepads * * @returns Number of gamepads currently connected (0-4) * * @example * ```typescript * const count = gamepad.getConnectedGamepadCount(); * console.log(`${count} gamepads connected`); * ``` */ getConnectedGamepadCount(): number; /** * Check if a gamepad is connected at the given index * * @param index - Gamepad index (0-3) * @returns True if gamepad is connected, false otherwise * * @example * ```typescript * if (gamepad.isGamepadConnected(0)) { * console.log('Player 1 controller ready!'); * } * ``` */ isGamepadConnected(index: number): boolean; /** * Get the state of a specific gamepad */ getGamepadState(index: number): GamepadState | null; /** * Get all connected gamepads */ getAllGamepads(): GamepadState[]; /** * Check if a button is currently pressed * * @param gamepadIndex - Gamepad index (0-3) * @param buttonIndex - Button index (0-16, standard mapping) * @returns True if button is pressed, false otherwise * @throws {Error} If gamepadIndex or buttonIndex is invalid * * @example * ```typescript * // Check A button (Xbox) / Cross (PlayStation) * if (gamepad.isButtonPressed(0, 0)) { * console.log('Action button pressed!'); * } * ``` */ isButtonPressed(gamepadIndex: number, buttonIndex: number): boolean; /** * Check if a button was just pressed this frame */ isButtonJustPressed(gamepadIndex: number, buttonIndex: number): boolean; /** * Check if a button was just released this frame */ isButtonJustReleased(gamepadIndex: number, buttonIndex: number): boolean; /** * Get the analog value of a button (0.0 to 1.0) */ getButtonValue(gamepadIndex: number, buttonIndex: number): number; /** * Get the value of an axis (-1.0 to 1.0, with deadzone applied) * * @param gamepadIndex - Gamepad index (0-3) * @param axisIndex - Axis index (0: left X, 1: left Y, 2: right X, 3: right Y) * @returns Axis value from -1.0 (left/up) to 1.0 (right/down) * @throws {Error} If gamepadIndex or axisIndex is invalid * * @example * ```typescript * const leftStickX = gamepad.getAxis(0, 0); * const leftStickY = gamepad.getAxis(0, 1); * * playerX += leftStickX * speed; * playerY += leftStickY * speed; * ``` */ getAxis(gamepadIndex: number, axisIndex: number): number; /** * Get the raw axis value without deadzone */ getAxisRaw(gamepadIndex: number, axisIndex: number): number; /** * Get left stick as a 2D vector (x: axis 0, y: axis 1) * * @param gamepadIndex - Gamepad index (0-3) * @returns Object with x and y properties (-1.0 to 1.0) * * @example * ```typescript * const leftStick = gamepad.getLeftStick(0); * * if (Math.abs(leftStick.x) > 0.5) { * console.log('Strong horizontal input'); * } * * // Calculate magnitude for movement * const magnitude = Math.sqrt(leftStick.x ** 2 + leftStick.y ** 2); * ``` */ getLeftStick(gamepadIndex: number): { x: number; y: number; }; /** * Get right stick as a 2D vector (x: axis 2, y: axis 3) */ getRightStick(gamepadIndex: number): { x: number; y: number; }; /** * Set gamepad-specific callbacks * * @param callbacks - Object containing callback functions * * @example * ```typescript * gamepad.setGamepadCallbacks({ * onGamepadConnected: (state) => { * console.log(`Gamepad connected: ${state.id}`); * }, * onButtonDown: (gamepadIndex, buttonIndex, value) => { * console.log(`Button ${buttonIndex} pressed with value ${value}`); * }, * onAxisMove: (gamepadIndex, axisIndex, value) => { * console.log(`Axis ${axisIndex} moved to ${value}`); * }, * }); * ``` */ setGamepadCallbacks(callbacks: GamepadCallbacks): void; /** * Clear all gamepad callbacks */ clearGamepadCallbacks(): void; setCallbacks(callbacks: GamepadCallbacks | unknown): void; private isGamepadCallbacks; private updateGamepadState; private triggerCallbacks; private handleGamepadConnected; private handleGamepadDisconnected; private applyDeadzone; private cloneGamepadState; } /** * React hook for gamepad inputs */ declare function useGamepadInputs(callbacks?: GamepadCallbacks, config?: GamepadInputsConfig): GamepadInputs; /** * UnifiedInputRouter - Unified input routing system * Provides a standardized API to query input states across all devices using numeric enums * * @example * ```ts * const router = new UnifiedInputRouter(); * * // Check if space key is pressed * const jump = router.getButton(InputDeviceType.Keyboard, KeyboardInput.Space); * * // Get mouse X position * const mouseX = router.getAxis(InputDeviceType.Mouse, MouseInput.PositionX); * * // Check gamepad button A * const action = router.getButton(InputDeviceType.Gamepad, GamepadInput.ButtonA); * ``` */ /** * Configuration options for the input router */ interface InputRouterConfig { enableKeyboardMouse?: boolean; enableGamepad?: boolean; enableMobile?: boolean; targetElement?: HTMLElement | Window; /** Separate target for mobile/touch input (default: uses targetElement) */ mobileTargetElement?: HTMLElement | Window; debug?: boolean; /** Keyboard-specific config */ keyboardConfig?: { preventDefault?: boolean; stopPropagation?: boolean; }; /** Mouse-specific config */ mouseConfig?: { preventDefault?: boolean; stopPropagation?: boolean; }; /** Mobile-specific config */ mobileConfig?: { preventDefault?: boolean; passive?: boolean; maxTouches?: number; }; } /** * Unified input router - manages all input devices with a type-safe enum-based API * Implements IInputSystem interface for dependency injection with core */ declare class UnifiedInputRouter implements IInputSystem { private keyboard; private mouse; private gamepad; private mobile; private config; constructor(config?: InputRouterConfig); private initialize; /** * Query a button state (returns boolean) */ getButton(device: InputDeviceType, input: number): boolean; /** * Query if a button was just pressed this frame (transition false→true) */ getButtonJustPressed(device: InputDeviceType, input: number): boolean; /** * Query if a button was just released this frame (transition true→false) */ getButtonJustReleased(device: InputDeviceType, input: number): boolean; /** * Query an axis value (returns number) */ getAxis(device: InputDeviceType, input: number): number; private getKeyboardButton; private getKeyboardButtonJustPressed; private getKeyboardButtonJustReleased; private getMouseButton; private getMouseButtonJustPressed; private getMouseButtonJustReleased; private getGamepadButton; private getGamepadButtonJustPressed; private getGamepadButtonJustReleased; private getTouchButton; private getTouchButtonJustPressed; private getTouchButtonJustReleased; private getMouseAxis; private getGamepadAxis; private getTouchAxis; /** * Get mouse position (screen coordinates) */ getMousePosition(): { x: number; y: number; } | null; /** * Reconfigure mobile input target after initialization * (Used when canvas is created after InputRouter construction) * Mobile input will ONLY be activated if a valid canvas is provided */ setMobileTarget(canvas: HTMLElement | Window): void; /** * Get text inputs captured this frame from keyboard * Returns actual characters typed (handles keyboard layout, modifiers, etc.) * * @returns Array of character strings typed this frame */ getTextInputs(): string[]; start(): void; stop(): void; reset(): void; /** * Poll transient states - Reset justPressed/justReleased flags * Should be called once per frame AFTER collecting input */ poll(): void; isListening(): boolean; destroy(): void; private log; private warn; } /** * InputCollector - Adapter between UnifiedInputRouter and InputBindingRegistry * * This module bridges the gap between: * - @utsp/input (raw device inputs) * - @utsp/core (logical input evaluation with InputBindingRegistry) * * InputCollector's sole responsibility is to read raw values from IInputSystem * and prepare them as Map<sourceId, value> for InputBindingRegistry.evaluateAxis/Button. * * @example * ```typescript * // Collect raw source values * const axisSources = InputCollector.collectAxisSources(axisBindings, inputRouter); * const buttonSources = InputCollector.collectButtonSources(buttonBindings, inputRouter); * * // Let InputBindingRegistry handle scale/sensitivity/invert/OR logic * axisBindings.forEach(binding => { * const value = registry.evaluateAxis(binding.bindingId, axisSources); * user.setAxis(binding.name, value); * }); * ``` */ /** * Button state with transition detection (pressed, justPressed, justReleased) */ interface ButtonStateWithTransitions { pressed: boolean; justPressed: boolean; justReleased: boolean; } /** * Input collection utilities * * Provides static methods to collect raw input values from an IInputSystem * and prepare them for InputBindingRegistry evaluation. */ declare class InputCollector { /** * Collects raw source values for all axis bindings * * Returns a Map where: * - Key: sourceId (from AxisBinding.sources) * - Value: raw axis value from the input router * * This method reads raw device values (keyboard -1/0/+1, mouse delta, gamepad stick, touch position) * WITHOUT applying scale/sensitivity/invert. InputBindingRegistry.evaluateAxis() will handle that. * * @param bindings - Axis bindings from InputBindingRegistry * @param router - Input system (typically UnifiedInputRouter) * @returns Map of sourceId → raw value * * @example * ```typescript * const registry = user.getInputBindingRegistry(); * const axisBindings = registry.getAllAxes(); * const sourceValues = InputCollector.collectAxisSources(axisBindings, inputRouter); * * // Then use InputBindingRegistry to evaluate with scale/sensitivity/invert: * axisBindings.forEach(binding => { * const finalValue = registry.evaluateAxis(binding.bindingId, sourceValues); * user.setAxis(binding.name, finalValue); * }); * ``` */ static collectAxisSources(bindings: AxisBinding[], router: IInputSystem): Map<number, number>; /** * Collects raw source states for all button bindings * * Returns a Map where: * - Key: sourceId (from ButtonBinding.sources) * - Value: raw button state (pressed/released) from the input router * * This method reads raw device button states WITHOUT applying OR logic. * InputBindingRegistry.evaluateButton() will handle the OR logic across all sources. * * @param bindings - Button bindings from InputBindingRegistry * @param router - Input system (typically UnifiedInputRouter) * @returns Map of sourceId → pressed state * * @example * ```typescript * const registry = user.getInputBindingRegistry(); * const buttonBindings = registry.getAllButtons(); * const sourceStates = InputCollector.collectButtonSources(buttonBindings, inputRouter); * * // Then use InputBindingRegistry to evaluate with OR logic: * buttonBindings.forEach(binding => { * const pressed = registry.evaluateButton(binding.bindingId, sourceStates); * user.setButton(binding.name, pressed); * }); * ``` */ static collectButtonSources(bindings: ButtonBinding[], router: IInputSystem): Map<number, boolean>; /** * Collects raw source states with transition detection for all button bindings * * Returns a Map where: * - Key: sourceId (from ButtonBinding.sources) * - Value: ButtonStateWithTransitions { pressed, justPressed, justReleased } * * This method reads raw device button states including transition flags. * Use this to collect justPressed/justReleased states in addition to pressed. * * @param bindings - Button bindings from InputBindingRegistry * @param router - Input system (typically UnifiedInputRouter with getButtonJustPressed/Released) * @returns Map of sourceId → ButtonStateWithTransitions * * @example * ```typescript * const registry = user.getInputBindingRegistry(); * const buttonBindings = registry.getAllButtons(); * const sourceStates = InputCollector.collectButtonSourcesWithTransitions(buttonBindings, inputRouter); * * buttonBindings.forEach(binding => { * // Evaluate each state independently (OR across sources) * const pressed = registry.evaluateButton(binding.bindingId, new Map([...sourceStates].map(([k,v]) => [k, v.pressed]))); * const justPressed = registry.evaluateButton(binding.bindingId, new Map([...sourceStates].map(([k,v]) => [k, v.justPressed]))); * const justReleased = registry.evaluateButton(binding.bindingId, new Map([...sourceStates].map(([k,v]) => [k, v.justReleased]))); * * user.setButton(binding.name, pressed); * user.setButton(\`\${binding.name}_justPressed\`, justPressed); * user.setButton(\`\${binding.name}_justReleased\`, justReleased); * }); * ``` */ static collectButtonSourcesWithTransitions(bindings: ButtonBinding[], router: IInputSystem): Map<number, ButtonStateWithTransitions>; /** * Calculate mouse position in grid coordinates * * Converts screen-space mouse position to grid cell coordinates (column/row). * Handles canvas scaling and provides an "over" flag for out-of-bounds detection. * * @param router - Input system (typically UnifiedInputRouter) * @param canvas - Canvas element to calculate relative position * @param gridWidth - Number of columns in the grid * @param gridHeight - Number of rows in the grid * @param rendererOffsets - Optional renderer content offsets (for centered rendering) * @returns Grid position {x: column, y: row, over: boolean} * * @example * ```typescript * const offsets = renderer.getOffsets?.() || { offsetX: 0, offsetY: 0 }; * const mousePos = InputCollector.collectMousePosition( * inputRouter, * renderer.getCanvas(), * 80, // columns * 24, // rows * offsets * ); * user.setMousePosition(mousePos.x, mousePos.y, mousePos.over, 0); * ``` */ static collectMousePosition(router: IInputSystem, canvas: HTMLCanvasElement, gridWidth: number, gridHeight: number, rendererOffsets?: { offsetX: number; offsetY: number; }): { x: number; y: number; over: boolean; }; /** * Calculate touch positions in grid coordinates * * Converts screen-space touch positions to grid cell coordinates for all active touches. * Handles canvas scaling and provides an "over" flag for out-of-bounds detection. * * @param router - Input system (typically UnifiedInputRouter) * @param canvas - Canvas element to calculate relative position * @param gridWidth - Number of columns in the grid * @param gridHeight - Number of rows in the grid * @param maxTouches - Maximum number of touches to check (default: 10) * @param rendererOffsets - Optional renderer content offsets (for centered rendering) * @returns Array of touch positions {id, x: column, y: row, over: boolean} * * @example * ```typescript * const offsets = renderer.getOffsets?.() || { offsetX: 0, offsetY: 0 }; * const touches = InputCollector.collectTouchPositions( * inputRouter, * renderer.getCanvas(), * 80, // columns * 24, // rows * 10, // max touches * offsets * ); * touches.forEach(touch => { * user.setTouchPosition(touch.id, touch.x, touch.y, touch.over, 0); * }); * ``` */ static collectTouchPositions(router: IInputSystem, canvas: HTMLCanvasElement, gridWidth: number, gridHeight: number, maxTouches?: number, rendererOffsets?: { offsetX: number; offsetY: number; }): Array<{ id: number; x: number; y: number; over: boolean; }>; /** * Collects text input events from keyboard * * Returns array of character strings typed this frame. * Uses event.key to capture actual characters (handles keyboard layout automatically). * * @param router - Input system (typically UnifiedInputRouter) * @returns Array of character strings typed this frame * * @example * ```typescript * const textInputs = InputCollector.collectTextInputs(inputRouter); * // textInputs might be: ['a', 'b', 'c', 'é', '@', ' '] * // Pass to network encoder or process locally * ``` */ static collectTextInputs(router: IInputSystem): string[]; } export { BaseInputs, GamepadInputs, InputCollector, InputEventType, KeyboardInputs, MobileInputs, MouseInputs, UnifiedInputRouter, isInputs, useGamepadInputs, useKeyboardInputs, useMobileInputs, useMouseInputs }; export type { ButtonState, GamepadButtonState, GamepadCallbacks, GamepadInputsConfig, GamepadState, IInputs, InputConfig, InputDevice, InputEvent, InputRouterConfig, KeyState, KeyboardCallbacks, MobileCallbacks, MobileInputsConfig, MotionData, MouseButtonState, MouseButtons, MouseCallbacks, TouchPoint };