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 zoom_delta: f32,
130 pub source: PointerSource,
133 consumed: Rc<Cell<bool>>,
136}
137
138impl PointerEvent {
139 pub fn new(kind: PointerEventKind, position: Point, global_position: Point) -> Self {
140 Self {
141 id: 0,
142 kind,
143 phase: match kind {
144 PointerEventKind::Down => PointerPhase::Start,
145 PointerEventKind::Move | PointerEventKind::Enter | PointerEventKind::Exit => {
146 PointerPhase::Move
147 }
148 PointerEventKind::Up => PointerPhase::End,
149 PointerEventKind::Cancel => PointerPhase::Cancel,
150 PointerEventKind::Scroll | PointerEventKind::Zoom => PointerPhase::Move,
151 },
152 position,
153 global_position,
154 scroll_delta: Point { x: 0.0, y: 0.0 },
155 buttons: PointerButtons::NONE,
156 time_ms: None,
157 zoom_delta: 1.0,
158 source: PointerSource::Unknown,
159 consumed: Rc::new(Cell::new(false)),
160 }
161 }
162
163 pub fn with_id(mut self, id: PointerId) -> Self {
165 self.id = id;
166 self
167 }
168
169 pub fn with_zoom_delta(mut self, zoom_delta: f32) -> Self {
171 self.zoom_delta = zoom_delta;
172 self
173 }
174
175 pub fn with_scroll_delta(mut self, scroll_delta: Point) -> Self {
177 self.scroll_delta = scroll_delta;
178 self
179 }
180
181 pub fn with_time_ms(mut self, time_ms: Option<i64>) -> Self {
183 self.time_ms = time_ms;
184 self
185 }
186
187 pub fn with_buttons(mut self, buttons: PointerButtons) -> Self {
189 self.buttons = buttons;
190 self
191 }
192
193 pub fn with_source(mut self, source: PointerSource) -> Self {
195 self.source = source;
196 self
197 }
198
199 pub fn consume(&self) {
204 self.consumed.set(true);
205 }
206
207 pub fn is_consumed(&self) -> bool {
212 self.consumed.get()
213 }
214
215 pub fn copy_with_local_position(&self, position: Point) -> Self {
217 Self {
218 id: self.id,
219 kind: self.kind,
220 phase: self.phase,
221 position,
222 global_position: self.global_position,
223 scroll_delta: self.scroll_delta,
224 buttons: self.buttons,
225 time_ms: self.time_ms,
226 zoom_delta: self.zoom_delta,
227 source: self.source,
228 consumed: self.consumed.clone(),
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn point(x: f32, y: f32) -> Point {
238 Point { x, y }
239 }
240
241 #[test]
242 fn pointer_event_clones_share_consumed_state() {
243 let event = PointerEvent::new(PointerEventKind::Move, point(1.0, 2.0), point(3.0, 4.0));
244 let cloned = event.clone();
245 assert!(!event.is_consumed());
246 assert!(!cloned.is_consumed());
247
248 cloned.consume();
249
250 assert!(event.is_consumed());
251 assert!(cloned.is_consumed());
252 }
253
254 #[test]
255 fn pointer_event_source_defaults_unknown_and_threads_through_copy() {
256 let event = PointerEvent::new(PointerEventKind::Down, point(1.0, 1.0), point(1.0, 1.0));
257 assert_eq!(event.source, PointerSource::Unknown);
258 assert!(!PointerSource::Unknown.is_touch_like());
259
260 let touch = event.with_source(PointerSource::Touch);
261 assert_eq!(touch.source, PointerSource::Touch);
262 assert!(PointerSource::Touch.is_touch_like());
263 assert!(PointerSource::Stylus.is_touch_like());
264 assert!(!PointerSource::Mouse.is_touch_like());
265
266 let local = touch.copy_with_local_position(point(5.0, 5.0));
268 assert_eq!(local.source, PointerSource::Touch);
269 }
270
271 #[test]
272 fn pointer_event_copy_with_local_position_preserves_consumption_state() {
273 let event = PointerEvent::new(PointerEventKind::Down, point(4.0, 5.0), point(4.0, 5.0));
274 let local = event.copy_with_local_position(point(1.0, 1.0));
275
276 assert_eq!(local.position, point(1.0, 1.0));
277 assert_eq!(local.global_position, event.global_position);
278 assert!(!local.is_consumed());
279
280 event.consume();
281
282 assert!(local.is_consumed());
283 }
284}