Skip to main content

sable_platform/
input.rs

1//! Input state tracking for keyboard, mouse, and gamepad.
2
3use std::collections::HashSet;
4
5/// Input events that can occur.
6#[derive(Debug, Clone)]
7pub enum InputEvent {
8    /// A key was pressed.
9    KeyPressed(KeyCode),
10    /// A key was released.
11    KeyReleased(KeyCode),
12    /// A mouse button was pressed.
13    MousePressed(MouseButton),
14    /// A mouse button was released.
15    MouseReleased(MouseButton),
16    /// The mouse cursor moved.
17    MouseMoved {
18        /// X position in physical pixels.
19        x: f64,
20        /// Y position in physical pixels.
21        y: f64,
22    },
23    /// The mouse wheel was scrolled.
24    MouseWheel {
25        /// Horizontal scroll delta.
26        delta_x: f64,
27        /// Vertical scroll delta.
28        delta_y: f64,
29    },
30}
31
32/// Keyboard key codes.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[allow(missing_docs)]
35pub enum KeyCode {
36    // Letters
37    A,
38    B,
39    C,
40    D,
41    E,
42    F,
43    G,
44    H,
45    I,
46    J,
47    K,
48    L,
49    M,
50    N,
51    O,
52    P,
53    Q,
54    R,
55    S,
56    T,
57    U,
58    V,
59    W,
60    X,
61    Y,
62    Z,
63
64    // Numbers
65    Num0,
66    Num1,
67    Num2,
68    Num3,
69    Num4,
70    Num5,
71    Num6,
72    Num7,
73    Num8,
74    Num9,
75
76    // Function keys
77    F1,
78    F2,
79    F3,
80    F4,
81    F5,
82    F6,
83    F7,
84    F8,
85    F9,
86    F10,
87    F11,
88    F12,
89
90    // Special keys
91    Escape,
92    Enter,
93    Space,
94    Tab,
95    Backspace,
96    Shift,
97    Control,
98    Alt,
99
100    // Arrow keys
101    Up,
102    Down,
103    Left,
104    Right,
105}
106
107/// Mouse button identifiers.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub enum MouseButton {
110    /// Left mouse button.
111    Left,
112    /// Right mouse button.
113    Right,
114    /// Middle mouse button (scroll wheel click).
115    Middle,
116    /// Back button (side button).
117    Back,
118    /// Forward button (side button).
119    Forward,
120    /// Other mouse button with a numeric identifier.
121    Other(u16),
122}
123
124/// Tracks the current state of input devices.
125#[derive(Debug, Default)]
126pub struct InputState {
127    /// Currently pressed keys.
128    pressed_keys: HashSet<KeyCode>,
129    /// Currently pressed mouse buttons.
130    pressed_mouse_buttons: HashSet<MouseButton>,
131    /// Current mouse position.
132    mouse_position: (f64, f64),
133    /// Mouse scroll delta accumulated this frame.
134    scroll_delta: (f64, f64),
135}
136
137impl InputState {
138    /// Create a new input state.
139    #[must_use]
140    pub fn new() -> Self {
141        Self::default()
142    }
143
144    /// Process an input event and update the state.
145    pub fn process_event(&mut self, event: &InputEvent) {
146        match event {
147            InputEvent::KeyPressed(key) => {
148                self.pressed_keys.insert(*key);
149            }
150            InputEvent::KeyReleased(key) => {
151                self.pressed_keys.remove(key);
152            }
153            InputEvent::MousePressed(button) => {
154                self.pressed_mouse_buttons.insert(*button);
155            }
156            InputEvent::MouseReleased(button) => {
157                self.pressed_mouse_buttons.remove(button);
158            }
159            InputEvent::MouseMoved { x, y } => {
160                self.mouse_position = (*x, *y);
161            }
162            InputEvent::MouseWheel { delta_x, delta_y } => {
163                self.scroll_delta.0 += delta_x;
164                self.scroll_delta.1 += delta_y;
165            }
166        }
167    }
168
169    /// Check if a key is currently pressed.
170    #[must_use]
171    pub fn is_key_pressed(&self, key: KeyCode) -> bool {
172        self.pressed_keys.contains(&key)
173    }
174
175    /// Check if a mouse button is currently pressed.
176    #[must_use]
177    pub fn is_mouse_button_pressed(&self, button: MouseButton) -> bool {
178        self.pressed_mouse_buttons.contains(&button)
179    }
180
181    /// Get the current mouse position in physical pixels.
182    #[must_use]
183    pub fn mouse_position(&self) -> (f64, f64) {
184        self.mouse_position
185    }
186
187    /// Get and reset the accumulated scroll delta for this frame.
188    pub fn take_scroll_delta(&mut self) -> (f64, f64) {
189        let delta = self.scroll_delta;
190        self.scroll_delta = (0.0, 0.0);
191        delta
192    }
193
194    /// Clear all input state (useful when window loses focus).
195    pub fn clear(&mut self) {
196        self.pressed_keys.clear();
197        self.pressed_mouse_buttons.clear();
198        self.scroll_delta = (0.0, 0.0);
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_input_state_default() {
208        let state = InputState::new();
209        assert!(!state.is_key_pressed(KeyCode::A));
210        assert!(!state.is_mouse_button_pressed(MouseButton::Left));
211        assert_eq!(state.mouse_position(), (0.0, 0.0));
212    }
213
214    #[test]
215    fn test_key_press_release() {
216        let mut state = InputState::new();
217
218        // Press key
219        state.process_event(&InputEvent::KeyPressed(KeyCode::W));
220        assert!(state.is_key_pressed(KeyCode::W));
221        assert!(!state.is_key_pressed(KeyCode::A));
222
223        // Press another key
224        state.process_event(&InputEvent::KeyPressed(KeyCode::A));
225        assert!(state.is_key_pressed(KeyCode::W));
226        assert!(state.is_key_pressed(KeyCode::A));
227
228        // Release first key
229        state.process_event(&InputEvent::KeyReleased(KeyCode::W));
230        assert!(!state.is_key_pressed(KeyCode::W));
231        assert!(state.is_key_pressed(KeyCode::A));
232    }
233
234    #[test]
235    fn test_mouse_button_press_release() {
236        let mut state = InputState::new();
237
238        state.process_event(&InputEvent::MousePressed(MouseButton::Left));
239        assert!(state.is_mouse_button_pressed(MouseButton::Left));
240        assert!(!state.is_mouse_button_pressed(MouseButton::Right));
241
242        state.process_event(&InputEvent::MouseReleased(MouseButton::Left));
243        assert!(!state.is_mouse_button_pressed(MouseButton::Left));
244    }
245
246    #[test]
247    fn test_mouse_movement() {
248        let mut state = InputState::new();
249
250        state.process_event(&InputEvent::MouseMoved { x: 100.0, y: 200.0 });
251        assert_eq!(state.mouse_position(), (100.0, 200.0));
252
253        state.process_event(&InputEvent::MouseMoved { x: 150.0, y: 250.0 });
254        assert_eq!(state.mouse_position(), (150.0, 250.0));
255    }
256
257    #[test]
258    fn test_scroll_accumulation() {
259        let mut state = InputState::new();
260
261        state.process_event(&InputEvent::MouseWheel {
262            delta_x: 1.0,
263            delta_y: 2.0,
264        });
265        state.process_event(&InputEvent::MouseWheel {
266            delta_x: 0.5,
267            delta_y: 1.0,
268        });
269
270        let delta = state.take_scroll_delta();
271        assert!((delta.0 - 1.5).abs() < f64::EPSILON);
272        assert!((delta.1 - 3.0).abs() < f64::EPSILON);
273
274        // After taking, delta should be reset
275        let delta = state.take_scroll_delta();
276        assert!((delta.0).abs() < f64::EPSILON);
277        assert!((delta.1).abs() < f64::EPSILON);
278    }
279
280    #[test]
281    fn test_clear() {
282        let mut state = InputState::new();
283
284        state.process_event(&InputEvent::KeyPressed(KeyCode::Space));
285        state.process_event(&InputEvent::MousePressed(MouseButton::Left));
286        state.process_event(&InputEvent::MouseWheel {
287            delta_x: 1.0,
288            delta_y: 1.0,
289        });
290
291        assert!(state.is_key_pressed(KeyCode::Space));
292        assert!(state.is_mouse_button_pressed(MouseButton::Left));
293
294        state.clear();
295
296        assert!(!state.is_key_pressed(KeyCode::Space));
297        assert!(!state.is_mouse_button_pressed(MouseButton::Left));
298        let delta = state.take_scroll_delta();
299        assert!((delta.0).abs() < f64::EPSILON);
300    }
301
302    #[test]
303    fn test_mouse_button_other() {
304        let mut state = InputState::new();
305
306        state.process_event(&InputEvent::MousePressed(MouseButton::Other(5)));
307        assert!(state.is_mouse_button_pressed(MouseButton::Other(5)));
308        assert!(!state.is_mouse_button_pressed(MouseButton::Other(6)));
309    }
310}
311