1use std::time::{Duration, Instant};
5
6use teksilo_canvas::Point;
7
8use crate::event::{ButtonMask, PointerButton};
9
10use super::{GestureEvent, GestureRecognizer, GestureResult, RawPointerEvent, TapEvent, distance};
11
12#[derive(Debug)]
20pub struct DoubleTapRecognizer {
21 max_distance: f32,
22 max_interval: Duration,
23 accept: ButtonMask,
24 first_tap_position: Option<Point>,
25 first_tap_time: Option<Instant>,
26 first_tap_button: Option<PointerButton>,
27 down_position: Option<Point>,
28 down_button: Option<PointerButton>,
29}
30
31impl DoubleTapRecognizer {
32 pub fn new() -> Self {
33 Self {
34 max_distance: 10.0,
35 max_interval: Duration::from_millis(300),
36 accept: ButtonMask::PRIMARY,
37 first_tap_position: None,
38 first_tap_time: None,
39 first_tap_button: None,
40 down_position: None,
41 down_button: None,
42 }
43 }
44
45 pub fn max_distance(mut self, d: f32) -> Self {
46 self.max_distance = d;
47 self
48 }
49
50 pub fn max_interval(mut self, interval: Duration) -> Self {
51 self.max_interval = interval;
52 self
53 }
54
55 pub fn accept_buttons(mut self, mask: impl Into<ButtonMask>) -> Self {
58 self.accept = mask.into();
59 self
60 }
61
62 pub fn accept_any_button(self) -> Self {
64 self.accept_buttons(ButtonMask::ALL)
65 }
66
67 pub fn process_at(&mut self, event: &RawPointerEvent, now: Instant) -> GestureResult {
69 match event {
70 RawPointerEvent::Down {
71 position, button, ..
72 } => {
73 if !self.accept.contains(*button) {
74 return GestureResult::Pending;
75 }
76 if let Some(first_button) = self.first_tap_button
81 && first_button != *button
82 {
83 self.first_tap_position = None;
84 self.first_tap_time = None;
85 self.first_tap_button = None;
86 }
87 self.down_position = Some(*position);
88 self.down_button = Some(*button);
89 GestureResult::Pending
90 }
91 RawPointerEvent::Move { position } => {
92 if let Some(down) = self.down_position
93 && distance(*position, down) > self.max_distance
94 {
95 return GestureResult::Failed;
96 }
97 GestureResult::Pending
98 }
99 RawPointerEvent::Up {
100 position,
101 button,
102 modifiers,
103 } => {
104 let Some(down) = self.down_position else {
105 return GestureResult::Failed;
106 };
107 let Some(down_button) = self.down_button else {
108 return GestureResult::Failed;
109 };
110 self.down_position = None;
111 self.down_button = None;
112 if *button != down_button {
113 return GestureResult::Failed;
114 }
115 if distance(*position, down) > self.max_distance {
116 return GestureResult::Failed;
117 }
118
119 if let (Some(first_pos), Some(first_time), Some(first_button)) = (
120 self.first_tap_position,
121 self.first_tap_time,
122 self.first_tap_button,
123 ) {
124 if first_button == *button
127 && distance(*position, first_pos) <= self.max_distance
128 && now.duration_since(first_time) <= self.max_interval
129 {
130 self.reset();
131 return GestureResult::Recognized(GestureEvent::DoubleTap(TapEvent {
132 position: *position,
133 button: *button,
134 modifiers: *modifiers,
135 }));
136 }
137 self.first_tap_position = Some(*position);
140 self.first_tap_time = Some(now);
141 self.first_tap_button = Some(*button);
142 GestureResult::Pending
143 } else {
144 self.first_tap_position = Some(*position);
146 self.first_tap_time = Some(now);
147 self.first_tap_button = Some(*button);
148 GestureResult::Pending
149 }
150 }
151 }
152 }
153}
154
155impl Default for DoubleTapRecognizer {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161impl GestureRecognizer for DoubleTapRecognizer {
162 fn process(&mut self, event: &RawPointerEvent) -> GestureResult {
163 self.process_at(event, Instant::now())
164 }
165
166 fn reset(&mut self) {
167 self.first_tap_position = None;
168 self.first_tap_time = None;
169 self.first_tap_button = None;
170 self.down_position = None;
171 self.down_button = None;
172 }
173
174 fn priority(&self) -> u32 {
175 15 }
177
178 fn resets_on_peer_recognition(&self) -> bool {
179 false
186 }
187}
188
189#[derive(Debug)]
198pub struct TripleTapRecognizer {
199 max_distance: f32,
200 max_interval: Duration,
201 accept: ButtonMask,
202 first_tap_position: Option<Point>,
203 first_tap_time: Option<Instant>,
204 first_tap_button: Option<PointerButton>,
205 second_tap_position: Option<Point>,
206 second_tap_time: Option<Instant>,
207 second_tap_button: Option<PointerButton>,
208 down_position: Option<Point>,
209 down_button: Option<PointerButton>,
210}
211
212impl TripleTapRecognizer {
213 pub fn new() -> Self {
214 Self {
215 max_distance: 10.0,
216 max_interval: Duration::from_millis(300),
217 accept: ButtonMask::PRIMARY,
218 first_tap_position: None,
219 first_tap_time: None,
220 first_tap_button: None,
221 second_tap_position: None,
222 second_tap_time: None,
223 second_tap_button: None,
224 down_position: None,
225 down_button: None,
226 }
227 }
228
229 pub fn max_distance(mut self, d: f32) -> Self {
230 self.max_distance = d;
231 self
232 }
233
234 pub fn max_interval(mut self, interval: Duration) -> Self {
235 self.max_interval = interval;
236 self
237 }
238
239 pub fn accept_buttons(mut self, mask: impl Into<ButtonMask>) -> Self {
242 self.accept = mask.into();
243 self
244 }
245
246 pub fn accept_any_button(self) -> Self {
248 self.accept_buttons(ButtonMask::ALL)
249 }
250
251 pub fn process_at(&mut self, event: &RawPointerEvent, now: Instant) -> GestureResult {
253 match event {
254 RawPointerEvent::Down {
255 position, button, ..
256 } => {
257 if !self.accept.contains(*button) {
258 return GestureResult::Pending;
259 }
260 let mismatch = self.first_tap_button.map(|b| b != *button).unwrap_or(false)
263 || self
264 .second_tap_button
265 .map(|b| b != *button)
266 .unwrap_or(false);
267 if mismatch {
268 self.first_tap_position = None;
269 self.first_tap_time = None;
270 self.first_tap_button = None;
271 self.second_tap_position = None;
272 self.second_tap_time = None;
273 self.second_tap_button = None;
274 }
275 self.down_position = Some(*position);
276 self.down_button = Some(*button);
277 GestureResult::Pending
278 }
279 RawPointerEvent::Move { position } => {
280 if let Some(down) = self.down_position
281 && distance(*position, down) > self.max_distance
282 {
283 return GestureResult::Failed;
284 }
285 GestureResult::Pending
286 }
287 RawPointerEvent::Up {
288 position,
289 button,
290 modifiers,
291 } => {
292 let Some(down) = self.down_position else {
293 return GestureResult::Failed;
294 };
295 let Some(down_button) = self.down_button else {
296 return GestureResult::Failed;
297 };
298 self.down_position = None;
299 self.down_button = None;
300 if *button != down_button {
301 return GestureResult::Failed;
302 }
303 if distance(*position, down) > self.max_distance {
304 return GestureResult::Failed;
305 }
306
307 if let (
310 Some(first_pos),
311 Some(first_time),
312 Some(first_button),
313 Some(second_pos),
314 Some(second_time),
315 Some(second_button),
316 ) = (
317 self.first_tap_position,
318 self.first_tap_time,
319 self.first_tap_button,
320 self.second_tap_position,
321 self.second_tap_time,
322 self.second_tap_button,
323 ) {
324 if first_button == *button
325 && second_button == *button
326 && distance(*position, second_pos) <= self.max_distance
327 && now.duration_since(second_time) <= self.max_interval
328 && distance(second_pos, first_pos) <= self.max_distance
329 && second_time.duration_since(first_time) <= self.max_interval
330 {
331 self.reset();
332 return GestureResult::Recognized(GestureEvent::TripleTap(TapEvent {
333 position: *position,
334 button: *button,
335 modifiers: *modifiers,
336 }));
337 }
338 self.first_tap_position = Some(*position);
341 self.first_tap_time = Some(now);
342 self.first_tap_button = Some(*button);
343 self.second_tap_position = None;
344 self.second_tap_time = None;
345 self.second_tap_button = None;
346 return GestureResult::Pending;
347 }
348
349 if let (Some(first_pos), Some(first_time), Some(first_button)) = (
351 self.first_tap_position,
352 self.first_tap_time,
353 self.first_tap_button,
354 ) {
355 if first_button == *button
357 && distance(*position, first_pos) <= self.max_distance
358 && now.duration_since(first_time) <= self.max_interval
359 {
360 self.second_tap_position = Some(*position);
361 self.second_tap_time = Some(now);
362 self.second_tap_button = Some(*button);
363 return GestureResult::Pending;
364 }
365 self.first_tap_position = Some(*position);
367 self.first_tap_time = Some(now);
368 self.first_tap_button = Some(*button);
369 self.second_tap_position = None;
370 self.second_tap_time = None;
371 self.second_tap_button = None;
372 return GestureResult::Pending;
373 }
374
375 self.first_tap_position = Some(*position);
377 self.first_tap_time = Some(now);
378 self.first_tap_button = Some(*button);
379 self.second_tap_position = None;
380 self.second_tap_time = None;
381 self.second_tap_button = None;
382 GestureResult::Pending
383 }
384 }
385 }
386}
387
388impl Default for TripleTapRecognizer {
389 fn default() -> Self {
390 Self::new()
391 }
392}
393
394impl GestureRecognizer for TripleTapRecognizer {
395 fn process(&mut self, event: &RawPointerEvent) -> GestureResult {
396 self.process_at(event, Instant::now())
397 }
398
399 fn reset(&mut self) {
400 self.first_tap_position = None;
401 self.first_tap_time = None;
402 self.first_tap_button = None;
403 self.second_tap_position = None;
404 self.second_tap_time = None;
405 self.second_tap_button = None;
406 self.down_position = None;
407 self.down_button = None;
408 }
409
410 fn priority(&self) -> u32 {
411 20
415 }
416
417 fn resets_on_peer_recognition(&self) -> bool {
418 false
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::event::Modifiers;
430 use crate::gesture::test_helpers::*;
431
432 #[test]
435 fn double_tap_recognized_within_interval() {
436 let mut rec = DoubleTapRecognizer::new().max_interval(Duration::from_millis(500));
437 let t0 = Instant::now();
438 let p = Point::new(10.0, 10.0);
439
440 rec.process_at(&down(p), t0);
442 rec.process_at(&up(p), t0 + Duration::from_millis(50));
443
444 rec.process_at(
446 &down(Point::new(11.0, 10.0)),
447 t0 + Duration::from_millis(200),
448 );
449 let result = rec.process_at(&up(Point::new(11.0, 10.0)), t0 + Duration::from_millis(250));
450 assert!(matches!(
451 result,
452 GestureResult::Recognized(GestureEvent::DoubleTap(_))
453 ));
454 }
455
456 #[test]
457 fn double_tap_fails_if_too_slow() {
458 let mut rec = DoubleTapRecognizer::new().max_interval(Duration::from_millis(300));
459 let t0 = Instant::now();
460 let p = Point::new(10.0, 10.0);
461
462 rec.process_at(&down(p), t0);
463 rec.process_at(&up(p), t0 + Duration::from_millis(50));
464
465 rec.process_at(&down(p), t0 + Duration::from_millis(400));
466 let result = rec.process_at(&up(p), t0 + Duration::from_millis(450));
467 assert!(matches!(result, GestureResult::Pending));
469 }
470
471 #[test]
472 fn double_tap_fails_if_too_far() {
473 let mut rec = DoubleTapRecognizer::new().max_distance(5.0);
474 let t0 = Instant::now();
475 let p = Point::new(10.0, 10.0);
476
477 rec.process_at(&down(p), t0);
478 rec.process_at(&up(p), t0 + Duration::from_millis(50));
479
480 rec.process_at(
482 &down(Point::new(50.0, 50.0)),
483 t0 + Duration::from_millis(100),
484 );
485 let result = rec.process_at(&up(Point::new(50.0, 50.0)), t0 + Duration::from_millis(150));
486 assert!(matches!(result, GestureResult::Pending));
488 }
489
490 #[test]
491 fn double_tap_button_mismatch_fails_at_second_down() {
492 let mut rec = DoubleTapRecognizer::new().accept_any_button();
495 let t0 = Instant::now();
496 let p = Point::new(10.0, 10.0);
497
498 rec.process_at(&down_btn(p, PointerButton::Primary), t0);
499 rec.process_at(
500 &up_btn(p, PointerButton::Primary),
501 t0 + Duration::from_millis(50),
502 );
503
504 rec.process_at(
505 &down_btn(p, PointerButton::Secondary),
506 t0 + Duration::from_millis(150),
507 );
508 let result = rec.process_at(
509 &up_btn(p, PointerButton::Secondary),
510 t0 + Duration::from_millis(200),
511 );
512 assert!(matches!(result, GestureResult::Pending));
513 }
514
515 #[test]
516 fn double_tap_carries_modifiers_from_second_up() {
517 let mut rec = DoubleTapRecognizer::new();
518 let t0 = Instant::now();
519 let p = Point::new(10.0, 10.0);
520
521 rec.process_at(&down(p), t0);
522 rec.process_at(&up(p), t0 + Duration::from_millis(50));
523
524 rec.process_at(&down(p), t0 + Duration::from_millis(150));
525 let result = rec.process_at(
526 &up_full(p, PointerButton::Primary, Modifiers::SHIFT),
527 t0 + Duration::from_millis(200),
528 );
529 match result {
530 GestureResult::Recognized(GestureEvent::DoubleTap(event)) => {
531 assert!(event.modifiers.shift());
532 }
533 other => panic!("expected DoubleTap with shift, got {:?}", other),
534 }
535 }
536
537 #[test]
540 fn triple_tap_recognized_within_intervals() {
541 let mut rec = TripleTapRecognizer::new();
542 let t0 = Instant::now();
543 let p = Point::new(10.0, 10.0);
544
545 for i in 0..3 {
547 let offset = Duration::from_millis(200 * i as u64);
548 rec.process_at(&down(p), t0 + offset);
549 let result = rec.process_at(&up(p), t0 + offset + Duration::from_millis(50));
550 if i < 2 {
551 assert!(matches!(result, GestureResult::Pending));
552 } else {
553 assert!(matches!(
554 result,
555 GestureResult::Recognized(GestureEvent::TripleTap(_))
556 ));
557 }
558 }
559 }
560
561 #[test]
562 fn triple_tap_fails_if_third_is_too_slow() {
563 let mut rec = TripleTapRecognizer::new().max_interval(Duration::from_millis(300));
564 let t0 = Instant::now();
565 let stamp = |ms| t0 + Duration::from_millis(ms);
566 let p = Point::new(10.0, 10.0);
567
568 rec.process_at(&down(p), stamp(0));
569 rec.process_at(&up(p), stamp(50));
570 rec.process_at(&down(p), stamp(200));
571 rec.process_at(&up(p), stamp(250));
572
573 rec.process_at(&down(p), stamp(700));
575 let result = rec.process_at(&up(p), stamp(750));
576 assert!(matches!(result, GestureResult::Pending));
577 }
578
579 #[test]
580 fn triple_tap_fails_if_third_is_too_far() {
581 let mut rec = TripleTapRecognizer::new().max_distance(5.0);
582 let t0 = Instant::now();
583 let stamp = |ms| t0 + Duration::from_millis(ms);
584 let p = Point::new(10.0, 10.0);
585
586 rec.process_at(&down(p), stamp(0));
587 rec.process_at(&up(p), stamp(50));
588 rec.process_at(&down(p), stamp(100));
589 rec.process_at(&up(p), stamp(150));
590
591 rec.process_at(&down(Point::new(30.0, 10.0)), stamp(200));
593 let result = rec.process_at(&up(Point::new(30.0, 10.0)), stamp(250));
594 assert!(matches!(result, GestureResult::Pending));
595 }
596
597 #[test]
598 fn triple_tap_button_mismatch_fails_at_third_down() {
599 let mut rec = TripleTapRecognizer::new().accept_any_button();
603 let t0 = Instant::now();
604 let stamp = |ms| t0 + Duration::from_millis(ms);
605 let p = Point::new(10.0, 10.0);
606
607 rec.process_at(&down_btn(p, PointerButton::Primary), stamp(0));
608 rec.process_at(&up_btn(p, PointerButton::Primary), stamp(50));
609 rec.process_at(&down_btn(p, PointerButton::Primary), stamp(150));
610 rec.process_at(&up_btn(p, PointerButton::Primary), stamp(200));
611
612 rec.process_at(&down_btn(p, PointerButton::Secondary), stamp(300));
613 let result = rec.process_at(&up_btn(p, PointerButton::Secondary), stamp(350));
614 assert!(matches!(result, GestureResult::Pending));
615 }
616}