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