use std::collections::HashMap;
use bevy_ecs::prelude::*;
use crate::input_state::{InputState, KeyCode, MouseButton};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InputBinding {
Key(KeyCode),
Mouse(MouseButton),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActionState {
Inactive,
JustPressed,
Pressed,
JustReleased,
}
impl ActionState {
pub fn is_active(&self) -> bool {
matches!(self, ActionState::JustPressed | ActionState::Pressed)
}
pub fn is_just_pressed(&self) -> bool {
matches!(self, ActionState::JustPressed)
}
pub fn is_just_released(&self) -> bool {
matches!(self, ActionState::JustReleased)
}
}
#[derive(Resource)]
pub struct ActionMap {
bindings: HashMap<String, Vec<InputBinding>>,
states: HashMap<String, ActionState>,
}
impl ActionMap {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
states: HashMap::new(),
}
}
pub fn add_binding(&mut self, action: &str, binding: InputBinding) {
self.bindings
.entry(action.to_string())
.or_default()
.push(binding);
self.states
.entry(action.to_string())
.or_insert(ActionState::Inactive);
}
pub fn update(&mut self, input: &InputState) {
for (action, bindings) in &self.bindings {
let any_active = bindings.iter().any(|b| match b {
InputBinding::Key(k) => input.is_key_pressed(*k),
InputBinding::Mouse(m) => input.is_mouse_pressed(*m),
});
let any_just_pressed = bindings.iter().any(|b| match b {
InputBinding::Key(k) => input.is_key_just_pressed(*k),
InputBinding::Mouse(m) => input.is_mouse_just_pressed(*m),
});
let any_just_released = bindings.iter().any(|b| match b {
InputBinding::Key(k) => input.is_key_just_released(*k),
InputBinding::Mouse(m) => input.is_mouse_just_released(*m),
});
let state = if any_just_pressed {
ActionState::JustPressed
} else if any_active {
ActionState::Pressed
} else if any_just_released {
ActionState::JustReleased
} else {
ActionState::Inactive
};
self.states.insert(action.clone(), state);
}
}
pub fn action_state(&self, action: &str) -> ActionState {
self.states.get(action).copied().unwrap_or(ActionState::Inactive)
}
pub fn is_action_active(&self, action: &str) -> bool {
self.action_state(action).is_active()
}
pub fn is_action_just_pressed(&self, action: &str) -> bool {
self.action_state(action).is_just_pressed()
}
pub fn is_action_just_released(&self, action: &str) -> bool {
self.action_state(action).is_just_released()
}
pub fn get_bindings(&self, action: &str) -> Option<&[InputBinding]> {
self.bindings.get(action).map(|v| v.as_slice())
}
pub fn clear_bindings(&mut self, action: &str) {
self.bindings.remove(action);
self.states.remove(action);
}
}
impl Default for ActionMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_action_state() {
assert!(ActionState::Pressed.is_active());
assert!(ActionState::JustPressed.is_active());
assert!(!ActionState::Inactive.is_active());
assert!(!ActionState::JustReleased.is_active());
}
#[test]
fn test_action_map_basic() {
let mut map = ActionMap::new();
map.add_binding("jump", InputBinding::Key(KeyCode::Space));
let mut input = InputState::new();
input.press_key(KeyCode::Space);
map.update(&input);
assert!(map.is_action_active("jump"));
assert!(map.is_action_just_pressed("jump"));
input.end_frame();
map.update(&input);
assert!(map.is_action_active("jump"));
assert!(!map.is_action_just_pressed("jump"));
input.release_key(KeyCode::Space);
map.update(&input);
assert!(!map.is_action_active("jump"));
assert!(map.is_action_just_released("jump"));
}
#[test]
fn test_multiple_bindings() {
let mut map = ActionMap::new();
map.add_binding("fire", InputBinding::Key(KeyCode::Space));
map.add_binding("fire", InputBinding::Mouse(MouseButton::Left));
let mut input = InputState::new();
input.press_mouse(MouseButton::Left);
map.update(&input);
assert!(map.is_action_active("fire"));
}
#[test]
fn test_unknown_action() {
let map = ActionMap::new();
assert_eq!(map.action_state("nonexistent"), ActionState::Inactive);
assert!(!map.is_action_active("nonexistent"));
}
}