teksilo_core/gesture/
tap.rs1use teksilo_canvas::Point;
5
6use crate::event::{ButtonMask, PointerButton};
7
8use super::{GestureEvent, GestureRecognizer, GestureResult, RawPointerEvent, TapEvent};
9
10#[derive(Debug)]
18pub struct TapRecognizer {
19 max_distance: f32,
20 accept: ButtonMask,
21 down_position: Option<Point>,
22 down_button: Option<PointerButton>,
23}
24
25impl TapRecognizer {
26 pub fn new() -> Self {
27 Self {
28 max_distance: 5.0,
29 accept: ButtonMask::PRIMARY,
30 down_position: None,
31 down_button: None,
32 }
33 }
34
35 pub fn max_distance(mut self, d: f32) -> Self {
36 self.max_distance = d;
37 self
38 }
39
40 pub fn accept_buttons(mut self, mask: impl Into<ButtonMask>) -> Self {
43 self.accept = mask.into();
44 self
45 }
46
47 pub fn accept_any_button(self) -> Self {
49 self.accept_buttons(ButtonMask::ALL)
50 }
51}
52
53impl Default for TapRecognizer {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl GestureRecognizer for TapRecognizer {
60 fn process(&mut self, event: &RawPointerEvent) -> GestureResult {
61 match event {
62 RawPointerEvent::Down {
63 position, button, ..
64 } => {
65 if !self.accept.contains(*button) {
66 return GestureResult::Pending;
67 }
68 self.down_position = Some(*position);
69 self.down_button = Some(*button);
70 GestureResult::Pending
71 }
72 RawPointerEvent::Move { position } => {
73 if let Some(down) = self.down_position {
74 let dx = position.x - down.x;
75 let dy = position.y - down.y;
76 if (dx * dx + dy * dy).sqrt() > self.max_distance {
77 self.down_position = None;
78 self.down_button = None;
79 return GestureResult::Failed;
80 }
81 }
82 GestureResult::Pending
83 }
84 RawPointerEvent::Up {
85 position,
86 button,
87 modifiers,
88 } => {
89 let Some(down) = self.down_position.take() else {
90 return GestureResult::Failed;
91 };
92 let Some(down_button) = self.down_button.take() else {
93 return GestureResult::Failed;
94 };
95 if *button != down_button {
96 return GestureResult::Failed;
97 }
98 let dx = position.x - down.x;
99 let dy = position.y - down.y;
100 if (dx * dx + dy * dy).sqrt() <= self.max_distance {
101 return GestureResult::Recognized(GestureEvent::Tap(TapEvent {
102 position: *position,
103 button: *button,
104 modifiers: *modifiers,
105 }));
106 }
107 GestureResult::Failed
108 }
109 }
110 }
111
112 fn reset(&mut self) {
113 self.down_position = None;
114 self.down_button = None;
115 }
116
117 fn priority(&self) -> u32 {
118 10
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::event::Modifiers;
126 use crate::gesture::test_helpers::*;
127
128 #[test]
129 fn tap_recognized_on_quick_down_up() {
130 let mut rec = TapRecognizer::new();
131 assert!(matches!(
132 rec.process(&down(Point::new(10.0, 10.0))),
133 GestureResult::Pending
134 ));
135 assert!(matches!(
136 rec.process(&up(Point::new(10.0, 10.0))),
137 GestureResult::Recognized(GestureEvent::Tap(_))
138 ));
139 }
140
141 #[test]
142 fn tap_fails_if_moved_too_far() {
143 let mut rec = TapRecognizer::new().max_distance(5.0);
144 rec.process(&down(Point::new(10.0, 10.0)));
145 assert!(matches!(
146 rec.process(&move_to(Point::new(20.0, 10.0))),
147 GestureResult::Failed
148 ));
149 }
150
151 #[test]
152 fn tap_default_filters_secondary_button() {
153 let mut rec = TapRecognizer::new();
154 assert!(matches!(
157 rec.process(&down_btn(Point::new(10.0, 10.0), PointerButton::Secondary)),
158 GestureResult::Pending
159 ));
160 assert!(matches!(
161 rec.process(&up_btn(Point::new(10.0, 10.0), PointerButton::Secondary)),
162 GestureResult::Failed
163 ));
164 }
165
166 #[test]
167 fn tap_default_filters_middle_button() {
168 let mut rec = TapRecognizer::new();
169 assert!(matches!(
170 rec.process(&down_btn(Point::new(10.0, 10.0), PointerButton::Middle)),
171 GestureResult::Pending
172 ));
173 assert!(matches!(
174 rec.process(&up_btn(Point::new(10.0, 10.0), PointerButton::Middle)),
175 GestureResult::Failed
176 ));
177 }
178
179 #[test]
180 fn tap_accept_secondary_recognises_right_click() {
181 let mut rec = TapRecognizer::new().accept_buttons(ButtonMask::SECONDARY);
182 rec.process(&down_btn(Point::new(10.0, 10.0), PointerButton::Secondary));
183 match rec.process(&up_btn(Point::new(10.0, 10.0), PointerButton::Secondary)) {
184 GestureResult::Recognized(GestureEvent::Tap(event)) => {
185 assert_eq!(event.button, PointerButton::Secondary);
186 }
187 other => panic!("expected Tap on right-click, got {:?}", other),
188 }
189 }
190
191 #[test]
192 fn tap_button_mismatch_fails() {
193 let mut rec = TapRecognizer::new().accept_any_button();
194 rec.process(&down_btn(Point::new(10.0, 10.0), PointerButton::Primary));
195 assert!(matches!(
197 rec.process(&up_btn(Point::new(10.0, 10.0), PointerButton::Secondary)),
198 GestureResult::Failed
199 ));
200 }
201
202 #[test]
203 fn tap_carries_modifiers_from_up() {
204 let mut rec = TapRecognizer::new();
205 rec.process(&down_full(
206 Point::new(10.0, 10.0),
207 PointerButton::Primary,
208 Modifiers::NONE,
209 ));
210 match rec.process(&up_full(
211 Point::new(10.0, 10.0),
212 PointerButton::Primary,
213 Modifiers::CTRL | Modifiers::SHIFT,
214 )) {
215 GestureResult::Recognized(GestureEvent::Tap(event)) => {
216 assert!(event.modifiers.ctrl());
217 assert!(event.modifiers.shift());
218 assert!(!event.modifiers.alt());
219 }
220 other => panic!("expected Tap with modifiers, got {:?}", other),
221 }
222 }
223
224 #[test]
225 fn tap_recognised_for_forward_or_back_when_accepted() {
226 let mut rec = TapRecognizer::new().accept_buttons(ButtonMask::FORWARD | ButtonMask::BACK);
227 rec.process(&down_btn(Point::new(0.0, 0.0), PointerButton::Forward));
228 assert!(matches!(
229 rec.process(&up_btn(Point::new(0.0, 0.0), PointerButton::Forward)),
230 GestureResult::Recognized(GestureEvent::Tap(_))
231 ));
232 rec.process(&down_btn(Point::new(0.0, 0.0), PointerButton::Back));
233 assert!(matches!(
234 rec.process(&up_btn(Point::new(0.0, 0.0), PointerButton::Back)),
235 GestureResult::Recognized(GestureEvent::Tap(_))
236 ));
237 }
238}