reaction 0.2.0

Universal low-latency input handling for game engines
Documentation
use crate::types::{GamepadAxis, GamepadButton, KeyCode, MouseButton};
#[cfg(feature = "config-files")]
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config-files", derive(Serialize, Deserialize))]
pub enum InputBinding {
    Key(KeyCode),
    MouseButton(MouseButton),
    GamepadButton(GamepadButton),
    GamepadAxis(GamepadAxis),
}

#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config-files", derive(Serialize, Deserialize))]
pub struct BindingConfig {
    pub input: InputBinding,

    #[cfg_attr(feature = "config-files", serde(default))]
    pub deadzone: f32, // For axes

    #[cfg_attr(feature = "config-files", serde(default = "default_scale"))]
    pub scale: f32, // Multiplier (e.g., -1.0 for inverted)

                    // Phase 6 will add modifiers (Shift/Ctrl)
}

#[allow(dead_code)]
fn default_scale() -> f32 {
    1.0
}

impl BindingConfig {
    pub fn new(input: InputBinding) -> Self {
        Self {
            input,
            deadzone: 0.1, // 10% is a safe default for most controllers.
            scale: 1.0,
        }
    }

    /// Set deadzone for this binding. Must be in the [0.0, 1.0] range.
    pub fn with_deadzone(mut self, deadzone: f32) -> crate::error::Result<Self> {
        if !deadzone.is_finite() || !(0.0..=1.0).contains(&deadzone) {
            return Err(crate::error::ReactionError::InvalidConfiguration(format!(
                "Deadzone {deadzone} is out of bounds. Must be between 0.0 and 1.0."
            )));
        }
        self.deadzone = deadzone;
        Ok(self)
    }

    /// Set the scale (multiplier) for the input value.
    pub fn with_scale(mut self, scale: f32) -> crate::error::Result<Self> {
        if !scale.is_finite() || scale == 0.0 {
            // A scale of 0.0 would effectively disable the binding, which is confusing.
            // Better to just not bind it or use a separate disabling mechanism.
            return Err(crate::error::ReactionError::InvalidConfiguration(format!(
                "Invalid scale: {scale}. Must be a non-zero finite number."
            )));
        }
        self.scale = scale;
        Ok(self)
    }
}