use crate::input::{ButtonStates, PadState};
use crate::{Gamepad, GamepadAxis, GamepadButton, GamepadStick, Key, MAX_GAMEPADS, MouseButton};
use glam::Vec2;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ButtonBinding {
Key(Key),
Mouse(MouseButton),
Gamepad {
pad: Gamepad,
button: GamepadButton,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AxisBinding {
Keys {
negative: Key,
positive: Key,
},
Gamepad {
pad: Gamepad,
axis: GamepadAxis,
deadzone: f32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Axis2Binding {
Keys {
left: Key,
right: Key,
up: Key,
down: Key,
},
Gamepad {
pad: Gamepad,
stick: GamepadStick,
deadzone: f32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Action {
Button(Vec<ButtonBinding>),
Axis(Vec<AxisBinding>),
Axis2(Vec<Axis2Binding>),
}
#[derive(Debug, Clone, Default)]
struct State {
down: bool,
pressed: bool,
released: bool,
axis: f32,
axis2: Vec2,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ActionMap {
bindings: BTreeMap<String, Action>,
#[serde(skip)]
states: BTreeMap<String, State>,
}
impl ActionMap {
pub fn set(&mut self, name: impl Into<String>, action: Action) {
let name = name.into();
self.states.insert(name.clone(), State::default());
self.bindings.insert(name, action);
}
pub fn remove(&mut self, name: &str) {
self.bindings.remove(name);
self.states.remove(name);
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub(crate) fn prepare(&mut self) {
self.states = self.bindings.keys().map(|n| (n.clone(), State::default())).collect();
}
pub(crate) fn end_frame(&mut self) {
for s in self.states.values_mut() {
s.pressed = false;
s.released = false;
}
}
pub(crate) fn down(&self, name: &str) -> bool {
self.states.get(name).is_some_and(|s| s.down)
}
pub(crate) fn pressed(&self, name: &str) -> bool {
self.states.get(name).is_some_and(|s| s.pressed)
}
pub(crate) fn released(&self, name: &str) -> bool {
self.states.get(name).is_some_and(|s| s.released)
}
pub(crate) fn axis(&self, name: &str) -> f32 {
self.states.get(name).map_or(0.0, |s| s.axis)
}
pub(crate) fn axis2(&self, name: &str) -> Vec2 {
self.states.get(name).map_or(Vec2::ZERO, |s| s.axis2)
}
pub(crate) fn refresh(
&mut self,
keys: &ButtonStates<{ Key::COUNT }>,
mouse: &ButtonStates<{ MouseButton::COUNT }>,
pads: &[PadState; MAX_GAMEPADS],
) {
let key = |k: Key| f32::from(keys.down[k as usize]);
let pad_axis = |p: Gamepad, a: GamepadAxis| pads.get(p.slot()).map_or(0.0, |s| s.axes[a as usize]);
for (name, action) in &self.bindings {
let s = self.states.get_mut(name).expect("installed action state");
match action {
Action::Button(bindings) => {
let down = bindings.iter().any(|b| match *b {
ButtonBinding::Key(k) => keys.down[k as usize],
ButtonBinding::Mouse(b) => mouse.down[b as usize],
ButtonBinding::Gamepad { pad, button } => {
pads.get(pad.slot()).is_some_and(|s| s.buttons.down[button as usize])
}
});
s.pressed |= !s.down && down;
s.released |= s.down && !down;
s.down = down;
}
Action::Axis(bindings) => {
s.axis = 0.0;
for b in bindings {
let v = match *b {
AxisBinding::Keys { negative, positive } => key(positive) - key(negative),
AxisBinding::Gamepad { pad, axis, deadzone } => {
deadzone_value(pad_axis(pad, axis), deadzone)
}
};
if v.abs() > s.axis.abs() {
s.axis = v;
}
}
}
Action::Axis2(bindings) => {
s.axis2 = Vec2::ZERO;
for b in bindings {
let v = match *b {
Axis2Binding::Keys { left, right, up, down } => {
Vec2::new(key(right) - key(left), key(down) - key(up)).clamp_length_max(1.0)
}
Axis2Binding::Gamepad { pad, stick, deadzone } => {
let (x, y) = match stick {
GamepadStick::Left => (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY),
GamepadStick::Right => (GamepadAxis::RightStickX, GamepadAxis::RightStickY),
};
let v = Vec2::new(pad_axis(pad, x), pad_axis(pad, y));
let len = v.length();
if len.is_finite() && len > 0.0 {
v / len * deadzone_value(len, deadzone)
} else {
Vec2::ZERO
}
}
};
if v.length_squared() > s.axis2.length_squared() {
s.axis2 = v;
}
}
}
}
}
}
}
fn deadzone_value(value: f32, deadzone: f32) -> f32 {
if !value.is_finite() || !(0.0..1.0).contains(&deadzone) {
return 0.0;
}
value.signum() * ((value.abs().min(1.0) - deadzone).max(0.0) / (1.0 - deadzone))
}
#[derive(Debug, Clone, Copy)]
pub enum InputEvent {
Key(Key, bool),
Mouse(MouseButton, bool),
GamepadButton(Gamepad, GamepadButton, bool),
GamepadAxis(Gamepad, GamepadAxis, f32),
FocusLost,
GamepadDisconnected(Gamepad),
}