Skip to main content

engine/
input.rs

1/**----------------------------------------------------------------------
2*!  Action-based input handling for keyboard, mouse, and gamepad.
3*----------------------------------------------------------------------**/
4#[cfg(not(target_arch = "wasm32"))]
5use gilrs::{Axis, Button, Event, EventType, Gilrs};
6use std::collections::HashMap;
7use std::fmt::Debug;
8use winit::event::{ElementState, KeyEvent, MouseButton};
9use winit::keyboard::{KeyCode, PhysicalKey};
10
11//? Trait for game-defined action enums.
12pub trait GameAction: Copy + Eq + Debug + 'static {
13    //* Total number of action variants.
14    fn count() -> usize;
15
16    //* Unique index for this action variant (0-based, must be < `count()`).
17    fn index(&self) -> usize;
18
19    //* Reconstruct an action from its index. Returns `None` for out-of-range indices.
20    fn from_index(index: usize) -> Option<Self>;
21
22    fn move_negative_x() -> Option<Self> {
23        None
24    }
25
26    fn move_positive_x() -> Option<Self> {
27        None
28    }
29
30    fn move_negative_y() -> Option<Self> {
31        None
32    }
33
34    fn move_positive_y() -> Option<Self> {
35        None
36    }
37}
38
39//? Mouse buttons that can be bound to game actions via `InputMap::bind_mouse()`.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum MouseBinding {
42    Left,
43    Right,
44    Middle,
45}
46
47//? Keyboard key enumeration (hardware-specific).
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum Key {
50    W,
51    A,
52    S,
53    D,
54    Space,
55    Shift,
56    Alt,
57    Up,
58    Down,
59    Left,
60    Right,
61    F12,
62    Escape,
63}
64
65impl Key {
66    fn from_keycode(code: KeyCode) -> Option<Self> {
67        match code {
68            KeyCode::KeyW => Some(Key::W),
69            KeyCode::KeyA => Some(Key::A),
70            KeyCode::KeyS => Some(Key::S),
71            KeyCode::KeyD => Some(Key::D),
72            KeyCode::Space => Some(Key::Space),
73            KeyCode::ShiftLeft => Some(Key::Shift),
74            KeyCode::AltLeft => Some(Key::Alt),
75            KeyCode::ArrowUp => Some(Key::Up),
76            KeyCode::ArrowDown => Some(Key::Down),
77            KeyCode::ArrowLeft => Some(Key::Left),
78            KeyCode::ArrowRight => Some(Key::Right),
79            KeyCode::F12 => Some(Key::F12),
80            KeyCode::Escape => Some(Key::Escape),
81            _ => None,
82        }
83    }
84}
85
86//? Input mapping between hardware inputs and game actions.
87pub struct InputMap<A: GameAction> {
88    keyboard_map: HashMap<Key, A>,
89    mouse_map: HashMap<MouseBinding, A>,
90    #[cfg(not(target_arch = "wasm32"))]
91    gamepad_map: HashMap<Button, A>,
92}
93
94impl<A: GameAction> InputMap<A> {
95    pub fn new() -> Self {
96        Self {
97            keyboard_map: HashMap::new(),
98            mouse_map: HashMap::new(),
99            #[cfg(not(target_arch = "wasm32"))]
100            gamepad_map: HashMap::new(),
101        }
102    }
103
104    pub fn bind_key(&mut self, key: Key, action: A) {
105        self.keyboard_map.insert(key, action);
106    }
107
108    pub fn bind_mouse(&mut self, button: MouseBinding, action: A) {
109        self.mouse_map.insert(button, action);
110    }
111
112    #[cfg(not(target_arch = "wasm32"))]
113    pub fn bind_button(&mut self, button: Button, action: A) {
114        self.gamepad_map.insert(button, action);
115    }
116
117    #[cfg(not(target_arch = "wasm32"))]
118    fn get_action_for_button(&self, button: Button) -> Option<A> {
119        self.gamepad_map.get(&button).copied()
120    }
121}
122
123impl<A: GameAction> Default for InputMap<A> {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129//? Input buffer entry: (action, time_pressed).
130#[derive(Debug, Clone, Copy)]
131struct BufferedInput<A: GameAction> {
132    action: A,
133    time_pressed: f32,
134}
135
136//? Input state tracking keyboard, mouse, and gamepad.
137pub struct InputState<A: GameAction> {
138    keys: [bool; 13],
139    keys_prev: [bool; 13],
140    actions: Vec<bool>,
141    actions_prev: Vec<bool>,
142    mouse_buttons: [bool; 3],
143    gamepad_axes: [f32; 2],
144
145    #[cfg(not(target_arch = "wasm32"))]
146    gamepad_buttons: Vec<bool>,
147    #[cfg(not(target_arch = "wasm32"))]
148    gamepad_start: bool,
149    #[cfg(not(target_arch = "wasm32"))]
150    gamepad_start_prev: bool,
151    input_map: InputMap<A>,
152
153    #[cfg(not(target_arch = "wasm32"))]
154    gilrs: Option<Gilrs>,
155    input_buffer: Vec<BufferedInput<A>>,
156    current_time: f32,
157}
158
159impl<A: GameAction> InputState<A> {
160    pub fn new() -> Self {
161        let action_count = A::count();
162        Self {
163            keys: [false; 13],
164            keys_prev: [false; 13],
165            actions: vec![false; action_count],
166            actions_prev: vec![false; action_count],
167            mouse_buttons: [false; 3],
168            gamepad_axes: [0.0; 2],
169            #[cfg(not(target_arch = "wasm32"))]
170            gamepad_buttons: vec![false; action_count],
171            #[cfg(not(target_arch = "wasm32"))]
172            gamepad_start: false,
173            #[cfg(not(target_arch = "wasm32"))]
174            gamepad_start_prev: false,
175            input_map: InputMap::new(),
176
177            #[cfg(not(target_arch = "wasm32"))]
178            gilrs: match Gilrs::new() {
179                Ok(g) => Some(g),
180                Err(e) => {
181                    log::warn!("Failed to initialize gamepad support: {e}");
182                    None
183                }
184            },
185            input_buffer: Vec::with_capacity(8),
186            current_time: 0.0,
187        }
188    }
189
190    #[cfg(not(target_arch = "wasm32"))]
191    pub fn is_gamepad_start_just_pressed(&self) -> bool {
192        self.gamepad_start && !self.gamepad_start_prev
193    }
194
195    #[cfg(target_arch = "wasm32")]
196    pub fn is_gamepad_start_just_pressed(&self) -> bool {
197        false
198    }
199
200    pub fn is_action_pressed(&self, action: A) -> bool {
201        self.actions[action.index()]
202    }
203
204    pub fn is_action_just_pressed(&self, action: A) -> bool {
205        let idx = action.index();
206        self.actions[idx] && !self.actions_prev[idx]
207    }
208
209    pub fn was_action_pressed_buffered(&self, action: A, buffer_window: f32) -> bool {
210        if self.is_action_just_pressed(action) {
211            return true;
212        }
213
214        for buffered in &self.input_buffer {
215            if buffered.action == action {
216                let elapsed = self.current_time - buffered.time_pressed;
217                if elapsed <= buffer_window {
218                    return true;
219                }
220            }
221        }
222
223        false
224    }
225
226    //? Check if a raw key is pressed (for non-action keys like Escape).
227    #[allow(dead_code)]
228    pub fn is_key_pressed(&self, key: Key) -> bool {
229        self.keys[key as usize]
230    }
231
232    //? Check if a raw key was just pressed this frame.
233    #[allow(dead_code)]
234    pub fn is_key_just_pressed(&self, key: Key) -> bool {
235        let idx = key as usize;
236        self.keys[idx] && !self.keys_prev[idx]
237    }
238
239    pub fn is_mouse_pressed(&self, button: MouseButton) -> bool {
240        match button {
241            MouseButton::Left => self.mouse_buttons[0],
242            MouseButton::Right => self.mouse_buttons[1],
243            MouseButton::Middle => self.mouse_buttons[2],
244            _ => false,
245        }
246    }
247
248    //? Get horizontal movement axis (-1.0 to 1.0).
249    pub fn get_move_x(&self) -> f32 {
250        let mut value = 0.0;
251
252        if let Some(left) = A::move_negative_x()
253            && self.is_action_pressed(left)
254        {
255            value -= 1.0;
256        }
257        if let Some(right) = A::move_positive_x()
258            && self.is_action_pressed(right)
259        {
260            value += 1.0;
261        }
262
263        if self.gamepad_axes[0].abs() > 0.1 {
264            value = self.gamepad_axes[0];
265        }
266
267        value
268    }
269
270    //? Get vertical movement axis (-1.0 to 1.0).
271    pub fn get_move_y(&self) -> f32 {
272        let mut value = 0.0;
273
274        if let Some(up) = A::move_negative_y()
275            && self.is_action_pressed(up)
276        {
277            value -= 1.0;
278        }
279        if let Some(down) = A::move_positive_y()
280            && self.is_action_pressed(down)
281        {
282            value += 1.0;
283        }
284
285        if self.gamepad_axes[1].abs() > 0.1 {
286            value = self.gamepad_axes[1];
287        }
288
289        value
290    }
291
292    pub fn any_keyboard_or_mouse(&self) -> bool {
293        self.keys.iter().any(|&k| k) || self.mouse_buttons.iter().any(|&b| b)
294    }
295
296    pub fn any_gamepad(&self) -> bool {
297        if self.gamepad_axes.iter().any(|&a| a.abs() > 0.1) {
298            return true;
299        }
300        #[cfg(not(target_arch = "wasm32"))]
301        {
302            if self.gamepad_buttons.iter().any(|&b| b) {
303                return true;
304            }
305        }
306        false
307    }
308
309    //? Called at the start of each frame. Saves previous state, rebuilds
310    //? actions from raw keyboard/mouse/gamepad, and polls gamepad events.
311    //? Also buffers new pressed actions and cleans up old buffer entries.
312    pub fn begin_frame(&mut self, delta_time: f32) {
313        self.actions_prev.clone_from(&self.actions);
314        self.current_time += delta_time;
315
316        //? Rebuild action state from raw keyboard state
317        self.actions.fill(false);
318        for (&key, &action) in &self.input_map.keyboard_map {
319            if self.keys[key as usize] {
320                self.actions[action.index()] = true;
321            }
322        }
323
324        //? Apply mouse button bindings from map
325        for (&binding, &action) in &self.input_map.mouse_map {
326            let idx = match binding {
327                MouseBinding::Left => 0,
328                MouseBinding::Right => 1,
329                MouseBinding::Middle => 2,
330            };
331            if self.mouse_buttons[idx] {
332                self.actions[action.index()] = true;
333            }
334        }
335
336        //? Poll and apply gamepad events (native only)
337        #[cfg(not(target_arch = "wasm32"))]
338        {
339            if let Some(ref mut gilrs) = self.gilrs {
340                while let Some(Event { event, .. }) = gilrs.next_event() {
341                    match event {
342                        EventType::ButtonPressed(button, _) => {
343                            if button == Button::Start {
344                                self.gamepad_start = true;
345                            } else if let Some(action) =
346                                self.input_map.get_action_for_button(button)
347                            {
348                                self.gamepad_buttons[action.index()] = true;
349                            }
350                        }
351                        EventType::ButtonReleased(button, _) => {
352                            if button == Button::Start {
353                                self.gamepad_start = false;
354                            } else if let Some(action) =
355                                self.input_map.get_action_for_button(button)
356                            {
357                                self.gamepad_buttons[action.index()] = false;
358                            }
359                        }
360                        EventType::AxisChanged(Axis::LeftStickX, value, _) => {
361                            self.gamepad_axes[0] = value;
362                        }
363                        EventType::AxisChanged(Axis::LeftStickY, value, _) => {
364                            self.gamepad_axes[1] = -value;
365                        }
366                        _ => {}
367                    }
368                }
369            }
370
371            //? Merge persistent gamepad button state into actions
372            let action_count = A::count();
373            for i in 0..action_count {
374                if self.gamepad_buttons[i] {
375                    self.actions[i] = true;
376                }
377            }
378        }
379
380        //? Buffer freshly pressed actions (for input buffering)
381        let action_count = A::count();
382        for i in 0..action_count {
383            if self.actions[i]
384                && !self.actions_prev[i]
385                && let Some(action) = A::from_index(i)
386            {
387                self.input_buffer.push(BufferedInput {
388                    action,
389                    time_pressed: self.current_time,
390                });
391            }
392        }
393
394        //? Clean up old buffer entries (older than 1 second)
395        self.input_buffer
396            .retain(|buffered| self.current_time - buffered.time_pressed < 1.0);
397    }
398
399    pub fn end_frame(&mut self) {
400        self.keys_prev = self.keys;
401        #[cfg(not(target_arch = "wasm32"))]
402        {
403            self.gamepad_start_prev = self.gamepad_start;
404        }
405    }
406
407    //? Handle winit keyboard events (updates raw key state only).
408    pub(crate) fn handle_key_event(&mut self, event: &KeyEvent) {
409        let pressed = event.state == ElementState::Pressed;
410
411        if let PhysicalKey::Code(keycode) = event.physical_key
412            && let Some(key) = Key::from_keycode(keycode)
413        {
414            self.keys[key as usize] = pressed;
415        }
416    }
417
418    //? Handle mouse button events (updates raw mouse state only).
419    pub(crate) fn handle_mouse_button(&mut self, button: MouseButton, pressed: bool) {
420        match button {
421            MouseButton::Left => self.mouse_buttons[0] = pressed,
422            MouseButton::Right => self.mouse_buttons[1] = pressed,
423            MouseButton::Middle => self.mouse_buttons[2] = pressed,
424            _ => {}
425        }
426    }
427
428    pub fn input_map_mut(&mut self) -> &mut InputMap<A> {
429        &mut self.input_map
430    }
431}
432
433impl<A: GameAction> Default for InputState<A> {
434    fn default() -> Self {
435        Self::new()
436    }
437}