cranpose_foundation/nodes/input/
types.rs1use 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 Zoom,
25 Enter,
26 Exit,
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
37pub enum PointerSource {
38 Mouse,
40 Touch,
42 Stylus,
44 #[default]
46 Unknown,
47}
48
49impl PointerSource {
50 pub fn is_touch_like(self) -> bool {
53 matches!(self, PointerSource::Touch | PointerSource::Stylus)
54 }
55}
56
57#[repr(u8)]
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub enum PointerButton {
60 Primary = 0,
61 Secondary = 1,
62 Middle = 2,
63 Back = 3,
64 Forward = 4,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct PointerButtons(u8);
69
70impl PointerButtons {
71 pub const NONE: Self = Self(0);
72
73 pub fn new() -> Self {
74 Self::NONE
75 }
76
77 pub fn with(mut self, button: PointerButton) -> Self {
78 self.insert(button);
79 self
80 }
81
82 pub fn insert(&mut self, button: PointerButton) {
83 self.0 |= 1 << (button as u8);
84 }
85
86 pub fn remove(&mut self, button: PointerButton) {
87 self.0 &= !(1 << (button as u8));
88 }
89
90 pub fn contains(&self, button: PointerButton) -> bool {
91 (self.0 & (1 << (button as u8))) != 0
92 }
93}
94
95impl Default for PointerButtons {
96 fn default() -> Self {
97 Self::NONE
98 }
99}
100
101#[derive(Clone, Debug)]
107pub struct PointerEvent {
108 pub id: PointerId,
109 pub kind: PointerEventKind,
110 pub phase: PointerPhase,
111 pub position: Point,
112 pub global_position: Point,
113 pub scroll_delta: Point,
118 pub buttons: PointerButtons,
119 pub time_ms: Option<i64>,
128 pub zoom_delta: f32,
131 pub source: PointerSource,
134 consumed: Rc<Cell<bool>>,
137}
138
139impl PointerEvent {
140 pub fn new(kind: PointerEventKind, position: Point, global_position: Point) -> Self {
141 Self {
142 id: 0,
143 kind,
144 phase: match kind {
145 PointerEventKind::Down => PointerPhase::Start,
146 PointerEventKind::Move | PointerEventKind::Enter | PointerEventKind::Exit => {
147 PointerPhase::Move
148 }
149 PointerEventKind::Up => PointerPhase::End,
150 PointerEventKind::Cancel => PointerPhase::Cancel,
151 PointerEventKind::Scroll | PointerEventKind::Zoom => PointerPhase::Move,
152 },
153 position,
154 global_position,
155 scroll_delta: Point { x: 0.0, y: 0.0 },
156 buttons: PointerButtons::NONE,
157 time_ms: None,
158 zoom_delta: 1.0,
159 source: PointerSource::Unknown,
160 consumed: Rc::new(Cell::new(false)),
161 }
162 }
163
164 pub fn with_id(mut self, id: PointerId) -> Self {
166 self.id = id;
167 self
168 }
169
170 pub fn with_zoom_delta(mut self, zoom_delta: f32) -> Self {
172 self.zoom_delta = zoom_delta;
173 self
174 }
175
176 pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
178 self.scroll_delta = scroll_delta;
179 self
180 }
181
182 pub fn with_time_ms(mut self, time_ms: Option<i64>) -> Self {
184 self.time_ms = time_ms;
185 self
186 }
187
188 pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
190 self.buttons = buttons;
191 self
192 }
193
194 pub fn with_source(mut self, source: PointerSource) -> Self {
196 self.source = source;
197 self
198 }
199
200 pub fn consume(&self) {
205 self.consumed.set(true);
206 }
207
208 pub fn is_consumed(&self) -> bool {
213 self.consumed.get()
214 }
215
216 pub fn copy_with_local_position(&self, position: Point) -> Self {
218 Self {
219 id: self.id,
220 kind: self.kind,
221 phase: self.phase,
222 position,
223 global_position: self.global_position,
224 scroll_delta: self.scroll_delta,
225 buttons: self.buttons,
226 time_ms: self.time_ms,
227 zoom_delta: self.zoom_delta,
228 source: self.source,
229 consumed: self.consumed.clone(),
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn point(x: f32, y: f32) -> Point {
239 Point { x, y }
240 }
241
242 #[test]
243 fn pointer_event_clones_share_consumed_state() {
244 let event = PointerEvent::new(PointerEventKind::Move, point(1.0, 2.0), point(3.0, 4.0));
245 let cloned = event.clone();
246 assert!(!event.is_consumed());
247 assert!(!cloned.is_consumed());
248
249 cloned.consume();
250
251 assert!(event.is_consumed());
252 assert!(cloned.is_consumed());
253 }
254
255 #[test]
256 fn pointer_event_source_defaults_unknown_and_threads_through_copy() {
257 let event = PointerEvent::new(PointerEventKind::Down, point(1.0, 1.0), point(1.0, 1.0));
258 assert_eq!(event.source, PointerSource::Unknown);
259 assert!(!PointerSource::Unknown.is_touch_like());
260
261 let touch = event.with_source(PointerSource::Touch);
262 assert_eq!(touch.source, PointerSource::Touch);
263 assert!(PointerSource::Touch.is_touch_like());
264 assert!(PointerSource::Stylus.is_touch_like());
265 assert!(!PointerSource::Mouse.is_touch_like());
266
267 let local = touch.copy_with_local_position(point(5.0, 5.0));
269 assert_eq!(local.source, PointerSource::Touch);
270 }
271
272 #[test]
273 fn pointer_event_copy_with_local_position_preserves_consumption_state() {
274 let event = PointerEvent::new(PointerEventKind::Down, point(4.0, 5.0), point(4.0, 5.0));
275 let local = event.copy_with_local_position(point(1.0, 1.0));
276
277 assert_eq!(local.position, point(1.0, 1.0));
278 assert_eq!(local.global_position, event.global_position);
279 assert!(!local.is_consumed());
280
281 event.consume();
282
283 assert!(local.is_consumed());
284 }
285}