reaction 0.2.0

Universal low-latency input handling for game engines
Documentation
use crate::state::gamepad::{GamepadState, GamepadsState};
use crate::state::keyboard::KeyboardState;
use crate::state::mouse::MouseState;
use crate::types::*;

/// Main input state container tracking keyboard, mouse, and gamepad input.
///
/// Frame Lifecycle:
/// 1. ACCUMULATION: Raw input polling updates state (press, move, etc.)
/// 2. USAGE: Game logic reads current state
/// 3. RESET: end_frame() clears transient state (delta, scroll, edge flags)
///
/// # Example
/// ```ignore
/// let mut input = AdvancedInputState::new();
///
/// // Simulate input
/// input.keyboard.press(KeyCode::KeyA, 0.0, 0);
/// input.mouse.move_to(100.0, 50.0);
///
/// // Use input
/// if input.is_key_down(KeyCode::KeyA) {
///     println!("A is pressed");
/// }
/// let (dx, dy) = input.mouse.delta;
///
/// // Frame end
/// input.end_frame(16.67);
/// ```
#[derive(Default)]
pub struct AdvancedInputState {
    pub keyboard: KeyboardState,
    pub mouse: MouseState,
    pub gamepads: GamepadsState,
    /// Current frame number (incremented each end_frame)
    pub frame: u32,
    /// Total elapsed time in seconds
    pub time: f64,
}

impl AdvancedInputState {
    /// Create new input state with all subsystems initialized.
    pub fn new() -> Self {
        Self {
            keyboard: KeyboardState::new(),
            mouse: MouseState::new(),
            gamepads: GamepadsState::new(),
            frame: 0,
            time: 0.0,
        }
    }

    /// Called at end of each game frame to reset transient state.
    ///
    /// # Arguments
    /// * `delta_time_ms` - Milliseconds elapsed this frame
    ///
    /// # Invariants
    /// - Clears mouse delta and scroll (they've been consumed)
    /// - Clears keyboard/gamepad just_pressed/released flags
    /// - Accumulates held_duration for pressed keys
    /// - Increments frame counter
    ///
    /// ⚠️ CRITICAL: end_frame() MUST be called AFTER all input polling
    pub fn end_frame(&mut self, delta_time_ms: f32) {
        self.frame += 1;
        self.time += delta_time_ms as f64;
        self.keyboard.end_frame(delta_time_ms);
        self.mouse.end_frame(delta_time_ms);
        self.gamepads.end_frame();
    }

    // === KEYBOARD ACCESSORS ===
    /// Returns true if key is currently held.
    pub fn is_key_down(&self, key: KeyCode) -> bool {
        self.keyboard.is_down(key)
    }

    /// Returns true only in the frame key was pressed.
    pub fn was_key_just_pressed(&self, key: KeyCode) -> bool {
        self.keyboard.was_just_pressed(key)
    }

    /// Returns true only in the frame key was released.
    pub fn was_key_just_released(&self, key: KeyCode) -> bool {
        self.keyboard.was_just_released(key)
    }

    /// Get how long a key has been held (in milliseconds).
    pub fn get_key_held_duration(&self, key: KeyCode) -> f32 {
        self.keyboard.get_held_duration(key)
    }

    // === MOUSE ACCESSORS ===
    /// Get current mouse position (absolute, in screen coords).
    pub fn get_mouse_position(&self) -> (f32, f32) {
        self.mouse.position
    }

    /// Get mouse movement this frame (accumulated from all polls).
    /// Reset to (0, 0) after each end_frame().
    pub fn get_mouse_delta(&self) -> (f32, f32) {
        self.mouse.delta
    }

    /// Get mouse scroll input this frame (accumulated).
    /// Reset to (0, 0) after each end_frame().
    pub fn get_mouse_scroll(&self) -> (f32, f32) {
        self.mouse.scroll
    }

    /// Returns true if mouse button is currently held.
    pub fn is_mouse_button_down(&self, button: MouseButton) -> bool {
        self.mouse.is_button_down(button)
    }

    /// Returns true only in the frame mouse button was pressed.
    pub fn was_mouse_button_just_pressed(&self, button: MouseButton) -> bool {
        self.mouse.was_button_just_pressed(button)
    }

    /// Returns true only in the frame mouse button was released.
    pub fn was_mouse_button_just_released(&self, button: MouseButton) -> bool {
        self.mouse.was_button_just_released(button)
    }

    // === GAMEPAD ACCESSORS ===
    /// Get gamepad state if connected.
    pub fn get_gamepad(&self, id: GamepadId) -> Option<&GamepadState> {
        self.gamepads.get(id)
    }

    /// Check if gamepad is connected.
    pub fn is_gamepad_connected(&self, id: GamepadId) -> bool {
        self.gamepads
            .get(id)
            .map(|gp| gp.connected)
            .unwrap_or(false)
    }
}