ui_input_state/
primary_pointer_state.rs

1// Copyright 2025 the UI Events Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4extern crate alloc;
5use alloc::vec::Vec;
6
7use ui_events::pointer::{
8    PointerButton, PointerButtonEvent, PointerButtons, PointerEvent, PointerState, PointerUpdate,
9};
10
11use dpi::{LogicalPosition, PhysicalPosition};
12
13/// A stateful view of the primary pointer.
14#[derive(Clone, Debug, Default)]
15pub struct PrimaryPointerState {
16    /// Buttons that were pressed during the current frame.
17    just_pressed: PointerButtons,
18    /// Buttons that were released during the current frame.
19    just_released: PointerButtons,
20    /// Current state.
21    current: PointerState,
22    /// Coalesced states, ordered by `time`.
23    coalesced: Vec<PointerState>,
24    /// Predicted states, ordered by `time`.
25    predicted: Vec<PointerState>,
26}
27
28impl PrimaryPointerState {
29    /// Return `true` if the `button` was pressed within the last frame.
30    ///
31    /// This corresponds to having received a [`PointerEvent::Down`] event
32    /// for that button on the primary pointing device.
33    pub fn is_just_pressed(&self, button: PointerButton) -> bool {
34        self.just_pressed.contains(button)
35    }
36
37    /// Return `true` if the `button` was released within the last frame.
38    ///
39    /// This corresponds to having received a [`PointerEvent::Up`] event
40    /// for that button on the primary pointing device.
41    pub fn is_just_released(&self, button: PointerButton) -> bool {
42        self.just_released.contains(button)
43    }
44
45    /// Return `true` if the Auxiliary button (usually middle mouse) was
46    /// pressed within the last frame.
47    pub fn is_auxiliary_just_pressed(&self) -> bool {
48        self.is_just_pressed(PointerButton::Auxiliary)
49    }
50
51    /// Return `true` if the Auxiliary button was released within the last frame.
52    pub fn is_auxiliary_just_released(&self) -> bool {
53        self.is_just_released(PointerButton::Auxiliary)
54    }
55
56    /// Return `true` if the Primary button (usually left mouse) was
57    /// pressed within the last frame.
58    pub fn is_primary_just_pressed(&self) -> bool {
59        self.is_just_pressed(PointerButton::Primary)
60    }
61
62    /// Return `true` if the Primary button was released within the last frame.
63    pub fn is_primary_just_released(&self) -> bool {
64        self.is_just_released(PointerButton::Primary)
65    }
66
67    /// Return `true` if the Secondary button (usually right mouse) was
68    /// pressed within the last frame.
69    pub fn is_secondary_just_pressed(&self) -> bool {
70        self.is_just_pressed(PointerButton::Secondary)
71    }
72
73    /// Return `true` if the Secondary button was released within the last frame.
74    pub fn is_secondary_just_released(&self) -> bool {
75        self.is_just_released(PointerButton::Secondary)
76    }
77
78    /// Return `true` if any button is currently held down.
79    pub fn is_any_down(&self) -> bool {
80        !self.current.buttons.is_empty()
81    }
82
83    /// Return `true` if the specified `button` is currently held down.
84    pub fn is_down(&self, button: PointerButton) -> bool {
85        self.current.buttons.contains(button)
86    }
87
88    /// Clear the per-frame state to prepare for a new frame.
89    pub fn clear_frame(&mut self) {
90        self.just_pressed.clear();
91        self.just_released.clear();
92        self.coalesced.clear();
93        // TODO: Persist predicted states that are not yet stale.
94        self.predicted.clear();
95    }
96
97    /// Current position.
98    ///
99    /// This will only give known positions.
100    pub fn current_position(&self) -> PhysicalPosition<f64> {
101        self.current.position
102    }
103
104    /// Current position (in logical units).
105    ///
106    /// This will only give known positions.
107    pub fn current_logical_position(&self) -> LogicalPosition<f64> {
108        self.current.logical_position()
109    }
110
111    /// Relative motion this frame.
112    pub fn motion(&self) -> PhysicalPosition<f64> {
113        let current = self.current.position;
114        let first = self
115            .coalesced
116            .first()
117            .map(|s| s.position)
118            .unwrap_or(current);
119        PhysicalPosition {
120            x: current.x - first.x,
121            y: current.y - first.y,
122        }
123    }
124
125    /// Relative motion this frame.
126    pub fn logical_motion(&self) -> LogicalPosition<f64> {
127        let current = self.current.logical_position();
128        let first = self
129            .coalesced
130            .first()
131            .map(|s| s.logical_position())
132            .unwrap_or(current);
133        LogicalPosition {
134            x: current.x - first.x,
135            y: current.y - first.y,
136        }
137    }
138
139    /// Push a state and coalesce the existing one if it is not the initial state.
140    fn push_state(&mut self, state: PointerState) {
141        if state.time != 0 {
142            // If `time` is 0, this is the initial state.
143            self.coalesced.push(state);
144        }
145    }
146
147    /// Update the state based on the given pointer event.
148    ///
149    /// Only events from the primary pointer are processed. Press and release
150    /// events update the `just_pressed`, `just_released`, and `down` states.
151    pub fn process_pointer_event(&mut self, event: PointerEvent) {
152        if !event.is_primary_pointer() {
153            return;
154        }
155
156        match event {
157            PointerEvent::Down(PointerButtonEvent {
158                button: Some(b),
159                state,
160                ..
161            }) => {
162                self.just_pressed.insert(b);
163                let mut state = state.clone();
164                core::mem::swap(&mut self.current, &mut state);
165                self.push_state(state);
166                // TODO: Propagate button state to predicted states.
167                self.predicted.clear();
168            }
169            PointerEvent::Up(PointerButtonEvent {
170                button: Some(b),
171                state,
172                ..
173            }) => {
174                self.just_released.insert(b);
175                let mut state = state.clone();
176                core::mem::swap(&mut self.current, &mut state);
177                self.push_state(state);
178                // TODO: Propagate button state to predicted states.
179                self.predicted.clear();
180            }
181            PointerEvent::Move(PointerUpdate {
182                current,
183                coalesced,
184                predicted,
185                ..
186            }) => {
187                self.coalesced.push(self.current.clone());
188                self.current = current.clone();
189                self.coalesced.extend(coalesced);
190                self.predicted.clear();
191                self.predicted.extend(predicted);
192            }
193            PointerEvent::Cancel(_) | PointerEvent::Leave(_) => {
194                // TODO: Validate these behaviors.
195                self.predicted.clear();
196                self.coalesced.clear();
197                self.current.buttons.clear();
198            }
199            _ => {}
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use alloc::vec;
208    use ui_events::pointer::{
209        PointerButtonEvent, PointerEvent, PointerId, PointerInfo, PointerState, PointerType,
210    };
211
212    /// Get a monotonically increasing time.
213    fn phony_time() -> u64 {
214        use core::sync::atomic::AtomicU64;
215        use core::sync::atomic::Ordering;
216        static TIME: AtomicU64 = AtomicU64::new(0);
217        TIME.fetch_add(1, Ordering::SeqCst)
218    }
219
220    fn make_down_event(button: PointerButton) -> PointerEvent {
221        PointerEvent::Down(PointerButtonEvent {
222            button: Some(button),
223            pointer: PointerInfo {
224                pointer_id: Some(PointerId::PRIMARY),
225                persistent_device_id: None,
226                pointer_type: PointerType::Mouse,
227            },
228            state: PointerState {
229                time: phony_time(),
230                buttons: button.into(),
231                ..Default::default()
232            },
233        })
234    }
235
236    fn make_up_event(button: PointerButton) -> PointerEvent {
237        PointerEvent::Up(PointerButtonEvent {
238            button: Some(button),
239            pointer: PointerInfo {
240                pointer_id: Some(PointerId::PRIMARY),
241                persistent_device_id: None,
242                pointer_type: PointerType::Mouse,
243            },
244            state: PointerState {
245                time: phony_time(),
246                ..Default::default()
247            },
248        })
249    }
250
251    #[test]
252    fn press_and_hold_primary() {
253        let mut state = PrimaryPointerState::default();
254        state.process_pointer_event(make_down_event(PointerButton::Primary));
255
256        assert!(state.is_primary_just_pressed());
257        assert!(state.is_down(PointerButton::Primary));
258        assert!(!state.is_primary_just_released());
259
260        state.clear_frame();
261
262        assert!(!state.is_primary_just_pressed());
263        assert!(state.is_down(PointerButton::Primary));
264    }
265
266    #[test]
267    fn press_and_release_primary_same_frame() {
268        let mut state = PrimaryPointerState::default();
269        state.process_pointer_event(make_down_event(PointerButton::Primary));
270        state.process_pointer_event(make_up_event(PointerButton::Primary));
271
272        assert!(state.is_primary_just_pressed());
273        assert!(state.is_primary_just_released());
274        assert!(!state.is_down(PointerButton::Primary));
275    }
276
277    #[test]
278    fn release_after_hold() {
279        let mut state = PrimaryPointerState::default();
280        state.process_pointer_event(make_down_event(PointerButton::Primary));
281        state.clear_frame();
282        state.process_pointer_event(make_up_event(PointerButton::Primary));
283
284        assert!(!state.is_primary_just_pressed());
285        assert!(state.is_primary_just_released());
286        assert!(!state.is_down(PointerButton::Primary));
287    }
288
289    fn make_move_event(
290        position: PhysicalPosition<f64>,
291        coalesced: Vec<PhysicalPosition<f64>>,
292        predicted: Vec<PhysicalPosition<f64>>,
293    ) -> PointerEvent {
294        let coalesced = coalesced
295            .iter()
296            .copied()
297            .map(|position| PointerState {
298                time: phony_time(),
299                position,
300                ..Default::default()
301            })
302            .collect();
303
304        let current = PointerState {
305            time: phony_time(),
306            position,
307            ..Default::default()
308        };
309
310        let predicted = predicted
311            .iter()
312            .copied()
313            .map(|position| PointerState {
314                time: phony_time(),
315                position,
316                ..Default::default()
317            })
318            .collect();
319
320        PointerEvent::Move(PointerUpdate {
321            pointer: PointerInfo {
322                pointer_id: Some(PointerId::PRIMARY),
323                persistent_device_id: None,
324                pointer_type: PointerType::Mouse,
325            },
326            current,
327            coalesced,
328            predicted,
329        })
330    }
331
332    fn make_cancel_event() -> PointerEvent {
333        PointerEvent::Cancel(PointerInfo {
334            pointer_id: Some(PointerId::PRIMARY),
335            persistent_device_id: None,
336            pointer_type: PointerType::Mouse,
337        })
338    }
339
340    fn make_leave_event() -> PointerEvent {
341        PointerEvent::Leave(PointerInfo {
342            pointer_id: Some(PointerId::PRIMARY),
343            persistent_device_id: None,
344            pointer_type: PointerType::Mouse,
345        })
346    }
347
348    #[test]
349    fn down_updates_current_buttons() {
350        let mut state = PrimaryPointerState::default();
351        state.process_pointer_event(make_down_event(PointerButton::Primary));
352
353        assert!(state.current.buttons.contains(PointerButton::Primary));
354        assert!(state.is_down(PointerButton::Primary));
355        assert!(state.is_any_down());
356    }
357
358    #[test]
359    fn up_updates_current_buttons() {
360        let mut state = PrimaryPointerState::default();
361        state.process_pointer_event(make_down_event(PointerButton::Primary));
362        state.process_pointer_event(make_up_event(PointerButton::Primary));
363
364        assert!(!state.current.buttons.contains(PointerButton::Primary));
365        assert!(!state.is_down(PointerButton::Primary));
366        assert!(!state.is_any_down());
367    }
368
369    #[test]
370    fn move_appends_to_coalesced_if_down() {
371        let mut state = PrimaryPointerState::default();
372        state.process_pointer_event(make_down_event(PointerButton::Primary));
373
374        state.process_pointer_event(make_move_event(
375            PhysicalPosition { x: 10.0, y: 10.0 },
376            vec![PhysicalPosition { x: 5.0, y: 5.0 }],
377            vec![],
378        ));
379
380        assert_eq!(state.coalesced.len(), 2);
381        assert_eq!(
382            state.coalesced[1].position,
383            PhysicalPosition { x: 5.0, y: 5.0 }
384        );
385        assert_eq!(
386            state.current.position,
387            PhysicalPosition { x: 10.0, y: 10.0 }
388        );
389    }
390
391    #[test]
392    fn move_appends_to_coalesced_even_if_not_down() {
393        let mut state = PrimaryPointerState::default();
394
395        state.process_pointer_event(make_move_event(
396            PhysicalPosition { x: 10.0, y: 10.0 },
397            vec![PhysicalPosition { x: 5.0, y: 5.0 }],
398            vec![],
399        ));
400
401        assert_eq!(state.coalesced.len(), 2);
402        assert_eq!(state.coalesced[0].position, PhysicalPosition::default());
403        assert_eq!(
404            state.coalesced[1].position,
405            PhysicalPosition { x: 5.0, y: 5.0 }
406        );
407        assert_eq!(
408            state.current.position,
409            PhysicalPosition { x: 10.0, y: 10.0 }
410        );
411    }
412
413    #[test]
414    fn move_sets_predicted() {
415        let mut state = PrimaryPointerState::default();
416
417        state.process_pointer_event(make_move_event(
418            PhysicalPosition { x: 10.0, y: 10.0 },
419            vec![],
420            vec![PhysicalPosition { x: 15.0, y: 15.0 }],
421        ));
422
423        assert_eq!(state.predicted.len(), 1);
424        assert_eq!(
425            state.predicted[0].position,
426            PhysicalPosition { x: 15.0, y: 15.0 }
427        );
428    }
429
430    #[test]
431    fn down_clears_predicted() {
432        let mut state = PrimaryPointerState::default();
433
434        state.process_pointer_event(make_move_event(
435            PhysicalPosition { x: 10.0, y: 10.0 },
436            vec![],
437            vec![PhysicalPosition { x: 15.0, y: 15.0 }],
438        ));
439
440        assert!(!state.predicted.is_empty());
441
442        state.process_pointer_event(make_down_event(PointerButton::Primary));
443
444        assert!(state.predicted.is_empty());
445    }
446
447    #[test]
448    fn up_clears_predicted() {
449        let mut state = PrimaryPointerState::default();
450        state.process_pointer_event(make_down_event(PointerButton::Primary));
451
452        state.process_pointer_event(make_move_event(
453            PhysicalPosition { x: 10.0, y: 10.0 },
454            vec![],
455            vec![PhysicalPosition { x: 15.0, y: 15.0 }],
456        ));
457
458        assert!(!state.predicted.is_empty());
459
460        state.process_pointer_event(make_up_event(PointerButton::Primary));
461
462        assert!(state.predicted.is_empty());
463    }
464
465    #[test]
466    fn cancel_clears_states() {
467        let mut state = PrimaryPointerState::default();
468        state.process_pointer_event(make_down_event(PointerButton::Primary));
469
470        assert!(state.predicted.is_empty());
471        assert!(!state.current.buttons.is_empty());
472
473        state.process_pointer_event(make_cancel_event());
474
475        assert!(state.coalesced.is_empty());
476        assert!(state.predicted.is_empty());
477        assert!(state.current.buttons.is_empty());
478    }
479
480    #[test]
481    fn leave_clears_states() {
482        let mut state = PrimaryPointerState::default();
483        state.process_pointer_event(make_down_event(PointerButton::Primary));
484
485        assert!(state.predicted.is_empty());
486        assert!(!state.current.buttons.is_empty());
487
488        state.process_pointer_event(make_leave_event());
489
490        assert!(state.coalesced.is_empty());
491        assert!(state.predicted.is_empty());
492        assert!(state.current.buttons.is_empty());
493    }
494
495    #[test]
496    fn clear_frame_clears_coalesced_and_predicted() {
497        let mut state = PrimaryPointerState::default();
498
499        state.process_pointer_event(make_move_event(
500            PhysicalPosition { x: 10.0, y: 10.0 },
501            vec![PhysicalPosition { x: 5.0, y: 5.0 }],
502            vec![PhysicalPosition { x: 15.0, y: 15.0 }],
503        ));
504
505        assert!(!state.coalesced.is_empty());
506        assert!(!state.predicted.is_empty());
507
508        state.clear_frame();
509
510        assert!(state.coalesced.is_empty());
511        assert!(state.predicted.is_empty());
512    }
513
514    #[test]
515    fn current_position_and_logical() {
516        let mut state = PrimaryPointerState::default();
517        let position = PhysicalPosition { x: 100.0, y: 200.0 };
518        let scale_factor = 2.0;
519
520        let current = PointerState {
521            time: phony_time(),
522            position,
523            scale_factor,
524            ..Default::default()
525        };
526
527        state.process_pointer_event(PointerEvent::Move(PointerUpdate {
528            pointer: PointerInfo {
529                pointer_id: Some(PointerId::PRIMARY),
530                persistent_device_id: None,
531                pointer_type: PointerType::Mouse,
532            },
533            current,
534            coalesced: vec![],
535            predicted: vec![],
536        }));
537
538        assert_eq!(state.current_position(), position);
539        assert_eq!(
540            state.current_logical_position(),
541            position.to_logical(scale_factor)
542        );
543    }
544
545    #[test]
546    fn motion_with_coalesced() {
547        let mut state = PrimaryPointerState::default();
548
549        state.process_pointer_event(make_move_event(
550            PhysicalPosition { x: 10.0, y: 20.0 },
551            vec![],
552            vec![],
553        ));
554
555        state.process_pointer_event(make_move_event(
556            PhysicalPosition { x: 30.0, y: 40.0 },
557            vec![PhysicalPosition { x: 15.0, y: 25.0 }],
558            vec![],
559        ));
560
561        assert_eq!(state.motion(), PhysicalPosition { x: 30.0, y: 40.0 });
562        assert_eq!(state.logical_motion(), LogicalPosition { x: 30.0, y: 40.0 });
563    }
564
565    #[test]
566    fn motion_without_coalesced() {
567        let mut state = PrimaryPointerState::default();
568
569        state.process_pointer_event(make_move_event(
570            PhysicalPosition { x: 30.0, y: 40.0 },
571            vec![],
572            vec![],
573        ));
574
575        assert_eq!(state.motion(), PhysicalPosition { x: 30.0, y: 40.0 });
576        assert_eq!(state.logical_motion(), LogicalPosition { x: 30.0, y: 40.0 });
577    }
578}