1#![forbid(unsafe_code)]
2
3use std::time::{Duration, Instant};
38
39use crate::event::{Event, KeyCode, KeyEventKind, Modifiers, MouseButton, MouseEventKind};
40use crate::semantic_event::{ChordKey, Position, SemanticEvent};
41
42#[derive(Debug, Clone)]
48pub struct GestureConfig {
49 pub multi_click_timeout: Duration,
51 pub long_press_threshold: Duration,
53 pub drag_threshold: u16,
55 pub chord_timeout: Duration,
57 pub swipe_velocity_threshold: f32,
59 pub click_tolerance: u16,
61}
62
63impl Default for GestureConfig {
64 fn default() -> Self {
65 Self {
66 multi_click_timeout: Duration::from_millis(300),
67 long_press_threshold: Duration::from_millis(500),
68 drag_threshold: 3,
69 chord_timeout: Duration::from_millis(1000),
70 swipe_velocity_threshold: 50.0,
71 click_tolerance: 1,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
82struct ClickState {
83 pos: Position,
84 button: MouseButton,
85 time: Instant,
86 count: u8,
87}
88
89#[derive(Debug, Clone)]
91struct DragTracker {
92 start_pos: Position,
93 button: MouseButton,
94 last_pos: Position,
95 started: bool,
96}
97
98pub struct GestureRecognizer {
108 config: GestureConfig,
109
110 last_click: Option<ClickState>,
112
113 mouse_down: Option<(Position, MouseButton, Instant)>,
115 drag: Option<DragTracker>,
116
117 long_press_pos: Option<(Position, Instant)>,
119 long_press_fired: bool,
120
121 chord_buffer: Vec<ChordKey>,
123 chord_start: Option<Instant>,
124}
125
126impl std::fmt::Debug for GestureRecognizer {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 f.debug_struct("GestureRecognizer")
129 .field("dragging", &self.is_dragging())
130 .field("chord_len", &self.chord_buffer.len())
131 .finish()
132 }
133}
134
135impl GestureRecognizer {
136 #[must_use]
138 pub fn new(config: GestureConfig) -> Self {
139 Self {
140 config,
141 last_click: None,
142 mouse_down: None,
143 drag: None,
144 long_press_pos: None,
145 long_press_fired: false,
146 chord_buffer: Vec::with_capacity(4),
147 chord_start: None,
148 }
149 }
150
151 pub fn process(&mut self, event: &Event, now: Instant) -> Vec<SemanticEvent> {
156 let mut out = Vec::with_capacity(2);
157
158 self.expire_chord(now);
160
161 match event {
162 Event::Mouse(mouse) => {
163 let pos = Position::new(mouse.x, mouse.y);
164 match mouse.kind {
165 MouseEventKind::Down(button) => {
166 self.on_mouse_down(pos, button, now, &mut out);
167 }
168 MouseEventKind::Up(button) => {
169 self.on_mouse_up(pos, button, now, &mut out);
170 }
171 MouseEventKind::Drag(button) => {
172 self.on_mouse_drag(pos, button, &mut out);
173 }
174 MouseEventKind::Moved => {
175 self.long_press_pos = None;
177 }
178 _ => {}
179 }
180 }
181 Event::Key(key) => {
182 if key.kind != KeyEventKind::Press {
183 return out;
184 }
185
186 if key.code == KeyCode::Escape {
188 if let Some(drag) = self.drag.take()
189 && drag.started
190 {
191 out.push(SemanticEvent::DragCancel);
192 }
193 self.mouse_down = None;
194 self.long_press_pos = None;
195 self.chord_buffer.clear();
196 self.chord_start = None;
197 return out;
198 }
199
200 let has_modifier = key
202 .modifiers
203 .intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER);
204
205 if has_modifier {
206 let chord_key = ChordKey::new(key.code, key.modifiers);
207 if self.chord_buffer.is_empty() {
208 self.chord_start = Some(now);
209 }
210 self.chord_buffer.push(chord_key);
211
212 if self.chord_buffer.len() >= 2 {
215 out.push(SemanticEvent::Chord {
216 sequence: self.chord_buffer.clone(),
217 });
218 self.chord_buffer.clear();
219 self.chord_start = None;
220 }
221 } else {
222 self.chord_buffer.clear();
224 self.chord_start = None;
225 }
226 }
227 Event::Focus(false) => {
228 if let Some(drag) = self.drag.take()
230 && drag.started
231 {
232 out.push(SemanticEvent::DragCancel);
233 }
234 self.mouse_down = None;
235 self.long_press_pos = None;
236 self.long_press_fired = false;
237 }
238 _ => {}
239 }
240
241 out
242 }
243
244 pub fn check_long_press(&mut self, now: Instant) -> Option<SemanticEvent> {
249 if self.long_press_fired {
250 return None;
251 }
252 if let Some((pos, down_time)) = self.long_press_pos {
253 let elapsed = now.duration_since(down_time);
254 if elapsed >= self.config.long_press_threshold {
255 self.long_press_fired = true;
256 return Some(SemanticEvent::LongPress {
257 pos,
258 duration: elapsed,
259 });
260 }
261 }
262 None
263 }
264
265 #[must_use]
267 pub fn is_dragging(&self) -> bool {
268 self.drag.as_ref().is_some_and(|d| d.started)
269 }
270
271 pub fn reset(&mut self) {
273 self.last_click = None;
274 self.mouse_down = None;
275 self.drag = None;
276 self.long_press_pos = None;
277 self.long_press_fired = false;
278 self.chord_buffer.clear();
279 self.chord_start = None;
280 }
281
282 #[must_use]
284 pub fn config(&self) -> &GestureConfig {
285 &self.config
286 }
287
288 pub fn set_config(&mut self, config: GestureConfig) {
290 self.config = config;
291 }
292}
293
294impl GestureRecognizer {
299 fn on_mouse_down(
300 &mut self,
301 pos: Position,
302 button: MouseButton,
303 now: Instant,
304 _out: &mut Vec<SemanticEvent>,
305 ) {
306 self.mouse_down = Some((pos, button, now));
307 self.drag = Some(DragTracker {
308 start_pos: pos,
309 button,
310 last_pos: pos,
311 started: false,
312 });
313 self.long_press_pos = Some((pos, now));
314 self.long_press_fired = false;
315 }
316
317 fn on_mouse_up(
318 &mut self,
319 pos: Position,
320 button: MouseButton,
321 now: Instant,
322 out: &mut Vec<SemanticEvent>,
323 ) {
324 self.long_press_pos = None;
325 self.long_press_fired = false;
326
327 if let Some(drag) = self.drag.take()
329 && drag.started
330 {
331 out.push(SemanticEvent::DragEnd {
332 start: drag.start_pos,
333 end: pos,
334 });
335 self.mouse_down = None;
336 return;
337 }
338
339 self.mouse_down = None;
341
342 let click_count = if let Some(ref last) = self.last_click {
344 if last.button == button
345 && last.pos.manhattan_distance(pos) <= u32::from(self.config.click_tolerance)
346 && now.duration_since(last.time) <= self.config.multi_click_timeout
347 && last.count < 3
348 {
349 last.count + 1
350 } else {
351 1
352 }
353 } else {
354 1
355 };
356
357 self.last_click = Some(ClickState {
358 pos,
359 button,
360 time: now,
361 count: click_count,
362 });
363
364 match click_count {
365 1 => out.push(SemanticEvent::Click { pos, button }),
366 2 => out.push(SemanticEvent::DoubleClick { pos, button }),
367 3 => out.push(SemanticEvent::TripleClick { pos, button }),
368 _ => out.push(SemanticEvent::Click { pos, button }),
369 }
370 }
371
372 fn on_mouse_drag(&mut self, pos: Position, button: MouseButton, out: &mut Vec<SemanticEvent>) {
373 self.long_press_pos = None;
375
376 let Some(ref mut drag) = self.drag else {
377 self.drag = Some(DragTracker {
379 start_pos: pos,
380 button,
381 last_pos: pos,
382 started: false,
383 });
384 return;
385 };
386
387 if !drag.started {
388 let distance = drag.start_pos.manhattan_distance(pos);
390 if distance >= u32::from(self.config.drag_threshold) {
391 drag.started = true;
392 out.push(SemanticEvent::DragStart {
393 pos: drag.start_pos,
394 button: drag.button,
395 });
396 }
397 }
398
399 if drag.started {
400 let delta = (
401 pos.x as i16 - drag.last_pos.x as i16,
402 pos.y as i16 - drag.last_pos.y as i16,
403 );
404 out.push(SemanticEvent::DragMove {
405 start: drag.start_pos,
406 current: pos,
407 delta,
408 });
409 }
410
411 drag.last_pos = pos;
412 }
413
414 fn expire_chord(&mut self, now: Instant) {
415 if let Some(start) = self.chord_start
416 && now.duration_since(start) > self.config.chord_timeout
417 {
418 self.chord_buffer.clear();
419 self.chord_start = None;
420 }
421 }
422}
423
424#[cfg(test)]
429mod tests {
430 use super::*;
431 use crate::event::{KeyEvent, MouseEvent};
432
433 fn now() -> Instant {
434 Instant::now()
435 }
436
437 fn mouse_down(x: u16, y: u16, button: MouseButton) -> Event {
438 Event::Mouse(MouseEvent {
439 kind: MouseEventKind::Down(button),
440 x,
441 y,
442 modifiers: Modifiers::NONE,
443 })
444 }
445
446 fn mouse_up(x: u16, y: u16, button: MouseButton) -> Event {
447 Event::Mouse(MouseEvent {
448 kind: MouseEventKind::Up(button),
449 x,
450 y,
451 modifiers: Modifiers::NONE,
452 })
453 }
454
455 fn mouse_drag(x: u16, y: u16, button: MouseButton) -> Event {
456 Event::Mouse(MouseEvent {
457 kind: MouseEventKind::Drag(button),
458 x,
459 y,
460 modifiers: Modifiers::NONE,
461 })
462 }
463
464 fn key_press(code: KeyCode, modifiers: Modifiers) -> Event {
465 Event::Key(KeyEvent {
466 code,
467 modifiers,
468 kind: KeyEventKind::Press,
469 })
470 }
471
472 fn esc() -> Event {
473 key_press(KeyCode::Escape, Modifiers::NONE)
474 }
475
476 const MS_50: Duration = Duration::from_millis(50);
477 const MS_100: Duration = Duration::from_millis(100);
478 const MS_200: Duration = Duration::from_millis(200);
479 const MS_500: Duration = Duration::from_millis(500);
480 const MS_600: Duration = Duration::from_millis(600);
481
482 #[test]
485 fn single_click() {
486 let mut gr = GestureRecognizer::new(GestureConfig::default());
487 let t = now();
488
489 let events = gr.process(&mouse_down(5, 5, MouseButton::Left), t);
490 assert!(events.is_empty());
491
492 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
493 assert_eq!(events.len(), 1);
494 assert!(matches!(
495 events[0],
496 SemanticEvent::Click {
497 pos: Position { x: 5, y: 5 },
498 button: MouseButton::Left,
499 }
500 ));
501 }
502
503 #[test]
504 fn double_click() {
505 let mut gr = GestureRecognizer::new(GestureConfig::default());
506 let t = now();
507
508 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
510 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
511
512 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_100);
514 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
515
516 assert_eq!(events.len(), 1);
517 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
518 }
519
520 #[test]
521 fn triple_click() {
522 let mut gr = GestureRecognizer::new(GestureConfig::default());
523 let t = now();
524
525 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
527 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
528
529 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_100);
531 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
532
533 gr.process(
535 &mouse_down(5, 5, MouseButton::Left),
536 t + Duration::from_millis(250),
537 );
538 let events = gr.process(
539 &mouse_up(5, 5, MouseButton::Left),
540 t + Duration::from_millis(280),
541 );
542
543 assert_eq!(events.len(), 1);
544 assert!(matches!(events[0], SemanticEvent::TripleClick { .. }));
545 }
546
547 #[test]
548 fn double_click_timeout_resets_to_single() {
549 let mut gr = GestureRecognizer::new(GestureConfig::default());
550 let t = now();
551
552 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
554 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
555
556 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_500);
558 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_600);
559
560 assert_eq!(events.len(), 1);
561 assert!(matches!(events[0], SemanticEvent::Click { .. }));
562 }
563
564 #[test]
565 fn different_position_resets_click_count() {
566 let mut gr = GestureRecognizer::new(GestureConfig::default());
567 let t = now();
568
569 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
571 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
572
573 gr.process(&mouse_down(20, 20, MouseButton::Left), t + MS_100);
575 let events = gr.process(&mouse_up(20, 20, MouseButton::Left), t + MS_200);
576
577 assert_eq!(events.len(), 1);
578 assert!(matches!(events[0], SemanticEvent::Click { .. }));
579 }
580
581 #[test]
582 fn different_button_resets_click_count() {
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::Right), t + MS_100);
592 let events = gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_200);
593
594 assert_eq!(events.len(), 1);
595 assert!(matches!(
596 events[0],
597 SemanticEvent::Click {
598 button: MouseButton::Right,
599 ..
600 }
601 ));
602 }
603
604 #[test]
605 fn click_position_tolerance() {
606 let mut gr = GestureRecognizer::new(GestureConfig::default());
607 let t = now();
608
609 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
611 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
612
613 gr.process(&mouse_down(6, 5, MouseButton::Left), t + MS_100);
615 let events = gr.process(&mouse_up(6, 5, MouseButton::Left), t + MS_200);
616
617 assert_eq!(events.len(), 1);
618 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
619 }
620
621 #[test]
624 fn drag_starts_after_threshold() {
625 let mut gr = GestureRecognizer::new(GestureConfig::default());
626 let t = now();
627
628 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
629
630 let events = gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_50);
632 assert!(events.is_empty());
633 assert!(!gr.is_dragging());
634
635 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_100);
637 assert!(!events.is_empty());
638 assert!(
639 events
640 .iter()
641 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
642 );
643 assert!(gr.is_dragging());
644 }
645
646 #[test]
647 fn drag_move_has_correct_delta() {
648 let mut gr = GestureRecognizer::new(GestureConfig::default());
649 let t = now();
650
651 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
652 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
654
655 let drag_move = events
657 .iter()
658 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
659 assert!(drag_move.is_some());
660 if let Some(SemanticEvent::DragMove {
661 start,
662 current,
663 delta,
664 }) = drag_move
665 {
666 assert_eq!(*start, Position::new(5, 5));
667 assert_eq!(*current, Position::new(10, 5));
668 assert_eq!(*delta, (5, 0));
669 }
670 }
671
672 #[test]
673 fn drag_end_on_mouse_up() {
674 let mut gr = GestureRecognizer::new(GestureConfig::default());
675 let t = now();
676
677 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
678 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
679
680 let events = gr.process(&mouse_up(12, 5, MouseButton::Left), t + MS_100);
682 assert_eq!(events.len(), 1);
683 assert!(matches!(
684 events[0],
685 SemanticEvent::DragEnd {
686 start: Position { x: 5, y: 5 },
687 end: Position { x: 12, y: 5 },
688 }
689 ));
690 assert!(!gr.is_dragging());
691 }
692
693 #[test]
694 fn drag_cancel_on_escape() {
695 let mut gr = GestureRecognizer::new(GestureConfig::default());
696 let t = now();
697
698 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
699 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
700 assert!(gr.is_dragging());
701
702 let events = gr.process(&esc(), t + MS_100);
703 assert_eq!(events.len(), 1);
704 assert!(matches!(events[0], SemanticEvent::DragCancel));
705 assert!(!gr.is_dragging());
706 }
707
708 #[test]
709 fn drag_prevents_click() {
710 let mut gr = GestureRecognizer::new(GestureConfig::default());
711 let t = now();
712
713 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
714 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
715
716 let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_100);
718 assert_eq!(events.len(), 1);
719 assert!(matches!(events[0], SemanticEvent::DragEnd { .. }));
720 assert!(
721 !events
722 .iter()
723 .any(|e| matches!(e, SemanticEvent::Click { .. }))
724 );
725 }
726
727 #[test]
730 fn long_press_fires_after_threshold() {
731 let mut gr = GestureRecognizer::new(GestureConfig::default());
732 let t = now();
733
734 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
735
736 assert!(gr.check_long_press(t + MS_200).is_none());
738
739 let lp = gr.check_long_press(t + MS_600);
741 assert!(lp.is_some());
742 if let Some(SemanticEvent::LongPress { pos, duration }) = lp {
743 assert_eq!(pos, Position::new(5, 5));
744 assert!(duration >= Duration::from_millis(500));
745 }
746 }
747
748 #[test]
749 fn long_press_not_repeated() {
750 let mut gr = GestureRecognizer::new(GestureConfig::default());
751 let t = now();
752
753 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
754
755 assert!(gr.check_long_press(t + MS_600).is_some());
757
758 assert!(
760 gr.check_long_press(t + Duration::from_millis(700))
761 .is_none()
762 );
763 }
764
765 #[test]
766 fn drag_cancels_long_press() {
767 let mut gr = GestureRecognizer::new(GestureConfig::default());
768 let t = now();
769
770 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
771 gr.process(&mouse_drag(6, 5, MouseButton::Left), t + MS_100);
773
774 assert!(gr.check_long_press(t + MS_600).is_none());
776 }
777
778 #[test]
779 fn mouse_up_cancels_long_press() {
780 let mut gr = GestureRecognizer::new(GestureConfig::default());
781 let t = now();
782
783 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
784 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
785
786 assert!(gr.check_long_press(t + MS_600).is_none());
788 }
789
790 #[test]
793 fn two_key_chord() {
794 let mut gr = GestureRecognizer::new(GestureConfig::default());
795 let t = now();
796
797 let events1 = gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
799 assert!(events1.is_empty()); let events2 = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
803 assert_eq!(events2.len(), 1);
804 if let SemanticEvent::Chord { sequence } = &events2[0] {
805 assert_eq!(sequence.len(), 2);
806 assert_eq!(sequence[0].code, KeyCode::Char('k'));
807 assert_eq!(sequence[1].code, KeyCode::Char('c'));
808 } else {
809 panic!("Expected Chord event");
810 }
811 }
812
813 #[test]
814 fn chord_timeout_clears_buffer() {
815 let mut gr = GestureRecognizer::new(GestureConfig::default());
816 let t = now();
817
818 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
820
821 let events = gr.process(
823 &key_press(KeyCode::Char('c'), Modifiers::CTRL),
824 t + Duration::from_millis(1100),
825 );
826
827 assert!(events.is_empty());
829 }
830
831 #[test]
832 fn non_modifier_key_clears_chord() {
833 let mut gr = GestureRecognizer::new(GestureConfig::default());
834 let t = now();
835
836 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
838
839 gr.process(&key_press(KeyCode::Char('x'), Modifiers::NONE), t + MS_50);
841
842 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
844 assert!(events.is_empty()); }
846
847 #[test]
848 fn escape_clears_chord() {
849 let mut gr = GestureRecognizer::new(GestureConfig::default());
850 let t = now();
851
852 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
853 gr.process(&esc(), t + MS_50);
854
855 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_100);
857 assert!(events.is_empty());
858 }
859
860 #[test]
863 fn focus_loss_cancels_drag() {
864 let mut gr = GestureRecognizer::new(GestureConfig::default());
865 let t = now();
866
867 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
868 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
869 assert!(gr.is_dragging());
870
871 let events = gr.process(&Event::Focus(false), t + MS_100);
872 assert_eq!(events.len(), 1);
873 assert!(matches!(events[0], SemanticEvent::DragCancel));
874 assert!(!gr.is_dragging());
875 }
876
877 #[test]
878 fn focus_loss_without_drag_is_silent() {
879 let mut gr = GestureRecognizer::new(GestureConfig::default());
880 let t = now();
881
882 let events = gr.process(&Event::Focus(false), t);
883 assert!(events.is_empty());
884 }
885
886 #[test]
889 fn reset_clears_all_state() {
890 let mut gr = GestureRecognizer::new(GestureConfig::default());
891 let t = now();
892
893 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
895 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
896 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t + MS_100);
897
898 assert!(gr.is_dragging());
899
900 gr.reset();
901
902 assert!(!gr.is_dragging());
903 assert!(gr.last_click.is_none());
904 assert!(gr.mouse_down.is_none());
905 assert!(gr.drag.is_none());
906 assert!(gr.chord_buffer.is_empty());
907 assert!(gr.chord_start.is_none());
908 }
909
910 #[test]
913 fn quadruple_click_wraps_to_single() {
914 let mut gr = GestureRecognizer::new(GestureConfig::default());
915 let t = now();
916
917 for i in 0..3u32 {
919 let offset = Duration::from_millis(i as u64 * 80);
920 gr.process(&mouse_down(5, 5, MouseButton::Left), t + offset);
921 gr.process(&mouse_up(5, 5, MouseButton::Left), t + offset + MS_50);
922 }
923
924 gr.process(
926 &mouse_down(5, 5, MouseButton::Left),
927 t + Duration::from_millis(260),
928 );
929 let events = gr.process(
930 &mouse_up(5, 5, MouseButton::Left),
931 t + Duration::from_millis(280),
932 );
933
934 assert_eq!(events.len(), 1);
935 assert!(matches!(events[0], SemanticEvent::Click { .. }));
936 }
937
938 #[test]
939 fn key_release_ignored() {
940 let mut gr = GestureRecognizer::new(GestureConfig::default());
941 let t = now();
942
943 let events = gr.process(
944 &Event::Key(KeyEvent {
945 code: KeyCode::Char('k'),
946 modifiers: Modifiers::CTRL,
947 kind: KeyEventKind::Release,
948 }),
949 t,
950 );
951 assert!(events.is_empty());
952 }
953
954 #[test]
955 fn debug_format() {
956 let gr = GestureRecognizer::new(GestureConfig::default());
957 let dbg = format!("{:?}", gr);
958 assert!(dbg.contains("GestureRecognizer"));
959 }
960
961 #[test]
964 fn right_click() {
965 let mut gr = GestureRecognizer::new(GestureConfig::default());
966 let t = now();
967
968 gr.process(&mouse_down(10, 10, MouseButton::Right), t);
969 let events = gr.process(&mouse_up(10, 10, MouseButton::Right), t + MS_50);
970
971 assert_eq!(events.len(), 1);
972 assert!(matches!(
973 events[0],
974 SemanticEvent::Click {
975 button: MouseButton::Right,
976 ..
977 }
978 ));
979 }
980
981 #[test]
982 fn middle_click() {
983 let mut gr = GestureRecognizer::new(GestureConfig::default());
984 let t = now();
985
986 gr.process(&mouse_down(10, 10, MouseButton::Middle), t);
987 let events = gr.process(&mouse_up(10, 10, MouseButton::Middle), t + MS_50);
988
989 assert_eq!(events.len(), 1);
990 assert!(matches!(
991 events[0],
992 SemanticEvent::Click {
993 button: MouseButton::Middle,
994 ..
995 }
996 ));
997 }
998
999 #[test]
1000 fn click_at_origin() {
1001 let mut gr = GestureRecognizer::new(GestureConfig::default());
1002 let t = now();
1003
1004 gr.process(&mouse_down(0, 0, MouseButton::Left), t);
1005 let events = gr.process(&mouse_up(0, 0, MouseButton::Left), t + MS_50);
1006
1007 assert_eq!(events.len(), 1);
1008 if let SemanticEvent::Click { pos, .. } = &events[0] {
1009 assert_eq!(pos.x, 0);
1010 assert_eq!(pos.y, 0);
1011 }
1012 }
1013
1014 #[test]
1015 fn click_at_max_position() {
1016 let mut gr = GestureRecognizer::new(GestureConfig::default());
1017 let t = now();
1018
1019 gr.process(&mouse_down(u16::MAX, u16::MAX, MouseButton::Left), t);
1020 let events = gr.process(&mouse_up(u16::MAX, u16::MAX, MouseButton::Left), t + MS_50);
1021
1022 assert_eq!(events.len(), 1);
1023 if let SemanticEvent::Click { pos, .. } = &events[0] {
1024 assert_eq!(pos.x, u16::MAX);
1025 assert_eq!(pos.y, u16::MAX);
1026 }
1027 }
1028
1029 #[test]
1030 fn double_click_right_button() {
1031 let mut gr = GestureRecognizer::new(GestureConfig::default());
1032 let t = now();
1033
1034 gr.process(&mouse_down(5, 5, MouseButton::Right), t);
1035 gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_50);
1036
1037 gr.process(&mouse_down(5, 5, MouseButton::Right), t + MS_100);
1038 let events = gr.process(&mouse_up(5, 5, MouseButton::Right), t + MS_200);
1039
1040 assert_eq!(events.len(), 1);
1041 assert!(matches!(
1042 events[0],
1043 SemanticEvent::DoubleClick {
1044 button: MouseButton::Right,
1045 ..
1046 }
1047 ));
1048 }
1049
1050 #[test]
1051 fn click_position_beyond_tolerance() {
1052 let mut gr = GestureRecognizer::new(GestureConfig::default());
1053 let t = now();
1054
1055 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1056 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1057
1058 gr.process(&mouse_down(8, 5, MouseButton::Left), t + MS_100);
1060 let events = gr.process(&mouse_up(8, 5, MouseButton::Left), t + MS_200);
1061
1062 assert_eq!(events.len(), 1);
1063 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1064 }
1065
1066 #[test]
1069 fn drag_with_right_button() {
1070 let mut gr = GestureRecognizer::new(GestureConfig::default());
1071 let t = now();
1072
1073 gr.process(&mouse_down(5, 5, MouseButton::Right), t);
1074 let events = gr.process(&mouse_drag(10, 5, MouseButton::Right), t + MS_50);
1075
1076 assert!(events.iter().any(|e| matches!(
1077 e,
1078 SemanticEvent::DragStart {
1079 button: MouseButton::Right,
1080 ..
1081 }
1082 )));
1083 }
1084
1085 #[test]
1086 fn drag_vertical() {
1087 let mut gr = GestureRecognizer::new(GestureConfig::default());
1088 let t = now();
1089
1090 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1091 let events = gr.process(&mouse_drag(5, 10, MouseButton::Left), t + MS_50);
1092
1093 assert!(
1094 events
1095 .iter()
1096 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1097 );
1098 let drag_move = events
1099 .iter()
1100 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1101 if let Some(SemanticEvent::DragMove { delta, .. }) = drag_move {
1102 assert_eq!(delta.0, 0); assert_eq!(delta.1, 5); }
1105 }
1106
1107 #[test]
1108 fn drag_multiple_moves() {
1109 let mut gr = GestureRecognizer::new(GestureConfig::default());
1110 let t = now();
1111
1112 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1113 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1114
1115 let events = gr.process(&mouse_drag(15, 5, MouseButton::Left), t + MS_100);
1117 let drag_move = events
1118 .iter()
1119 .find(|e| matches!(e, SemanticEvent::DragMove { .. }));
1120 if let Some(SemanticEvent::DragMove {
1121 start,
1122 current,
1123 delta,
1124 }) = drag_move
1125 {
1126 assert_eq!(*start, Position::new(5, 5));
1127 assert_eq!(*current, Position::new(15, 5));
1128 assert_eq!(*delta, (5, 0)); }
1130 }
1131
1132 #[test]
1133 fn drag_threshold_exactly_met() {
1134 let mut gr = GestureRecognizer::new(GestureConfig::default());
1135 let t = now();
1136
1137 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1138 let events = gr.process(&mouse_drag(8, 5, MouseButton::Left), t + MS_50);
1140
1141 assert!(
1142 events
1143 .iter()
1144 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1145 );
1146 }
1147
1148 #[test]
1149 fn drag_threshold_one_below() {
1150 let mut gr = GestureRecognizer::new(GestureConfig::default());
1151 let t = now();
1152
1153 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1154 let events = gr.process(&mouse_drag(7, 5, MouseButton::Left), t + MS_50);
1156
1157 assert!(
1158 !events
1159 .iter()
1160 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1161 );
1162 assert!(!gr.is_dragging());
1163 }
1164
1165 #[test]
1166 fn drag_state_reset_after_end() {
1167 let mut gr = GestureRecognizer::new(GestureConfig::default());
1168 let t = now();
1169
1170 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1172 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1173 gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_100);
1174
1175 assert!(!gr.is_dragging());
1176
1177 gr.process(&mouse_down(20, 20, MouseButton::Left), t + MS_200);
1179 let events = gr.process(
1180 &mouse_up(20, 20, MouseButton::Left),
1181 t + Duration::from_millis(250),
1182 );
1183
1184 assert_eq!(events.len(), 1);
1186 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1187 }
1188
1189 #[test]
1190 fn no_click_after_drag_cancel() {
1191 let mut gr = GestureRecognizer::new(GestureConfig::default());
1192 let t = now();
1193
1194 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1195 gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1196 gr.process(&esc(), t + MS_100); let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_200);
1200 assert!(
1203 !events
1204 .iter()
1205 .any(|e| matches!(e, SemanticEvent::DragEnd { .. }))
1206 );
1207 }
1208
1209 #[test]
1212 fn long_press_correct_position() {
1213 let mut gr = GestureRecognizer::new(GestureConfig::default());
1214 let t = now();
1215
1216 gr.process(&mouse_down(42, 17, MouseButton::Left), t);
1217 let lp = gr.check_long_press(t + MS_600);
1218
1219 if let Some(SemanticEvent::LongPress { pos, .. }) = lp {
1220 assert_eq!(pos, Position::new(42, 17));
1221 } else {
1222 panic!("Expected LongPress");
1223 }
1224 }
1225
1226 #[test]
1227 fn long_press_with_custom_threshold() {
1228 let config = GestureConfig {
1229 long_press_threshold: Duration::from_millis(200),
1230 ..Default::default()
1231 };
1232 let mut gr = GestureRecognizer::new(config);
1233 let t = now();
1234
1235 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1236
1237 assert!(
1239 gr.check_long_press(t + Duration::from_millis(150))
1240 .is_none()
1241 );
1242
1243 assert!(
1245 gr.check_long_press(t + Duration::from_millis(250))
1246 .is_some()
1247 );
1248 }
1249
1250 #[test]
1251 fn long_press_resets_on_new_mouse_down() {
1252 let mut gr = GestureRecognizer::new(GestureConfig::default());
1253 let t = now();
1254
1255 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1256 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
1257
1258 gr.process(&mouse_down(10, 10, MouseButton::Left), t + MS_200);
1260
1261 assert!(gr.check_long_press(t + MS_600).is_none());
1263
1264 assert!(
1266 gr.check_long_press(t + Duration::from_millis(750))
1267 .is_some()
1268 );
1269 }
1270
1271 #[test]
1274 fn alt_key_chord() {
1275 let mut gr = GestureRecognizer::new(GestureConfig::default());
1276 let t = now();
1277
1278 gr.process(&key_press(KeyCode::Char('x'), Modifiers::ALT), t);
1279 let events = gr.process(&key_press(KeyCode::Char('y'), Modifiers::ALT), t + MS_100);
1280
1281 assert_eq!(events.len(), 1);
1282 if let SemanticEvent::Chord { sequence } = &events[0] {
1283 assert_eq!(sequence.len(), 2);
1284 assert!(sequence[0].modifiers.contains(Modifiers::ALT));
1285 }
1286 }
1287
1288 #[test]
1289 fn mixed_modifier_chord() {
1290 let mut gr = GestureRecognizer::new(GestureConfig::default());
1291 let t = now();
1292
1293 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1294 let events = gr.process(&key_press(KeyCode::Char('d'), Modifiers::ALT), t + MS_100);
1295
1296 assert_eq!(events.len(), 1);
1297 if let SemanticEvent::Chord { sequence } = &events[0] {
1298 assert_eq!(sequence[0].modifiers, Modifiers::CTRL);
1299 assert_eq!(sequence[1].modifiers, Modifiers::ALT);
1300 }
1301 }
1302
1303 #[test]
1304 fn chord_with_function_key() {
1305 let mut gr = GestureRecognizer::new(GestureConfig::default());
1306 let t = now();
1307
1308 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1309 let events = gr.process(&key_press(KeyCode::F(1), Modifiers::CTRL), t + MS_100);
1310
1311 assert_eq!(events.len(), 1);
1312 if let SemanticEvent::Chord { sequence } = &events[0] {
1313 assert_eq!(sequence[1].code, KeyCode::F(1));
1314 }
1315 }
1316
1317 #[test]
1318 fn single_modifier_key_no_chord_emitted() {
1319 let mut gr = GestureRecognizer::new(GestureConfig::default());
1320 let t = now();
1321
1322 let events = gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1324 assert!(events.is_empty());
1325 }
1326
1327 #[test]
1330 fn custom_click_tolerance() {
1331 let config = GestureConfig {
1332 click_tolerance: 5,
1333 ..Default::default()
1334 };
1335 let mut gr = GestureRecognizer::new(config);
1336 let t = now();
1337
1338 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1339 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1340
1341 gr.process(&mouse_down(10, 5, MouseButton::Left), t + MS_100);
1343 let events = gr.process(&mouse_up(10, 5, MouseButton::Left), t + MS_200);
1344
1345 assert!(matches!(events[0], SemanticEvent::DoubleClick { .. }));
1346 }
1347
1348 #[test]
1349 fn custom_drag_threshold() {
1350 let config = GestureConfig {
1351 drag_threshold: 10,
1352 ..Default::default()
1353 };
1354 let mut gr = GestureRecognizer::new(config);
1355 let t = now();
1356
1357 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1358 let events = gr.process(&mouse_drag(10, 5, MouseButton::Left), t + MS_50);
1360 assert!(!gr.is_dragging());
1361 assert!(events.is_empty());
1362
1363 let events = gr.process(&mouse_drag(15, 5, MouseButton::Left), t + MS_100);
1365 assert!(gr.is_dragging());
1366 assert!(
1367 events
1368 .iter()
1369 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1370 );
1371 }
1372
1373 #[test]
1374 fn custom_multi_click_timeout() {
1375 let config = GestureConfig {
1376 multi_click_timeout: Duration::from_millis(100),
1377 ..Default::default()
1378 };
1379 let mut gr = GestureRecognizer::new(config);
1380 let t = now();
1381
1382 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1383 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1384
1385 gr.process(
1387 &mouse_down(5, 5, MouseButton::Left),
1388 t + Duration::from_millis(150),
1389 );
1390 let events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_200);
1391
1392 assert!(matches!(events[0], SemanticEvent::Click { .. }));
1393 }
1394
1395 #[test]
1396 fn config_getter_and_setter() {
1397 let mut gr = GestureRecognizer::new(GestureConfig::default());
1398
1399 assert_eq!(gr.config().drag_threshold, 3);
1400
1401 let new_config = GestureConfig {
1402 drag_threshold: 10,
1403 ..Default::default()
1404 };
1405 gr.set_config(new_config);
1406
1407 assert_eq!(gr.config().drag_threshold, 10);
1408 }
1409
1410 #[test]
1413 fn click_then_drag_are_independent() {
1414 let mut gr = GestureRecognizer::new(GestureConfig::default());
1415 let t = now();
1416
1417 gr.process(&mouse_down(5, 5, MouseButton::Left), t);
1419 let click_events = gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_50);
1420 assert!(matches!(click_events[0], SemanticEvent::Click { .. }));
1421
1422 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_200);
1424 let drag_events = gr.process(
1425 &mouse_drag(10, 5, MouseButton::Left),
1426 t + Duration::from_millis(250),
1427 );
1428 assert!(
1429 drag_events
1430 .iter()
1431 .any(|e| matches!(e, SemanticEvent::DragStart { .. }))
1432 );
1433
1434 let end_events = gr.process(
1435 &mouse_up(10, 5, MouseButton::Left),
1436 t + Duration::from_millis(300),
1437 );
1438 assert!(matches!(end_events[0], SemanticEvent::DragEnd { .. }));
1439 }
1440
1441 #[test]
1442 fn interleaved_mouse_and_keyboard() {
1443 let mut gr = GestureRecognizer::new(GestureConfig::default());
1444 let t = now();
1445
1446 gr.process(&key_press(KeyCode::Char('k'), Modifiers::CTRL), t);
1448
1449 gr.process(&mouse_down(5, 5, MouseButton::Left), t + MS_50);
1451 gr.process(&mouse_up(5, 5, MouseButton::Left), t + MS_100);
1452
1453 let events = gr.process(&key_press(KeyCode::Char('c'), Modifiers::CTRL), t + MS_200);
1455
1456 assert_eq!(events.len(), 1);
1457 assert!(matches!(events[0], SemanticEvent::Chord { .. }));
1458 }
1459
1460 #[test]
1461 fn rapid_clicks_produce_correct_sequence() {
1462 let mut gr = GestureRecognizer::new(GestureConfig::default());
1463 let t = now();
1464 let mut results = Vec::new();
1465
1466 for i in 0..5u32 {
1467 let offset = Duration::from_millis(i as u64 * 60);
1468 gr.process(&mouse_down(5, 5, MouseButton::Left), t + offset);
1469 let events = gr.process(
1470 &mouse_up(5, 5, MouseButton::Left),
1471 t + offset + Duration::from_millis(30),
1472 );
1473 results.extend(events);
1474 }
1475
1476 assert!(results.len() == 5);
1478 assert!(matches!(results[0], SemanticEvent::Click { .. }));
1479 assert!(matches!(results[1], SemanticEvent::DoubleClick { .. }));
1480 assert!(matches!(results[2], SemanticEvent::TripleClick { .. }));
1481 assert!(matches!(results[3], SemanticEvent::Click { .. }));
1482 assert!(matches!(results[4], SemanticEvent::DoubleClick { .. }));
1483 }
1484
1485 #[test]
1486 fn tick_event_ignored() {
1487 let mut gr = GestureRecognizer::new(GestureConfig::default());
1488 let t = now();
1489
1490 let events = gr.process(&Event::Tick, t);
1491 assert!(events.is_empty());
1492 }
1493
1494 #[test]
1495 fn resize_event_ignored() {
1496 let mut gr = GestureRecognizer::new(GestureConfig::default());
1497 let t = now();
1498
1499 let events = gr.process(
1500 &Event::Resize {
1501 width: 80,
1502 height: 24,
1503 },
1504 t,
1505 );
1506 assert!(events.is_empty());
1507 }
1508
1509 #[test]
1510 fn focus_gain_ignored() {
1511 let mut gr = GestureRecognizer::new(GestureConfig::default());
1512 let t = now();
1513
1514 let events = gr.process(&Event::Focus(true), t);
1515 assert!(events.is_empty());
1516 }
1517
1518 #[test]
1519 fn default_config_values() {
1520 let config = GestureConfig::default();
1521 assert_eq!(config.multi_click_timeout, Duration::from_millis(300));
1522 assert_eq!(config.long_press_threshold, Duration::from_millis(500));
1523 assert_eq!(config.drag_threshold, 3);
1524 assert_eq!(config.chord_timeout, Duration::from_millis(1000));
1525 assert_eq!(config.click_tolerance, 1);
1526 }
1527}