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