ui_events/pointer/
mod.rs

1// Copyright 2025 the UI Events Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Pointer Event Types
5
6mod buttons;
7
8pub use buttons::{PointerButton, PointerButtons};
9
10extern crate alloc;
11use alloc::vec::Vec;
12
13use core::num::NonZeroU64;
14
15use dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
16use keyboard_types::Modifiers;
17
18use crate::ScrollDelta;
19
20/// A unique identifier for the pointer.
21///
22/// PointerId(1) is reserved for the primary pointer.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub struct PointerId(NonZeroU64);
25
26impl PointerId {
27    /// The id of the primary pointer.
28    pub const PRIMARY: Self = Self(NonZeroU64::MIN);
29
30    /// Make a new `PointerId` from a `u64`.
31    #[inline(always)]
32    pub fn new(n: u64) -> Option<Self> {
33        NonZeroU64::new(n).map(PointerId)
34    }
35
36    /// Return `true` if this is the primary `PointerId`.
37    #[inline(always)]
38    pub fn is_primary_pointer(self) -> bool {
39        self == Self::PRIMARY
40    }
41}
42
43/// An identifier for the pointing device that is stable across the session.
44///
45/// PointerId(1) is reserved for the primary pointer.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
47pub struct PersistentDeviceId(NonZeroU64);
48
49impl PersistentDeviceId {
50    /// Make a new `PersistentDeviceId` from a `u64`.
51    #[inline(always)]
52    pub fn new(n: u64) -> Option<Self> {
53        NonZeroU64::new(n).map(PersistentDeviceId)
54    }
55}
56
57/// The type of device that has generated a pointer event.
58#[non_exhaustive]
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
60#[repr(u8)]
61pub enum PointerType {
62    /// The type of device could not be determined.
63    #[default]
64    Unknown,
65    /// A mouse.
66    Mouse,
67    /// A pen or stylus.
68    Pen,
69    /// A touch contact.
70    Touch,
71}
72
73/// Identifying information about a pointer, stable across states.
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
75pub struct PointerInfo {
76    /// Pointer ID.
77    ///
78    /// [`PointerId::PRIMARY`] is reserved for the primary pointer,
79    /// so when converting platform pointer IDs on a platform that
80    /// does not reserve this value, add an offset to avoid collision.
81    ///
82    /// `None` is for events not originating from a pointing device.
83    pub pointer_id: Option<PointerId>,
84    /// Persistent device ID.
85    ///
86    /// This should be set when the platform can identify a given pointing
87    /// device during the whole session, and associate it with events.
88    /// If this is not possible for the given event, it should be `None`.
89    pub persistent_device_id: Option<PersistentDeviceId>,
90    /// Pointer type.
91    pub pointer_type: PointerType,
92}
93
94impl PointerInfo {
95    /// Returns `true` if this is the primary pointer.
96    #[inline(always)]
97    pub fn is_primary_pointer(&self) -> bool {
98        self.pointer_id.is_some_and(PointerId::is_primary_pointer)
99    }
100}
101
102/// Orientation of a pointer.
103#[derive(Clone, Copy, Debug, PartialEq)]
104pub struct PointerOrientation {
105    /// Spherical altitude.
106    ///
107    /// 0 is parallel to the surface, π/2 is perpendicular.
108    pub altitude: f32,
109    /// Spherical azimuth.
110    ///
111    /// 0 is the positive x axis, π/2 is positive y.
112    pub azimuth: f32,
113}
114
115impl Default for PointerOrientation {
116    fn default() -> Self {
117        Self {
118            altitude: core::f32::consts::FRAC_PI_2,
119            azimuth: core::f32::consts::FRAC_PI_2,
120        }
121    }
122}
123
124/// The size of an input, usually touch.
125///
126/// If this is not provided by the underlying API, platform, or device,
127/// then it should be a single pixel.
128pub type ContactGeometry = PhysicalSize<f64>;
129
130/// A single pointer state.
131#[derive(Clone, Debug, PartialEq)]
132pub struct PointerState {
133    /// `u64` nanoseconds real time.
134    ///
135    /// The base time is not important, except by convention, and should
136    /// generally be the same at least for states originating from the
137    /// same device.
138    pub time: u64,
139    /// Position.
140    pub position: PhysicalPosition<f64>,
141    /// Pressed buttons.
142    pub buttons: PointerButtons,
143    /// Modifiers state.
144    pub modifiers: Modifiers,
145    /// Click or tap count associated with the pointer.
146    pub count: u8,
147    /// The size of an input, usually touch.
148    ///
149    /// If this is not provided by the underlying API, platform, or device,
150    /// then it should be a single pixel.
151    pub contact_geometry: ContactGeometry,
152    /// Orientation.
153    pub orientation: PointerOrientation,
154    /// Normalized pressure in range 0..1.
155    ///
156    /// Where pressure is not reported by the platform, it
157    /// is always 0.5 when activated and 0.0 when not.
158    pub pressure: f32,
159    /// Normalized ‘tangential pressure’ in range -1..1.
160    ///
161    /// This is an arbitrary parameter and, despite its name,
162    /// it may not originate from a pressure sensitive control.
163    /// This is often controlled by something like a wheel on the
164    /// barrel of an ‘airbrush’ style pen.
165    pub tangential_pressure: f32,
166    /// The scale factor of the window/screen where this pointer event occurred.
167    pub scale_factor: f64,
168}
169
170impl PointerState {
171    /// Returns the pointer position as a logical `kurbo::Point`.
172    ///
173    /// The position is converted from physical pixels to logical coordinates
174    /// using the state's scale factor.
175    #[cfg(feature = "kurbo")]
176    pub fn logical_point(&self) -> kurbo::Point {
177        let log = self.position.to_logical(self.scale_factor);
178        kurbo::Point { x: log.x, y: log.y }
179    }
180
181    /// Returns the pointer position as a physical `kurbo::Point`.
182    #[cfg(feature = "kurbo")]
183    pub fn physical_point(&self) -> kurbo::Point {
184        kurbo::Point {
185            x: self.position.x,
186            y: self.position.y,
187        }
188    }
189
190    /// Returns the pointer position in logical coordinates.
191    ///
192    /// This converts the physical position to logical coordinates using
193    /// the state's scale factor, providing DPI-independent positioning.
194    pub fn logical_position(&self) -> LogicalPosition<f64> {
195        self.position.to_logical(self.scale_factor)
196    }
197}
198
199impl Default for PointerState {
200    fn default() -> Self {
201        Self {
202            time: 0,
203            position: PhysicalPosition::<f64>::default(),
204            buttons: PointerButtons::default(),
205            modifiers: Modifiers::default(),
206            count: 0,
207            contact_geometry: ContactGeometry {
208                width: 1.0,
209                height: 1.0,
210            },
211            orientation: PointerOrientation::default(),
212            // No buttons pressed, therefore no pressure.
213            pressure: 0.0,
214            tangential_pressure: 0.0,
215            scale_factor: 1.,
216        }
217    }
218}
219
220/// A pointer update, along with coalesced and predicted states.
221#[derive(Clone, Debug, PartialEq)]
222pub struct PointerUpdate {
223    /// Identifying information about pointer.
224    pub pointer: PointerInfo,
225    /// Current state.
226    pub current: PointerState,
227    /// Coalesced states, ordered by `time`.
228    ///
229    /// Coalescing is application-specific.
230    /// On the web, the browser does its own coalescing, whereas
231    /// on other platforms you may do your own, or forego it
232    /// altogether, delivering every state.
233    pub coalesced: Vec<PointerState>,
234    /// Predicted states, ordered by `time`.
235    ///
236    /// Some platforms provide predicted states directly,
237    /// and you may choose to add your own predictor.
238    pub predicted: Vec<PointerState>,
239}
240
241impl PointerUpdate {
242    /// Returns `true` if this is the primary pointer.
243    #[inline(always)]
244    pub fn is_primary_pointer(&self) -> bool {
245        self.pointer.is_primary_pointer()
246    }
247}
248
249/// An event representing a [`PointerButton`] that was pressed or released.
250#[derive(Clone, Debug)]
251pub struct PointerButtonEvent {
252    /// The [`PointerButton`] that was pressed.
253    pub button: Option<PointerButton>,
254    /// Identity of the pointer.
255    pub pointer: PointerInfo,
256    /// The state of the pointer (i.e. position, pressure, etc.).
257    pub state: PointerState,
258}
259
260/// An event representing a scroll
261#[derive(Clone, Debug)]
262pub struct PointerScrollEvent {
263    /// Identity of the pointer.
264    pub pointer: PointerInfo,
265    /// The delta of the scroll.
266    pub delta: ScrollDelta,
267    /// The state of the pointer (i.e. position, pressure, etc.).
268    pub state: PointerState,
269}
270
271/// A touchpad gesture for pointer.
272#[derive(Clone, Debug)]
273pub enum PointerGesture {
274    /// Pinch factor.
275    ///
276    /// This is a signed quantity as a fraction of the current scale,
277    /// to derive a new scale you might use `1. + x * s` where `s`
278    /// is your current scale, and `x` is this factor.
279    Pinch(f32),
280    /// Clockwise rotation in radians.
281    Rotate(f32),
282}
283
284/// An event representing a gesture
285#[derive(Clone, Debug)]
286pub struct PointerGestureEvent {
287    /// Identity of the pointer.
288    pub pointer: PointerInfo,
289    /// The gesture being performed.
290    pub gesture: PointerGesture,
291    /// The state of the pointer (i.e. position, pressure, etc.).
292    pub state: PointerState,
293}
294
295/// A standard `PointerEvent`.
296///
297/// This is intentionally limited to standard pointer events,
298/// and it is expected that applications and frameworks that
299/// support more event types will use this as a base and add
300/// what they need in a conversion.
301#[derive(Clone, Debug)]
302pub enum PointerEvent {
303    /// A [`PointerButton`] was pressed.
304    Down(PointerButtonEvent),
305    /// A [`PointerButton`] was released.
306    Up(PointerButtonEvent),
307    /// Pointer moved.
308    Move(PointerUpdate),
309    /// Pointer motion was cancelled.
310    ///
311    /// Usually this is a touch which was taken over somewhere else.
312    /// You should try to undo the effect of the gesture when you receive this.
313    Cancel(PointerInfo),
314    /// Pointer entered the area that receives this event.
315    Enter(PointerInfo),
316    /// Pointer left the area that receives these events.
317    Leave(PointerInfo),
318    /// A scroll was requested at the pointer location.
319    ///
320    /// Usually this is caused by a mouse wheel or a touchpad.
321    Scroll(PointerScrollEvent),
322    /// Gesture at pointer.
323    Gesture(PointerGestureEvent),
324}
325
326impl PointerEvent {
327    /// Returns `true` if this event is for the primary pointer.
328    #[inline(always)]
329    pub fn is_primary_pointer(&self) -> bool {
330        match self {
331            Self::Down(PointerButtonEvent { pointer, .. })
332            | Self::Up(PointerButtonEvent { pointer, .. })
333            | Self::Move(PointerUpdate { pointer, .. })
334            | Self::Cancel(pointer)
335            | Self::Enter(pointer)
336            | Self::Leave(pointer)
337            | Self::Scroll(PointerScrollEvent { pointer, .. })
338            | Self::Gesture(PointerGestureEvent { pointer, .. }) => pointer.is_primary_pointer(),
339        }
340    }
341}