Skip to main content

libdqg/
input.rs

1use std::collections::HashSet;
2
3use crate::types::KeyCode;
4
5pub struct InputState {
6    keys_held: HashSet<KeyCode>,
7    keys_pressed: HashSet<KeyCode>,
8}
9
10impl InputState {
11    pub fn new() -> Self {
12        Self {
13            keys_held: HashSet::new(),
14            keys_pressed: HashSet::new(),
15        }
16    }
17
18    pub fn handle_event(&mut self, event: &winit::event::KeyEvent) {
19        if let winit::keyboard::PhysicalKey::Code(keycode) = event.physical_key {
20            let native_keycode = match KeyCode::from_winit_keycode(keycode) {
21                Some(keycode) => keycode,
22                None => todo!("Handle unmapped keycode: {:?}", keycode),
23            };
24            if event.state.is_pressed() {
25                if !self.keys_held.contains(&native_keycode) {
26                    self.keys_pressed.insert(native_keycode);
27                }
28                self.keys_held.insert(native_keycode);
29            } else {
30                self.keys_held.remove(&native_keycode);
31            }
32        }
33    }
34
35    pub fn clear_frame_states(&mut self) {
36        self.keys_pressed.clear();
37    }
38
39    pub fn is_key_held(&self, keycode: KeyCode) -> bool {
40        self.keys_held.contains(&keycode)
41    }
42
43    pub fn is_key_pressed(&self, keycode: KeyCode) -> bool {
44        self.keys_pressed.contains(&keycode)
45    }
46
47    pub fn keys_pressed(&self) -> impl Iterator<Item = KeyCode> + '_ {
48        self.keys_pressed.iter().copied()
49    }
50
51    pub fn keys_held(&self) -> impl Iterator<Item = KeyCode> + '_ {
52        self.keys_held.iter().copied()
53    }
54
55    /// Builds an [`InputState`] with a fixed held/pressed set directly, bypassing
56    /// [`InputState::handle_event`]'s `winit::event::KeyEvent` plumbing — there's no lightweight
57    /// way to construct a real one outside of an actual window/event loop, and tests (e.g.
58    /// `core::scripting`'s input-scripting tests) just need specific keys reported as
59    /// held/pressed.
60    #[cfg(test)]
61    pub(crate) fn for_test(held: impl IntoIterator<Item = KeyCode>, pressed: impl IntoIterator<Item = KeyCode>) -> Self {
62        Self { keys_held: held.into_iter().collect(), keys_pressed: pressed.into_iter().collect() }
63    }
64}
65
66/// Per-frame mouse state: cursor position, button press/hold, and wheel motion.
67///
68/// Mirrors [`InputState`] but is tracked separately since it's fed from a different set of
69/// `winit` events and updated by different call sites in `App`.
70pub struct MouseState {
71    position: (f32, f32),
72    buttons_held: HashSet<winit::event::MouseButton>,
73    buttons_pressed: HashSet<winit::event::MouseButton>,
74    wheel_delta: f32,
75}
76
77impl MouseState {
78    pub fn new() -> Self {
79        Self {
80            position: (0.0, 0.0),
81            buttons_held: HashSet::new(),
82            buttons_pressed: HashSet::new(),
83            wheel_delta: 0.0,
84        }
85    }
86
87    pub fn set_position(&mut self, x: f32, y: f32) {
88        self.position = (x, y);
89    }
90
91    pub fn handle_button(&mut self, button: winit::event::MouseButton, state: winit::event::ElementState) {
92        if state.is_pressed() {
93            if !self.buttons_held.contains(&button) {
94                self.buttons_pressed.insert(button);
95            }
96            self.buttons_held.insert(button);
97        } else {
98            self.buttons_held.remove(&button);
99        }
100    }
101
102    pub fn handle_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
103        self.wheel_delta += match delta {
104            winit::event::MouseScrollDelta::LineDelta(_, y) => y,
105            winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 120.0,
106        };
107    }
108
109    pub fn clear_frame_states(&mut self) {
110        self.buttons_pressed.clear();
111        self.wheel_delta = 0.0;
112    }
113
114    pub fn position(&self) -> (f32, f32) {
115        self.position
116    }
117
118    pub fn is_button_held(&self, button: winit::event::MouseButton) -> bool {
119        self.buttons_held.contains(&button)
120    }
121
122    pub fn is_button_pressed(&self, button: winit::event::MouseButton) -> bool {
123        self.buttons_pressed.contains(&button)
124    }
125
126    pub fn buttons_held(&self) -> impl Iterator<Item = winit::event::MouseButton> + '_ {
127        self.buttons_held.iter().copied()
128    }
129
130    pub fn buttons_pressed(&self) -> impl Iterator<Item = winit::event::MouseButton> + '_ {
131        self.buttons_pressed.iter().copied()
132    }
133
134    pub fn wheel_delta(&self) -> f32 {
135        self.wheel_delta
136    }
137
138    /// Builds a [`MouseState`] with fixed position/held/pressed/wheel values directly, the mouse
139    /// counterpart to [`InputState::for_test`] — see its doc comment for why this exists instead
140    /// of driving one through real `winit` events.
141    #[cfg(test)]
142    pub(crate) fn for_test(
143        position: (f32, f32),
144        held: impl IntoIterator<Item = winit::event::MouseButton>,
145        pressed: impl IntoIterator<Item = winit::event::MouseButton>,
146        wheel_delta: f32,
147    ) -> Self {
148        Self { position, buttons_held: held.into_iter().collect(), buttons_pressed: pressed.into_iter().collect(), wheel_delta }
149    }
150}