teksilo_core/gesture/
drag.rs1use teksilo_canvas::{Point, Vec2};
5
6use crate::event::PointerButton;
7
8use super::{GestureEvent, GestureRecognizer, GestureResult, RawPointerEvent};
9
10#[derive(Debug)]
12pub struct DragRecognizer {
13 threshold: f32,
14 down_position: Option<Point>,
15 down_button: Option<PointerButton>,
16 dragging: bool,
17 last_position: Option<Point>,
18}
19
20impl DragRecognizer {
21 pub fn new() -> Self {
22 Self {
23 threshold: 5.0,
24 down_position: None,
25 down_button: None,
26 dragging: false,
27 last_position: None,
28 }
29 }
30
31 pub fn threshold(mut self, t: f32) -> Self {
32 self.threshold = t;
33 self
34 }
35}
36
37impl Default for DragRecognizer {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl GestureRecognizer for DragRecognizer {
44 fn process(&mut self, event: &RawPointerEvent) -> GestureResult {
45 match event {
46 RawPointerEvent::Down {
47 position, button, ..
48 } => {
49 self.down_position = Some(*position);
50 self.down_button = Some(*button);
51 self.last_position = Some(*position);
52 self.dragging = false;
53 GestureResult::Pending
54 }
55 RawPointerEvent::Move { position } => {
56 let Some(down) = self.down_position else {
57 return GestureResult::Pending;
58 };
59
60 if self.dragging {
61 let last = self.last_position.unwrap_or(down);
62 let delta = Vec2::new(position.x - last.x, position.y - last.y);
63 self.last_position = Some(*position);
64 return GestureResult::Recognized(GestureEvent::DragMoved {
65 position: *position,
66 delta,
67 });
68 }
69
70 let dx = position.x - down.x;
71 let dy = position.y - down.y;
72 if (dx * dx + dy * dy).sqrt() >= self.threshold {
73 self.dragging = true;
74 self.last_position = Some(*position);
75 return GestureResult::Recognized(GestureEvent::DragStarted {
83 position: down,
84 button: self.down_button.unwrap_or(PointerButton::Primary),
85 });
86 }
87
88 GestureResult::Pending
89 }
90 RawPointerEvent::Up { position, .. } => {
91 if self.dragging {
92 self.dragging = false;
93 self.down_position = None;
94 return GestureResult::Recognized(GestureEvent::DragEnded {
95 position: *position,
96 });
97 }
98 self.down_position = None;
99 GestureResult::Failed
100 }
101 }
102 }
103
104 fn reset(&mut self) {
105 self.down_position = None;
106 self.down_button = None;
107 self.dragging = false;
108 self.last_position = None;
109 }
110
111 fn priority(&self) -> u32 {
112 20
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::gesture::test_helpers::*;
120
121 #[test]
122 fn drag_recognizer_fires_after_threshold() {
123 let mut rec = DragRecognizer::new().threshold(5.0);
124
125 assert!(matches!(
126 rec.process(&down(Point::new(10.0, 10.0))),
127 GestureResult::Pending
128 ));
129
130 assert!(matches!(
132 rec.process(&move_to(Point::new(12.0, 10.0))),
133 GestureResult::Pending
134 ));
135
136 assert!(matches!(
138 rec.process(&move_to(Point::new(20.0, 10.0))),
139 GestureResult::Recognized(GestureEvent::DragStarted { .. })
140 ));
141 }
142
143 #[test]
144 fn drag_emits_moved_and_ended() {
145 let mut rec = DragRecognizer::new().threshold(1.0);
146 rec.process(&down(Point::new(0.0, 0.0)));
147 rec.process(&move_to(Point::new(10.0, 0.0)));
149
150 let result = rec.process(&move_to(Point::new(15.0, 0.0)));
152 assert!(matches!(
153 result,
154 GestureResult::Recognized(GestureEvent::DragMoved { .. })
155 ));
156
157 let result = rec.process(&up(Point::new(15.0, 0.0)));
159 assert!(matches!(
160 result,
161 GestureResult::Recognized(GestureEvent::DragEnded { .. })
162 ));
163 }
164
165 #[test]
166 fn drag_fails_on_up_without_movement() {
167 let mut rec = DragRecognizer::new().threshold(5.0);
168 rec.process(&down(Point::new(10.0, 10.0)));
169 assert!(matches!(
170 rec.process(&up(Point::new(10.0, 10.0))),
171 GestureResult::Failed
172 ));
173 }
174
175 #[test]
176 fn reset_clears_state() {
177 let mut rec = DragRecognizer::new().threshold(1.0);
178 rec.process(&down(Point::new(0.0, 0.0)));
179 rec.process(&move_to(Point::new(10.0, 0.0)));
180 rec.reset();
181
182 assert!(matches!(
184 rec.process(&move_to(Point::new(20.0, 0.0))),
185 GestureResult::Pending
186 ));
187 }
188}