1#![allow(
2 clippy::unwrap_used,
3 clippy::disallowed_methods,
4 clippy::many_single_char_names
5)]
6use crate::event::{Event, GestureState, PointerId, PointerType, TouchId};
12use crate::geometry::Point;
13use std::collections::HashMap;
14use std::time::{Duration, Instant};
15
16#[derive(Debug, Clone)]
18pub struct GestureConfig {
19 pub pan_threshold: f32,
21 pub tap_timeout_ms: u64,
23 pub tap_slop: f32,
25 pub long_press_ms: u64,
27 pub double_tap_ms: u64,
29 pub pinch_threshold: f32,
31 pub rotate_threshold: f32,
33}
34
35impl Default for GestureConfig {
36 fn default() -> Self {
37 Self {
38 pan_threshold: 10.0,
39 tap_timeout_ms: 300,
40 tap_slop: 10.0,
41 long_press_ms: 500,
42 double_tap_ms: 300,
43 pinch_threshold: 0.05,
44 rotate_threshold: 0.05,
45 }
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct TouchPoint {
52 pub id: TouchId,
54 pub start_position: Point,
56 pub current_position: Point,
58 pub previous_position: Point,
60 pub start_time: Instant,
62 pub pressure: f32,
64}
65
66impl TouchPoint {
67 pub fn new(id: TouchId, position: Point, pressure: f32) -> Self {
69 let now = Instant::now();
70 Self {
71 id,
72 start_position: position,
73 current_position: position,
74 previous_position: position,
75 start_time: now,
76 pressure,
77 }
78 }
79
80 pub fn update(&mut self, position: Point, pressure: f32) {
82 self.previous_position = self.current_position;
83 self.current_position = position;
84 self.pressure = pressure;
85 }
86
87 pub fn total_distance(&self) -> f32 {
89 self.start_position.distance(&self.current_position)
90 }
91
92 pub fn delta(&self) -> Point {
94 self.current_position - self.previous_position
95 }
96
97 pub fn duration(&self) -> Duration {
99 self.start_time.elapsed()
100 }
101}
102
103#[derive(Debug, Clone, Default)]
105pub enum RecognizedGesture {
106 #[default]
108 None,
109 Tap { position: Point, count: u8 },
111 LongPress { position: Point },
113 Pan {
115 delta: Point,
116 velocity: Point,
117 state: GestureState,
118 },
119 Pinch {
121 scale: f32,
122 center: Point,
123 state: GestureState,
124 },
125 Rotate {
127 angle: f32,
128 center: Point,
129 state: GestureState,
130 },
131}
132
133#[derive(Debug, Clone)]
135struct LastTap {
136 position: Point,
137 time: Instant,
138 count: u8,
139}
140
141#[derive(Debug)]
143pub struct GestureRecognizer {
144 config: GestureConfig,
146 touches: HashMap<TouchId, TouchPoint>,
148 current_gesture: RecognizedGesture,
150 initial_pinch_distance: Option<f32>,
152 initial_rotation_angle: Option<f32>,
154 last_tap: Option<LastTap>,
156 velocity_samples: Vec<(Point, Instant)>,
158}
159
160impl GestureRecognizer {
161 pub fn new() -> Self {
163 Self::with_config(GestureConfig::default())
164 }
165
166 pub fn with_config(config: GestureConfig) -> Self {
168 Self {
169 config,
170 touches: HashMap::new(),
171 current_gesture: RecognizedGesture::None,
172 initial_pinch_distance: None,
173 initial_rotation_angle: None,
174 last_tap: None,
175 velocity_samples: Vec::new(),
176 }
177 }
178
179 pub fn config(&self) -> &GestureConfig {
181 &self.config
182 }
183
184 pub fn touch_count(&self) -> usize {
186 self.touches.len()
187 }
188
189 pub fn touch(&self, id: TouchId) -> Option<&TouchPoint> {
191 self.touches.get(&id)
192 }
193
194 pub fn process(&mut self, event: &Event) -> Option<Event> {
196 match event {
197 Event::TouchStart {
198 id,
199 position,
200 pressure,
201 } => self.on_touch_start(*id, *position, *pressure),
202 Event::TouchMove {
203 id,
204 position,
205 pressure,
206 } => self.on_touch_move(*id, *position, *pressure),
207 Event::TouchEnd { id, position } => self.on_touch_end(*id, *position),
208 Event::TouchCancel { id } => self.on_touch_cancel(*id),
209 _ => None,
210 }
211 }
212
213 fn on_touch_start(&mut self, id: TouchId, position: Point, pressure: f32) -> Option<Event> {
214 let touch = TouchPoint::new(id, position, pressure);
215 self.touches.insert(id, touch);
216
217 if self.touches.len() == 2 {
219 self.init_two_finger_tracking();
220 }
221
222 None
223 }
224
225 fn on_touch_move(&mut self, id: TouchId, position: Point, pressure: f32) -> Option<Event> {
226 {
227 let touch = self.touches.get_mut(&id)?;
228 touch.update(position, pressure);
229 }
230
231 match self.touches.len() {
232 1 => self.recognize_single_finger_move(),
233 2 => self.recognize_two_finger_move(),
234 _ => None,
235 }
236 }
237
238 fn on_touch_end(&mut self, id: TouchId, _position: Point) -> Option<Event> {
239 let touch = self.touches.remove(&id)?;
240
241 if self.touches.is_empty() {
243 let duration = touch.duration();
244 let distance = touch.total_distance();
245
246 if duration.as_millis() < u128::from(self.config.tap_timeout_ms)
247 && distance < self.config.tap_slop
248 {
249 return self.handle_tap(touch.start_position);
250 }
251
252 self.end_active_gesture()
254 } else if self.touches.len() == 1 {
255 self.end_two_finger_gesture()
257 } else {
258 None
259 }
260 }
261
262 fn on_touch_cancel(&mut self, id: TouchId) -> Option<Event> {
263 self.touches.remove(&id);
264
265 if self.touches.is_empty() {
266 self.end_active_gesture()
267 } else {
268 None
269 }
270 }
271
272 fn init_two_finger_tracking(&mut self) {
273 let (dist, angle) = if let Some((t1, t2)) = self.get_two_touches() {
274 let dist = t1.current_position.distance(&t2.current_position);
275 let angle = (t2.current_position.y - t1.current_position.y)
276 .atan2(t2.current_position.x - t1.current_position.x);
277 (Some(dist), Some(angle))
278 } else {
279 (None, None)
280 };
281 self.initial_pinch_distance = dist;
282 self.initial_rotation_angle = angle;
283 }
284
285 fn recognize_single_finger_move(&mut self) -> Option<Event> {
286 let touch = self.touches.values().next()?;
287 let distance = touch.total_distance();
288
289 if distance >= self.config.pan_threshold {
290 let delta = touch.delta();
291 let velocity = self.calculate_velocity();
292
293 let state = match &self.current_gesture {
294 RecognizedGesture::Pan { .. } => GestureState::Changed,
295 _ => GestureState::Started,
296 };
297
298 self.current_gesture = RecognizedGesture::Pan {
299 delta,
300 velocity,
301 state,
302 };
303
304 Some(Event::GesturePan {
305 delta,
306 velocity,
307 state,
308 })
309 } else {
310 None
311 }
312 }
313
314 fn recognize_two_finger_move(&mut self) -> Option<Event> {
315 let (t1, t2) = self.get_two_touches()?;
316
317 let current_distance = t1.current_position.distance(&t2.current_position);
318 let initial_distance = self.initial_pinch_distance?;
319 let scale = current_distance / initial_distance;
320
321 let current_angle = self.angle_between(&t1.current_position, &t2.current_position);
322 let initial_angle = self.initial_rotation_angle?;
323 let angle_delta = current_angle - initial_angle;
324
325 let center = Point::new(
326 (t1.current_position.x + t2.current_position.x) / 2.0,
327 (t1.current_position.y + t2.current_position.y) / 2.0,
328 );
329
330 let scale_change = (scale - 1.0).abs();
332 let angle_change = angle_delta.abs();
333
334 if scale_change > self.config.pinch_threshold || angle_change > self.config.rotate_threshold
335 {
336 if scale_change > angle_change {
338 let state = match &self.current_gesture {
339 RecognizedGesture::Pinch { .. } => GestureState::Changed,
340 _ => GestureState::Started,
341 };
342
343 self.current_gesture = RecognizedGesture::Pinch {
344 scale,
345 center,
346 state,
347 };
348
349 Some(Event::GesturePinch {
350 scale,
351 center,
352 state,
353 })
354 } else {
355 let state = match &self.current_gesture {
356 RecognizedGesture::Rotate { .. } => GestureState::Changed,
357 _ => GestureState::Started,
358 };
359
360 self.current_gesture = RecognizedGesture::Rotate {
361 angle: angle_delta,
362 center,
363 state,
364 };
365
366 Some(Event::GestureRotate {
367 angle: angle_delta,
368 center,
369 state,
370 })
371 }
372 } else {
373 None
374 }
375 }
376
377 fn handle_tap(&mut self, position: Point) -> Option<Event> {
378 let now = Instant::now();
379
380 let count = if let Some(last) = &self.last_tap {
381 if now.duration_since(last.time).as_millis() < u128::from(self.config.double_tap_ms)
382 && position.distance(&last.position) < self.config.tap_slop
383 {
384 last.count + 1
385 } else {
386 1
387 }
388 } else {
389 1
390 };
391
392 self.last_tap = Some(LastTap {
393 position,
394 time: now,
395 count,
396 });
397
398 Some(Event::GestureTap { position, count })
399 }
400
401 fn end_active_gesture(&mut self) -> Option<Event> {
402 let result = match &self.current_gesture {
403 RecognizedGesture::Pan {
404 delta, velocity, ..
405 } => Some(Event::GesturePan {
406 delta: *delta,
407 velocity: *velocity,
408 state: GestureState::Ended,
409 }),
410 _ => None,
411 };
412
413 self.current_gesture = RecognizedGesture::None;
414 self.velocity_samples.clear();
415 result
416 }
417
418 fn end_two_finger_gesture(&mut self) -> Option<Event> {
419 let result = match &self.current_gesture {
420 RecognizedGesture::Pinch { scale, center, .. } => Some(Event::GesturePinch {
421 scale: *scale,
422 center: *center,
423 state: GestureState::Ended,
424 }),
425 RecognizedGesture::Rotate { angle, center, .. } => Some(Event::GestureRotate {
426 angle: *angle,
427 center: *center,
428 state: GestureState::Ended,
429 }),
430 _ => None,
431 };
432
433 self.current_gesture = RecognizedGesture::None;
434 self.initial_pinch_distance = None;
435 self.initial_rotation_angle = None;
436 result
437 }
438
439 fn get_two_touches(&self) -> Option<(&TouchPoint, &TouchPoint)> {
440 let mut iter = self.touches.values();
441 let t1 = iter.next()?;
442 let t2 = iter.next()?;
443 Some((t1, t2))
444 }
445
446 fn angle_between(&self, p1: &Point, p2: &Point) -> f32 {
447 (p2.y - p1.y).atan2(p2.x - p1.x)
448 }
449
450 fn calculate_velocity(&mut self) -> Point {
451 let now = Instant::now();
452
453 self.velocity_samples
455 .retain(|(_, time)| now.duration_since(*time).as_millis() < 100);
456
457 if let Some(touch) = self.touches.values().next() {
458 self.velocity_samples.push((touch.current_position, now));
459 }
460
461 if self.velocity_samples.len() < 2 {
462 return Point::ORIGIN;
463 }
464
465 let (first_pos, first_time) = self.velocity_samples.first().expect("checked len >= 2");
466 let (last_pos, last_time) = self.velocity_samples.last().expect("checked len >= 2");
467
468 let dt = last_time.duration_since(*first_time).as_secs_f32();
469 if dt < 0.001 {
470 return Point::ORIGIN;
471 }
472
473 Point::new(
474 (last_pos.x - first_pos.x) / dt,
475 (last_pos.y - first_pos.y) / dt,
476 )
477 }
478
479 pub fn check_long_press(&mut self) -> Option<Event> {
482 if self.touches.len() != 1 {
483 return None;
484 }
485
486 let touch = self.touches.values().next()?;
487
488 if touch.duration().as_millis() >= u128::from(self.config.long_press_ms)
489 && touch.total_distance() < self.config.tap_slop
490 && matches!(self.current_gesture, RecognizedGesture::None)
491 {
492 self.current_gesture = RecognizedGesture::LongPress {
493 position: touch.start_position,
494 };
495
496 Some(Event::GestureLongPress {
497 position: touch.start_position,
498 })
499 } else {
500 None
501 }
502 }
503
504 pub fn reset(&mut self) {
506 self.touches.clear();
507 self.current_gesture = RecognizedGesture::None;
508 self.initial_pinch_distance = None;
509 self.initial_rotation_angle = None;
510 self.velocity_samples.clear();
511 }
512}
513
514impl Default for GestureRecognizer {
515 fn default() -> Self {
516 Self::new()
517 }
518}
519
520#[derive(Debug)]
522pub struct PointerGestureRecognizer {
523 pointers: HashMap<PointerId, PointerInfo>,
525 config: GestureConfig,
527 primary_pointer: Option<PointerId>,
529}
530
531#[derive(Debug, Clone)]
533pub struct PointerInfo {
534 pub id: PointerId,
536 pub pointer_type: PointerType,
538 pub start_position: Point,
540 pub current_position: Point,
542 pub start_time: Instant,
544 pub is_primary: bool,
546 pub pressure: f32,
548}
549
550impl PointerGestureRecognizer {
551 pub fn new() -> Self {
553 Self::with_config(GestureConfig::default())
554 }
555
556 pub fn with_config(config: GestureConfig) -> Self {
558 Self {
559 pointers: HashMap::new(),
560 config,
561 primary_pointer: None,
562 }
563 }
564
565 pub fn config(&self) -> &GestureConfig {
567 &self.config
568 }
569
570 pub fn pointer_count(&self) -> usize {
572 self.pointers.len()
573 }
574
575 pub fn primary(&self) -> Option<&PointerInfo> {
577 self.primary_pointer.and_then(|id| self.pointers.get(&id))
578 }
579
580 pub fn process(&mut self, event: &Event) -> Option<Event> {
582 match event {
583 Event::PointerDown {
584 pointer_id,
585 pointer_type,
586 position,
587 pressure,
588 is_primary,
589 ..
590 } => {
591 let info = PointerInfo {
592 id: *pointer_id,
593 pointer_type: *pointer_type,
594 start_position: *position,
595 current_position: *position,
596 start_time: Instant::now(),
597 is_primary: *is_primary,
598 pressure: *pressure,
599 };
600
601 if *is_primary || self.primary_pointer.is_none() {
602 self.primary_pointer = Some(*pointer_id);
603 }
604
605 self.pointers.insert(*pointer_id, info);
606 None
607 }
608 Event::PointerMove {
609 pointer_id,
610 position,
611 pressure,
612 ..
613 } => {
614 if let Some(info) = self.pointers.get_mut(pointer_id) {
615 info.current_position = *position;
616 info.pressure = *pressure;
617 }
618 None
619 }
620 Event::PointerUp { pointer_id, .. } | Event::PointerCancel { pointer_id } => {
621 self.pointers.remove(pointer_id);
622 if self.primary_pointer == Some(*pointer_id) {
623 self.primary_pointer = self.pointers.keys().next().copied();
624 }
625 None
626 }
627 _ => None,
628 }
629 }
630
631 pub fn reset(&mut self) {
633 self.pointers.clear();
634 self.primary_pointer = None;
635 }
636}
637
638impl Default for PointerGestureRecognizer {
639 fn default() -> Self {
640 Self::new()
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647
648 #[test]
650 fn test_gesture_config_default() {
651 let config = GestureConfig::default();
652 assert_eq!(config.pan_threshold, 10.0);
653 assert_eq!(config.tap_timeout_ms, 300);
654 assert_eq!(config.tap_slop, 10.0);
655 assert_eq!(config.long_press_ms, 500);
656 assert_eq!(config.double_tap_ms, 300);
657 assert!((config.pinch_threshold - 0.05).abs() < 0.001);
658 assert!((config.rotate_threshold - 0.05).abs() < 0.001);
659 }
660
661 #[test]
662 fn test_gesture_config_custom() {
663 let config = GestureConfig {
664 pan_threshold: 20.0,
665 tap_timeout_ms: 200,
666 tap_slop: 15.0,
667 long_press_ms: 800,
668 double_tap_ms: 400,
669 pinch_threshold: 0.1,
670 rotate_threshold: 0.1,
671 };
672 assert_eq!(config.pan_threshold, 20.0);
673 assert_eq!(config.long_press_ms, 800);
674 }
675
676 #[test]
678 fn test_touch_point_new() {
679 let point = TouchPoint::new(TouchId::new(1), Point::new(100.0, 200.0), 0.5);
680 assert_eq!(point.id, TouchId(1));
681 assert_eq!(point.start_position, Point::new(100.0, 200.0));
682 assert_eq!(point.current_position, Point::new(100.0, 200.0));
683 assert_eq!(point.pressure, 0.5);
684 }
685
686 #[test]
687 fn test_touch_point_update() {
688 let mut point = TouchPoint::new(TouchId::new(1), Point::new(100.0, 200.0), 0.5);
689 point.update(Point::new(150.0, 250.0), 0.8);
690
691 assert_eq!(point.current_position, Point::new(150.0, 250.0));
692 assert_eq!(point.previous_position, Point::new(100.0, 200.0));
693 assert_eq!(point.pressure, 0.8);
694 }
695
696 #[test]
697 fn test_touch_point_total_distance() {
698 let mut point = TouchPoint::new(TouchId::new(1), Point::new(0.0, 0.0), 0.5);
699 point.update(Point::new(3.0, 4.0), 0.5);
700
701 assert!((point.total_distance() - 5.0).abs() < 0.001);
702 }
703
704 #[test]
705 fn test_touch_point_delta() {
706 let mut point = TouchPoint::new(TouchId::new(1), Point::new(0.0, 0.0), 0.5);
707 point.update(Point::new(10.0, 20.0), 0.5);
708 point.update(Point::new(15.0, 25.0), 0.5);
709
710 let delta = point.delta();
711 assert_eq!(delta.x, 5.0);
712 assert_eq!(delta.y, 5.0);
713 }
714
715 #[test]
717 fn test_recognizer_new() {
718 let recognizer = GestureRecognizer::new();
719 assert_eq!(recognizer.touch_count(), 0);
720 }
721
722 #[test]
723 fn test_recognizer_with_config() {
724 let config = GestureConfig {
725 pan_threshold: 20.0,
726 ..Default::default()
727 };
728 let recognizer = GestureRecognizer::with_config(config);
729 assert_eq!(recognizer.config().pan_threshold, 20.0);
730 }
731
732 #[test]
733 fn test_recognizer_touch_start() {
734 let mut recognizer = GestureRecognizer::new();
735
736 let event = Event::TouchStart {
737 id: TouchId::new(1),
738 position: Point::new(100.0, 200.0),
739 pressure: 0.5,
740 };
741
742 recognizer.process(&event);
743 assert_eq!(recognizer.touch_count(), 1);
744
745 let touch = recognizer.touch(TouchId::new(1)).unwrap();
746 assert_eq!(touch.start_position, Point::new(100.0, 200.0));
747 }
748
749 #[test]
750 fn test_recognizer_touch_move() {
751 let mut recognizer = GestureRecognizer::new();
752
753 recognizer.process(&Event::TouchStart {
755 id: TouchId::new(1),
756 position: Point::new(100.0, 200.0),
757 pressure: 0.5,
758 });
759
760 recognizer.process(&Event::TouchMove {
762 id: TouchId::new(1),
763 position: Point::new(150.0, 250.0),
764 pressure: 0.6,
765 });
766
767 let touch = recognizer.touch(TouchId::new(1)).unwrap();
768 assert_eq!(touch.current_position, Point::new(150.0, 250.0));
769 }
770
771 #[test]
772 fn test_recognizer_touch_end() {
773 let mut recognizer = GestureRecognizer::new();
774
775 recognizer.process(&Event::TouchStart {
776 id: TouchId::new(1),
777 position: Point::new(100.0, 200.0),
778 pressure: 0.5,
779 });
780
781 recognizer.process(&Event::TouchEnd {
782 id: TouchId::new(1),
783 position: Point::new(100.0, 200.0),
784 });
785
786 assert_eq!(recognizer.touch_count(), 0);
787 }
788
789 #[test]
790 fn test_recognizer_tap() {
791 let mut recognizer = GestureRecognizer::new();
792
793 recognizer.process(&Event::TouchStart {
794 id: TouchId::new(1),
795 position: Point::new(100.0, 200.0),
796 pressure: 0.5,
797 });
798
799 let result = recognizer.process(&Event::TouchEnd {
801 id: TouchId::new(1),
802 position: Point::new(100.0, 200.0),
803 });
804
805 assert!(matches!(result, Some(Event::GestureTap { count: 1, .. })));
806 }
807
808 #[test]
809 fn test_recognizer_pan() {
810 let mut recognizer = GestureRecognizer::new();
811
812 recognizer.process(&Event::TouchStart {
813 id: TouchId::new(1),
814 position: Point::new(100.0, 200.0),
815 pressure: 0.5,
816 });
817
818 let result = recognizer.process(&Event::TouchMove {
820 id: TouchId::new(1),
821 position: Point::new(150.0, 250.0), pressure: 0.5,
823 });
824
825 assert!(matches!(
826 result,
827 Some(Event::GesturePan {
828 state: GestureState::Started,
829 ..
830 })
831 ));
832 }
833
834 #[test]
835 fn test_recognizer_pan_continued() {
836 let mut recognizer = GestureRecognizer::new();
837
838 recognizer.process(&Event::TouchStart {
839 id: TouchId::new(1),
840 position: Point::new(100.0, 200.0),
841 pressure: 0.5,
842 });
843
844 recognizer.process(&Event::TouchMove {
846 id: TouchId::new(1),
847 position: Point::new(150.0, 250.0),
848 pressure: 0.5,
849 });
850
851 let result = recognizer.process(&Event::TouchMove {
853 id: TouchId::new(1),
854 position: Point::new(200.0, 300.0),
855 pressure: 0.5,
856 });
857
858 assert!(matches!(
859 result,
860 Some(Event::GesturePan {
861 state: GestureState::Changed,
862 ..
863 })
864 ));
865 }
866
867 #[test]
868 fn test_recognizer_two_touches() {
869 let mut recognizer = GestureRecognizer::new();
870
871 recognizer.process(&Event::TouchStart {
872 id: TouchId::new(1),
873 position: Point::new(100.0, 200.0),
874 pressure: 0.5,
875 });
876
877 recognizer.process(&Event::TouchStart {
878 id: TouchId::new(2),
879 position: Point::new(200.0, 200.0),
880 pressure: 0.5,
881 });
882
883 assert_eq!(recognizer.touch_count(), 2);
884 }
885
886 #[test]
887 fn test_recognizer_pinch() {
888 let mut recognizer = GestureRecognizer::with_config(GestureConfig {
889 pinch_threshold: 0.01,
890 ..Default::default()
891 });
892
893 recognizer.process(&Event::TouchStart {
895 id: TouchId::new(1),
896 position: Point::new(100.0, 200.0),
897 pressure: 0.5,
898 });
899
900 recognizer.process(&Event::TouchStart {
901 id: TouchId::new(2),
902 position: Point::new(200.0, 200.0),
903 pressure: 0.5,
904 });
905
906 recognizer.process(&Event::TouchMove {
908 id: TouchId::new(1),
909 position: Point::new(50.0, 200.0),
910 pressure: 0.5,
911 });
912
913 let result = recognizer.process(&Event::TouchMove {
914 id: TouchId::new(2),
915 position: Point::new(250.0, 200.0),
916 pressure: 0.5,
917 });
918
919 assert!(matches!(result, Some(Event::GesturePinch { .. })));
920 }
921
922 #[test]
923 fn test_recognizer_reset() {
924 let mut recognizer = GestureRecognizer::new();
925
926 recognizer.process(&Event::TouchStart {
927 id: TouchId::new(1),
928 position: Point::new(100.0, 200.0),
929 pressure: 0.5,
930 });
931
932 assert_eq!(recognizer.touch_count(), 1);
933
934 recognizer.reset();
935 assert_eq!(recognizer.touch_count(), 0);
936 }
937
938 #[test]
939 fn test_recognizer_touch_cancel() {
940 let mut recognizer = GestureRecognizer::new();
941
942 recognizer.process(&Event::TouchStart {
943 id: TouchId::new(1),
944 position: Point::new(100.0, 200.0),
945 pressure: 0.5,
946 });
947
948 recognizer.process(&Event::TouchCancel {
949 id: TouchId::new(1),
950 });
951
952 assert_eq!(recognizer.touch_count(), 0);
953 }
954
955 #[test]
956 fn test_recognizer_ignores_non_touch_events() {
957 let mut recognizer = GestureRecognizer::new();
958
959 let result = recognizer.process(&Event::MouseMove {
960 position: Point::new(100.0, 200.0),
961 });
962
963 assert!(result.is_none());
964 assert_eq!(recognizer.touch_count(), 0);
965 }
966
967 #[test]
969 fn test_pointer_recognizer_new() {
970 let recognizer = PointerGestureRecognizer::new();
971 assert_eq!(recognizer.pointer_count(), 0);
972 assert!(recognizer.primary().is_none());
973 }
974
975 #[test]
976 fn test_pointer_recognizer_pointer_down() {
977 let mut recognizer = PointerGestureRecognizer::new();
978
979 recognizer.process(&Event::PointerDown {
980 pointer_id: PointerId::new(1),
981 pointer_type: PointerType::Touch,
982 position: Point::new(100.0, 200.0),
983 pressure: 0.5,
984 is_primary: true,
985 button: None,
986 });
987
988 assert_eq!(recognizer.pointer_count(), 1);
989
990 let primary = recognizer.primary().unwrap();
991 assert_eq!(primary.id, PointerId(1));
992 assert_eq!(primary.pointer_type, PointerType::Touch);
993 assert!(primary.is_primary);
994 }
995
996 #[test]
997 fn test_pointer_recognizer_pointer_move() {
998 let mut recognizer = PointerGestureRecognizer::new();
999
1000 recognizer.process(&Event::PointerDown {
1001 pointer_id: PointerId::new(1),
1002 pointer_type: PointerType::Mouse,
1003 position: Point::new(100.0, 200.0),
1004 pressure: 0.5,
1005 is_primary: true,
1006 button: None,
1007 });
1008
1009 recognizer.process(&Event::PointerMove {
1010 pointer_id: PointerId::new(1),
1011 pointer_type: PointerType::Mouse,
1012 position: Point::new(150.0, 250.0),
1013 pressure: 0.6,
1014 is_primary: true,
1015 });
1016
1017 let primary = recognizer.primary().unwrap();
1018 assert_eq!(primary.current_position, Point::new(150.0, 250.0));
1019 }
1020
1021 #[test]
1022 fn test_pointer_recognizer_pointer_up() {
1023 let mut recognizer = PointerGestureRecognizer::new();
1024
1025 recognizer.process(&Event::PointerDown {
1026 pointer_id: PointerId::new(1),
1027 pointer_type: PointerType::Pen,
1028 position: Point::new(100.0, 200.0),
1029 pressure: 0.5,
1030 is_primary: true,
1031 button: None,
1032 });
1033
1034 recognizer.process(&Event::PointerUp {
1035 pointer_id: PointerId::new(1),
1036 pointer_type: PointerType::Pen,
1037 position: Point::new(100.0, 200.0),
1038 is_primary: true,
1039 button: None,
1040 });
1041
1042 assert_eq!(recognizer.pointer_count(), 0);
1043 assert!(recognizer.primary().is_none());
1044 }
1045
1046 #[test]
1047 fn test_pointer_recognizer_multiple_pointers() {
1048 let mut recognizer = PointerGestureRecognizer::new();
1049
1050 recognizer.process(&Event::PointerDown {
1052 pointer_id: PointerId::new(1),
1053 pointer_type: PointerType::Touch,
1054 position: Point::new(100.0, 200.0),
1055 pressure: 0.5,
1056 is_primary: true,
1057 button: None,
1058 });
1059
1060 recognizer.process(&Event::PointerDown {
1062 pointer_id: PointerId::new(2),
1063 pointer_type: PointerType::Touch,
1064 position: Point::new(200.0, 200.0),
1065 pressure: 0.5,
1066 is_primary: false,
1067 button: None,
1068 });
1069
1070 assert_eq!(recognizer.pointer_count(), 2);
1071
1072 let primary = recognizer.primary().unwrap();
1073 assert_eq!(primary.id, PointerId(1));
1074 }
1075
1076 #[test]
1077 fn test_pointer_recognizer_primary_changes_on_remove() {
1078 let mut recognizer = PointerGestureRecognizer::new();
1079
1080 recognizer.process(&Event::PointerDown {
1081 pointer_id: PointerId::new(1),
1082 pointer_type: PointerType::Touch,
1083 position: Point::new(100.0, 200.0),
1084 pressure: 0.5,
1085 is_primary: true,
1086 button: None,
1087 });
1088
1089 recognizer.process(&Event::PointerDown {
1090 pointer_id: PointerId::new(2),
1091 pointer_type: PointerType::Touch,
1092 position: Point::new(200.0, 200.0),
1093 pressure: 0.5,
1094 is_primary: false,
1095 button: None,
1096 });
1097
1098 recognizer.process(&Event::PointerUp {
1100 pointer_id: PointerId::new(1),
1101 pointer_type: PointerType::Touch,
1102 position: Point::new(100.0, 200.0),
1103 is_primary: true,
1104 button: None,
1105 });
1106
1107 assert_eq!(recognizer.pointer_count(), 1);
1108 assert!(recognizer.primary().is_some());
1110 }
1111
1112 #[test]
1113 fn test_pointer_recognizer_reset() {
1114 let mut recognizer = PointerGestureRecognizer::new();
1115
1116 recognizer.process(&Event::PointerDown {
1117 pointer_id: PointerId::new(1),
1118 pointer_type: PointerType::Touch,
1119 position: Point::new(100.0, 200.0),
1120 pressure: 0.5,
1121 is_primary: true,
1122 button: None,
1123 });
1124
1125 recognizer.reset();
1126
1127 assert_eq!(recognizer.pointer_count(), 0);
1128 assert!(recognizer.primary().is_none());
1129 }
1130
1131 #[test]
1132 fn test_pointer_recognizer_cancel() {
1133 let mut recognizer = PointerGestureRecognizer::new();
1134
1135 recognizer.process(&Event::PointerDown {
1136 pointer_id: PointerId::new(1),
1137 pointer_type: PointerType::Touch,
1138 position: Point::new(100.0, 200.0),
1139 pressure: 0.5,
1140 is_primary: true,
1141 button: None,
1142 });
1143
1144 recognizer.process(&Event::PointerCancel {
1145 pointer_id: PointerId::new(1),
1146 });
1147
1148 assert_eq!(recognizer.pointer_count(), 0);
1149 }
1150
1151 #[test]
1153 fn test_recognized_gesture_default() {
1154 let gesture = RecognizedGesture::default();
1155 assert!(matches!(gesture, RecognizedGesture::None));
1156 }
1157
1158 #[test]
1160 fn test_pointer_info_clone() {
1161 let info = PointerInfo {
1162 id: PointerId::new(1),
1163 pointer_type: PointerType::Mouse,
1164 start_position: Point::new(100.0, 200.0),
1165 current_position: Point::new(150.0, 250.0),
1166 start_time: Instant::now(),
1167 is_primary: true,
1168 pressure: 0.8,
1169 };
1170
1171 let cloned = info.clone();
1172 assert_eq!(cloned.id, info.id);
1173 assert_eq!(cloned.pointer_type, info.pointer_type);
1174 assert_eq!(cloned.pressure, info.pressure);
1175 }
1176
1177 #[test]
1182 fn test_gesture_config_debug() {
1183 let config = GestureConfig::default();
1184 let debug = format!("{config:?}");
1185 assert!(debug.contains("GestureConfig"));
1186 }
1187
1188 #[test]
1189 fn test_gesture_config_clone() {
1190 let config = GestureConfig {
1191 pan_threshold: 25.0,
1192 ..Default::default()
1193 };
1194 let cloned = config;
1195 assert_eq!(cloned.pan_threshold, 25.0);
1196 }
1197
1198 #[test]
1199 fn test_touch_point_debug() {
1200 let point = TouchPoint::new(TouchId::new(1), Point::new(100.0, 200.0), 0.5);
1201 let debug = format!("{point:?}");
1202 assert!(debug.contains("TouchPoint"));
1203 }
1204
1205 #[test]
1206 fn test_touch_point_clone() {
1207 let point = TouchPoint::new(TouchId::new(1), Point::new(100.0, 200.0), 0.5);
1208 let cloned = point.clone();
1209 assert_eq!(cloned.id, point.id);
1210 assert_eq!(cloned.start_position, point.start_position);
1211 }
1212
1213 #[test]
1214 fn test_touch_point_duration() {
1215 let point = TouchPoint::new(TouchId::new(1), Point::new(100.0, 200.0), 0.5);
1216 let duration = point.duration();
1217 if duration.as_millis() >= 100 {
1219 eprintln!(
1220 "[PERF WARNING] touch point duration {}ms (target <100ms)",
1221 duration.as_millis()
1222 );
1223 }
1224 }
1225
1226 #[test]
1227 fn test_recognized_gesture_debug() {
1228 let gesture = RecognizedGesture::Tap {
1229 position: Point::new(50.0, 50.0),
1230 count: 2,
1231 };
1232 let debug = format!("{gesture:?}");
1233 assert!(debug.contains("Tap"));
1234 }
1235
1236 #[test]
1237 fn test_recognized_gesture_clone() {
1238 let gesture = RecognizedGesture::Pan {
1239 delta: Point::new(10.0, 20.0),
1240 velocity: Point::new(100.0, 200.0),
1241 state: GestureState::Started,
1242 };
1243 let cloned = gesture;
1244 assert!(matches!(cloned, RecognizedGesture::Pan { .. }));
1245 }
1246
1247 #[test]
1248 fn test_recognized_gesture_all_variants() {
1249 let gestures = vec![
1250 RecognizedGesture::None,
1251 RecognizedGesture::Tap {
1252 position: Point::ORIGIN,
1253 count: 1,
1254 },
1255 RecognizedGesture::LongPress {
1256 position: Point::ORIGIN,
1257 },
1258 RecognizedGesture::Pan {
1259 delta: Point::ORIGIN,
1260 velocity: Point::ORIGIN,
1261 state: GestureState::Started,
1262 },
1263 RecognizedGesture::Pinch {
1264 scale: 1.0,
1265 center: Point::ORIGIN,
1266 state: GestureState::Changed,
1267 },
1268 RecognizedGesture::Rotate {
1269 angle: 0.5,
1270 center: Point::ORIGIN,
1271 state: GestureState::Ended,
1272 },
1273 ];
1274
1275 for gesture in gestures {
1276 let debug = format!("{gesture:?}");
1277 assert!(!debug.is_empty());
1278 }
1279 }
1280
1281 #[test]
1282 fn test_gesture_recognizer_default() {
1283 let recognizer = GestureRecognizer::default();
1284 assert_eq!(recognizer.touch_count(), 0);
1285 assert_eq!(recognizer.config().pan_threshold, 10.0);
1286 }
1287
1288 #[test]
1289 fn test_gesture_recognizer_debug() {
1290 let recognizer = GestureRecognizer::new();
1291 let debug = format!("{recognizer:?}");
1292 assert!(debug.contains("GestureRecognizer"));
1293 }
1294
1295 #[test]
1296 fn test_gesture_recognizer_touch_move_unknown_id() {
1297 let mut recognizer = GestureRecognizer::new();
1298
1299 let result = recognizer.process(&Event::TouchMove {
1301 id: TouchId::new(99),
1302 position: Point::new(150.0, 250.0),
1303 pressure: 0.5,
1304 });
1305
1306 assert!(result.is_none());
1307 }
1308
1309 #[test]
1310 fn test_gesture_recognizer_touch_end_unknown_id() {
1311 let mut recognizer = GestureRecognizer::new();
1312
1313 let result = recognizer.process(&Event::TouchEnd {
1315 id: TouchId::new(99),
1316 position: Point::new(100.0, 200.0),
1317 });
1318
1319 assert!(result.is_none());
1320 }
1321
1322 #[test]
1323 fn test_gesture_recognizer_pan_end() {
1324 let mut recognizer = GestureRecognizer::new();
1325
1326 recognizer.process(&Event::TouchStart {
1327 id: TouchId::new(1),
1328 position: Point::new(100.0, 200.0),
1329 pressure: 0.5,
1330 });
1331
1332 recognizer.process(&Event::TouchMove {
1334 id: TouchId::new(1),
1335 position: Point::new(150.0, 250.0),
1336 pressure: 0.5,
1337 });
1338
1339 let result = recognizer.process(&Event::TouchEnd {
1341 id: TouchId::new(1),
1342 position: Point::new(150.0, 250.0),
1343 });
1344
1345 assert!(matches!(
1346 result,
1347 Some(Event::GesturePan {
1348 state: GestureState::Ended,
1349 ..
1350 })
1351 ));
1352 }
1353
1354 #[test]
1355 fn test_gesture_recognizer_rotate() {
1356 let mut recognizer = GestureRecognizer::with_config(GestureConfig {
1357 rotate_threshold: 0.01,
1358 pinch_threshold: 1.0, ..Default::default()
1360 });
1361
1362 recognizer.process(&Event::TouchStart {
1364 id: TouchId::new(1),
1365 position: Point::new(100.0, 200.0),
1366 pressure: 0.5,
1367 });
1368
1369 recognizer.process(&Event::TouchStart {
1370 id: TouchId::new(2),
1371 position: Point::new(200.0, 200.0),
1372 pressure: 0.5,
1373 });
1374
1375 recognizer.process(&Event::TouchMove {
1377 id: TouchId::new(1),
1378 position: Point::new(100.0, 150.0),
1379 pressure: 0.5,
1380 });
1381
1382 let result = recognizer.process(&Event::TouchMove {
1383 id: TouchId::new(2),
1384 position: Point::new(200.0, 250.0),
1385 pressure: 0.5,
1386 });
1387
1388 assert!(matches!(result, Some(Event::GestureRotate { .. })));
1389 }
1390
1391 #[test]
1392 fn test_gesture_recognizer_two_finger_end() {
1393 let mut recognizer = GestureRecognizer::with_config(GestureConfig {
1394 pinch_threshold: 0.01,
1395 ..Default::default()
1396 });
1397
1398 recognizer.process(&Event::TouchStart {
1400 id: TouchId::new(1),
1401 position: Point::new(100.0, 200.0),
1402 pressure: 0.5,
1403 });
1404
1405 recognizer.process(&Event::TouchStart {
1406 id: TouchId::new(2),
1407 position: Point::new(200.0, 200.0),
1408 pressure: 0.5,
1409 });
1410
1411 recognizer.process(&Event::TouchMove {
1413 id: TouchId::new(1),
1414 position: Point::new(50.0, 200.0),
1415 pressure: 0.5,
1416 });
1417
1418 recognizer.process(&Event::TouchMove {
1419 id: TouchId::new(2),
1420 position: Point::new(250.0, 200.0),
1421 pressure: 0.5,
1422 });
1423
1424 let result = recognizer.process(&Event::TouchEnd {
1426 id: TouchId::new(1),
1427 position: Point::new(50.0, 200.0),
1428 });
1429
1430 assert!(matches!(
1431 result,
1432 Some(Event::GesturePinch {
1433 state: GestureState::Ended,
1434 ..
1435 })
1436 ));
1437 }
1438
1439 #[test]
1440 fn test_gesture_recognizer_three_touches() {
1441 let mut recognizer = GestureRecognizer::new();
1442
1443 recognizer.process(&Event::TouchStart {
1444 id: TouchId::new(1),
1445 position: Point::new(100.0, 200.0),
1446 pressure: 0.5,
1447 });
1448
1449 recognizer.process(&Event::TouchStart {
1450 id: TouchId::new(2),
1451 position: Point::new(200.0, 200.0),
1452 pressure: 0.5,
1453 });
1454
1455 recognizer.process(&Event::TouchStart {
1456 id: TouchId::new(3),
1457 position: Point::new(300.0, 200.0),
1458 pressure: 0.5,
1459 });
1460
1461 assert_eq!(recognizer.touch_count(), 3);
1462
1463 let result = recognizer.process(&Event::TouchMove {
1465 id: TouchId::new(1),
1466 position: Point::new(150.0, 250.0),
1467 pressure: 0.5,
1468 });
1469
1470 assert!(result.is_none());
1471 }
1472
1473 #[test]
1474 fn test_pointer_recognizer_debug() {
1475 let recognizer = PointerGestureRecognizer::new();
1476 let debug = format!("{recognizer:?}");
1477 assert!(debug.contains("PointerGestureRecognizer"));
1478 }
1479
1480 #[test]
1481 fn test_pointer_recognizer_default() {
1482 let recognizer = PointerGestureRecognizer::default();
1483 assert_eq!(recognizer.pointer_count(), 0);
1484 }
1485
1486 #[test]
1487 fn test_pointer_recognizer_with_config() {
1488 let config = GestureConfig {
1489 pan_threshold: 30.0,
1490 ..Default::default()
1491 };
1492 let recognizer = PointerGestureRecognizer::with_config(config);
1493 assert_eq!(recognizer.config().pan_threshold, 30.0);
1494 }
1495
1496 #[test]
1497 fn test_pointer_recognizer_ignores_non_pointer_events() {
1498 let mut recognizer = PointerGestureRecognizer::new();
1499
1500 let result = recognizer.process(&Event::MouseMove {
1501 position: Point::new(100.0, 200.0),
1502 });
1503
1504 assert!(result.is_none());
1505 assert_eq!(recognizer.pointer_count(), 0);
1506 }
1507
1508 #[test]
1509 fn test_pointer_recognizer_move_unknown_pointer() {
1510 let mut recognizer = PointerGestureRecognizer::new();
1511
1512 let result = recognizer.process(&Event::PointerMove {
1513 pointer_id: PointerId::new(99),
1514 pointer_type: PointerType::Touch,
1515 position: Point::new(150.0, 250.0),
1516 pressure: 0.5,
1517 is_primary: true,
1518 });
1519
1520 assert!(result.is_none());
1521 }
1522
1523 #[test]
1524 fn test_pointer_info_debug() {
1525 let info = PointerInfo {
1526 id: PointerId::new(1),
1527 pointer_type: PointerType::Touch,
1528 start_position: Point::new(100.0, 200.0),
1529 current_position: Point::new(100.0, 200.0),
1530 start_time: Instant::now(),
1531 is_primary: true,
1532 pressure: 0.5,
1533 };
1534 let debug = format!("{info:?}");
1535 assert!(debug.contains("PointerInfo"));
1536 }
1537
1538 #[test]
1539 fn test_pointer_recognizer_first_non_primary_becomes_primary() {
1540 let mut recognizer = PointerGestureRecognizer::new();
1541
1542 recognizer.process(&Event::PointerDown {
1544 pointer_id: PointerId::new(1),
1545 pointer_type: PointerType::Touch,
1546 position: Point::new(100.0, 200.0),
1547 pressure: 0.5,
1548 is_primary: false,
1549 button: None,
1550 });
1551
1552 assert!(recognizer.primary().is_some());
1554 }
1555
1556 #[test]
1557 fn test_gesture_recognizer_below_pan_threshold() {
1558 let mut recognizer = GestureRecognizer::new();
1559
1560 recognizer.process(&Event::TouchStart {
1561 id: TouchId::new(1),
1562 position: Point::new(100.0, 200.0),
1563 pressure: 0.5,
1564 });
1565
1566 let result = recognizer.process(&Event::TouchMove {
1568 id: TouchId::new(1),
1569 position: Point::new(102.0, 202.0), pressure: 0.5,
1571 });
1572
1573 assert!(result.is_none());
1574 }
1575
1576 #[test]
1577 fn test_gesture_recognizer_below_pinch_threshold() {
1578 let mut recognizer = GestureRecognizer::new();
1579
1580 recognizer.process(&Event::TouchStart {
1581 id: TouchId::new(1),
1582 position: Point::new(100.0, 200.0),
1583 pressure: 0.5,
1584 });
1585
1586 recognizer.process(&Event::TouchStart {
1587 id: TouchId::new(2),
1588 position: Point::new(200.0, 200.0),
1589 pressure: 0.5,
1590 });
1591
1592 let result = recognizer.process(&Event::TouchMove {
1594 id: TouchId::new(1),
1595 position: Point::new(99.0, 200.0),
1596 pressure: 0.5,
1597 });
1598
1599 assert!(result.is_none());
1600 }
1601}