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)]
36pub enum PointerSource {
37 Mouse,
39 Touch,
41 Stylus,
43 #[default]
45 Unknown,
46}
47
48impl PointerSource {
49 pub fn is_touch_like(self) -> bool {
52 matches!(self, PointerSource::Touch | PointerSource::Stylus)
53 }
54}
55
56#[repr(u8)]
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum PointerButton {
59 Primary = 0,
60 Secondary = 1,
61 Middle = 2,
62 Back = 3,
63 Forward = 4,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub struct PointerButtons(u8);
68
69impl PointerButtons {
70 pub const NONE: Self = Self(0);
71
72 pub fn new() -> Self {
73 Self::NONE
74 }
75
76 pub fn with(mut self, button: PointerButton) -> Self {
77 self.insert(button);
78 self
79 }
80
81 pub fn insert(&mut self, button: PointerButton) {
82 self.0 |= 1 << (button as u8);
83 }
84
85 pub fn remove(&mut self, button: PointerButton) {
86 self.0 &= !(1 << (button as u8));
87 }
88
89 pub fn contains(&self, button: PointerButton) -> bool {
90 (self.0 & (1 << (button as u8))) != 0
91 }
92}
93
94impl Default for PointerButtons {
95 fn default() -> Self {
96 Self::NONE
97 }
98}
99
100#[derive(Clone, Debug)]
106pub struct PointerEvent {
107 pub id: PointerId,
108 pub kind: PointerEventKind,
109 pub phase: PointerPhase,
110 pub position: Point,
111 pub global_position: Point,
112 pub scroll_delta: Point,
117 pub buttons: PointerButtons,
118 pub time_ms: Option<i64>,
127 pub animation_time_nanos: Option<u64>,
131 pub zoom_delta: f32,
134 pub source: PointerSource,
137 consumed: Rc<Cell<bool>>,
140}
141
142impl PointerEvent {
143 pub fn new(kind: PointerEventKind, position: Point, global_position: Point) -> Self {
144 Self {
145 id: 0,
146 kind,
147 phase: match kind {
148 PointerEventKind::Down => PointerPhase::Start,
149 PointerEventKind::Move | PointerEventKind::Enter | PointerEventKind::Exit => {
150 PointerPhase::Move
151 }
152 PointerEventKind::Up => PointerPhase::End,
153 PointerEventKind::Cancel => PointerPhase::Cancel,
154 PointerEventKind::Scroll | PointerEventKind::Zoom => PointerPhase::Move,
155 },
156 position,
157 global_position,
158 scroll_delta: Point { x: 0.0, y: 0.0 },
159 buttons: PointerButtons::NONE,
160 time_ms: None,
161 animation_time_nanos: None,
162 zoom_delta: 1.0,
163 source: PointerSource::Unknown,
164 consumed: Rc::new(Cell::new(false)),
165 }
166 }
167
168 pub fn with_id(mut self, id: PointerId) -> Self {
170 self.id = id;
171 self
172 }
173
174 pub fn with_zoom_delta(mut self, zoom_delta: f32) -> Self {
176 self.zoom_delta = zoom_delta;
177 self
178 }
179
180 pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
182 self.scroll_delta = scroll_delta;
183 self
184 }
185
186 pub fn with_time_ms(mut self, time_ms: Option<i64>) -> Self {
188 self.time_ms = time_ms;
189 self
190 }
191
192 pub fn with_animation_time_nanos(mut self, time_nanos: u64) -> Self {
194 self.animation_time_nanos = Some(time_nanos);
195 self
196 }
197
198 pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
200 self.buttons = buttons;
201 self
202 }
203
204 pub fn with_source(mut self, source: PointerSource) -> Self {
206 self.source = source;
207 self
208 }
209
210 pub fn consume(&self) {
215 self.consumed.set(true);
216 }
217
218 pub fn is_consumed(&self) -> bool {
223 self.consumed.get()
224 }
225
226 pub fn copy_with_local_position(&self, position: Point) -> Self {
228 Self {
229 id: self.id,
230 kind: self.kind,
231 phase: self.phase,
232 position,
233 global_position: self.global_position,
234 scroll_delta: self.scroll_delta,
235 buttons: self.buttons,
236 time_ms: self.time_ms,
237 animation_time_nanos: self.animation_time_nanos,
238 zoom_delta: self.zoom_delta,
239 source: self.source,
240 consumed: self.consumed.clone(),
241 }
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 fn point(x: f32, y: f32) -> Point {
250 Point { x, y }
251 }
252
253 #[test]
254 fn pointer_event_clones_share_consumed_state() {
255 let event = PointerEvent::new(PointerEventKind::Move, point(1.0, 2.0), point(3.0, 4.0));
256 let cloned = event.clone();
257 assert!(!event.is_consumed());
258 assert!(!cloned.is_consumed());
259
260 cloned.consume();
261
262 assert!(event.is_consumed());
263 assert!(cloned.is_consumed());
264 }
265
266 #[test]
267 fn pointer_event_source_defaults_unknown_and_threads_through_copy() {
268 let event = PointerEvent::new(PointerEventKind::Down, point(1.0, 1.0), point(1.0, 1.0));
269 assert_eq!(event.source, PointerSource::Unknown);
270 assert!(!PointerSource::Unknown.is_touch_like());
271
272 let touch = event.with_source(PointerSource::Touch);
273 assert_eq!(touch.source, PointerSource::Touch);
274 assert!(PointerSource::Touch.is_touch_like());
275 assert!(PointerSource::Stylus.is_touch_like());
276 assert!(!PointerSource::Mouse.is_touch_like());
277
278 let local = touch.copy_with_local_position(point(5.0, 5.0));
280 assert_eq!(local.source, PointerSource::Touch);
281 }
282
283 #[test]
284 fn pointer_event_copy_with_local_position_preserves_consumption_state() {
285 let event = PointerEvent::new(PointerEventKind::Down, point(4.0, 5.0), point(4.0, 5.0))
286 .with_time_ms(Some(123))
287 .with_animation_time_nanos(456_000_000);
288 let local = event.copy_with_local_position(point(1.0, 1.0));
289
290 assert_eq!(local.position, point(1.0, 1.0));
291 assert_eq!(local.global_position, event.global_position);
292 assert_eq!(local.time_ms, Some(123));
293 assert_eq!(local.animation_time_nanos, Some(456_000_000));
294 assert!(!local.is_consumed());
295
296 event.consume();
297
298 assert!(local.is_consumed());
299 }
300}