ailloli_ui_core/event/
pointer.rs

1//! Provider-neutral pointer identity, high-fidelity samples, and routed events.
2
3use crate::{Point, Size};
4
5use super::keyboard::Modifiers;
6
7/// Provider-neutral pointer button identifier.
8///
9/// Possible values are left, middle, right, and an uninterpreted provider
10/// number through [`PointerButton::Other`].
11///
12/// # Examples
13///
14/// ```
15/// use ailloli_ui_core::event::PointerButton;
16/// assert_ne!(PointerButton::Left, PointerButton::Other(4));
17/// ```
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PointerButton {
20    /// Primary/left button.
21    Left,
22    /// Middle/auxiliary button.
23    Middle,
24    /// Secondary/right button.
25    Right,
26    /// Additional platform button number with no framework interpretation.
27    Other(u16),
28}
29
30/// Compatibility name for [`PointerButton`].
31///
32/// New APIs and consumer code should prefer [`PointerButton`]. The alias is
33/// intentionally not deprecated yet so existing applications can migrate
34/// without turning warning-deny builds red.
35///
36/// # Examples
37///
38/// ```
39/// use ailloli_ui_core::event::{MouseButton, PointerButton};
40/// let button: MouseButton = PointerButton::Left;
41/// assert_eq!(button, PointerButton::Left);
42/// ```
43pub type MouseButton = PointerButton;
44
45/// Provider-neutral pointer identifier.
46///
47/// Identifiers are stable within one logical window presentation generation.
48///
49/// # Examples
50///
51/// ```
52/// use ailloli_ui_core::event::PointerId;
53/// assert_eq!(PointerId::new(7).get(), 7);
54/// ```
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub struct PointerId(u64);
57
58impl PointerId {
59    /// Conventional identifier used by the compatibility mouse event path.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use ailloli_ui_core::event::PointerId;
65    /// assert_eq!(PointerId::MOUSE.get(), 0);
66    /// ```
67    pub const MOUSE: Self = Self(0);
68
69    /// Wraps a provider-assigned numeric pointer identifier.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use ailloli_ui_core::event::PointerId;
75    /// assert_eq!(PointerId::new(9).get(), 9);
76    /// ```
77    pub const fn new(value: u64) -> Self {
78        Self(value)
79    }
80
81    /// Returns the wrapped provider-assigned number.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use ailloli_ui_core::event::PointerId;
87    /// assert_eq!(PointerId::new(9).get(), 9);
88    /// ```
89    pub const fn get(self) -> u64 {
90        self.0
91    }
92}
93
94impl Default for PointerId {
95    fn default() -> Self {
96        Self::MOUSE
97    }
98}
99
100/// Physical source that produced a pointer event.
101///
102/// Possible values distinguish mouse, touch, pen, eraser, and other providers.
103///
104/// # Examples
105///
106/// ```
107/// use ailloli_ui_core::event::PointerSource;
108/// assert_eq!(PointerSource::default(), PointerSource::Mouse);
109/// ```
110#[non_exhaustive]
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
112pub enum PointerSource {
113    /// Conventional mouse or trackpad pointer; this is the default.
114    #[default]
115    Mouse,
116    /// Direct or indirect touch contact.
117    Touch,
118    /// Pen or stylus tip.
119    Pen,
120    /// Pen eraser end.
121    Eraser,
122    /// Provider source not represented by a dedicated variant.
123    Other,
124}
125
126/// Whether a pointer gesture is allowed to activate action controls.
127///
128/// Possible values are normal activation and focus-only activation.
129///
130/// # Examples
131///
132/// ```
133/// use ailloli_ui_core::event::ActivationKind;
134/// assert_eq!(ActivationKind::default(), ActivationKind::Normal);
135/// ```
136#[non_exhaustive]
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
138pub enum ActivationKind {
139    /// Gesture may focus and activate controls; this is the default.
140    #[default]
141    Normal,
142    /// The platform used this gesture only to activate/focus the application.
143    FocusOnly,
144}
145
146/// Invalid provider-neutral pointer data.
147///
148/// # Examples
149///
150/// ```
151/// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSampleError, PointerSource}, Point};
152/// assert_eq!(PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::new(f32::NAN, 0.0)).unwrap_err(), PointerSampleError::NonFinitePosition);
153/// ```
154#[non_exhaustive]
155#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
156pub enum PointerSampleError {
157    /// At least one logical position coordinate is NaN or infinite.
158    #[error("pointer position must be finite")]
159    NonFinitePosition,
160    /// Pressure is non-finite or outside the normalized `0.0..=1.0` range.
161    #[error("pointer pressure must be finite and within 0..=1")]
162    InvalidPressure,
163    /// Either tilt component is non-finite or outside `-90.0..=90.0` degrees.
164    #[error("pointer tilt must be finite and within -90..=90 degrees")]
165    InvalidTilt,
166    /// Twist is non-finite or outside the half-open `0.0..360.0` degree range.
167    #[error("pointer twist must be finite and within 0..360 degrees")]
168    InvalidTwist,
169    /// Contact width or height is non-finite or negative.
170    #[error("pointer contact size must be finite and non-negative")]
171    InvalidContactSize,
172}
173
174/// Optional high-fidelity data associated with a pointer event.
175///
176/// Mouse events normally provide only `id`, `source`, and `position`. Touch and
177/// pen adapters can additionally supply normalized pressure, tilt, twist, and
178/// contact size without leaking provider-specific event types into the runtime.
179/// `is_primary` identifies the primary pointer for its source. Providers that
180/// can expose multiple simultaneous pointers should use
181/// [`PointerSample::new_with_primary`] or [`PointerSample::with_primary`]
182/// instead of relying on the compatibility default of [`PointerSample::new`].
183///
184/// # Examples
185///
186/// ```
187/// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
188/// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::new(1.0, 2.0))?;
189/// assert!(sample.is_primary());
190/// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
191/// ```
192#[non_exhaustive]
193#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct PointerSample {
195    /// Stable pointer identity within the provider session.
196    id: PointerId,
197    /// Device family that produced the sample.
198    source: PointerSource,
199    /// Pointer location in logical window coordinates.
200    position: Point,
201    /// Whether this is the primary pointer for its source.
202    is_primary: bool,
203    /// Optional normalized pressure in the inclusive range `0.0..=1.0`.
204    pressure: Option<f32>,
205    /// Optional horizontal and vertical tilt angles in degrees.
206    tilt: Option<(f32, f32)>,
207    /// Optional clockwise barrel rotation in degrees.
208    twist: Option<f32>,
209    /// Optional contact footprint in logical pixels.
210    contact_size: Option<Size>,
211    /// Activation semantics inferred from the provider event.
212    activation: ActivationKind,
213}
214
215impl PointerSample {
216    /// Creates a sample for the compatibility single-pointer path.
217    ///
218    /// This constructor keeps the original three-argument API and treats the
219    /// sample as primary. Multi-pointer providers should use
220    /// [`Self::new_with_primary`] so secondary pointers are represented
221    /// explicitly.
222    ///
223    /// # Errors
224    ///
225    /// Returns [`PointerSampleError::NonFinitePosition`] unless both logical
226    /// position coordinates are finite.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
232    /// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::new(1.0, 2.0))?;
233    /// assert_eq!(sample.position(), Point::new(1.0, 2.0));
234    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
235    /// ```
236    pub fn new(
237        id: PointerId,
238        source: PointerSource,
239        position: Point,
240    ) -> Result<Self, PointerSampleError> {
241        Self::new_with_primary(id, source, position, true)
242    }
243
244    /// Creates a sample with an explicit primary-pointer classification.
245    ///
246    /// Optional pressure, tilt, twist, and contact size start as `None`, and
247    /// activation starts as [`ActivationKind::Normal`].
248    ///
249    /// # Errors
250    ///
251    /// Returns [`PointerSampleError::NonFinitePosition`] unless both logical
252    /// position coordinates are finite.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
258    /// let sample = PointerSample::new_with_primary(PointerId::new(2), PointerSource::Touch, Point::new(1.0, 2.0), false)?;
259    /// assert!(!sample.is_primary());
260    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
261    /// ```
262    pub fn new_with_primary(
263        id: PointerId,
264        source: PointerSource,
265        position: Point,
266        is_primary: bool,
267    ) -> Result<Self, PointerSampleError> {
268        if !position.x.is_finite() || !position.y.is_finite() {
269            return Err(PointerSampleError::NonFinitePosition);
270        }
271        Ok(Self {
272            id,
273            source,
274            position,
275            is_primary,
276            pressure: None,
277            tilt: None,
278            twist: None,
279            contact_size: None,
280            activation: ActivationKind::Normal,
281        })
282    }
283
284    /// Returns the provider-neutral pointer identifier.
285    ///
286    /// # Examples
287    ///
288    /// ```
289    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
290    /// let sample = PointerSample::new(PointerId::new(2), PointerSource::Touch, Point::default())?;
291    /// assert_eq!(sample.id(), PointerId::new(2));
292    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
293    /// ```
294    pub const fn id(&self) -> PointerId {
295        self.id
296    }
297
298    /// Returns the physical source classification.
299    ///
300    /// # Examples
301    ///
302    /// ```
303    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
304    /// let sample = PointerSample::new(PointerId::new(2), PointerSource::Pen, Point::default())?;
305    /// assert_eq!(sample.source(), PointerSource::Pen);
306    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
307    /// ```
308    pub const fn source(&self) -> PointerSource {
309        self.source
310    }
311
312    /// Returns the logical window position.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
318    /// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::new(3.0, 4.0))?;
319    /// assert_eq!(sample.position(), Point::new(3.0, 4.0));
320    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
321    /// ```
322    pub const fn position(&self) -> Point {
323        self.position
324    }
325
326    /// Returns whether this is the primary pointer for its source.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
332    /// assert!(PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::default())?.is_primary());
333    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
334    /// ```
335    pub const fn is_primary(&self) -> bool {
336        self.is_primary
337    }
338
339    /// Returns normalized pressure, or `None` when the provider omitted it.
340    ///
341    /// # Examples
342    ///
343    /// ```
344    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
345    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_pressure(0.5)?;
346    /// assert_eq!(sample.pressure(), Some(0.5));
347    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
348    /// ```
349    pub const fn pressure(&self) -> Option<f32> {
350        self.pressure
351    }
352
353    /// Returns `(x, y)` pen tilt in degrees, or `None` when unavailable.
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
359    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_tilt(10.0, -5.0)?;
360    /// assert_eq!(sample.tilt(), Some((10.0, -5.0)));
361    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
362    /// ```
363    pub const fn tilt(&self) -> Option<(f32, f32)> {
364        self.tilt
365    }
366
367    /// Returns clockwise pen twist in `0.0..360.0` degrees, when available.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
373    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_twist(45.0)?;
374    /// assert_eq!(sample.twist(), Some(45.0));
375    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
376    /// ```
377    pub const fn twist(&self) -> Option<f32> {
378        self.twist
379    }
380
381    /// Returns logical contact width/height, or `None` when unavailable.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point, Size};
387    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Touch, Point::default())?.with_contact_size(Size::new(4.0, 5.0))?;
388    /// assert_eq!(sample.contact_size(), Some(Size::new(4.0, 5.0)));
389    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
390    /// ```
391    pub const fn contact_size(&self) -> Option<Size> {
392        self.contact_size
393    }
394
395    /// Returns whether this gesture may activate controls.
396    ///
397    /// # Examples
398    ///
399    /// ```
400    /// use ailloli_ui_core::{event::{ActivationKind, PointerId, PointerSample, PointerSource}, Point};
401    /// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::default())?.with_activation(ActivationKind::FocusOnly);
402    /// assert_eq!(sample.activation(), ActivationKind::FocusOnly);
403    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
404    /// ```
405    pub const fn activation(&self) -> ActivationKind {
406        self.activation
407    }
408
409    /// Sets normalized pressure in the inclusive range `0.0..=1.0`.
410    ///
411    /// # Errors
412    ///
413    /// Returns [`PointerSampleError::InvalidPressure`] for non-finite or
414    /// out-of-range input.
415    ///
416    /// # Examples
417    ///
418    /// ```
419    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
420    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_pressure(0.75)?;
421    /// assert_eq!(sample.pressure(), Some(0.75));
422    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
423    /// ```
424    pub fn with_pressure(mut self, pressure: f32) -> Result<Self, PointerSampleError> {
425        if !pressure.is_finite() || !(0.0..=1.0).contains(&pressure) {
426            return Err(PointerSampleError::InvalidPressure);
427        }
428        self.pressure = Some(pressure);
429        Ok(self)
430    }
431
432    /// Sets pen tilt components in inclusive degrees `-90.0..=90.0`.
433    ///
434    /// # Errors
435    ///
436    /// Returns [`PointerSampleError::InvalidTilt`] if either component is
437    /// non-finite or outside the supported range.
438    ///
439    /// # Examples
440    ///
441    /// ```
442    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
443    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_tilt(20.0, -20.0)?;
444    /// assert_eq!(sample.tilt(), Some((20.0, -20.0)));
445    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
446    /// ```
447    pub fn with_tilt(mut self, x: f32, y: f32) -> Result<Self, PointerSampleError> {
448        if !x.is_finite()
449            || !y.is_finite()
450            || !(-90.0..=90.0).contains(&x)
451            || !(-90.0..=90.0).contains(&y)
452        {
453            return Err(PointerSampleError::InvalidTilt);
454        }
455        self.tilt = Some((x, y));
456        Ok(self)
457    }
458
459    /// Sets clockwise pen twist in the half-open range `0.0..360.0` degrees.
460    ///
461    /// # Errors
462    ///
463    /// Returns [`PointerSampleError::InvalidTwist`] for non-finite input or for
464    /// a value below zero or at least 360 degrees.
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
470    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Pen, Point::default())?.with_twist(180.0)?;
471    /// assert_eq!(sample.twist(), Some(180.0));
472    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
473    /// ```
474    pub fn with_twist(mut self, twist: f32) -> Result<Self, PointerSampleError> {
475        if !twist.is_finite() || !(0.0..360.0).contains(&twist) {
476            return Err(PointerSampleError::InvalidTwist);
477        }
478        self.twist = Some(twist);
479        Ok(self)
480    }
481
482    /// Sets the non-negative contact size in logical pixels.
483    ///
484    /// Zero width or height is valid and distinct from `None`.
485    ///
486    /// # Errors
487    ///
488    /// Returns [`PointerSampleError::InvalidContactSize`] for non-finite or
489    /// negative dimensions.
490    ///
491    /// # Examples
492    ///
493    /// ```
494    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point, Size};
495    /// let sample = PointerSample::new(PointerId::new(1), PointerSource::Touch, Point::default())?.with_contact_size(Size::new(4.0, 5.0))?;
496    /// assert_eq!(sample.contact_size(), Some(Size::new(4.0, 5.0)));
497    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
498    /// ```
499    pub fn with_contact_size(mut self, size: Size) -> Result<Self, PointerSampleError> {
500        if !size.w.is_finite() || !size.h.is_finite() || size.w < 0.0 || size.h < 0.0 {
501            return Err(PointerSampleError::InvalidContactSize);
502        }
503        self.contact_size = Some(size);
504        Ok(self)
505    }
506
507    /// Replaces the control-activation classification.
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// use ailloli_ui_core::{event::{ActivationKind, PointerId, PointerSample, PointerSource}, Point};
513    /// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::default())?.with_activation(ActivationKind::FocusOnly);
514    /// assert_eq!(sample.activation(), ActivationKind::FocusOnly);
515    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
516    /// ```
517    pub const fn with_activation(mut self, activation: ActivationKind) -> Self {
518        self.activation = activation;
519        self
520    }
521
522    /// Overrides the primary-pointer classification.
523    ///
524    /// # Examples
525    ///
526    /// ```
527    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
528    /// let sample = PointerSample::new(PointerId::new(2), PointerSource::Touch, Point::default())?.with_primary(false);
529    /// assert!(!sample.is_primary());
530    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
531    /// ```
532    pub const fn with_primary(mut self, is_primary: bool) -> Self {
533        self.is_primary = is_primary;
534        self
535    }
536}
537
538/// Scroll wheel delta in lines or pixels.
539///
540/// # Examples
541///
542/// ```
543/// use ailloli_ui_core::event::WheelDelta;
544/// let delta = WheelDelta::PixelDelta { x: 1.0, y: -2.0 };
545/// assert!(matches!(delta, WheelDelta::PixelDelta { .. }));
546/// ```
547#[derive(Debug, Clone, Copy, PartialEq)]
548pub enum WheelDelta {
549    /// Abstract line units (platform-defined).
550    LineDelta {
551        /// Horizontal platform line units.
552        x: f32,
553        /// Vertical platform line units.
554        y: f32,
555    },
556    /// Logical pixel delta.
557    PixelDelta {
558        /// Horizontal logical pixels.
559        x: f32,
560        /// Vertical logical pixels.
561        y: f32,
562    },
563}
564
565/// Pointer move, button, cancellation, or wheel event.
566///
567/// # Examples
568///
569/// ```
570/// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
571/// let event = PointerEvent::moved(Point::new(1.0, 2.0), Modifiers::default());
572/// assert_eq!(event.position(), Point::new(1.0, 2.0));
573/// ```
574#[non_exhaustive]
575#[derive(Debug, Clone, PartialEq)]
576pub enum PointerEvent {
577    /// Cursor moved to `pos`.
578    Moved {
579        /// Current logical window position.
580        pos: Point,
581        /// Modifier snapshot at the time of motion.
582        modifiers: Modifiers,
583    },
584    /// Button pressed or released at `pos`.
585    Button {
586        /// Logical window position of the transition.
587        pos: Point,
588        /// Button whose state changed.
589        button: PointerButton,
590        /// `true` for press and `false` for release.
591        pressed: bool,
592        /// Modifier snapshot at the time of transition.
593        modifiers: Modifiers,
594    },
595    /// The active gesture was cancelled by the host or input provider.
596    ///
597    /// Cancellation never represents a click release. Runtimes must clear
598    /// press/capture state for the corresponding [`PointerId`].
599    Cancelled {
600        /// Last known logical window position.
601        pos: Point,
602        /// Modifier snapshot at cancellation time.
603        modifiers: Modifiers,
604    },
605    /// Scroll wheel at `pos`.
606    Wheel {
607        /// Logical window position associated with the wheel input.
608        pos: Point,
609        /// Provider wheel displacement before widget-specific normalization.
610        delta: WheelDelta,
611        /// Modifier snapshot at wheel time.
612        modifiers: Modifiers,
613        /// High-resolution trackpad scroll when supported.
614        precise: bool,
615    },
616}
617
618impl PointerEvent {
619    /// Creates a pointer movement event in logical coordinates.
620    ///
621    /// # Examples
622    ///
623    /// ```
624    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
625    /// assert_eq!(PointerEvent::moved(Point::new(1.0, 2.0), Modifiers::default()).position(), Point::new(1.0, 2.0));
626    /// ```
627    pub const fn moved(pos: Point, modifiers: Modifiers) -> Self {
628        Self::Moved { pos, modifiers }
629    }
630
631    /// Creates a pointer-button transition in logical coordinates.
632    ///
633    /// # Examples
634    ///
635    /// ```
636    /// use ailloli_ui_core::{event::{Modifiers, PointerButton, PointerEvent}, Point};
637    /// let event = PointerEvent::button(Point::default(), PointerButton::Left, true, Modifiers::default());
638    /// assert_eq!(event.button_transition(), Some((PointerButton::Left, true)));
639    /// ```
640    pub const fn button(
641        pos: Point,
642        button: PointerButton,
643        pressed: bool,
644        modifiers: Modifiers,
645    ) -> Self {
646        Self::Button {
647            pos,
648            button,
649            pressed,
650            modifiers,
651        }
652    }
653
654    /// Creates a pointer cancellation event.
655    ///
656    /// # Examples
657    ///
658    /// ```
659    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
660    /// assert!(PointerEvent::cancelled(Point::default(), Modifiers::default()).is_cancelled());
661    /// ```
662    pub const fn cancelled(pos: Point, modifiers: Modifiers) -> Self {
663        Self::Cancelled { pos, modifiers }
664    }
665
666    /// Creates a pointer-wheel event in logical coordinates.
667    ///
668    /// # Examples
669    ///
670    /// ```
671    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent, WheelDelta}, Point};
672    /// let event = PointerEvent::wheel(Point::default(), WheelDelta::LineDelta { x: 0.0, y: 1.0 }, Modifiers::default(), false);
673    /// assert!(event.wheel_delta().is_some());
674    /// ```
675    pub const fn wheel(pos: Point, delta: WheelDelta, modifiers: Modifiers, precise: bool) -> Self {
676        Self::Wheel {
677            pos,
678            delta,
679            modifiers,
680            precise,
681        }
682    }
683
684    /// Logical position associated with this event.
685    ///
686    /// # Examples
687    ///
688    /// ```
689    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
690    /// let event = PointerEvent::moved(Point::new(2.0, 3.0), Modifiers::default());
691    /// assert_eq!(event.position(), Point::new(2.0, 3.0));
692    /// ```
693    pub const fn position(&self) -> Point {
694        match self {
695            Self::Moved { pos, .. }
696            | Self::Button { pos, .. }
697            | Self::Cancelled { pos, .. }
698            | Self::Wheel { pos, .. } => *pos,
699        }
700    }
701
702    /// Keyboard modifiers observed with this event.
703    ///
704    /// # Examples
705    ///
706    /// ```
707    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
708    /// let modifiers = Modifiers { shift: true, ..Modifiers::default() };
709    /// assert!(PointerEvent::moved(Point::default(), modifiers).modifiers().shift);
710    /// ```
711    pub const fn modifiers(&self) -> Modifiers {
712        match self {
713            Self::Moved { modifiers, .. }
714            | Self::Button { modifiers, .. }
715            | Self::Cancelled { modifiers, .. }
716            | Self::Wheel { modifiers, .. } => *modifiers,
717        }
718    }
719
720    /// Returns the button and transition state for a button event.
721    ///
722    /// The boolean is `true` for press and `false` for release; all other event
723    /// variants return `None`.
724    ///
725    /// # Examples
726    ///
727    /// ```
728    /// use ailloli_ui_core::{event::{Modifiers, PointerButton, PointerEvent}, Point};
729    /// let event = PointerEvent::button(Point::default(), PointerButton::Right, false, Modifiers::default());
730    /// assert_eq!(event.button_transition(), Some((PointerButton::Right, false)));
731    /// ```
732    pub const fn button_transition(&self) -> Option<(PointerButton, bool)> {
733        match self {
734            Self::Button {
735                button, pressed, ..
736            } => Some((*button, *pressed)),
737            Self::Moved { .. } | Self::Cancelled { .. } | Self::Wheel { .. } => None,
738        }
739    }
740
741    /// Returns wheel displacement and the high-resolution flag for a wheel event.
742    ///
743    /// # Examples
744    ///
745    /// ```
746    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent, WheelDelta}, Point};
747    /// let event = PointerEvent::wheel(Point::default(), WheelDelta::PixelDelta { x: 0.0, y: 2.0 }, Modifiers::default(), true);
748    /// assert_eq!(event.wheel_delta(), Some((WheelDelta::PixelDelta { x: 0.0, y: 2.0 }, true)));
749    /// ```
750    pub const fn wheel_delta(&self) -> Option<(WheelDelta, bool)> {
751        match self {
752            Self::Wheel { delta, precise, .. } => Some((*delta, *precise)),
753            Self::Moved { .. } | Self::Button { .. } | Self::Cancelled { .. } => None,
754        }
755    }
756
757    /// Returns whether the provider cancelled the active gesture.
758    ///
759    /// # Examples
760    ///
761    /// ```
762    /// use ailloli_ui_core::{event::{Modifiers, PointerEvent}, Point};
763    /// assert!(!PointerEvent::moved(Point::default(), Modifiers::default()).is_cancelled());
764    /// ```
765    pub const fn is_cancelled(&self) -> bool {
766        matches!(self, Self::Cancelled { .. })
767    }
768}