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