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    input_map: InputMap<A>,
148
149    #[cfg(not(target_arch = "wasm32"))]
150    gilrs: Option<Gilrs>,
151    input_buffer: Vec<BufferedInput<A>>,
152    current_time: f32,
153}
154
155impl<A: GameAction> InputState<A> {
156    pub fn new() -> Self {
157        let action_count = A::count();
158        Self {
159            keys: [false; 13],
160            keys_prev: [false; 13],
161            actions: vec![false; action_count],
162            actions_prev: vec![false; action_count],
163            mouse_buttons: [false; 3],
164            gamepad_axes: [0.0; 2],
165            #[cfg(not(target_arch = "wasm32"))]
166            gamepad_buttons: vec![false; action_count],
167            input_map: InputMap::new(),
168
169            #[cfg(not(target_arch = "wasm32"))]
170            gilrs: match Gilrs::new() {
171                Ok(g) => Some(g),
172                Err(e) => {
173                    log::warn!("Failed to initialize gamepad support: {e}");
174                    None
175                }
176            },
177            input_buffer: Vec::with_capacity(8),
178            current_time: 0.0,
179        }
180    }
181
182    pub fn is_action_pressed(&self, action: A) -> bool {
183        self.actions[action.index()]
184    }
185
186    pub fn is_action_just_pressed(&self, action: A) -> bool {
187        let idx = action.index();
188        self.actions[idx] && !self.actions_prev[idx]
189    }
190
191    pub fn was_action_pressed_buffered(&self, action: A, buffer_window: f32) -> bool {
192        if self.is_action_just_pressed(action) {
193            return true;
194        }
195
196        for buffered in &self.input_buffer {
197            if buffered.action == action {
198                let elapsed = self.current_time - buffered.time_pressed;
199                if elapsed <= buffer_window {
200                    return true;
201                }
202            }
203        }
204
205        false
206    }
207
208    //? Check if a raw key is pressed (for non-action keys like Escape).
209    #[allow(dead_code)]
210    pub fn is_key_pressed(&self, key: Key) -> bool {
211        self.keys[key as usize]
212    }
213
214    //? Check if a raw key was just pressed this frame.
215    #[allow(dead_code)]
216    pub fn is_key_just_pressed(&self, key: Key) -> bool {
217        let idx = key as usize;
218        self.keys[idx] && !self.keys_prev[idx]
219    }
220
221    pub fn is_mouse_pressed(&self, button: MouseButton) -> bool {
222        match button {
223            MouseButton::Left => self.mouse_buttons[0],
224            MouseButton::Right => self.mouse_buttons[1],
225            MouseButton::Middle => self.mouse_buttons[2],
226            _ => false,
227        }
228    }
229
230    //? Get horizontal movement axis (-1.0 to 1.0).
231    pub fn get_move_x(&self) -> f32 {
232        let mut value = 0.0;
233
234        if let Some(left) = A::move_negative_x()
235            && self.is_action_pressed(left)
236        {
237            value -= 1.0;
238        }
239        if let Some(right) = A::move_positive_x()
240            && self.is_action_pressed(right)
241        {
242            value += 1.0;
243        }
244
245        if self.gamepad_axes[0].abs() > 0.1 {
246            value = self.gamepad_axes[0];
247        }
248
249        value
250    }
251
252    //? Get vertical movement axis (-1.0 to 1.0).
253    pub fn get_move_y(&self) -> f32 {
254        let mut value = 0.0;
255
256        if let Some(up) = A::move_negative_y()
257            && self.is_action_pressed(up)
258        {
259            value -= 1.0;
260        }
261        if let Some(down) = A::move_positive_y()
262            && self.is_action_pressed(down)
263        {
264            value += 1.0;
265        }
266
267        if self.gamepad_axes[1].abs() > 0.1 {
268            value = self.gamepad_axes[1];
269        }
270
271        value
272    }
273
274    pub fn any_keyboard_or_mouse(&self) -> bool {
275        self.keys.iter().any(|&k| k) || self.mouse_buttons.iter().any(|&b| b)
276    }
277
278    pub fn any_gamepad(&self) -> bool {
279        if self.gamepad_axes.iter().any(|&a| a.abs() > 0.1) {
280            return true;
281        }
282        #[cfg(not(target_arch = "wasm32"))]
283        {
284            if self.gamepad_buttons.iter().any(|&b| b) {
285                return true;
286            }
287        }
288        false
289    }
290
291    //? Called at the start of each frame. Saves previous state, rebuilds
292    //? actions from raw keyboard/mouse/gamepad, and polls gamepad events.
293    //? Also buffers new pressed actions and cleans up old buffer entries.
294    pub fn begin_frame(&mut self, delta_time: f32) {
295        self.actions_prev.clone_from(&self.actions);
296        self.current_time += delta_time;
297
298        //? Rebuild action state from raw keyboard state
299        self.actions.fill(false);
300        for (&key, &action) in &self.input_map.keyboard_map {
301            if self.keys[key as usize] {
302                self.actions[action.index()] = true;
303            }
304        }
305
306        //? Apply mouse button bindings from map
307        for (&binding, &action) in &self.input_map.mouse_map {
308            let idx = match binding {
309                MouseBinding::Left => 0,
310                MouseBinding::Right => 1,
311                MouseBinding::Middle => 2,
312            };
313            if self.mouse_buttons[idx] {
314                self.actions[action.index()] = true;
315            }
316        }
317
318        //? Poll and apply gamepad events (native only)
319        #[cfg(not(target_arch = "wasm32"))]
320        {
321            if let Some(ref mut gilrs) = self.gilrs {
322                while let Some(Event { event, .. }) = gilrs.next_event() {
323                    match event {
324                        EventType::ButtonPressed(button, _) => {
325                            if let Some(action) = self.input_map.get_action_for_button(button) {
326                                self.gamepad_buttons[action.index()] = true;
327                            }
328                        }
329                        EventType::ButtonReleased(button, _) => {
330                            if let Some(action) = self.input_map.get_action_for_button(button) {
331                                self.gamepad_buttons[action.index()] = false;
332                            }
333                        }
334                        EventType::AxisChanged(Axis::LeftStickX, value, _) => {
335                            self.gamepad_axes[0] = value;
336                        }
337                        EventType::AxisChanged(Axis::LeftStickY, value, _) => {
338                            self.gamepad_axes[1] = -value;
339                        }
340                        _ => {}
341                    }
342                }
343            }
344
345            //? Merge persistent gamepad button state into actions
346            let action_count = A::count();
347            for i in 0..action_count {
348                if self.gamepad_buttons[i] {
349                    self.actions[i] = true;
350                }
351            }
352        }
353
354        //? Buffer freshly pressed actions (for input buffering)
355        let action_count = A::count();
356        for i in 0..action_count {
357            if self.actions[i] && !self.actions_prev[i]
358                && let Some(action) = A::from_index(i)
359            {
360                self.input_buffer.push(BufferedInput {
361                    action,
362                    time_pressed: self.current_time,
363                });
364            }
365        }
366
367        //? Clean up old buffer entries (older than 1 second)
368        self.input_buffer
369            .retain(|buffered| self.current_time - buffered.time_pressed < 1.0);
370    }
371
372    pub fn end_frame(&mut self) {
373        self.keys_prev = self.keys;
374    }
375
376    //? Handle winit keyboard events (updates raw key state only).
377    pub(crate) fn handle_key_event(&mut self, event: &KeyEvent) {
378        let pressed = event.state == ElementState::Pressed;
379
380        if let PhysicalKey::Code(keycode) = event.physical_key
381            && let Some(key) = Key::from_keycode(keycode)
382        {
383            self.keys[key as usize] = pressed;
384        }
385    }
386
387    //? Handle mouse button events (updates raw mouse state only).
388    pub(crate) fn handle_mouse_button(&mut self, button: MouseButton, pressed: bool) {
389        match button {
390            MouseButton::Left => self.mouse_buttons[0] = pressed,
391            MouseButton::Right => self.mouse_buttons[1] = pressed,
392            MouseButton::Middle => self.mouse_buttons[2] = pressed,
393            _ => {}
394        }
395    }
396
397    pub fn input_map_mut(&mut self) -> &mut InputMap<A> {
398        &mut self.input_map
399    }
400}
401
402impl<A: GameAction> Default for InputState<A> {
403    fn default() -> Self {
404        Self::new()
405    }
406}