Skip to main content

proof_engine/input/
mod.rs

1//! Input handling — keyboard, mouse, scroll, and window events.
2//!
3//! `InputState` is rebuilt fresh each frame by the render pipeline and handed
4//! to game code inside `ProofGame::update()`. All transient state (just-pressed,
5//! just-released, delta values) is cleared at the start of each frame.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use proof_engine::input::{InputState, Key};
11//!
12//! fn my_update(input: &InputState) {
13//!     if input.just_pressed(Key::Space) {
14//!         println!("space!");
15//!     }
16//!     if input.mouse_left {
17//!         println!("dragging at ({}, {})", input.mouse_x, input.mouse_y);
18//!     }
19//! }
20//! ```
21
22pub mod keybindings;
23
24use glam::Vec2;
25use std::collections::HashSet;
26
27// ── InputState ────────────────────────────────────────────────────────────────
28
29/// Complete snapshot of all input this frame.
30///
31/// Created and updated by the `Pipeline` each frame.  Game code receives a
32/// `&mut ProofEngine` whose `.input` field holds this value.
33#[derive(Clone, Default)]
34pub struct InputState {
35    // ── Keyboard ──────────────────────────────────────────────────────────────
36    /// All keys currently held down.
37    pub keys_pressed:       HashSet<Key>,
38    /// Keys that transitioned to pressed this frame.
39    pub keys_just_pressed:  HashSet<Key>,
40    /// Keys that transitioned to released this frame.
41    pub keys_just_released: HashSet<Key>,
42
43    // ── Mouse position ────────────────────────────────────────────────────────
44    /// Cursor X in window pixels (top-left origin).
45    pub mouse_x:      f32,
46    /// Cursor Y in window pixels (top-left origin).
47    pub mouse_y:      f32,
48    /// Mouse movement since last frame, in pixels.
49    pub mouse_delta:  Vec2,
50    /// Cursor in normalized device coordinates ([-1, 1], Y-up).
51    pub mouse_ndc:    Vec2,
52
53    // ── Mouse buttons ─────────────────────────────────────────────────────────
54    pub mouse_left:               bool,
55    pub mouse_left_just_pressed:  bool,
56    pub mouse_left_just_released: bool,
57
58    pub mouse_right:               bool,
59    pub mouse_right_just_pressed:  bool,
60    pub mouse_right_just_released: bool,
61
62    pub mouse_middle:              bool,
63    pub mouse_middle_just_pressed: bool,
64
65    // ── Scroll ────────────────────────────────────────────────────────────────
66    /// Vertical scroll delta this frame (positive = scroll up / zoom in).
67    pub scroll_delta: f32,
68
69    // ── Window ────────────────────────────────────────────────────────────────
70    /// Set if the window was resized this frame. Contains (width, height).
71    pub window_resized: Option<(u32, u32)>,
72    /// Set if the user requested a quit (Alt+F4, OS close button).
73    pub quit_requested: bool,
74}
75
76impl InputState {
77    pub fn new() -> Self { Self::default() }
78
79    // ── Keyboard queries ──────────────────────────────────────────────────────
80
81    /// Returns true while the key is held down.
82    pub fn is_pressed(&self, key: Key) -> bool {
83        self.keys_pressed.contains(&key)
84    }
85
86    /// Returns true on the frame the key was first pressed.
87    pub fn just_pressed(&self, key: Key) -> bool {
88        self.keys_just_pressed.contains(&key)
89    }
90
91    /// Returns true on the frame the key was released.
92    pub fn just_released(&self, key: Key) -> bool {
93        self.keys_just_released.contains(&key)
94    }
95
96    /// Returns true if any of the given keys are held.
97    pub fn any_pressed(&self, keys: &[Key]) -> bool {
98        keys.iter().any(|k| self.keys_pressed.contains(k))
99    }
100
101    /// Returns true if all of the given keys are held simultaneously.
102    pub fn all_pressed(&self, keys: &[Key]) -> bool {
103        keys.iter().all(|k| self.keys_pressed.contains(k))
104    }
105
106    /// Returns true if a modifier key (Shift, Ctrl, Alt) is held.
107    pub fn shift(&self) -> bool {
108        self.is_pressed(Key::LShift) || self.is_pressed(Key::RShift)
109    }
110
111    pub fn ctrl(&self) -> bool {
112        self.is_pressed(Key::LCtrl) || self.is_pressed(Key::RCtrl)
113    }
114
115    pub fn alt(&self) -> bool {
116        self.is_pressed(Key::LAlt) || self.is_pressed(Key::RAlt)
117    }
118
119    // ── Mouse queries ──────────────────────────────────────────────────────────
120
121    /// Mouse position as a Vec2 in window pixels.
122    pub fn mouse_pos(&self) -> Vec2 {
123        Vec2::new(self.mouse_x, self.mouse_y)
124    }
125
126    /// Returns true while the left mouse button is held.
127    pub fn mouse_left_down(&self) -> bool { self.mouse_left }
128
129    /// Returns true on the frame the left button was first pressed.
130    pub fn mouse_left_click(&self) -> bool { self.mouse_left_just_pressed }
131
132    /// Returns true while the right mouse button is held.
133    pub fn mouse_right_down(&self) -> bool { self.mouse_right }
134
135    /// Returns true on the frame the right button was first pressed.
136    pub fn mouse_right_click(&self) -> bool { self.mouse_right_just_pressed }
137
138    /// Returns true if the mouse is being dragged with the left button held.
139    pub fn mouse_drag(&self) -> bool {
140        self.mouse_left && self.mouse_delta.length_squared() > f32::EPSILON
141    }
142
143    /// Returns the mouse delta scaled to NDC space (suitable for camera control).
144    pub fn mouse_delta_ndc(&self, window_width: u32, window_height: u32) -> Vec2 {
145        Vec2::new(
146            self.mouse_delta.x / window_width.max(1) as f32 * 2.0,
147            -self.mouse_delta.y / window_height.max(1) as f32 * 2.0,
148        )
149    }
150
151    // ── WASD / arrow movement helpers ─────────────────────────────────────────
152
153    /// Returns a movement vector from WASD keys: (right, up) in [-1, 1].
154    /// `W`/`S` → Y axis, `A`/`D` → X axis.
155    pub fn wasd(&self) -> Vec2 {
156        let x = if self.is_pressed(Key::D) || self.is_pressed(Key::Right) { 1.0 }
157                else if self.is_pressed(Key::A) || self.is_pressed(Key::Left) { -1.0 }
158                else { 0.0 };
159        let y = if self.is_pressed(Key::W) || self.is_pressed(Key::Up) { 1.0 }
160                else if self.is_pressed(Key::S) || self.is_pressed(Key::Down) { -1.0 }
161                else { 0.0 };
162        Vec2::new(x, y)
163    }
164
165    /// Returns a normalized movement vector from arrow keys.
166    pub fn arrows(&self) -> Vec2 {
167        let x = if self.is_pressed(Key::Right) { 1.0 }
168                else if self.is_pressed(Key::Left) { -1.0 }
169                else { 0.0 };
170        let y = if self.is_pressed(Key::Up) { 1.0 }
171                else if self.is_pressed(Key::Down) { -1.0 }
172                else { 0.0 };
173        Vec2::new(x, y)
174    }
175
176    // ── Internal: called by pipeline at start of frame ─────────────────────────
177
178    /// Clear all per-frame transient state. Called by the pipeline before processing events.
179    pub fn clear_frame(&mut self) {
180        self.keys_just_pressed.clear();
181        self.keys_just_released.clear();
182        self.scroll_delta              = 0.0;
183        self.mouse_delta               = Vec2::ZERO;
184        self.mouse_left_just_pressed   = false;
185        self.mouse_left_just_released  = false;
186        self.mouse_right_just_pressed  = false;
187        self.mouse_right_just_released = false;
188        self.mouse_middle_just_pressed = false;
189        self.window_resized            = None;
190        self.quit_requested            = false;
191    }
192}
193
194// ── Key enum ──────────────────────────────────────────────────────────────────
195
196/// Keyboard key codes understood by the engine.
197///
198/// Maps 1:1 to logical key names rather than physical scan codes.
199/// Use `InputState::is_pressed(Key::X)` to query any key.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
201pub enum Key {
202    // ── Letters ───────────────────────────────────────────────────────────────
203    A, B, C, D, E, F, G, H, I, J, K, L, M,
204    N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
205
206    // ── Digits ────────────────────────────────────────────────────────────────
207    Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9,
208
209    // ── Navigation ────────────────────────────────────────────────────────────
210    Up, Down, Left, Right,
211    PageUp, PageDown,
212    Home, End,
213    Insert, Delete,
214
215    // ── Action ────────────────────────────────────────────────────────────────
216    Enter, Escape, Space, Backspace, Tab,
217
218    // ── Function ──────────────────────────────────────────────────────────────
219    F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
220
221    // ── Punctuation ───────────────────────────────────────────────────────────
222    Slash, Backslash, Period, Comma, Semicolon, Quote,
223    LBracket, RBracket, Minus, Equals, Backtick,
224
225    // ── Modifiers ─────────────────────────────────────────────────────────────
226    LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
227}
228
229impl Key {
230    /// Human-readable name for the key (for display in key-binding menus).
231    pub fn display_name(&self) -> &'static str {
232        match self {
233            Key::A => "A", Key::B => "B", Key::C => "C", Key::D => "D",
234            Key::E => "E", Key::F => "F", Key::G => "G", Key::H => "H",
235            Key::I => "I", Key::J => "J", Key::K => "K", Key::L => "L",
236            Key::M => "M", Key::N => "N", Key::O => "O", Key::P => "P",
237            Key::Q => "Q", Key::R => "R", Key::S => "S", Key::T => "T",
238            Key::U => "U", Key::V => "V", Key::W => "W", Key::X => "X",
239            Key::Y => "Y", Key::Z => "Z",
240            Key::Num0 => "0", Key::Num1 => "1", Key::Num2 => "2", Key::Num3 => "3",
241            Key::Num4 => "4", Key::Num5 => "5", Key::Num6 => "6", Key::Num7 => "7",
242            Key::Num8 => "8", Key::Num9 => "9",
243            Key::Up => "↑", Key::Down => "↓", Key::Left => "←", Key::Right => "→",
244            Key::PageUp => "PgUp", Key::PageDown => "PgDn",
245            Key::Home => "Home", Key::End => "End",
246            Key::Insert => "Ins", Key::Delete => "Del",
247            Key::Enter => "Enter", Key::Escape => "Esc", Key::Space => "Space",
248            Key::Backspace => "Backspace", Key::Tab => "Tab",
249            Key::F1 => "F1", Key::F2 => "F2", Key::F3 => "F3", Key::F4 => "F4",
250            Key::F5 => "F5", Key::F6 => "F6", Key::F7 => "F7", Key::F8 => "F8",
251            Key::F9 => "F9", Key::F10 => "F10", Key::F11 => "F11", Key::F12 => "F12",
252            Key::Slash => "/", Key::Backslash => "\\", Key::Period => ".",
253            Key::Comma => ",", Key::Semicolon => ";", Key::Quote => "'",
254            Key::LBracket => "[", Key::RBracket => "]",
255            Key::Minus => "-", Key::Equals => "=", Key::Backtick => "`",
256            Key::LShift | Key::RShift => "Shift",
257            Key::LCtrl  | Key::RCtrl  => "Ctrl",
258            Key::LAlt   | Key::RAlt   => "Alt",
259        }
260    }
261
262    /// All printable/action keys (excludes modifiers). Useful for rebinding UIs.
263    pub fn all_bindable() -> &'static [Key] {
264        &[
265            Key::A, Key::B, Key::C, Key::D, Key::E, Key::F, Key::G, Key::H,
266            Key::I, Key::J, Key::K, Key::L, Key::M, Key::N, Key::O, Key::P,
267            Key::Q, Key::R, Key::S, Key::T, Key::U, Key::V, Key::W, Key::X,
268            Key::Y, Key::Z,
269            Key::Num0, Key::Num1, Key::Num2, Key::Num3, Key::Num4,
270            Key::Num5, Key::Num6, Key::Num7, Key::Num8, Key::Num9,
271            Key::Up, Key::Down, Key::Left, Key::Right,
272            Key::Enter, Key::Escape, Key::Space, Key::Backspace, Key::Tab,
273            Key::F1, Key::F2, Key::F3, Key::F4, Key::F5, Key::F6,
274            Key::F7, Key::F8, Key::F9, Key::F10, Key::F11, Key::F12,
275        ]
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn clear_frame_resets_transient() {
285        let mut input = InputState::new();
286        input.keys_just_pressed.insert(Key::Space);
287        input.scroll_delta = 3.0;
288        input.mouse_delta  = Vec2::new(10.0, 5.0);
289        input.clear_frame();
290        assert!(input.keys_just_pressed.is_empty());
291        assert_eq!(input.scroll_delta, 0.0);
292        assert_eq!(input.mouse_delta, Vec2::ZERO);
293    }
294
295    #[test]
296    fn wasd_returns_correct_direction() {
297        let mut input = InputState::new();
298        input.keys_pressed.insert(Key::D);
299        input.keys_pressed.insert(Key::W);
300        let v = input.wasd();
301        assert!(v.x > 0.0 && v.y > 0.0);
302    }
303
304    #[test]
305    fn shift_ctrl_helpers() {
306        let mut input = InputState::new();
307        assert!(!input.shift());
308        input.keys_pressed.insert(Key::LShift);
309        assert!(input.shift());
310        assert!(!input.ctrl());
311    }
312
313    #[test]
314    fn mouse_drag_only_when_moving() {
315        let mut input = InputState::new();
316        input.mouse_left = true;
317        assert!(!input.mouse_drag()); // no movement
318        input.mouse_delta = Vec2::new(3.0, 0.0);
319        assert!(input.mouse_drag());
320    }
321}