9eead719b0
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
148 lines
5.0 KiB
Rust
148 lines
5.0 KiB
Rust
//! [`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<Binding>` 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<Binding> = ron::from_str(&s).unwrap();
|
|
assert_eq!(parsed, bindings);
|
|
}
|
|
}
|