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}