ui_input_state/
primary_pointer_state.rs

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