//! [`Binding`] — one physical input that can drive a named action. //! //! A binding is the smallest unit an [`ActionMap`](super::ActionMap) maps //! action names to. The enum is intentionally small (keys and mouse buttons //! today; gamepad / pointer-axis variants will be added without breaking //! existing serialized maps as long as new variants are appended). use serde::{Deserialize, Serialize}; use winit::event::MouseButton; use winit::keyboard::KeyCode; use super::InputState; /// One physical input that can be bound to a named action. /// /// Two bindings compare equal only if they refer to the exact same physical /// input — the enum derives `Hash`/`Eq` so a `HashSet` can be used /// to deduplicate a key's contribution to multiple actions without /// allocating per-action sets. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Binding { /// A keyboard key, identified by layout-independent physical position /// (the same `KeyCode` an [`InputState`] query takes). Key(KeyCode), /// A mouse button. Mouse(MouseButton), } impl Binding { /// `true` if this binding's `pressed` edge fired in `input` this frame. pub fn pressed(&self, input: &InputState) -> bool { match *self { Binding::Key(k) => input.pressed(k), Binding::Mouse(b) => input.mouse_pressed(b), } } /// `true` if this binding's `released` edge fired in `input` this frame. pub fn released(&self, input: &InputState) -> bool { match *self { Binding::Key(k) => input.released(k), Binding::Mouse(b) => input.mouse_released(b), } } /// `true` if this binding is currently held down in `input`. pub fn held(&self, input: &InputState) -> bool { match *self { Binding::Key(k) => input.held(k), Binding::Mouse(b) => input.mouse_held(b), } } /// `true` if this binding was held *going into* this frame — i.e. it was /// held continuously from before the current frame's events arrived. /// Used by [`ActionMap`](super::ActionMap) to recover prior-frame state /// from the current frame's snapshot alone, without storing a previous /// `InputState`. /// /// Derivation: a binding was held before the frame iff it is currently /// held or was released this frame (either way it was down going in), /// **except** when it was also pressed this frame — a same-frame tap /// goes idle → pressed → released, so it was not held going in. pub(crate) fn held_before_frame(&self, input: &InputState) -> bool { (self.held(input) || self.released(input)) && !self.pressed(input) } } #[cfg(test)] mod tests { use super::*; #[test] fn key_binding_routes_to_keyboard_queries() { let mut input = InputState::new(); let b = Binding::Key(KeyCode::Space); input.press_key(KeyCode::Space); assert!(b.pressed(&input)); assert!(b.held(&input)); assert!(!b.released(&input)); input.end_frame(); assert!(!b.pressed(&input)); assert!(b.held(&input)); input.release_key(KeyCode::Space); assert!(b.released(&input)); assert!(!b.held(&input)); } #[test] fn mouse_binding_routes_to_mouse_queries() { let mut input = InputState::new(); let b = Binding::Mouse(MouseButton::Right); input.press_mouse(MouseButton::Right); assert!(b.pressed(&input)); assert!(b.held(&input)); input.end_frame(); input.release_mouse(MouseButton::Right); assert!(b.released(&input)); assert!(!b.held(&input)); } #[test] fn held_before_frame_distinguishes_press_release_tap() { let b = Binding::Key(KeyCode::KeyJ); // Idle → pressed this frame. Not held before. let mut input = InputState::new(); input.press_key(KeyCode::KeyJ); assert!(!b.held_before_frame(&input)); // Held continuously. Held before. let mut input = InputState::new(); input.press_key(KeyCode::KeyJ); input.end_frame(); assert!(b.held_before_frame(&input)); // Held → released this frame. Held before. let mut input = InputState::new(); input.press_key(KeyCode::KeyJ); input.end_frame(); input.release_key(KeyCode::KeyJ); assert!(b.held_before_frame(&input)); // Same-frame tap (idle → pressed → released). Not held before. let mut input = InputState::new(); input.press_key(KeyCode::KeyJ); input.release_key(KeyCode::KeyJ); assert!(!b.held_before_frame(&input)); } #[test] fn ron_round_trip_preserves_key_and_mouse_variants() { let bindings = vec![ Binding::Key(KeyCode::Space), Binding::Mouse(MouseButton::Left), Binding::Key(KeyCode::ShiftLeft), ]; let s = ron::to_string(&bindings).unwrap(); let parsed: Vec = ron::from_str(&s).unwrap(); assert_eq!(parsed, bindings); } }