Skip to main content

cranpose_foundation/nodes/input/
types.rs

1use cranpose_ui_graphics::Point;
2use std::cell::Cell;
3use std::rc::Rc;
4
5pub type PointerId = u64;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum PointerPhase {
9    Start,
10    Move,
11    End,
12    Cancel,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum PointerEventKind {
17    Down,
18    Move,
19    Up,
20    Cancel,
21    Scroll,
22}
23
24#[repr(u8)]
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum PointerButton {
27    Primary = 0,
28    Secondary = 1,
29    Middle = 2,
30    Back = 3,
31    Forward = 4,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct PointerButtons(u8);
36
37impl PointerButtons {
38    pub const NONE: Self = Self(0);
39
40    pub fn new() -> Self {
41        Self::NONE
42    }
43
44    pub fn with(mut self, button: PointerButton) -> Self {
45        self.insert(button);
46        self
47    }
48
49    pub fn insert(&mut self, button: PointerButton) {
50        self.0 |= 1 << (button as u8);
51    }
52
53    pub fn remove(&mut self, button: PointerButton) {
54        self.0 &= !(1 << (button as u8));
55    }
56
57    pub fn contains(&self, button: PointerButton) -> bool {
58        (self.0 & (1 << (button as u8))) != 0
59    }
60}
61
62impl Default for PointerButtons {
63    fn default() -> Self {
64        Self::NONE
65    }
66}
67
68/// Pointer event with consumption tracking for gesture disambiguation.
69///
70/// Events can be consumed by handlers (e.g., scroll) to prevent other handlers
71/// (e.g., clicks) from receiving them. This enables proper gesture disambiguation
72/// matching Jetpack Compose's event consumption pattern.
73#[derive(Clone, Debug)]
74pub struct PointerEvent {
75    pub id: PointerId,
76    pub kind: PointerEventKind,
77    pub phase: PointerPhase,
78    pub position: Point,
79    pub global_position: Point,
80    /// Scroll delta in logical pixels.
81    ///
82    /// This is non-zero for [`PointerEventKind::Scroll`] events and zero for
83    /// button/move events.
84    pub scroll_delta: Point,
85    pub buttons: PointerButtons,
86    /// Tracks whether this event has been consumed by a handler.
87    /// Shared via Rc<Cell> so consumption can be tracked across copies.
88    consumed: Rc<Cell<bool>>,
89}
90
91impl PointerEvent {
92    pub fn new(kind: PointerEventKind, position: Point, global_position: Point) -> Self {
93        Self {
94            id: 0,
95            kind,
96            phase: match kind {
97                PointerEventKind::Down => PointerPhase::Start,
98                PointerEventKind::Move => PointerPhase::Move,
99                PointerEventKind::Up => PointerPhase::End,
100                PointerEventKind::Cancel => PointerPhase::Cancel,
101                PointerEventKind::Scroll => PointerPhase::Move,
102            },
103            position,
104            global_position,
105            scroll_delta: Point { x: 0.0, y: 0.0 },
106            buttons: PointerButtons::NONE,
107            consumed: Rc::new(Cell::new(false)),
108        }
109    }
110
111    /// Set the scroll delta for this event.
112    pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
113        self.scroll_delta = scroll_delta;
114        self
115    }
116
117    /// Set the buttons state for this event
118    pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
119        self.buttons = buttons;
120        self
121    }
122
123    /// Mark this event as consumed, preventing other handlers from processing it.
124    ///
125    /// Example: Scroll gestures consume events once dragging starts to prevent
126    /// child buttons from firing clicks.
127    pub fn consume(&self) {
128        self.consumed.set(true);
129    }
130
131    /// Check if this event has been consumed by another handler.
132    ///
133    /// Handlers should check this before processing events. For example,
134    /// clickable should not fire if the event was consumed by a scroll gesture.
135    pub fn is_consumed(&self) -> bool {
136        self.consumed.get()
137    }
138
139    /// Creates a copy of this event with a new local position, sharing the consumption state.
140    pub fn copy_with_local_position(&self, position: Point) -> Self {
141        Self {
142            id: self.id,
143            kind: self.kind,
144            phase: self.phase,
145            position,
146            global_position: self.global_position,
147            scroll_delta: self.scroll_delta,
148            buttons: self.buttons,
149            consumed: self.consumed.clone(),
150        }
151    }
152}