Skip to main content

game_gem/
input.rs

1//! Input handling: keyboard, mouse, and gamepad.
2//!
3//! Key advantages over macroquad:
4//! - **Pressed/released detection per frame** (macroquad requires manual tracking)
5//! - **Input actions** (remappable keybindings with a single name)
6//! - **Mouse gesture detection** (drag, click, double-click)
7//! - **Text input** support for UI
8//!
9//! Access via `ctx.input` inside your [`GameState`].
10
11use crate::math::Vec2;
12
13// ─────────────────────────────────────────────
14// Key definitions
15// ─────────────────────────────────────────────
16
17/// Physical keyboard key.
18///
19/// Covers the common keyboard layout. For full coverage, use `KeyCode` from the
20/// underlying windowing layer.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22#[repr(u32)]
23pub enum KeyCode {
24    // Letters
25    A, B, C, D, E, F, G, H, I, J, K, L, M,
26    N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
27    // Numbers
28    Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9, Key0,
29    // Function keys
30    F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
31    // Special
32    Space, Enter, Escape, Tab, Backspace, Delete, Insert,
33    Home, End, PageUp, PageDown,
34    // Arrows
35    Up, Down, Left, Right,
36    // Modifiers
37    LeftShift, RightShift, LeftCtrl, RightCtrl,
38    LeftAlt, RightAlt, LeftSuper, RightSuper,
39    // Punctuation
40    Semicolon, Comma, Period, Slash, Backslash,
41    LeftBracket, RightBracket, Equals, Minus,
42    Apostrophe, Backquote,
43    // Other
44    CapsLock, ScrollLock, NumLock, PrintScreen,
45    Pause, ContextMenu,
46    /// Unknown / unmapped key.
47    Unknown,
48}
49
50// ─────────────────────────────────────────────
51// Mouse
52// ─────────────────────────────────────────────
53
54/// Mouse button.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub enum MouseButton {
57    Left,
58    Right,
59    Middle,
60    Button4,
61    Button5,
62}
63
64/// Mouse state for the current frame.
65#[derive(Debug, Clone)]
66pub struct MouseState {
67    /// Current pixel position.
68    pub position: Vec2,
69    /// Position at the start of the current drag (if any).
70    pub drag_start: Option<Vec2>,
71    /// Delta movement this frame.
72    pub delta: Vec2,
73    /// Scroll wheel delta this frame.
74    pub scroll: Vec2,
75    /// Which buttons are currently held down.
76    pub held: std::collections::HashSet<MouseButton>,
77    /// Buttons that were pressed this frame (down edge).
78    pub pressed_this_frame: Vec<MouseButton>,
79    /// Buttons that were released this frame (up edge).
80    pub released_this_frame: Vec<MouseButton>,
81    /// Whether the cursor is visible.
82    pub cursor_visible: bool,
83    /// Whether the cursor is locked (grabbed).
84    pub cursor_grabbed: bool,
85}
86
87impl Default for MouseState {
88    fn default() -> Self {
89        Self {
90            position: Vec2::ZERO,
91            drag_start: None,
92            delta: Vec2::ZERO,
93            scroll: Vec2::ZERO,
94            held: std::collections::HashSet::new(),
95            pressed_this_frame: Vec::new(),
96            released_this_frame: Vec::new(),
97            cursor_visible: true,
98            cursor_grabbed: false,
99        }
100    }
101}
102
103impl MouseState {
104    /// Is the given button currently held?
105    pub fn is_down(&self, button: MouseButton) -> bool {
106        self.held.contains(&button)
107    }
108
109    /// Was the button pressed this frame?
110    pub fn is_pressed(&self, button: MouseButton) -> bool {
111        self.pressed_this_frame.contains(&button)
112    }
113
114    /// Was the button released this frame?
115    pub fn is_released(&self, button: MouseButton) -> bool {
116        self.released_this_frame.contains(&button)
117    }
118
119    /// Is any mouse button held?
120    pub fn is_any_down(&self) -> bool {
121        !self.held.is_empty()
122    }
123
124    /// Check if the user is dragging with the left button.
125    pub fn is_dragging(&self) -> bool {
126        self.held.contains(&MouseButton::Left) && self.drag_start.is_some()
127    }
128
129    /// Get the drag vector (from start to current position), or `None` if not dragging.
130    pub fn drag_vector(&self) -> Option<Vec2> {
131        self.drag_start.map(|start| self.position - start)
132    }
133
134    /// Set cursor visibility.
135    pub fn set_cursor_visible(&mut self, visible: bool) {
136        self.cursor_visible = visible;
137    }
138
139    /// Set cursor grab (locks cursor to window).
140    pub fn set_cursor_grabbed(&mut self, grabbed: bool) {
141        self.cursor_grabbed = grabbed;
142    }
143}
144
145// ─────────────────────────────────────────────
146// Keyboard
147// ─────────────────────────────────────────────
148
149/// Keyboard state for the current frame.
150#[derive(Debug, Clone)]
151pub struct KeyboardState {
152    /// Keys currently held down.
153    held: std::collections::HashSet<KeyCode>,
154    /// Keys pressed this frame.
155    pub pressed_this_frame: Vec<KeyCode>,
156    /// Keys released this frame.
157    pub released_this_frame: Vec<KeyCode>,
158    /// Currently buffered text input (from IME / key events).
159    text_buffer: String,
160}
161
162impl Default for KeyboardState {
163    fn default() -> Self {
164        Self {
165            held: std::collections::HashSet::new(),
166            pressed_this_frame: Vec::new(),
167            released_this_frame: Vec::new(),
168            text_buffer: String::new(),
169        }
170    }
171}
172
173impl KeyboardState {
174    /// Is the key currently held?
175    #[inline]
176    pub fn is_down(&self, key: KeyCode) -> bool {
177        self.held.contains(&key)
178    }
179
180    /// Was the key pressed this frame (down-edge)?
181    #[inline]
182    pub fn is_pressed(&self, key: KeyCode) -> bool {
183        self.pressed_this_frame.contains(&key)
184    }
185
186    /// Was the key released this frame (up-edge)?
187    #[inline]
188    pub fn is_released(&self, key: KeyCode) -> bool {
189        self.released_this_frame.contains(&key)
190    }
191
192    /// Are all given keys held down simultaneously?
193    pub fn are_all_down(&self, keys: &[KeyCode]) -> bool {
194        keys.iter().all(|k| self.held.contains(k))
195    }
196
197    /// Is any of the given keys held down?
198    pub fn is_any_down(&self, keys: &[KeyCode]) -> bool {
199        keys.iter().any(|k| self.held.contains(k))
200    }
201
202    /// Take the text input buffer (clears it).
203    pub fn take_text(&mut self) -> String {
204        std::mem::take(&mut self.text_buffer)
205    }
206
207    /// Peek at the text buffer without clearing.
208    pub fn text(&self) -> &str {
209        &self.text_buffer
210    }
211}
212
213// ─────────────────────────────────────────────
214// Input Actions (remappable bindings)
215// ─────────────────────────────────────────────
216
217/// A named input action that can be bound to multiple keys/buttons.
218///
219/// # Example
220/// ```
221/// let mut jump = InputAction::new("jump");
222/// jump.bind_key(KeyCode::Space);
223/// jump.bind_key(KeyCode::Up);
224/// jump.bind_mouse(MouseButton::Left);
225///
226/// // In update:
227/// if jump.is_pressed(&ctx.input) { player.jump(); }
228/// ```
229#[derive(Debug, Clone)]
230pub struct InputAction {
231    /// Human-readable name.
232    pub name: String,
233    /// Bound keyboard keys.
234    pub keys: Vec<KeyCode>,
235    /// Bound mouse buttons.
236    pub mouse_buttons: Vec<MouseButton>,
237    /// Positive gamepad axis + threshold (axis_index, threshold).
238    pub positive_axis: Vec<(u32, f32)>,
239    /// Negative gamepad axis + threshold.
240    pub negative_axis: Vec<(u32, f32)>,
241    /// Gamepad buttons.
242    pub gamepad_buttons: Vec<u32>,
243}
244
245impl InputAction {
246    /// Create a new named input action.
247    pub fn new(name: &str) -> Self {
248        Self {
249            name: name.to_string(),
250            keys: Vec::new(),
251            mouse_buttons: Vec::new(),
252            positive_axis: Vec::new(),
253            negative_axis: Vec::new(),
254            gamepad_buttons: Vec::new(),
255        }
256    }
257
258    /// Bind a keyboard key to this action.
259    pub fn bind_key(&mut self, key: KeyCode) -> &mut Self {
260        self.keys.push(key);
261        self
262    }
263
264    /// Bind a mouse button to this action.
265    pub fn bind_mouse(&mut self, button: MouseButton) -> &mut Self {
266        self.mouse_buttons.push(button);
267        self
268    }
269
270    /// Check if this action is currently active (held).
271    pub fn is_down(&self, input: &InputState) -> bool {
272        for key in &self.keys {
273            if input.keyboard.is_down(*key) {
274                return true;
275            }
276        }
277        for btn in &self.mouse_buttons {
278            if input.mouse.is_down(*btn) {
279                return true;
280            }
281        }
282        false
283    }
284
285    /// Check if this action was just activated (pressed edge).
286    pub fn is_pressed(&self, input: &InputState) -> bool {
287        for key in &self.keys {
288            if input.keyboard.is_pressed(*key) {
289                return true;
290            }
291        }
292        for btn in &self.mouse_buttons {
293            if input.mouse.is_pressed(*btn) {
294                return true;
295            }
296        }
297        false
298    }
299}
300
301// ─────────────────────────────────────────────
302// Unified input state
303// ─────────────────────────────────────────────
304
305/// Unified input state, accessible via `ctx.input`.
306#[derive(Debug, Clone, Default)]
307pub struct InputState {
308    /// Keyboard state.
309    pub keyboard: KeyboardState,
310    /// Mouse state.
311    pub mouse: MouseState,
312    /// Registered input actions.
313    actions: Vec<InputAction>,
314}
315
316impl InputState {
317    /// Register a new input action.
318    pub fn register_action(&mut self, action: InputAction) {
319        self.actions.push(action);
320    }
321
322    /// Find an action by name.
323    pub fn action(&self, name: &str) -> Option<&InputAction> {
324        self.actions.iter().find(|a| a.name == name)
325    }
326
327    /// Check if a named action is currently active.
328    pub fn is_action_down(&self, name: &str) -> bool {
329        self.actions.iter().any(|a| a.name == name && a.is_down(self))
330    }
331
332    /// Check if a named action was just pressed.
333    pub fn is_action_pressed(&self, name: &str) -> bool {
334        self.actions.iter().any(|a| a.name == name && a.is_pressed(self))
335    }
336}