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}