1use mulciber_platform::{
2 ButtonState, InputEvent, KeyCode, LogicalPosition, Modifiers, PointerButton, ScrollDelta,
3};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct ScrollSample {
8 delta: ScrollDelta,
9 position: LogicalPosition,
10}
11
12impl ScrollSample {
13 #[must_use]
15 pub const fn delta(self) -> ScrollDelta {
16 self.delta
17 }
18
19 #[must_use]
21 pub const fn position(self) -> LogicalPosition {
22 self.position
23 }
24}
25
26#[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 #[must_use]
61 pub const fn focused(&self) -> bool {
62 self.focused
63 }
64
65 #[must_use]
67 pub const fn modifiers(&self) -> Modifiers {
68 self.modifiers
69 }
70
71 #[must_use]
73 pub const fn pointer_position(&self) -> Option<LogicalPosition> {
74 self.pointer_position
75 }
76
77 #[must_use]
79 pub fn key_held(&self, key: KeyCode) -> bool {
80 self.held_keys.contains(&key)
81 }
82
83 #[must_use]
85 pub fn key_pressed(&self, key: KeyCode) -> bool {
86 self.pressed_keys.contains(&key)
87 }
88
89 #[must_use]
91 pub fn key_released(&self, key: KeyCode) -> bool {
92 self.released_keys.contains(&key)
93 }
94
95 #[must_use]
97 pub fn pointer_button_held(&self, button: PointerButton) -> bool {
98 self.held_pointer_buttons.contains(&button)
99 }
100
101 #[must_use]
103 pub fn pointer_button_pressed(&self, button: PointerButton) -> bool {
104 self.pressed_pointer_buttons.contains(&button)
105 }
106
107 #[must_use]
109 pub fn pointer_button_released(&self, button: PointerButton) -> bool {
110 self.released_pointer_buttons.contains(&button)
111 }
112
113 #[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}