reaction 0.2.0

Universal low-latency input handling for game engines
Documentation
use crate::types::{GamepadAxis, GamepadButton, GamepadId};
use std::collections::HashMap;

#[derive(Debug, Clone, Default)]
pub struct GamepadButtonState {
    pub is_down: bool,
    pub just_pressed_frame: Option<u32>,
    pub just_released_frame: Option<u32>,
    pub value: f32, // For pressure sensitive buttons
}

#[derive(Debug, Clone, Default)]
pub struct GamepadAxisState {
    pub value: f32,
    pub threshold: f32,
}

#[derive(Debug, Clone)]
pub struct GamepadState {
    pub id: GamepadId,
    pub connected: bool,
    pub buttons: HashMap<GamepadButton, GamepadButtonState>,
    pub axes: HashMap<GamepadAxis, GamepadAxisState>,
    pub deadzone: f32,
}

impl GamepadState {
    pub fn new(id: GamepadId) -> Self {
        Self {
            id,
            connected: false,
            buttons: HashMap::new(),
            axes: HashMap::new(),
            deadzone: 0.1, // Default deadzone
        }
    }

    pub fn set_button(&mut self, button: GamepadButton, pressed: bool, value: f32, frame: u32) {
        let state = self.buttons.entry(button).or_default();
        if pressed && !state.is_down {
            state.just_pressed_frame = Some(frame);
        } else if !pressed && state.is_down {
            state.just_released_frame = Some(frame);
        }
        state.is_down = pressed;
        state.value = value;
    }

    pub fn set_axis(&mut self, axis: GamepadAxis, value: f32) {
        // Apply deadzone logic simply here, can be more advanced later
        let filtered_value = if value.abs() < self.deadzone {
            0.0
        } else {
            value
        };

        let state = self.axes.entry(axis).or_default();
        state.value = filtered_value;
    }

    pub fn end_frame(&mut self) {
        for state in self.buttons.values_mut() {
            state.just_pressed_frame = None;
            state.just_released_frame = None;
        }
    }

    pub fn is_button_down(&self, button: GamepadButton) -> bool {
        self.buttons
            .get(&button)
            .map(|b| b.is_down)
            .unwrap_or(false)
    }

    pub fn get_axis(&self, axis: GamepadAxis) -> f32 {
        self.axes.get(&axis).map(|a| a.value).unwrap_or(0.0)
    }
}

pub struct GamepadsState {
    gamepads: HashMap<GamepadId, GamepadState>,
}

impl Default for GamepadsState {
    fn default() -> Self {
        Self::new()
    }
}

impl GamepadsState {
    pub fn new() -> Self {
        Self {
            gamepads: HashMap::new(),
        }
    }

    pub fn get(&self, id: GamepadId) -> Option<&GamepadState> {
        self.gamepads.get(&id)
    }

    pub fn get_mut(&mut self, id: GamepadId) -> &mut GamepadState {
        self.gamepads
            .entry(id)
            .or_insert_with(|| GamepadState::new(id))
    }

    pub fn end_frame(&mut self) {
        for gamepad in self.gamepads.values_mut() {
            gamepad.end_frame();
        }
    }
}