Skip to main content

mulciber_runtime/
input.rs

1use mulciber_platform::{
2    ButtonState, InputEvent, KeyCode, LogicalPosition, Modifiers, PointerButton, ScrollDelta,
3};
4
5/// One scroll transition retained in the frame input snapshot.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct ScrollSample {
8    delta: ScrollDelta,
9    position: LogicalPosition,
10}
11
12impl ScrollSample {
13    /// Returns the precise or coarse scroll delta supplied by the platform.
14    #[must_use]
15    pub const fn delta(self) -> ScrollDelta {
16        self.delta
17    }
18
19    /// Returns the pointer position associated with the scroll transition.
20    #[must_use]
21    pub const fn position(self) -> LogicalPosition {
22        self.position
23    }
24}
25
26/// Held controls and transitions accumulated for one game frame.
27#[derive(Debug)]
28pub struct InputSnapshot {
29    focused: bool,
30    modifiers: Modifiers,
31    pointer_position: Option<LogicalPosition>,
32    held_keys: Vec<KeyCode>,
33    pressed_keys: Vec<KeyCode>,
34    released_keys: Vec<KeyCode>,
35    held_pointer_buttons: Vec<PointerButton>,
36    pressed_pointer_buttons: Vec<PointerButton>,
37    released_pointer_buttons: Vec<PointerButton>,
38    scroll: Vec<ScrollSample>,
39}
40
41impl Default for InputSnapshot {
42    fn default() -> Self {
43        Self {
44            focused: true,
45            modifiers: Modifiers::default(),
46            pointer_position: None,
47            held_keys: Vec::new(),
48            pressed_keys: Vec::new(),
49            released_keys: Vec::new(),
50            held_pointer_buttons: Vec::new(),
51            pressed_pointer_buttons: Vec::new(),
52            released_pointer_buttons: Vec::new(),
53            scroll: Vec::new(),
54        }
55    }
56}
57
58impl InputSnapshot {
59    /// Returns whether the application window currently has keyboard focus.
60    #[must_use]
61    pub const fn focused(&self) -> bool {
62        self.focused
63    }
64
65    /// Returns the latest aggregate modifier state.
66    #[must_use]
67    pub const fn modifiers(&self) -> Modifiers {
68        self.modifiers
69    }
70
71    /// Returns the latest known pointer position in logical window coordinates.
72    #[must_use]
73    pub const fn pointer_position(&self) -> Option<LogicalPosition> {
74        self.pointer_position
75    }
76
77    /// Returns whether `key` is currently held.
78    #[must_use]
79    pub fn key_held(&self, key: KeyCode) -> bool {
80        self.held_keys.contains(&key)
81    }
82
83    /// Returns whether `key` became pressed during the current frame.
84    #[must_use]
85    pub fn key_pressed(&self, key: KeyCode) -> bool {
86        self.pressed_keys.contains(&key)
87    }
88
89    /// Returns whether `key` became released during the current frame.
90    #[must_use]
91    pub fn key_released(&self, key: KeyCode) -> bool {
92        self.released_keys.contains(&key)
93    }
94
95    /// Returns whether `button` is currently held.
96    #[must_use]
97    pub fn pointer_button_held(&self, button: PointerButton) -> bool {
98        self.held_pointer_buttons.contains(&button)
99    }
100
101    /// Returns whether `button` became pressed during the current frame.
102    #[must_use]
103    pub fn pointer_button_pressed(&self, button: PointerButton) -> bool {
104        self.pressed_pointer_buttons.contains(&button)
105    }
106
107    /// Returns whether `button` became released during the current frame.
108    #[must_use]
109    pub fn pointer_button_released(&self, button: PointerButton) -> bool {
110        self.released_pointer_buttons.contains(&button)
111    }
112
113    /// Returns the ordered scroll transitions accumulated during the current frame.
114    #[must_use]
115    pub fn scroll(&self) -> &[ScrollSample] {
116        &self.scroll
117    }
118
119    pub(crate) fn handle_event(&mut self, event: InputEvent) {
120        match event {
121            InputEvent::FocusChanged { focused } => self.set_focus(focused),
122            InputEvent::Keyboard {
123                key,
124                state,
125                modifiers,
126                ..
127            } => {
128                self.modifiers = modifiers;
129                update_button(
130                    key,
131                    state,
132                    &mut self.held_keys,
133                    &mut self.pressed_keys,
134                    &mut self.released_keys,
135                );
136            }
137            InputEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
138            InputEvent::PointerMoved {
139                position,
140                modifiers,
141            } => {
142                self.pointer_position = Some(position);
143                self.modifiers = modifiers;
144            }
145            InputEvent::PointerButton {
146                button,
147                state,
148                position,
149                modifiers,
150            } => {
151                self.pointer_position = Some(position);
152                self.modifiers = modifiers;
153                update_button(
154                    button,
155                    state,
156                    &mut self.held_pointer_buttons,
157                    &mut self.pressed_pointer_buttons,
158                    &mut self.released_pointer_buttons,
159                );
160            }
161            InputEvent::Scroll {
162                delta,
163                position,
164                modifiers,
165            } => {
166                self.pointer_position = Some(position);
167                self.modifiers = modifiers;
168                self.scroll.push(ScrollSample { delta, position });
169            }
170            _ => {}
171        }
172    }
173
174    fn set_focus(&mut self, focused: bool) {
175        self.focused = focused;
176        if !focused {
177            self.release_all();
178        }
179    }
180
181    pub(crate) fn release_all(&mut self) {
182        self.released_keys.append(&mut self.held_keys);
183        self.released_pointer_buttons
184            .append(&mut self.held_pointer_buttons);
185        self.modifiers = Modifiers::default();
186    }
187
188    pub(crate) fn end_frame(&mut self) {
189        self.pressed_keys.clear();
190        self.released_keys.clear();
191        self.pressed_pointer_buttons.clear();
192        self.released_pointer_buttons.clear();
193        self.scroll.clear();
194    }
195}
196
197fn update_button<T: Copy + PartialEq>(
198    button: T,
199    state: ButtonState,
200    held: &mut Vec<T>,
201    pressed: &mut Vec<T>,
202    released: &mut Vec<T>,
203) {
204    match state {
205        ButtonState::Pressed if !held.contains(&button) => {
206            held.push(button);
207            pressed.push(button);
208        }
209        ButtonState::Released => {
210            if let Some(index) = held.iter().position(|candidate| *candidate == button) {
211                held.swap_remove(index);
212                released.push(button);
213            }
214        }
215        ButtonState::Pressed => {}
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use mulciber_platform::{
222        ButtonState, InputEvent, KeyCode, LogicalPosition, Modifiers, PointerButton, ScrollDelta,
223    };
224
225    use super::InputSnapshot;
226
227    fn key(key: KeyCode, state: ButtonState, repeat: bool) -> InputEvent {
228        InputEvent::Keyboard {
229            key,
230            state,
231            repeat,
232            modifiers: Modifiers::default(),
233        }
234    }
235
236    #[test]
237    fn key_transitions_last_one_frame_while_held_state_persists() {
238        let mut input = InputSnapshot::default();
239        input.handle_event(key(KeyCode::KeyW, ButtonState::Pressed, false));
240        assert!(input.key_pressed(KeyCode::KeyW));
241        assert!(input.key_held(KeyCode::KeyW));
242
243        input.end_frame();
244        assert!(!input.key_pressed(KeyCode::KeyW));
245        assert!(input.key_held(KeyCode::KeyW));
246
247        input.handle_event(key(KeyCode::KeyW, ButtonState::Released, false));
248        assert!(input.key_released(KeyCode::KeyW));
249        assert!(!input.key_held(KeyCode::KeyW));
250    }
251
252    #[test]
253    fn repeated_key_events_do_not_create_new_press_transitions() {
254        let mut input = InputSnapshot::default();
255        input.handle_event(key(KeyCode::KeyW, ButtonState::Pressed, false));
256        input.end_frame();
257        input.handle_event(key(KeyCode::KeyW, ButtonState::Pressed, true));
258        assert!(!input.key_pressed(KeyCode::KeyW));
259        assert!(input.key_held(KeyCode::KeyW));
260    }
261
262    #[test]
263    fn focus_loss_releases_every_held_control() {
264        let mut input = InputSnapshot::default();
265        input.handle_event(key(KeyCode::KeyW, ButtonState::Pressed, false));
266        input.handle_event(InputEvent::PointerButton {
267            button: PointerButton::Primary,
268            state: ButtonState::Pressed,
269            position: LogicalPosition::new(4.0, 7.0),
270            modifiers: Modifiers::default(),
271        });
272        input.end_frame();
273
274        input.handle_event(InputEvent::FocusChanged { focused: false });
275        assert!(!input.focused());
276        assert!(!input.key_held(KeyCode::KeyW));
277        assert!(input.key_released(KeyCode::KeyW));
278        assert!(!input.pointer_button_held(PointerButton::Primary));
279        assert!(input.pointer_button_released(PointerButton::Primary));
280    }
281
282    #[test]
283    fn scroll_preserves_units_order_and_positions() {
284        let mut input = InputSnapshot::default();
285        let position = LogicalPosition::new(4.0, 7.0);
286        input.handle_event(InputEvent::Scroll {
287            delta: ScrollDelta::Precise { x: 1.5, y: -2.0 },
288            position,
289            modifiers: Modifiers::default(),
290        });
291        assert_eq!(input.scroll().len(), 1);
292        assert_eq!(input.scroll()[0].position(), position);
293        assert_eq!(
294            input.scroll()[0].delta(),
295            ScrollDelta::Precise { x: 1.5, y: -2.0 }
296        );
297    }
298}