anvilkit_input/
action_map.rs1use std::collections::HashMap;
7use bevy_ecs::prelude::*;
8
9use crate::input_state::{InputState, KeyCode, MouseButton};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum InputBinding {
26 Key(KeyCode),
27 Mouse(MouseButton),
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ActionState {
42 Inactive,
44 JustPressed,
46 Pressed,
48 JustReleased,
50}
51
52impl ActionState {
53 pub fn is_active(&self) -> bool {
55 matches!(self, ActionState::JustPressed | ActionState::Pressed)
56 }
57
58 pub fn is_just_pressed(&self) -> bool {
60 matches!(self, ActionState::JustPressed)
61 }
62
63 pub fn is_just_released(&self) -> bool {
65 matches!(self, ActionState::JustReleased)
66 }
67}
68
69#[derive(Resource)]
92pub struct ActionMap {
93 bindings: HashMap<String, Vec<InputBinding>>,
95 states: HashMap<String, ActionState>,
97}
98
99impl ActionMap {
100 pub fn new() -> Self {
102 Self {
103 bindings: HashMap::new(),
104 states: HashMap::new(),
105 }
106 }
107
108 pub fn add_binding(&mut self, action: &str, binding: InputBinding) {
110 self.bindings
111 .entry(action.to_string())
112 .or_default()
113 .push(binding);
114 self.states
115 .entry(action.to_string())
116 .or_insert(ActionState::Inactive);
117 }
118
119 pub fn update(&mut self, input: &InputState) {
121 for (action, bindings) in &self.bindings {
122 let any_active = bindings.iter().any(|b| match b {
123 InputBinding::Key(k) => input.is_key_pressed(*k),
124 InputBinding::Mouse(m) => input.is_mouse_pressed(*m),
125 });
126 let any_just_pressed = bindings.iter().any(|b| match b {
127 InputBinding::Key(k) => input.is_key_just_pressed(*k),
128 InputBinding::Mouse(m) => input.is_mouse_just_pressed(*m),
129 });
130 let any_just_released = bindings.iter().any(|b| match b {
131 InputBinding::Key(k) => input.is_key_just_released(*k),
132 InputBinding::Mouse(m) => input.is_mouse_just_released(*m),
133 });
134
135 let state = if any_just_pressed {
136 ActionState::JustPressed
137 } else if any_active {
138 ActionState::Pressed
139 } else if any_just_released {
140 ActionState::JustReleased
141 } else {
142 ActionState::Inactive
143 };
144
145 self.states.insert(action.clone(), state);
146 }
147 }
148
149 pub fn action_state(&self, action: &str) -> ActionState {
151 self.states.get(action).copied().unwrap_or(ActionState::Inactive)
152 }
153
154 pub fn is_action_active(&self, action: &str) -> bool {
156 self.action_state(action).is_active()
157 }
158
159 pub fn is_action_just_pressed(&self, action: &str) -> bool {
161 self.action_state(action).is_just_pressed()
162 }
163
164 pub fn is_action_just_released(&self, action: &str) -> bool {
166 self.action_state(action).is_just_released()
167 }
168
169 pub fn get_bindings(&self, action: &str) -> Option<&[InputBinding]> {
171 self.bindings.get(action).map(|v| v.as_slice())
172 }
173
174 pub fn clear_bindings(&mut self, action: &str) {
176 self.bindings.remove(action);
177 self.states.remove(action);
178 }
179}
180
181impl Default for ActionMap {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_action_state() {
193 assert!(ActionState::Pressed.is_active());
194 assert!(ActionState::JustPressed.is_active());
195 assert!(!ActionState::Inactive.is_active());
196 assert!(!ActionState::JustReleased.is_active());
197 }
198
199 #[test]
200 fn test_action_map_basic() {
201 let mut map = ActionMap::new();
202 map.add_binding("jump", InputBinding::Key(KeyCode::Space));
203
204 let mut input = InputState::new();
205 input.press_key(KeyCode::Space);
206
207 map.update(&input);
208 assert!(map.is_action_active("jump"));
209 assert!(map.is_action_just_pressed("jump"));
210
211 input.end_frame();
212 map.update(&input);
213 assert!(map.is_action_active("jump"));
214 assert!(!map.is_action_just_pressed("jump"));
215
216 input.release_key(KeyCode::Space);
217 map.update(&input);
218 assert!(!map.is_action_active("jump"));
219 assert!(map.is_action_just_released("jump"));
220 }
221
222 #[test]
223 fn test_multiple_bindings() {
224 let mut map = ActionMap::new();
225 map.add_binding("fire", InputBinding::Key(KeyCode::Space));
226 map.add_binding("fire", InputBinding::Mouse(MouseButton::Left));
227
228 let mut input = InputState::new();
229 input.press_mouse(MouseButton::Left);
230
231 map.update(&input);
232 assert!(map.is_action_active("fire"));
233 }
234
235 #[test]
236 fn test_unknown_action() {
237 let map = ActionMap::new();
238 assert_eq!(map.action_state("nonexistent"), ActionState::Inactive);
239 assert!(!map.is_action_active("nonexistent"));
240 }
241}