khora_core/platform/
input_map.rs1use std::borrow::Cow;
43use std::collections::{HashMap, HashSet};
44
45use super::input::{InputEvent, KeyCode, MouseButton};
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
52pub struct Action(pub Cow<'static, str>);
53
54impl Action {
55 pub fn as_str(&self) -> &str {
57 &self.0
58 }
59}
60
61impl From<&'static str> for Action {
62 fn from(s: &'static str) -> Self {
63 Self(Cow::Borrowed(s))
64 }
65}
66
67impl From<String> for Action {
68 fn from(s: String) -> Self {
69 Self(Cow::Owned(s))
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum InputBinding {
76 Key(KeyCode),
78 Mouse(MouseButton),
80}
81
82#[derive(Debug, Default)]
85pub struct InputMap {
86 bindings: HashMap<Action, Vec<InputBinding>>,
88 inverse: HashMap<InputBinding, Vec<Action>>,
90 pressed: HashSet<Action>,
92 just_pressed: HashSet<Action>,
94 just_released: HashSet<Action>,
96}
97
98impl InputMap {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn bind(&mut self, action: impl Into<Action>, binding: InputBinding) {
107 let action = action.into();
108 self.bindings
109 .entry(action.clone())
110 .or_default()
111 .push(binding);
112 self.inverse.entry(binding).or_default().push(action);
113 }
114
115 pub fn unbind(&mut self, action: &Action, binding: &InputBinding) -> bool {
117 let removed_from_bindings = self
118 .bindings
119 .get_mut(action)
120 .map(|v| {
121 let len_before = v.len();
122 v.retain(|b| b != binding);
123 v.len() != len_before
124 })
125 .unwrap_or(false);
126 if removed_from_bindings {
127 if let Some(actions) = self.inverse.get_mut(binding) {
128 actions.retain(|a| a != action);
129 }
130 }
131 removed_from_bindings
132 }
133
134 pub fn clear_action(&mut self, action: &Action) {
136 if let Some(bindings) = self.bindings.remove(action) {
137 for b in bindings {
138 if let Some(actions) = self.inverse.get_mut(&b) {
139 actions.retain(|a| a != action);
140 }
141 }
142 }
143 self.pressed.remove(action);
144 self.just_pressed.remove(action);
145 self.just_released.remove(action);
146 }
147
148 pub fn is_pressed(&self, action: &str) -> bool {
150 self.pressed.iter().any(|a| a.as_str() == action)
151 }
152
153 pub fn just_pressed(&self, action: &str) -> bool {
156 self.just_pressed.iter().any(|a| a.as_str() == action)
157 }
158
159 pub fn just_released(&self, action: &str) -> bool {
162 self.just_released.iter().any(|a| a.as_str() == action)
163 }
164
165 pub fn update(&mut self, events: &[InputEvent]) {
170 self.just_pressed.clear();
171 self.just_released.clear();
172
173 for event in events {
174 match event {
175 InputEvent::KeyPressed { key_code } => {
176 self.fire_press(InputBinding::Key(*key_code));
177 }
178 InputEvent::KeyReleased { key_code } => {
179 self.fire_release(InputBinding::Key(*key_code));
180 }
181 InputEvent::MouseButtonPressed { button } => {
182 self.fire_press(InputBinding::Mouse(*button));
183 }
184 InputEvent::MouseButtonReleased { button } => {
185 self.fire_release(InputBinding::Mouse(*button));
186 }
187 _ => {} }
189 }
190 }
191
192 fn fire_press(&mut self, binding: InputBinding) {
193 if let Some(actions) = self.inverse.get(&binding) {
194 for action in actions {
195 if self.pressed.insert(action.clone()) {
196 self.just_pressed.insert(action.clone());
197 }
198 }
199 }
200 }
201
202 fn fire_release(&mut self, binding: InputBinding) {
203 if let Some(actions) = self.inverse.get(&binding) {
204 for action in actions {
205 if self.pressed.remove(action) {
206 self.just_released.insert(action.clone());
207 }
208 }
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn bind_and_press() {
219 let mut m = InputMap::new();
220 m.bind("jump", InputBinding::Key(KeyCode::Space));
221 m.update(&[InputEvent::KeyPressed {
222 key_code: KeyCode::Space,
223 }]);
224 assert!(m.is_pressed("jump"));
225 assert!(m.just_pressed("jump"));
226 }
227
228 #[test]
229 fn just_pressed_clears_next_frame() {
230 let mut m = InputMap::new();
231 m.bind("jump", InputBinding::Key(KeyCode::Space));
232 m.update(&[InputEvent::KeyPressed {
233 key_code: KeyCode::Space,
234 }]);
235 assert!(m.just_pressed("jump"));
236
237 m.update(&[]);
238 assert!(m.is_pressed("jump"), "still held — no release event");
239 assert!(!m.just_pressed("jump"), "edge should be cleared");
240 }
241
242 #[test]
243 fn release_event_clears_pressed() {
244 let mut m = InputMap::new();
245 m.bind("fire", InputBinding::Mouse(MouseButton::Left));
246
247 m.update(&[InputEvent::MouseButtonPressed {
248 button: MouseButton::Left,
249 }]);
250 assert!(m.is_pressed("fire"));
251
252 m.update(&[InputEvent::MouseButtonReleased {
253 button: MouseButton::Left,
254 }]);
255 assert!(!m.is_pressed("fire"));
256 assert!(m.just_released("fire"));
257 }
258
259 #[test]
260 fn multi_binding_either_works() {
261 let mut m = InputMap::new();
262 m.bind("jump", InputBinding::Key(KeyCode::Space));
263 m.bind("jump", InputBinding::Mouse(MouseButton::Left));
264
265 m.update(&[InputEvent::MouseButtonPressed {
267 button: MouseButton::Left,
268 }]);
269 assert!(m.is_pressed("jump"));
270
271 m.update(&[InputEvent::MouseButtonReleased {
278 button: MouseButton::Left,
279 }]);
280 assert!(!m.is_pressed("jump"));
281 }
282
283 #[test]
284 fn unbind_removes_inverse_index() {
285 let mut m = InputMap::new();
286 let action = Action::from("jump");
287 let binding = InputBinding::Key(KeyCode::Space);
288 m.bind(action.clone(), binding);
289 assert!(m.unbind(&action, &binding));
290
291 m.update(&[InputEvent::KeyPressed {
292 key_code: KeyCode::Space,
293 }]);
294 assert!(!m.is_pressed("jump"));
295 }
296}