tengri/Content/Input/Components/AC_InputDevice.ts

182 lines
6.3 KiB
TypeScript

// Input/Components/AC_InputDevice.ts
import type { AC_ToastSystem } from '#root/Toasts/Components/AC_ToastSystem.ts';
import { ActorComponent } from '#root/UE/ActorComponent.ts';
import { EHardwareDevicePrimaryType } from '#root/UE/EHardwareDevicePrimaryType.ts';
import type { Float } from '#root/UE/Float.ts';
import { InputDeviceSubsystem } from '#root/UE/InputDeviceSubsystem.ts';
import type { Integer } from '#root/UE/Integer.ts';
import { SystemLibrary } from '#root/UE/SystemLibrary.ts';
import { E_MessageType } from '#root/UI/Enums/E_MessageType.ts';
/**
* Input Device Detection Component
* Minimal wrapper around Unreal Engine's native InputDeviceSubsystem
* Provides simple binary classification: Gamepad vs Everything Else
*/
export class AC_InputDevice extends ActorComponent {
// ════════════════════════════════════════════════════════════════════════════════════════
// FUNCTIONS
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Get current active input device type
* @returns Current device type (cached from delegate events)
* @category Device Queries
* @pure true
*/
public GetCurrentInputDevice(): EHardwareDevicePrimaryType {
return this.CurrentDevice;
}
/**
* Check if current device is keyboard/mouse
* @returns True if keyboard is active
* @category Device Queries
* @pure true
*/
public IsKeyboard(): boolean {
return this.CurrentDevice === EHardwareDevicePrimaryType.KeyboardAndMouse;
}
/**
* Check if current device is gamepad/controller
* @returns True if gamepad is active
* @category Device Queries
* @pure true
*/
public IsGamepad(): boolean {
return this.CurrentDevice === EHardwareDevicePrimaryType.Gamepad;
}
/**
* Initialize device detection system with delegate registration
* @param ToastComponentRef - Toast system for debug notifications
* @category System Setup
*/
public InitializeDeviceDetection(ToastComponentRef: AC_ToastSystem): void {
this.ToastComponent = ToastComponentRef;
this.RegisterHardwareDeviceDelegate();
this.DetectInitialDevice();
this.IsInitialized = true;
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast(
`Device Detection Initialized: ${this.CurrentDevice}`,
E_MessageType.Success
);
}
}
/**
* Register OnInputHardwareDeviceChanged delegate
* Core of the event-driven approach
* @category System Setup
*/
private RegisterHardwareDeviceDelegate(): void {
InputDeviceSubsystem.OnInputHardwareDeviceChanged.BindEvent(
this.OnInputHardwareDeviceChanged.bind(this)
);
}
/**
* Detect initial device on system startup
* Fallback for getting device before first hardware change event
* @category System Setup
*/
private DetectInitialDevice(): void {
this.CurrentDevice =
InputDeviceSubsystem.GetMostRecentlyUsedHardwareDevice().PrimaryDeviceType;
}
/**
* Handle hardware device change events
* Called automatically when input hardware changes
* @param UserId - User ID (usually 0 for single-player)
* @param DeviceId - Device name string
* @category Event Handling
*/
private OnInputHardwareDeviceChanged(
UserId?: Integer,
DeviceId?: Integer
): void {
this.ProcessDeviceChange(
InputDeviceSubsystem.GetInputDeviceHardwareIdentifier(DeviceId ?? 0)
.PrimaryDeviceType
);
}
/**
* Process device change with debouncing
* Prevents rapid switching and manages device state
* @param NewDevice - Newly detected device type
* @category Device Management
*/
private ProcessDeviceChange(NewDevice: EHardwareDevicePrimaryType): void {
if (NewDevice !== this.CurrentDevice && this.CanProcessDeviceChange()) {
this.CurrentDevice = NewDevice;
this.LastDeviceChangeTime = SystemLibrary.GetGameTimeInSeconds();
if (SystemLibrary.IsValid(this.ToastComponent)) {
this.ToastComponent.ShowToast(
`Input switched to ${NewDevice}`,
E_MessageType.Info
);
}
}
}
/**
* Check if device change can be processed (debouncing)
* Prevents rapid device switching within cooldown period
* @returns True if enough time has passed since last change
* @category Debouncing
* @pure true
*/
private CanProcessDeviceChange(): boolean {
const HasCooldownExpired = (): boolean =>
SystemLibrary.GetGameTimeInSeconds() - this.LastDeviceChangeTime >=
this.DeviceChangeCooldown;
return HasCooldownExpired();
}
// ════════════════════════════════════════════════════════════════════════════════════════
// VARIABLES
// ════════════════════════════════════════════════════════════════════════════════════════
/**
* Reference to toast system for debug notifications
* @category Components
*/
private ToastComponent: AC_ToastSystem | null = null;
/**
* Current active input device type
* Updated by hardware device change events
* @category Device State
*/
private CurrentDevice: EHardwareDevicePrimaryType =
EHardwareDevicePrimaryType.Unspecified;
/**
* System initialization status
* @category System State
*/
public IsInitialized: boolean = false;
/**
* Last device change timestamp for debouncing
* @category Debouncing
*/
public LastDeviceChangeTime: Float = 0;
/**
* Minimum time between device changes (prevents flickering)
* Recommended: 300-500ms for most games
* @category Debouncing
* @instanceEditable true
*/
private DeviceChangeCooldown: Float = 0.3;
}