1#![forbid(unsafe_code)]
2
3use web_time::{Duration, Instant};
43
44use crate::event::{Event, KeyCode, KeyEventKind, Modifiers, MouseButton, MouseEventKind};
45use crate::semantic_event::{ChordKey, Position, SemanticEvent, SwipeDirection};
46
47#[derive(Debug, Clone)]
53pub struct GestureConfig {
54 pub multi_click_timeout: Duration,
56 pub long_press_threshold: Duration,
58 pub drag_threshold: u16,
60 pub chord_timeout: Duration,
62 pub swipe_velocity_threshold: f32,
71 pub click_tolerance: u16,
73}
74
75impl Default for GestureConfig {
76 fn default() -> Self {
77 Self {
78 multi_click_timeout: Duration::from_millis(300),
79 long_press_threshold: Duration::from_millis(500),
80 drag_threshold: 3,
81 chord_timeout: Duration::from_millis(1000),
82 swipe_velocity_threshold: 50.0,
83 click_tolerance: 1,
84 }
85 }
86}
87
88#[derive(Debug, Clone)]
94struct ClickState {
95 pos: Position,
96 button: MouseButton,
97 time: Instant,
98 count: u8,
99}
100
101#[derive(Debug, Clone)]
103struct DragTracker {
104 start_pos: Position,
105 button: MouseButton,
106 last_pos: Position,
107 started: bool,
108 first_move: Option<Instant>,
111}
112
113pub struct GestureRecognizer {
123 config: GestureConfig,
124
125 last_click: Option<ClickState>,
127
128 mouse_down: Option<(Position, MouseButton, Instant)>,
130 drag: Option<DragTracker>,
131
132 long_press_pos: Option<(Position, Instant)>,
134 long_press_fired: bool,
135
136 chord_buffer: Vec<ChordKey>,
138 chord_start: Option<Instant>,
139}
140
141impl std::fmt::Debug for GestureRecognizer {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.debug_struct("GestureRecognizer")
144 .field("dragging", &self.is_dragging())
145 .field("chord_len", &self.chord_buffer.len())
146 .finish()
147 }
148}
149
150impl GestureRecognizer {
151 #[must_use]
153 pub fn new(config: GestureConfig) -> Self {
154 Self {
155 config,
156 last_click: None,
157 mouse_down: None,
158 drag: None,
159 long_press_pos: None,
160 long_press_fired: false,
161 chord_buffer: Vec::with_capacity(4),
162 chord_start: None,
163 }
164 }
165
166 #[inline]
171 pub fn process(&mut self, event: &Event, now: Instant) -> Vec<SemanticEvent> {
172 let mut out = Vec::with_capacity(2);
173
174 self.expire_chord(now);
176
177 match event {
178 Event::Mouse(mouse) => {
179 let pos = Position::new(mouse.x, mouse.y);
180 match mouse.kind {
181 MouseEventKind::Down(button) => {
182 self.on_mouse_down(pos, button, now, &mut out);
183 }
184 MouseEventKind::Up(button) => {
185 self.on_mouse_up(pos, button, now, &mut out);
186 }
187 MouseEventKind::Drag(button) => {
188 self.on_mouse_drag(pos, button, now, &mut out);
189 }
190 MouseEventKind::Moved => {
191 self.long_press_pos = None;
193 }
194 _ => {}
195 }
196 }
197 Event::Key(key) => {
198 if key.kind != KeyEventKind::Press {
199 return out;
200 }
201
202 if key.code == KeyCode::Escape {
204 if let Some(drag) = self.drag.take()
205 && drag.started
206 {
207 out.push(SemanticEvent::DragCancel);
208 }
209 self.mouse_down = None;
210 self.long_press_pos = None;
211 self.chord_buffer.clear();
212 self.chord_start = None;
213 return out;
214 }
215
216 let has_modifier = key
218 .modifiers
219 .intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER);
220
221 if has_modifier {
222 let chord_key = ChordKey::new(key.code, key.modifiers);
223 if self.chord_buffer.is_empty() {
224 self.chord_start = Some(now);
225 }
226 self.chord_buffer.push(chord_key);
227
228 if self.chord_buffer.len() >= 2 {
231 out.push(SemanticEvent::Chord {
232 sequence: self.chord_buffer.clone(),
233 });
234 self.chord_buffer.clear();
235 self.chord_start = None;
236 }
237 } else {
238 self.chord_buffer.clear();
240 self.chord_start = None;
241 }
242 }
243 Event::Focus(false) => {
244 if let Some(drag) = self.drag.take()
246 && drag.started
247 {
248 out.push(SemanticEvent::DragCancel);
249 }
250 self.mouse_down = None;
251 self.long_press_pos = None;
252 self.long_press_fired = false;
253 }
254 _ => {}
255 }
256
257 out
258 }
259
260 #[inline]
265 pub fn check_long_press(&mut self, now: Instant) -> Option<SemanticEvent> {
266 if self.long_press_fired {
267 return None;
268 }
269 if let Some((pos, down_time)) = self.long_press_pos {
270 let elapsed = now.saturating_duration_since(down_time);
271 if elapsed >= self.config.long_press_threshold {
272 self.long_press_fired = true;
273 return Some(SemanticEvent::LongPress {
274 pos,
275 duration: elapsed,
276 });
277 }
278 }
279 None
280 }
281
282 #[inline]
284 #[must_use]
285 pub fn is_dragging(&self) -> bool {
286 self.drag.as_ref().is_some_and(|d| d.started)
287 }
288
289 pub fn reset(&mut self) {
291 self.last_click = None;
292 self.mouse_down = None;
293 self.drag = None;
294 self.long_press_pos = None;
295 self.long_press_fired = false;
296 self.chord_buffer.clear();
297 self.chord_start = None;
298 }
299
300 #[inline]
302 #[must_use]
303 pub fn config(&self) -> &GestureConfig {
304 &self.config
305 }
306
307 pub fn set_config(&mut self, config: GestureConfig) {
309 self.config = config;
310 }
311}
312
313impl GestureRecognizer {
318 fn on_mouse_down(
319 &mut self,
320 pos: Position,
321 button: MouseButton,
322 now: Instant,
323 _out: &mut Vec<SemanticEvent>,
324 ) {
325 self.mouse_down = Some((pos, button, now));
326 self.drag = Some(DragTracker {
327 start_pos: pos,
328 button,
329 last_pos: pos,
330 started: false,
331 first_move: None,
332 });
333 self.long_press_pos = Some((pos, now));
334 self.long_press_fired = false;
335 }
336
337 fn on_mouse_up(
338 &mut self,
339 pos: Position,
340 button: MouseButton,
341 now: Instant,
342 out: &mut Vec<SemanticEvent>,
343 ) {
344 self.long_press_pos = None;
345 self.long_press_fired = false;
346
347 if let Some(drag) = self.drag.take()
349 && drag.started
350 {
351 out.push(SemanticEvent::DragEnd {
352 start: drag.start_pos,
353 end: pos,
354 });
355 if let Some(swipe) = self.classify_swipe(&drag, pos, now) {
356 out.push(swipe);
357 }
358 self.mouse_down = None;
359 return;
360 }
361
362 let pressed = self.mouse_down.take();
367 if !matches!(pressed, Some((_, down_button, _)) if down_button == button) {
368 return;
369 }
370
371 let click_count = if let Some(ref last) = self.last_click {
373 if last.button == button
374 && last.pos.manhattan_distance(pos) <= u32::from(self.config.click_tolerance)
375 && now.saturating_duration_since(last.time) <= self.config.multi_click_timeout
376 && last.count < 3
377 {
378 last.count + 1
379 } else {
380 1
381 }
382 } else {
383 1
384 };
385
386 self.last_click = Some(ClickState {
387 pos,
388 button,
389 time: now,
390 count: click_count,
391 });
392
393 match click_count {
394 1 => out.push(SemanticEvent::Click { pos, button }),
395 2 => out.push(SemanticEvent::DoubleClick { pos, button }),
396 3 => out.push(SemanticEvent::TripleClick { pos, button }),
397 _ => out.push(SemanticEvent::Click { pos, button }),
398 }
399 }
400
401 fn on_mouse_drag(
402 &mut self,
403 pos: Position,
404 button: MouseButton,
405 now: Instant,
406 out: &mut Vec<SemanticEvent>,
407 ) {
408 self.long_press_pos = None;
410
411 let Some(ref mut drag) = self.drag else {
412 self.drag = Some(DragTracker {
414 start_pos: pos,
415 button,
416 last_pos: pos,
417 started: false,
418 first_move: Some(now),
419 });
420 return;
421 };
422 drag.first_move.get_or_insert(now);
423
424 if !drag.started {
425 let distance = drag.start_pos.manhattan_distance(pos);
427 if distance >= u32::from(self.config.drag_threshold) {
428 drag.started = true;
429 out.push(SemanticEvent::DragStart {
430 pos: drag.start_pos,
431 button: drag.button,
432 });
433 }
434 }
435
436 if drag.started {
437 let axis = |now: u16, before: u16| {
441 let d = i32::from(now) - i32::from(before);
442 i16::try_from(d).unwrap_or(if d < 0 { i16::MIN } else { i16::MAX })
443 };
444 let delta = (axis(pos.x, drag.last_pos.x), axis(pos.y, drag.last_pos.y));
445 out.push(SemanticEvent::DragMove {
446 start: drag.start_pos,
447 current: pos,
448 delta,
449 });
450 }
451
452 drag.last_pos = pos;
453 }
454
455 fn classify_swipe(
459 &self,
460 drag: &DragTracker,
461 end: Position,
462 now: Instant,
463 ) -> Option<SemanticEvent> {
464 let dx = i32::from(end.x) - i32::from(drag.start_pos.x);
465 let dy = i32::from(end.y) - i32::from(drag.start_pos.y);
466 let direction = match dx.abs().cmp(&dy.abs()) {
467 std::cmp::Ordering::Greater if dx > 0 => SwipeDirection::Right,
468 std::cmp::Ordering::Greater => SwipeDirection::Left,
469 std::cmp::Ordering::Less if dy > 0 => SwipeDirection::Down,
470 std::cmp::Ordering::Less => SwipeDirection::Up,
471 std::cmp::Ordering::Equal => return None,
473 };
474 let distance = u16::try_from(dx.abs().max(dy.abs())).unwrap_or(u16::MAX);
476 if distance < self.config.drag_threshold {
477 return None;
478 }
479 let elapsed = now
482 .saturating_duration_since(drag.first_move?)
483 .max(Duration::from_millis(1));
484 let velocity = f32::from(distance) / elapsed.as_secs_f32();
485 (velocity >= self.config.swipe_velocity_threshold).then_some(SemanticEvent::Swipe {
486 direction,
487 distance,
488 velocity,
489 })
490 }
491
492 fn expire_chord(&mut self, now: Instant) {
493 if let Some(start) = self.chord_start
494 && now.saturating_duration_since(start) > self.config.chord_timeout
495 {
496 self.chord_buffer.clear();
497 self.chord_start = None;
498 }
499 }
500}
501
502#[cfg(test)]
507mod tests {
508 use super::*;
509 use crate::event::{KeyEvent, MouseEvent};
510
511 fn now() -> Instant {
512 Instant::now()
513 }
514
515 fn mouse_down(x: u16, y: u16, button: MouseButton) -> Event {
516 Event::Mouse(MouseEvent {
517 kind: MouseEventKind::Down(button),
518 x,
519 y,
520 modifiers: Modifiers::NONE,
521 })
522 }
523
524 fn mouse_up(x: u16, y: u16, button: MouseButton) -> Event {
525 Event::Mouse(MouseEvent {
526 kind: MouseEventKind::Up(button),
527 x,
528 y,
529 modifiers: Modifiers::NONE,
530 })
531 }
532
533 fn mouse_drag(x: u16, y: u16, button: MouseButton) -> Event {
534 Event::Mouse(MouseEvent {
535 kind: MouseEventKind::Drag(button),
536 x,
537 y,
538 modifiers: Modifiers::NONE,
539 })
540 }
541
542 fn key_press(code: KeyCode, modifiers: Modifiers) -> Event {
543 Event::Key(KeyEvent {
544 code,
545 modifiers,
546 kind: KeyEventKind::Press,
547 })
548 }
549
550 fn esc() -> Event {
551 key_press(KeyCode::Escape, Modifiers::NONE)
552 }
553
554 const MS_50: Duration = Duration::from_millis(50);
555 const MS_100: Duration = Duration::from_millis(100);
556 const MS_200: Duration = Duration::from_millis(200);
557 const MS_500: Duration = Duration::from_millis(500);
558 const MS_600: Duration = Duration::from_millis(600);
559
560 #[test]
563 fn single_click() {
564 let mut gr = GestureRecognizer::new(GestureConfig::default());
565 let t = now();
566
567 let events = gr.process(&mouse_down(5, 5, MouseButton::Left), t);
568 assert!(events.is_empty());
569
570 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
571 assert_eq!(events.len(), 1);
572 assert!(matches!(
573 events[0],
574 SemanticEvent::Click {
575 pos: Position { x: 5, y: 5 },
576 button: MouseButton::Left,
577 }
578 ));
579 }
580
581 #[test]
582 fn double_click() {
583 let mut gr = GestureRecognizer::new(GestureConfig::default());
584 let t = now();
585
586 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
588 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
589
590 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_100);
592 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
593
594 assert_eq!(events.len(), 1);
595 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
596 }
597
598 #[test]
599 fn triple_click() {
600 let mut gr = GestureRecognizer::new(GestureConfig::default());
601 let t = now();
602
603 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
605 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
606
607 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_100);
609 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
610
611 gr.process(
613 &mouse_down(5, 5, MouseButton::Left),
614 t + Duration::from_millis(250),
615 );
616 let events = gr.process(
617 &mouse_up(5, 5, MouseButton::Left),
618 t + Duration::from_millis(280),
619 );
620
621 assert_eq!(events.len(), 1);
622 assert!(matches!(events[0], SemanticEvent::TripleClick { .. }));
623 }
624
625 #[test]
626 fn double_click_timeout_resets_to_single() {
627 let mut gr = GestureRecognizer::new(GestureConfig::default());
628 let t = now();
629
630 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
632 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
633
634 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_500);
636 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_600);
637
638 assert_eq!(events.len(), 1);
639 assert!(matches!(events[0], SemanticEvent::Click { .. }));
640 }
641
642 #[test]
643 fn different_position_resets_click_count() {
644 let mut gr = GestureRecognizer::new(GestureConfig::default());
645 let t = now();
646
647 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
649 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
650
651 gr.process(&mouse_down(20, 20, MouseButton::Left), t + MS_100);
653 let events = gr.process(&mouse_up(20, 20, MouseButton::Left), t + MS_200);
654
655 assert_eq!(events.len(), 1);
656 assert!(matches!(events[0], SemanticEvent::Click { .. }));
657 }
658
659 #[test]
660 fn different_button_resets_click_count() {
661 let mut gr = GestureRecognizer::new(GestureConfig::default());
662 let t = now();
663
664 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
666 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
667
668 gr.process(&mouse_down(5, 5, MouseButton::Right), t + MS_100);
670 let events = gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_200);
671
672 assert_eq!(events.len(), 1);
673 assert!(matches!(
674 events[0],
675 SemanticEvent::Click {
676 button: MouseButton::Right,
677 ..
678 }
679 ));
680 }
681
682 #[test]
683 fn click_position_tolerance() {
684 let mut gr = GestureRecognizer::new(GestureConfig::default());
685 let t = now();
686
687 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
689 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
690
691 gr.process(&mouse_down(6, 5, MouseButton::Left), t + MS_100);
693 let events = gr.process(&mouse_up(6, 5, MouseButton::Left), t + MS_200);
694
695 assert_eq!(events.len(), 1);
696 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
697 }
698
699 #[test]
702 fn drag_starts_after_threshold() {
703 let mut gr = GestureRecognizer::new(GestureConfig::default());
704 let t = now();
705
706 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
707
708 let events = gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_50);
710 assert!(events.is_empty());
711 assert!(!gr.is_dragging());
712
713 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_100);
715 assert!(!events.is_empty());
716 assert!(
717 events
718 .iter()
719 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
720 );
721 assert!(gr.is_dragging());
722 }
723
724 #[test]
725 fn drag_move_has_correct_delta() {
726 let mut gr = GestureRecognizer::new(GestureConfig::default());
727 let t = now();
728
729 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
730 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
732
733 let drag_move = events
735 .iter()
736 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
737 assert!(drag_move.is_some());
738 if let Some(SemanticEvent::DragMove {
739 start,
740 current,
741 delta,
742 }) = drag_move
743 {
744 assert_eq!(*start, Position::new(5, 5));
745 assert_eq!(*current, Position::new(10, 5));
746 assert_eq!(*delta, (5, 0));
747 }
748 }
749
750 #[test]
751 fn drag_end_on_mouse_up() {
752 let mut gr = GestureRecognizer::new(GestureConfig::default());
753 let t = now();
754
755 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
756 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
757
758 let events = gr.process(&mouse_up(12, 5, MouseButton::Left), t + MS_100);
761 assert_eq!(events.len(), 2);
762 assert!(matches!(
763 events[0],
764 SemanticEvent::DragEnd {
765 start: Position { x: 5, y: 5 },
766 end: Position { x: 12, y: 5 },
767 }
768 ));
769 match events[1] {
770 SemanticEvent::Swipe {
771 direction,
772 distance,
773 velocity,
774 } => {
775 assert_eq!(direction, SwipeDirection::Right);
776 assert_eq!(distance, 7);
777 assert!((velocity - 140.0).abs() < 1e-3, "velocity {velocity}");
778 }
779 ref other => panic!("expected Swipe, got {other:?}"),
780 }
781 assert!(!gr.is_dragging());
782 }
783
784 #[test]
785 fn drag_cancel_on_escape() {
786 let mut gr = GestureRecognizer::new(GestureConfig::default());
787 let t = now();
788
789 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
790 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
791 assert!(gr.is_dragging());
792
793 let events = gr.process(&esc(), t + MS_100);
794 assert_eq!(events.len(), 1);
795 assert!(matches!(events[0], SemanticEvent::DragCancel));
796 assert!(!gr.is_dragging());
797 }
798
799 #[test]
800 fn drag_prevents_click() {
801 let mut gr = GestureRecognizer::new(GestureConfig::default());
802 let t = now();
803
804 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
805 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
806
807 let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_100);
809 assert!(matches!(events[0], SemanticEvent::DragEnd { .. }));
810 assert!(
811 !events
812 .iter()
813 .any(|e| matches!(e, SemanticEvent::Click { .. }))
814 );
815 }
816
817 #[test]
820 fn long_press_fires_after_threshold() {
821 let mut gr = GestureRecognizer::new(GestureConfig::default());
822 let t = now();
823
824 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
825
826 assert!(gr.check_long_press(t + MS_200).is_none());
828
829 let lp = gr.check_long_press(t + MS_600);
831 assert!(lp.is_some());
832 if let Some(SemanticEvent::LongPress { pos, duration }) = lp {
833 assert_eq!(pos, Position::new(5, 5));
834 assert!(duration >= Duration::from_millis(500));
835 }
836 }
837
838 #[test]
839 fn long_press_not_repeated() {
840 let mut gr = GestureRecognizer::new(GestureConfig::default());
841 let t = now();
842
843 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
844
845 assert!(gr.check_long_press(t + MS_600).is_some());
847
848 assert!(
850 gr.check_long_press(t + Duration::from_millis(700))
851 .is_none()
852 );
853 }
854
855 #[test]
856 fn drag_cancels_long_press() {
857 let mut gr = GestureRecognizer::new(GestureConfig::default());
858 let t = now();
859
860 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
861 gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_100);
863
864 assert!(gr.check_long_press(t + MS_600).is_none());
866 }
867
868 #[test]
869 fn mouse_up_cancels_long_press() {
870 let mut gr = GestureRecognizer::new(GestureConfig::default());
871 let t = now();
872
873 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
874 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
875
876 assert!(gr.check_long_press(t + MS_600).is_none());
878 }
879
880 #[test]
883 fn two_key_chord() {
884 let mut gr = GestureRecognizer::new(GestureConfig::default());
885 let t = now();
886
887 let events1 = gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
889 assert!(events1.is_empty()); let events2 = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
893 assert_eq!(events2.len(), 1);
894 if let SemanticEvent::Chord { sequence } = &events2[0] {
895 assert_eq!(sequence.len(), 2);
896 assert_eq!(sequence[0].code, KeyCode::Char('k'));
897 assert_eq!(sequence[1].code, KeyCode::Char('c'));
898 } else {
899 panic!("Expected Chord event");
900 }
901 }
902
903 #[test]
904 fn chord_timeout_clears_buffer() {
905 let mut gr = GestureRecognizer::new(GestureConfig::default());
906 let t = now();
907
908 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
910
911 let events = gr.process(
913 &key_press(KeyCode::Char('c'), Modifiers::CTRL),
914 t + Duration::from_millis(1100),
915 );
916
917 assert!(events.is_empty());
919 }
920
921 #[test]
922 fn non_modifier_key_clears_chord() {
923 let mut gr = GestureRecognizer::new(GestureConfig::default());
924 let t = now();
925
926 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
928
929 gr.process(&key_press(KeyCode::Char('x'), Modifiers::NONE), t + MS_50);
931
932 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
934 assert!(events.is_empty()); }
936
937 #[test]
938 fn escape_clears_chord() {
939 let mut gr = GestureRecognizer::new(GestureConfig::default());
940 let t = now();
941
942 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
943 gr.process(&esc(), t + MS_50);
944
945 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
947 assert!(events.is_empty());
948 }
949
950 #[test]
953 fn focus_loss_cancels_drag() {
954 let mut gr = GestureRecognizer::new(GestureConfig::default());
955 let t = now();
956
957 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
958 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
959 assert!(gr.is_dragging());
960
961 let events = gr.process(&Event::Focus(false), t + MS_100);
962 assert_eq!(events.len(), 1);
963 assert!(matches!(events[0], SemanticEvent::DragCancel));
964 assert!(!gr.is_dragging());
965 }
966
967 #[test]
968 fn focus_loss_without_drag_is_silent() {
969 let mut gr = GestureRecognizer::new(GestureConfig::default());
970 let t = now();
971
972 let events = gr.process(&Event::Focus(false), t);
973 assert!(events.is_empty());
974 }
975
976 #[test]
979 fn reset_clears_all_state() {
980 let mut gr = GestureRecognizer::new(GestureConfig::default());
981 let t = now();
982
983 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
985 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
986 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t + MS_100);
987
988 assert!(gr.is_dragging());
989
990 gr.reset();
991
992 assert!(!gr.is_dragging());
993 assert!(gr.last_click.is_none());
994 assert!(gr.mouse_down.is_none());
995 assert!(gr.drag.is_none());
996 assert!(gr.chord_buffer.is_empty());
997 assert!(gr.chord_start.is_none());
998 }
999
1000 #[test]
1003 fn quadruple_click_wraps_to_single() {
1004 let mut gr = GestureRecognizer::new(GestureConfig::default());
1005 let t = now();
1006
1007 for i in 0..3u32 {
1009 let offset = Duration::from_millis(i as u64 * 80);
1010 gr.process(&mouse_down(5, 5, MouseButton::Left), t + offset);
1011 gr.process(&mouse_up(5, 5, MouseButton::Left), t + offset + MS_50);
1012 }
1013
1014 gr.process(
1016 &mouse_down(5, 5, MouseButton::Left),
1017 t + Duration::from_millis(260),
1018 );
1019 let events = gr.process(
1020 &mouse_up(5, 5, MouseButton::Left),
1021 t + Duration::from_millis(280),
1022 );
1023
1024 assert_eq!(events.len(), 1);
1025 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1026 }
1027
1028 #[test]
1029 fn key_release_ignored() {
1030 let mut gr = GestureRecognizer::new(GestureConfig::default());
1031 let t = now();
1032
1033 let events = gr.process(
1034 &Event::Key(KeyEvent {
1035 code: KeyCode::Char('k'),
1036 modifiers: Modifiers::CTRL,
1037 kind: KeyEventKind::Release,
1038 }),
1039 t,
1040 );
1041 assert!(events.is_empty());
1042 }
1043
1044 #[test]
1045 fn debug_format() {
1046 let gr = GestureRecognizer::new(GestureConfig::default());
1047 let dbg = format!("{:?}", gr);
1048 assert!(dbg.contains("GestureRecognizer"));
1049 }
1050
1051 #[test]
1054 fn right_click() {
1055 let mut gr = GestureRecognizer::new(GestureConfig::default());
1056 let t = now();
1057
1058 gr.process(&mouse_down(10, 10, MouseButton::Right), t);
1059 let events = gr.process(&mouse_up(10, 10, MouseButton::Right), t + MS_50);
1060
1061 assert_eq!(events.len(), 1);
1062 assert!(matches!(
1063 events[0],
1064 SemanticEvent::Click {
1065 button: MouseButton::Right,
1066 ..
1067 }
1068 ));
1069 }
1070
1071 #[test]
1072 fn middle_click() {
1073 let mut gr = GestureRecognizer::new(GestureConfig::default());
1074 let t = now();
1075
1076 gr.process(&mouse_down(10, 10, MouseButton::Middle), t);
1077 let events = gr.process(&mouse_up(10, 10, MouseButton::Middle), t + MS_50);
1078
1079 assert_eq!(events.len(), 1);
1080 assert!(matches!(
1081 events[0],
1082 SemanticEvent::Click {
1083 button: MouseButton::Middle,
1084 ..
1085 }
1086 ));
1087 }
1088
1089 #[test]
1090 fn click_at_origin() {
1091 let mut gr = GestureRecognizer::new(GestureConfig::default());
1092 let t = now();
1093
1094 gr.process(&mouse_down(0, 0, MouseButton::Left), t);
1095 let events = gr.process(&mouse_up(0, 0, MouseButton::Left), t + MS_50);
1096
1097 assert_eq!(events.len(), 1);
1098 if let SemanticEvent::Click { pos, .. } = &events[0] {
1099 assert_eq!(pos.x, 0);
1100 assert_eq!(pos.y, 0);
1101 }
1102 }
1103
1104 #[test]
1105 fn click_at_max_position() {
1106 let mut gr = GestureRecognizer::new(GestureConfig::default());
1107 let t = now();
1108
1109 gr.process(&mouse_down(u16::MAX, u16::MAX, MouseButton::Left), t);
1110 let events = gr.process(&mouse_up(u16::MAX, u16::MAX, MouseButton::Left), t + MS_50);
1111
1112 assert_eq!(events.len(), 1);
1113 if let SemanticEvent::Click { pos, .. } = &events[0] {
1114 assert_eq!(pos.x, u16::MAX);
1115 assert_eq!(pos.y, u16::MAX);
1116 }
1117 }
1118
1119 #[test]
1120 fn double_click_right_button() {
1121 let mut gr = GestureRecognizer::new(GestureConfig::default());
1122 let t = now();
1123
1124 gr.process(&mouse_down(5, 5, MouseButton::Right), t);
1125 gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_50);
1126
1127 gr.process(&mouse_down(5, 5, MouseButton::Right), t + MS_100);
1128 let events = gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_200);
1129
1130 assert_eq!(events.len(), 1);
1131 assert!(matches!(
1132 events[0],
1133 SemanticEvent::DoubleClick {
1134 button: MouseButton::Right,
1135 ..
1136 }
1137 ));
1138 }
1139
1140 #[test]
1141 fn click_position_beyond_tolerance() {
1142 let mut gr = GestureRecognizer::new(GestureConfig::default());
1143 let t = now();
1144
1145 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1146 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1147
1148 gr.process(&mouse_down(8, 5, MouseButton::Left), t + MS_100);
1150 let events = gr.process(&mouse_up(8, 5, MouseButton::Left), t + MS_200);
1151
1152 assert_eq!(events.len(), 1);
1153 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1154 }
1155
1156 #[test]
1159 fn drag_with_right_button() {
1160 let mut gr = GestureRecognizer::new(GestureConfig::default());
1161 let t = now();
1162
1163 gr.process(&mouse_down(5, 5, MouseButton::Right), t);
1164 let events = gr.process(&mouse_drag(10, 5, MouseButton::Right), t + MS_50);
1165
1166 assert!(events.iter().any(|e| matches!(
1167 e,
1168 SemanticEvent::DragStart {
1169 button: MouseButton::Right,
1170 ..
1171 }
1172 )));
1173 }
1174
1175 #[test]
1176 fn drag_vertical() {
1177 let mut gr = GestureRecognizer::new(GestureConfig::default());
1178 let t = now();
1179
1180 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1181 let events = gr.process(&mouse_drag(5, 10, MouseButton::Left), t + MS_50);
1182
1183 assert!(
1184 events
1185 .iter()
1186 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1187 );
1188 let drag_move = events
1189 .iter()
1190 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1191 if let Some(SemanticEvent::DragMove { delta, .. }) = drag_move {
1192 assert_eq!(delta.0, 0); assert_eq!(delta.1, 5); }
1195 }
1196
1197 #[test]
1198 fn drag_multiple_moves() {
1199 let mut gr = GestureRecognizer::new(GestureConfig::default());
1200 let t = now();
1201
1202 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1203 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1204
1205 let events = gr.process(&mouse_drag(15, 5, MouseButton::Left), t + MS_100);
1207 let drag_move = events
1208 .iter()
1209 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1210 if let Some(SemanticEvent::DragMove {
1211 start,
1212 current,
1213 delta,
1214 }) = drag_move
1215 {
1216 assert_eq!(*start, Position::new(5, 5));
1217 assert_eq!(*current, Position::new(15, 5));
1218 assert_eq!(*delta, (5, 0)); }
1220 }
1221
1222 #[test]
1223 fn drag_threshold_exactly_met() {
1224 let mut gr = GestureRecognizer::new(GestureConfig::default());
1225 let t = now();
1226
1227 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1228 let events = gr.process(&mouse_drag(8, 5, MouseButton::Left), t + MS_50);
1230
1231 assert!(
1232 events
1233 .iter()
1234 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1235 );
1236 }
1237
1238 #[test]
1239 fn drag_threshold_one_below() {
1240 let mut gr = GestureRecognizer::new(GestureConfig::default());
1241 let t = now();
1242
1243 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1244 let events = gr.process(&mouse_drag(7, 5, MouseButton::Left), t + MS_50);
1246
1247 assert!(
1248 !events
1249 .iter()
1250 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1251 );
1252 assert!(!gr.is_dragging());
1253 }
1254
1255 #[test]
1256 fn drag_state_reset_after_end() {
1257 let mut gr = GestureRecognizer::new(GestureConfig::default());
1258 let t = now();
1259
1260 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1262 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1263 gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_100);
1264
1265 assert!(!gr.is_dragging());
1266
1267 gr.process(&mouse_down(20, 20, MouseButton::Left), t + MS_200);
1269 let events = gr.process(
1270 &mouse_up(20, 20, MouseButton::Left),
1271 t + Duration::from_millis(250),
1272 );
1273
1274 assert_eq!(events.len(), 1);
1276 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1277 }
1278
1279 #[test]
1280 fn no_click_after_drag_cancel() {
1281 let mut gr = GestureRecognizer::new(GestureConfig::default());
1282 let t = now();
1283
1284 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1285 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1286 gr.process(&esc(), t + MS_100); let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_200);
1290 assert!(
1293 !events
1294 .iter()
1295 .any(|e| matches!(e, SemanticEvent::DragEnd { .. }))
1296 );
1297 }
1298
1299 #[test]
1302 fn long_press_correct_position() {
1303 let mut gr = GestureRecognizer::new(GestureConfig::default());
1304 let t = now();
1305
1306 gr.process(&mouse_down(42, 17, MouseButton::Left), t);
1307 let lp = gr.check_long_press(t + MS_600);
1308
1309 if let Some(SemanticEvent::LongPress { pos, .. }) = lp {
1310 assert_eq!(pos, Position::new(42, 17));
1311 } else {
1312 panic!("Expected LongPress");
1313 }
1314 }
1315
1316 #[test]
1317 fn long_press_with_custom_threshold() {
1318 let config = GestureConfig {
1319 long_press_threshold: Duration::from_millis(200),
1320 ..Default::default()
1321 };
1322 let mut gr = GestureRecognizer::new(config);
1323 let t = now();
1324
1325 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1326
1327 assert!(
1329 gr.check_long_press(t + Duration::from_millis(150))
1330 .is_none()
1331 );
1332
1333 assert!(
1335 gr.check_long_press(t + Duration::from_millis(250))
1336 .is_some()
1337 );
1338 }
1339
1340 #[test]
1341 fn long_press_resets_on_new_mouse_down() {
1342 let mut gr = GestureRecognizer::new(GestureConfig::default());
1343 let t = now();
1344
1345 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1346 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
1347
1348 gr.process(&mouse_down(10, 10, MouseButton::Left), t + MS_200);
1350
1351 assert!(gr.check_long_press(t + MS_600).is_none());
1353
1354 assert!(
1356 gr.check_long_press(t + Duration::from_millis(750))
1357 .is_some()
1358 );
1359 }
1360
1361 #[test]
1364 fn alt_key_chord() {
1365 let mut gr = GestureRecognizer::new(GestureConfig::default());
1366 let t = now();
1367
1368 gr.process(&key_press(KeyCode::Char('x'), Modifiers::ALT), t);
1369 let events = gr.process(&key_press(KeyCode::Char('y'), Modifiers::ALT), t + MS_100);
1370
1371 assert_eq!(events.len(), 1);
1372 if let SemanticEvent::Chord { sequence } = &events[0] {
1373 assert_eq!(sequence.len(), 2);
1374 assert!(sequence[0].modifiers.contains(Modifiers::ALT));
1375 }
1376 }
1377
1378 #[test]
1379 fn mixed_modifier_chord() {
1380 let mut gr = GestureRecognizer::new(GestureConfig::default());
1381 let t = now();
1382
1383 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1384 let events = gr.process(&key_press(KeyCode::Char('d'), Modifiers::ALT), t + MS_100);
1385
1386 assert_eq!(events.len(), 1);
1387 if let SemanticEvent::Chord { sequence } = &events[0] {
1388 assert_eq!(sequence[0].modifiers, Modifiers::CTRL);
1389 assert_eq!(sequence[1].modifiers, Modifiers::ALT);
1390 }
1391 }
1392
1393 #[test]
1394 fn chord_with_function_key() {
1395 let mut gr = GestureRecognizer::new(GestureConfig::default());
1396 let t = now();
1397
1398 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1399 let events = gr.process(&key_press(KeyCode::F(1), Modifiers::CTRL), t + MS_100);
1400
1401 assert_eq!(events.len(), 1);
1402 if let SemanticEvent::Chord { sequence } = &events[0] {
1403 assert_eq!(sequence[1].code, KeyCode::F(1));
1404 }
1405 }
1406
1407 #[test]
1408 fn single_modifier_key_no_chord_emitted() {
1409 let mut gr = GestureRecognizer::new(GestureConfig::default());
1410 let t = now();
1411
1412 let events = gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1414 assert!(events.is_empty());
1415 }
1416
1417 #[test]
1420 fn custom_click_tolerance() {
1421 let config = GestureConfig {
1422 click_tolerance: 5,
1423 ..Default::default()
1424 };
1425 let mut gr = GestureRecognizer::new(config);
1426 let t = now();
1427
1428 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1429 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1430
1431 gr.process(&mouse_down(10, 5, MouseButton::Left), t + MS_100);
1433 let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_200);
1434
1435 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
1436 }
1437
1438 #[test]
1439 fn custom_drag_threshold() {
1440 let config = GestureConfig {
1441 drag_threshold: 10,
1442 ..Default::default()
1443 };
1444 let mut gr = GestureRecognizer::new(config);
1445 let t = now();
1446
1447 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1448 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1450 assert!(!gr.is_dragging());
1451 assert!(events.is_empty());
1452
1453 let events = gr.process(&mouse_drag(15, 5, MouseButton::Left), t + MS_100);
1455 assert!(gr.is_dragging());
1456 assert!(
1457 events
1458 .iter()
1459 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1460 );
1461 }
1462
1463 #[test]
1464 fn custom_multi_click_timeout() {
1465 let config = GestureConfig {
1466 multi_click_timeout: Duration::from_millis(100),
1467 ..Default::default()
1468 };
1469 let mut gr = GestureRecognizer::new(config);
1470 let t = now();
1471
1472 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1473 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1474
1475 gr.process(
1477 &mouse_down(5, 5, MouseButton::Left),
1478 t + Duration::from_millis(150),
1479 );
1480 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
1481
1482 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1483 }
1484
1485 #[test]
1486 fn config_getter_and_setter() {
1487 let mut gr = GestureRecognizer::new(GestureConfig::default());
1488
1489 assert_eq!(gr.config().drag_threshold, 3);
1490
1491 let new_config = GestureConfig {
1492 drag_threshold: 10,
1493 ..Default::default()
1494 };
1495 gr.set_config(new_config);
1496
1497 assert_eq!(gr.config().drag_threshold, 10);
1498 }
1499
1500 #[test]
1503 fn click_then_drag_are_independent() {
1504 let mut gr = GestureRecognizer::new(GestureConfig::default());
1505 let t = now();
1506
1507 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1509 let click_events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1510 assert!(matches!(click_events[0], SemanticEvent::Click { .. }));
1511
1512 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_200);
1514 let drag_events = gr.process(
1515 &mouse_drag(10, 5, MouseButton::Left),
1516 t + Duration::from_millis(250),
1517 );
1518 assert!(
1519 drag_events
1520 .iter()
1521 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1522 );
1523
1524 let end_events = gr.process(
1525 &mouse_up(10, 5, MouseButton::Left),
1526 t + Duration::from_millis(300),
1527 );
1528 assert!(matches!(end_events[0], SemanticEvent::DragEnd { .. }));
1529 }
1530
1531 #[test]
1532 fn interleaved_mouse_and_keyboard() {
1533 let mut gr = GestureRecognizer::new(GestureConfig::default());
1534 let t = now();
1535
1536 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1538
1539 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_50);
1541 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
1542
1543 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_200);
1545
1546 assert_eq!(events.len(), 1);
1547 assert!(matches!(events[0], SemanticEvent::Chord { .. }));
1548 }
1549
1550 #[test]
1551 fn rapid_clicks_produce_correct_sequence() {
1552 let mut gr = GestureRecognizer::new(GestureConfig::default());
1553 let t = now();
1554 let mut results = Vec::new();
1555
1556 for i in 0..5u32 {
1557 let offset = Duration::from_millis(i as u64 * 60);
1558 gr.process(&mouse_down(5, 5, MouseButton::Left), t + offset);
1559 let events = gr.process(
1560 &mouse_up(5, 5, MouseButton::Left),
1561 t + offset + Duration::from_millis(30),
1562 );
1563 results.extend(events);
1564 }
1565
1566 assert!(results.len() == 5);
1568 assert!(matches!(results[0], SemanticEvent::Click { .. }));
1569 assert!(matches!(results[1], SemanticEvent::DoubleClick { .. }));
1570 assert!(matches!(results[2], SemanticEvent::TripleClick { .. }));
1571 assert!(matches!(results[3], SemanticEvent::Click { .. }));
1572 assert!(matches!(results[4], SemanticEvent::DoubleClick { .. }));
1573 }
1574
1575 #[test]
1576 fn tick_event_ignored() {
1577 let mut gr = GestureRecognizer::new(GestureConfig::default());
1578 let t = now();
1579
1580 let events = gr.process(&Event::Tick, t);
1581 assert!(events.is_empty());
1582 }
1583
1584 #[test]
1585 fn resize_event_ignored() {
1586 let mut gr = GestureRecognizer::new(GestureConfig::default());
1587 let t = now();
1588
1589 let events = gr.process(
1590 &Event::Resize {
1591 width: 80,
1592 height: 24,
1593 },
1594 t,
1595 );
1596 assert!(events.is_empty());
1597 }
1598
1599 #[test]
1600 fn focus_gain_ignored() {
1601 let mut gr = GestureRecognizer::new(GestureConfig::default());
1602 let t = now();
1603
1604 let events = gr.process(&Event::Focus(true), t);
1605 assert!(events.is_empty());
1606 }
1607
1608 #[test]
1609 fn default_config_values() {
1610 let config = GestureConfig::default();
1611 assert_eq!(config.multi_click_timeout, Duration::from_millis(300));
1612 assert_eq!(config.long_press_threshold, Duration::from_millis(500));
1613 assert_eq!(config.drag_threshold, 3);
1614 assert_eq!(config.chord_timeout, Duration::from_millis(1000));
1615 assert_eq!(config.click_tolerance, 1);
1616 }
1617
1618 fn mouse_moved(x: u16, y: u16) -> Event {
1621 Event::Mouse(MouseEvent {
1622 kind: MouseEventKind::Moved,
1623 x,
1624 y,
1625 modifiers: Modifiers::NONE,
1626 })
1627 }
1628
1629 fn mouse_scroll_up(x: u16, y: u16) -> Event {
1630 Event::Mouse(MouseEvent {
1631 kind: MouseEventKind::ScrollUp,
1632 x,
1633 y,
1634 modifiers: Modifiers::NONE,
1635 })
1636 }
1637
1638 fn mouse_scroll_down(x: u16, y: u16) -> Event {
1639 Event::Mouse(MouseEvent {
1640 kind: MouseEventKind::ScrollDown,
1641 x,
1642 y,
1643 modifiers: Modifiers::NONE,
1644 })
1645 }
1646
1647 #[test]
1648 fn drag_without_prior_mouse_down() {
1649 let mut gr = GestureRecognizer::new(GestureConfig::default());
1651 let t = now();
1652
1653 let events = gr.process(&mouse_drag(10, 10, MouseButton::Left), t);
1654 assert!(events.is_empty()); assert!(!gr.is_dragging());
1656
1657 let events = gr.process(&mouse_drag(20, 10, MouseButton::Left), t + MS_50);
1659 assert!(
1660 events
1661 .iter()
1662 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1663 );
1664 assert!(gr.is_dragging());
1665 }
1666
1667 #[test]
1668 fn mouse_moved_cancels_long_press() {
1669 let mut gr = GestureRecognizer::new(GestureConfig::default());
1670 let t = now();
1671
1672 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1673
1674 gr.process(&mouse_moved(6, 5), t + MS_100);
1676
1677 assert!(gr.check_long_press(t + MS_600).is_none());
1679 }
1680
1681 #[test]
1682 fn mouse_moved_does_not_affect_drag_tracker() {
1683 let mut gr = GestureRecognizer::new(GestureConfig::default());
1684 let t = now();
1685
1686 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1687
1688 let events = gr.process(&mouse_moved(20, 20), t + MS_50);
1690 assert!(events.is_empty());
1691
1692 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_100);
1694 assert!(
1695 events
1696 .iter()
1697 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1698 );
1699 }
1700
1701 #[test]
1702 fn escape_during_unstarted_drag_no_cancel() {
1703 let mut gr = GestureRecognizer::new(GestureConfig::default());
1705 let t = now();
1706
1707 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1708 gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_50);
1710 assert!(!gr.is_dragging());
1711
1712 let events = gr.process(&esc(), t + MS_100);
1713 assert!(events.is_empty()); }
1715
1716 #[test]
1717 fn focus_loss_during_unstarted_drag_no_cancel() {
1718 let mut gr = GestureRecognizer::new(GestureConfig::default());
1720 let t = now();
1721
1722 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1723 gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_50);
1725 assert!(!gr.is_dragging());
1726
1727 let events = gr.process(&Event::Focus(false), t + MS_100);
1728 assert!(events.is_empty()); }
1730
1731 #[test]
1732 fn chord_with_super_modifier() {
1733 let mut gr = GestureRecognizer::new(GestureConfig::default());
1734 let t = now();
1735
1736 gr.process(&key_press(KeyCode::Char('a'), Modifiers::SUPER), t);
1737 let events = gr.process(&key_press(KeyCode::Char('b'), Modifiers::SUPER), t + MS_100);
1738
1739 assert_eq!(events.len(), 1);
1740 if let SemanticEvent::Chord { sequence } = &events[0] {
1741 assert_eq!(sequence.len(), 2);
1742 assert!(sequence[0].modifiers.contains(Modifiers::SUPER));
1743 assert!(sequence[1].modifiers.contains(Modifiers::SUPER));
1744 } else {
1745 panic!("Expected Chord event");
1746 }
1747 }
1748
1749 #[test]
1750 fn long_press_exactly_at_threshold() {
1751 let mut gr = GestureRecognizer::new(GestureConfig::default());
1752 let t = now();
1753
1754 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1755
1756 let lp = gr.check_long_press(t + Duration::from_millis(500));
1758 assert!(lp.is_some());
1759 if let Some(SemanticEvent::LongPress { duration, .. }) = lp {
1760 assert_eq!(duration, Duration::from_millis(500));
1761 }
1762 }
1763
1764 #[test]
1765 fn long_press_one_ms_before_threshold() {
1766 let mut gr = GestureRecognizer::new(GestureConfig::default());
1767 let t = now();
1768
1769 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1770
1771 assert!(
1773 gr.check_long_press(t + Duration::from_millis(499))
1774 .is_none()
1775 );
1776 }
1777
1778 #[test]
1779 fn drag_negative_direction() {
1780 let mut gr = GestureRecognizer::new(GestureConfig::default());
1782 let t = now();
1783
1784 gr.process(&mouse_down(20, 10, MouseButton::Left), t);
1785 let events = gr.process(&mouse_drag(15, 10, MouseButton::Left), t + MS_50);
1786
1787 let drag_move = events
1788 .iter()
1789 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1790 if let Some(SemanticEvent::DragMove { delta, .. }) = drag_move {
1791 assert_eq!(delta.0, -5); assert_eq!(delta.1, 0);
1793 } else {
1794 panic!("Expected DragMove");
1795 }
1796 }
1797
1798 #[test]
1799 fn drag_negative_y_direction() {
1800 let mut gr = GestureRecognizer::new(GestureConfig::default());
1802 let t = now();
1803
1804 gr.process(&mouse_down(5, 20, MouseButton::Left), t);
1805 let events = gr.process(&mouse_drag(5, 15, MouseButton::Left), t + MS_50);
1806
1807 let drag_move = events
1808 .iter()
1809 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1810 if let Some(SemanticEvent::DragMove { delta, .. }) = drag_move {
1811 assert_eq!(delta.0, 0);
1812 assert_eq!(delta.1, -5); } else {
1814 panic!("Expected DragMove");
1815 }
1816 }
1817
1818 #[test]
1819 fn multiple_sequential_drags() {
1820 let mut gr = GestureRecognizer::new(GestureConfig::default());
1821 let t = now();
1822
1823 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1825 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1826 gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_100);
1827 assert!(!gr.is_dragging());
1828
1829 gr.process(&mouse_down(20, 20, MouseButton::Left), t + MS_200);
1831 let events = gr.process(
1832 &mouse_drag(30, 20, MouseButton::Left),
1833 t + Duration::from_millis(250),
1834 );
1835
1836 assert!(events.iter().any(|e| matches!(
1837 e,
1838 SemanticEvent::DragStart {
1839 pos: Position { x: 20, y: 20 },
1840 ..
1841 }
1842 )));
1843 assert!(gr.is_dragging());
1844
1845 let events = gr.process(
1846 &mouse_up(30, 20, MouseButton::Left),
1847 t + Duration::from_millis(300),
1848 );
1849 assert!(matches!(
1850 events[0],
1851 SemanticEvent::DragEnd {
1852 start: Position { x: 20, y: 20 },
1853 end: Position { x: 30, y: 20 },
1854 }
1855 ));
1856 }
1857
1858 #[test]
1859 fn check_long_press_no_mouse_down() {
1860 let mut gr = GestureRecognizer::new(GestureConfig::default());
1862 let t = now();
1863
1864 assert!(gr.check_long_press(t).is_none());
1865 assert!(gr.check_long_press(t + Duration::from_secs(10)).is_none());
1866 }
1867
1868 #[test]
1869 fn zero_drag_threshold() {
1870 let config = GestureConfig {
1872 drag_threshold: 0,
1873 ..Default::default()
1874 };
1875 let mut gr = GestureRecognizer::new(config);
1876 let t = now();
1877
1878 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1879 let events = gr.process(&mouse_drag(5, 5, MouseButton::Left), t + MS_50);
1881 assert!(
1882 events
1883 .iter()
1884 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1885 );
1886 assert!(gr.is_dragging());
1887 }
1888
1889 #[test]
1890 fn zero_click_tolerance() {
1891 let config = GestureConfig {
1893 click_tolerance: 0,
1894 ..Default::default()
1895 };
1896 let mut gr = GestureRecognizer::new(config);
1897 let t = now();
1898
1899 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1900 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1901
1902 gr.process(&mouse_down(6, 5, MouseButton::Left), t + MS_100);
1904 let events = gr.process(&mouse_up(6, 5, MouseButton::Left), t + MS_200);
1905 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1906
1907 gr.process(
1909 &mouse_down(6, 5, MouseButton::Left),
1910 t + Duration::from_millis(250),
1911 );
1912 let events = gr.process(
1913 &mouse_up(6, 5, MouseButton::Left),
1914 t + Duration::from_millis(280),
1915 );
1916 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
1917 }
1918
1919 #[test]
1920 fn scroll_events_ignored() {
1921 let mut gr = GestureRecognizer::new(GestureConfig::default());
1922 let t = now();
1923
1924 let events = gr.process(&mouse_scroll_up(5, 5), t);
1925 assert!(events.is_empty());
1926
1927 let events = gr.process(&mouse_scroll_down(5, 5), t + MS_50);
1928 assert!(events.is_empty());
1929 }
1930
1931 #[test]
1932 fn key_repeat_ignored() {
1933 let mut gr = GestureRecognizer::new(GestureConfig::default());
1934 let t = now();
1935
1936 let events = gr.process(
1937 &Event::Key(KeyEvent {
1938 code: KeyCode::Char('k'),
1939 modifiers: Modifiers::CTRL,
1940 kind: KeyEventKind::Repeat,
1941 }),
1942 t,
1943 );
1944 assert!(events.is_empty());
1945 }
1946
1947 #[test]
1948 fn double_click_at_exact_timeout_boundary() {
1949 let mut gr = GestureRecognizer::new(GestureConfig::default());
1951 let t = now();
1952
1953 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1954 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1955
1956 gr.process(
1960 &mouse_down(5, 5, MouseButton::Left),
1961 t + Duration::from_millis(350),
1962 );
1963 let events = gr.process(
1964 &mouse_up(5, 5, MouseButton::Left),
1965 t + Duration::from_millis(350),
1966 );
1967 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
1968 }
1969
1970 #[test]
1971 fn mouse_up_position_becomes_click_position() {
1972 let mut gr = GestureRecognizer::new(GestureConfig::default());
1974 let t = now();
1975
1976 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1977 let events = gr.process(&mouse_up(6, 6, MouseButton::Left), t + MS_50);
1979
1980 assert_eq!(events.len(), 1);
1981 if let SemanticEvent::Click { pos, .. } = &events[0] {
1982 assert_eq!(*pos, Position::new(6, 6)); } else {
1984 panic!("Expected Click");
1985 }
1986 }
1987
1988 #[test]
1989 fn multiple_mouse_downs_overwrite_state() {
1990 let mut gr = GestureRecognizer::new(GestureConfig::default());
1992 let t = now();
1993
1994 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1995 gr.process(&mouse_down(20, 20, MouseButton::Right), t + MS_50);
1996
1997 let events = gr.process(&mouse_drag(30, 20, MouseButton::Right), t + MS_100);
1999 if let Some(SemanticEvent::DragStart { pos, button }) = events
2000 .iter()
2001 .find(|e| matches!(e, SemanticEvent::DragStart { .. }))
2002 {
2003 assert_eq!(*pos, Position::new(20, 20));
2004 assert_eq!(*button, MouseButton::Right);
2005 } else {
2006 panic!("Expected DragStart from second mouse-down position");
2007 }
2008 }
2009
2010 #[test]
2011 fn triple_click_then_timeout_then_single() {
2012 let mut gr = GestureRecognizer::new(GestureConfig::default());
2013 let t = now();
2014
2015 for i in 0..3u32 {
2017 let offset = Duration::from_millis(i as u64 * 80);
2018 gr.process(&mouse_down(5, 5, MouseButton::Left), t + offset);
2019 gr.process(&mouse_up(5, 5, MouseButton::Left), t + offset + MS_50);
2020 }
2021
2022 let late = t + Duration::from_secs(2);
2024 gr.process(&mouse_down(5, 5, MouseButton::Left), late);
2025 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), late + MS_50);
2026
2027 assert_eq!(events.len(), 1);
2028 assert!(matches!(events[0], SemanticEvent::Click { .. }));
2029 }
2030
2031 #[test]
2032 fn chord_ctrl_alt_combined_modifier() {
2033 let mut gr = GestureRecognizer::new(GestureConfig::default());
2035 let t = now();
2036
2037 let mods = Modifiers::CTRL | Modifiers::ALT;
2038 gr.process(&key_press(KeyCode::Char('a'), mods), t);
2039 let events = gr.process(&key_press(KeyCode::Char('b'), Modifiers::CTRL), t + MS_100);
2040
2041 assert_eq!(events.len(), 1);
2042 if let SemanticEvent::Chord { sequence } = &events[0] {
2043 assert_eq!(sequence[0].modifiers, Modifiers::CTRL | Modifiers::ALT);
2044 assert_eq!(sequence[1].modifiers, Modifiers::CTRL);
2045 }
2046 }
2047
2048 #[test]
2049 fn chord_timeout_then_new_chord() {
2050 let mut gr = GestureRecognizer::new(GestureConfig::default());
2052 let t = now();
2053
2054 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
2056
2057 gr.process(
2060 &key_press(KeyCode::Char('a'), Modifiers::CTRL),
2061 t + Duration::from_millis(1100),
2062 );
2063 let events = gr.process(
2065 &key_press(KeyCode::Char('b'), Modifiers::CTRL),
2066 t + Duration::from_millis(1200),
2067 );
2068
2069 assert_eq!(events.len(), 1);
2070 if let SemanticEvent::Chord { sequence } = &events[0] {
2071 assert_eq!(sequence.len(), 2);
2072 assert_eq!(sequence[0].code, KeyCode::Char('a'));
2073 assert_eq!(sequence[1].code, KeyCode::Char('b'));
2074 }
2075 }
2076
2077 #[test]
2078 fn drag_start_and_move_emitted_together() {
2079 let mut gr = GestureRecognizer::new(GestureConfig::default());
2082 let t = now();
2083
2084 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2085 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
2086
2087 assert_eq!(events.len(), 2);
2088 assert!(matches!(events[0], SemanticEvent::DragStart { .. }));
2089 assert!(matches!(events[1], SemanticEvent::DragMove { .. }));
2090 }
2091
2092 #[test]
2093 fn config_clone() {
2094 let config = GestureConfig {
2095 drag_threshold: 42,
2096 click_tolerance: 7,
2097 ..Default::default()
2098 };
2099 let cloned = config.clone();
2100 assert_eq!(cloned.drag_threshold, 42);
2101 assert_eq!(cloned.click_tolerance, 7);
2102 assert_eq!(cloned.multi_click_timeout, config.multi_click_timeout);
2103 }
2104
2105 #[test]
2106 fn scroll_does_not_affect_click_state() {
2107 let mut gr = GestureRecognizer::new(GestureConfig::default());
2108 let t = now();
2109
2110 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2112 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
2113
2114 gr.process(&mouse_scroll_up(5, 5), t + MS_100);
2116
2117 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_200);
2119 let events = gr.process(
2120 &mouse_up(5, 5, MouseButton::Left),
2121 t + Duration::from_millis(250),
2122 );
2123 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
2124 }
2125
2126 #[test]
2127 fn escape_clears_mouse_down_state() {
2128 let mut gr = GestureRecognizer::new(GestureConfig::default());
2129 let t = now();
2130
2131 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2132 gr.process(&esc(), t + MS_50);
2133
2134 assert!(gr.check_long_press(t + MS_600).is_none());
2136
2137 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
2140 assert!(events.is_empty(), "{events:?}");
2141
2142 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_200);
2144 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200 + MS_50);
2145 assert!(
2146 matches!(events[..], [SemanticEvent::Click { .. }]),
2147 "{events:?}"
2148 );
2149 }
2150
2151 #[test]
2152 fn focus_loss_clears_long_press_fired_flag() {
2153 let mut gr = GestureRecognizer::new(GestureConfig::default());
2154 let t = now();
2155
2156 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2158 assert!(gr.check_long_press(t + MS_600).is_some());
2159 assert!(
2160 gr.check_long_press(t + Duration::from_millis(700))
2161 .is_none()
2162 ); gr.process(&Event::Focus(false), t + Duration::from_millis(800));
2166
2167 gr.process(
2169 &mouse_down(10, 10, MouseButton::Left),
2170 t + Duration::from_secs(1),
2171 );
2172 assert!(
2173 gr.check_long_press(t + Duration::from_millis(1600))
2174 .is_some()
2175 );
2176 }
2177
2178 #[test]
2179 fn drag_at_u16_max_coordinates() {
2180 let mut gr = GestureRecognizer::new(GestureConfig::default());
2181 let t = now();
2182
2183 gr.process(&mouse_down(u16::MAX - 5, u16::MAX, MouseButton::Left), t);
2184 let events = gr.process(
2185 &mouse_drag(u16::MAX, u16::MAX, MouseButton::Left),
2186 t + MS_50,
2187 );
2188
2189 assert!(
2191 events
2192 .iter()
2193 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
2194 );
2195
2196 if let Some(SemanticEvent::DragMove { delta, .. }) = events
2198 .iter()
2199 .find(|e| matches!(e, SemanticEvent::DragMove { .. }))
2200 {
2201 assert_eq!(delta.0, 5);
2202 assert_eq!(delta.1, 0);
2203 }
2204 }
2205
2206 #[test]
2207 fn drag_delta_crosses_i16_max_and_saturates_huge_jumps() {
2208 let mut gr = GestureRecognizer::new(GestureConfig::default());
2211 let t = now();
2212 let delta_of = |events: Vec<SemanticEvent>| {
2213 events.into_iter().find_map(|e| match e {
2214 SemanticEvent::DragMove { delta, .. } => Some(delta),
2215 _ => None,
2216 })
2217 };
2218 gr.process(&mouse_down(32_760, 0, MouseButton::Left), t);
2219 let started = gr.process(&mouse_drag(32_767, 0, MouseButton::Left), t + MS_50);
2220 assert_eq!(delta_of(started), Some((7, 0)));
2221 let crossed = gr.process(&mouse_drag(32_768, 0, MouseButton::Left), t + MS_50);
2222 assert_eq!(delta_of(crossed), Some((1, 0)));
2223 let back = gr.process(&mouse_drag(0, u16::MAX, MouseButton::Left), t + MS_50);
2224 assert_eq!(delta_of(back), Some((i16::MIN, i16::MAX)));
2225 }
2226
2227 #[test]
2228 fn reset_during_long_press_pending() {
2229 let mut gr = GestureRecognizer::new(GestureConfig::default());
2230 let t = now();
2231
2232 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2233 gr.reset();
2235
2236 assert!(gr.check_long_press(t + MS_600).is_none());
2238 }
2239
2240 #[test]
2243 fn release_without_a_press_is_not_a_click() {
2244 let mut gr = GestureRecognizer::new(GestureConfig::default());
2245 let t = now();
2246 assert!(gr.process(&mouse_up(5, 5, MouseButton::Left), t).is_empty());
2247
2248 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
2250 gr.process(&Event::Focus(false), t + MS_50);
2251 assert!(
2252 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100)
2253 .is_empty()
2254 );
2255 }
2256
2257 #[test]
2258 fn release_of_another_button_is_not_a_click() {
2259 let mut gr = GestureRecognizer::new(GestureConfig::default());
2260 let t = now();
2261 gr.process(&mouse_down(5, 5, MouseButton::Right), t);
2262 assert!(
2263 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50)
2264 .is_empty()
2265 );
2266 }
2267
2268 fn flick(
2273 gr: &mut GestureRecognizer,
2274 from: (u16, u16),
2275 via: (u16, u16),
2276 to: (u16, u16),
2277 first_move: Duration,
2278 release: Duration,
2279 ) -> Vec<SemanticEvent> {
2280 let t = now();
2281 gr.process(&mouse_down(from.0, from.1, MouseButton::Left), t);
2282 gr.process(&mouse_drag(via.0, via.1, MouseButton::Left), t + first_move);
2283 gr.process(&mouse_up(to.0, to.1, MouseButton::Left), t + release)
2284 }
2285
2286 fn swipe_of(events: &[SemanticEvent]) -> Option<(SwipeDirection, u16, f32)> {
2287 events.iter().find_map(|e| match *e {
2288 SemanticEvent::Swipe {
2289 direction,
2290 distance,
2291 velocity,
2292 } => Some((direction, distance, velocity)),
2293 _ => None,
2294 })
2295 }
2296
2297 #[test]
2298 fn swipe_follows_drag_end_in_each_direction() {
2299 let cases = [
2300 ((10, 10), (20, 10), SwipeDirection::Right),
2301 ((20, 10), (10, 10), SwipeDirection::Left),
2302 ((10, 20), (10, 10), SwipeDirection::Up),
2303 ((10, 10), (10, 20), SwipeDirection::Down),
2304 ];
2305 for (from, to, want) in cases {
2306 let mut gr = GestureRecognizer::new(GestureConfig::default());
2307 let events = flick(&mut gr, from, to, to, MS_50, MS_100);
2308 assert!(
2309 matches!(events[0], SemanticEvent::DragEnd { .. }),
2310 "{events:?}"
2311 );
2312 let (direction, distance, velocity) = swipe_of(&events).expect("swipe");
2314 assert_eq!((direction, distance), (want, 10));
2315 assert!((velocity - 200.0).abs() < 1e-3, "velocity {velocity}");
2316 assert_eq!(events.len(), 2);
2317 }
2318 }
2319
2320 #[test]
2321 fn swipe_threshold_is_inclusive_and_read_from_config() {
2322 let mut gr = GestureRecognizer::new(GestureConfig::default());
2324 let at = flick(&mut gr, (0, 0), (5, 0), (5, 0), MS_50, MS_50 + MS_100);
2325 assert_eq!(swipe_of(&at).map(|s| s.0), Some(SwipeDirection::Right));
2326
2327 let mut gr = GestureRecognizer::new(GestureConfig::default());
2329 let under = flick(
2330 &mut gr,
2331 (0, 0),
2332 (5, 0),
2333 (5, 0),
2334 MS_50,
2335 MS_50 + MS_100 + Duration::from_millis(1),
2336 );
2337 assert!(
2338 matches!(under[..], [SemanticEvent::DragEnd { .. }]),
2339 "{under:?}"
2340 );
2341
2342 let mut gr = GestureRecognizer::new(GestureConfig {
2344 swipe_velocity_threshold: 51.0,
2345 ..GestureConfig::default()
2346 });
2347 let raised = flick(&mut gr, (0, 0), (5, 0), (5, 0), MS_50, MS_50 + MS_100);
2348 assert!(swipe_of(&raised).is_none(), "{raised:?}");
2349 }
2350
2351 #[test]
2352 fn slow_drag_is_not_a_swipe() {
2353 let mut gr = GestureRecognizer::new(GestureConfig::default());
2355 let events = flick(
2356 &mut gr,
2357 (0, 0),
2358 (5, 0),
2359 (20, 0),
2360 MS_50,
2361 MS_50 + Duration::from_secs(1),
2362 );
2363 assert!(
2364 matches!(events[..], [SemanticEvent::DragEnd { .. }]),
2365 "{events:?}"
2366 );
2367 }
2368
2369 #[test]
2370 fn swipe_velocity_is_measured_from_first_movement_not_the_press() {
2371 let mut gr = GestureRecognizer::new(GestureConfig::default());
2373 let hold = Duration::from_millis(400);
2374 let events = flick(&mut gr, (0, 5), (10, 5), (10, 5), hold, hold + MS_50);
2375 let (direction, _, velocity) = swipe_of(&events).expect("swipe");
2376 assert_eq!(direction, SwipeDirection::Right);
2377 assert!((velocity - 200.0).abs() < 1e-3, "velocity {velocity}");
2378 }
2379
2380 #[test]
2381 fn diagonal_and_out_and_back_drags_are_not_swipes() {
2382 let mut gr = GestureRecognizer::new(GestureConfig::default());
2384 let diagonal = flick(&mut gr, (0, 0), (8, 8), (8, 8), MS_50, MS_100);
2385 assert!(swipe_of(&diagonal).is_none(), "{diagonal:?}");
2386
2387 let mut gr = GestureRecognizer::new(GestureConfig::default());
2390 let back = flick(&mut gr, (10, 0), (20, 0), (11, 0), MS_50, MS_100);
2391 assert!(matches!(back[0], SemanticEvent::DragEnd { .. }));
2392 assert!(swipe_of(&back).is_none(), "{back:?}");
2393 }
2394
2395 #[test]
2396 fn cancelled_drag_never_swipes() {
2397 let mut gr = GestureRecognizer::new(GestureConfig::default());
2398 let t = now();
2399 gr.process(&mouse_down(0, 0, MouseButton::Left), t);
2400 gr.process(&mouse_drag(10, 0, MouseButton::Left), t + MS_50);
2401 assert!(matches!(
2402 gr.process(&esc(), t + MS_50)[..],
2403 [SemanticEvent::DragCancel]
2404 ));
2405 let up = gr.process(&mouse_up(20, 0, MouseButton::Left), t + MS_100);
2406 assert!(swipe_of(&up).is_none(), "{up:?}");
2407 }
2408
2409 #[test]
2410 fn same_instant_release_has_finite_velocity() {
2411 let mut gr = GestureRecognizer::new(GestureConfig::default());
2414 let events = flick(&mut gr, (0, 0), (30, 0), (30, 0), MS_50, MS_50);
2415 let (_, distance, velocity) = swipe_of(&events).expect("swipe");
2416 assert_eq!(distance, 30);
2417 assert!(
2418 velocity.is_finite() && velocity >= 0.0,
2419 "velocity {velocity}"
2420 );
2421 assert!((velocity - 30_000.0).abs() < 1e-1, "velocity {velocity}");
2422 }
2423
2424 #[test]
2425 fn swipe_distance_saturates_on_extreme_coordinates() {
2426 let mut gr = GestureRecognizer::new(GestureConfig::default());
2427 let events = flick(&mut gr, (0, 0), (u16::MAX, 0), (u16::MAX, 0), MS_50, MS_100);
2428 assert_eq!(swipe_of(&events).map(|s| s.1), Some(u16::MAX));
2429 }
2430}