presentar_core/
gesture.rs

1#![allow(
2    clippy::unwrap_used,
3    clippy::disallowed_methods,
4    clippy::many_single_char_names
5)]
6//! Gesture recognition from touch/pointer events.
7//!
8//! This module provides gesture recognizers that process raw touch and pointer
9//! events and emit high-level gesture events like pinch, rotate, pan, tap, etc.
10
11use crate::event::{Event, GestureState, PointerId, PointerType, TouchId};
12use crate::geometry::Point;
13use std::collections::HashMap;
14use std::time::{Duration, Instant};
15
16/// Configuration for gesture recognition.
17#[derive(Debug, Clone)]
18pub struct GestureConfig {
19    /// Minimum distance to start a pan gesture (in pixels).
20    pub pan_threshold: f32,
21    /// Maximum time for a tap (in milliseconds).
22    pub tap_timeout_ms: u64,
23    /// Maximum movement for a tap to still be valid.
24    pub tap_slop: f32,
25    /// Time required for a long press (in milliseconds).
26    pub long_press_ms: u64,
27    /// Maximum time between taps for a double tap.
28    pub double_tap_ms: u64,
29    /// Minimum scale change to start pinch.
30    pub pinch_threshold: f32,
31    /// Minimum rotation to start rotate gesture (radians).
32    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/// Active touch point being tracked.
50#[derive(Debug, Clone)]
51pub struct TouchPoint {
52    /// Touch ID.
53    pub id: TouchId,
54    /// Starting position.
55    pub start_position: Point,
56    /// Current position.
57    pub current_position: Point,
58    /// Previous position.
59    pub previous_position: Point,
60    /// When the touch started.
61    pub start_time: Instant,
62    /// Pressure (0.0-1.0).
63    pub pressure: f32,
64}
65
66impl TouchPoint {
67    /// Create a new touch point.
68    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    /// Update the touch point position.
81    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    /// Get the total distance moved from start.
88    pub fn total_distance(&self) -> f32 {
89        self.start_position.distance(&self.current_position)
90    }
91
92    /// Get the delta from previous position.
93    pub fn delta(&self) -> Point {
94        self.current_position - self.previous_position
95    }
96
97    /// Get duration since touch started.
98    pub fn duration(&self) -> Duration {
99        self.start_time.elapsed()
100    }
101}
102
103/// State of a recognized gesture.
104#[derive(Debug, Clone, Default)]
105pub enum RecognizedGesture {
106    /// No gesture recognized yet.
107    #[default]
108    None,
109    /// Tap gesture (with tap count).
110    Tap { position: Point, count: u8 },
111    /// Long press gesture.
112    LongPress { position: Point },
113    /// Pan/drag gesture.
114    Pan {
115        delta: Point,
116        velocity: Point,
117        state: GestureState,
118    },
119    /// Pinch gesture.
120    Pinch {
121        scale: f32,
122        center: Point,
123        state: GestureState,
124    },
125    /// Rotate gesture.
126    Rotate {
127        angle: f32,
128        center: Point,
129        state: GestureState,
130    },
131}
132
133/// Tracks last tap for double-tap detection.
134#[derive(Debug, Clone)]
135struct LastTap {
136    position: Point,
137    time: Instant,
138    count: u8,
139}
140
141/// Multi-touch gesture recognizer.
142#[derive(Debug)]
143pub struct GestureRecognizer {
144    /// Configuration.
145    config: GestureConfig,
146    /// Active touch points.
147    touches: HashMap<TouchId, TouchPoint>,
148    /// Currently recognized gesture.
149    current_gesture: RecognizedGesture,
150    /// Initial distance between two fingers (for pinch).
151    initial_pinch_distance: Option<f32>,
152    /// Initial angle between two fingers (for rotate).
153    initial_rotation_angle: Option<f32>,
154    /// Last tap info for double-tap detection.
155    last_tap: Option<LastTap>,
156    /// Velocity tracking for pan.
157    velocity_samples: Vec<(Point, Instant)>,
158}
159
160impl GestureRecognizer {
161    /// Create a new gesture recognizer with default config.
162    pub fn new() -> Self {
163        Self::with_config(GestureConfig::default())
164    }
165
166    /// Create a new gesture recognizer with custom config.
167    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    /// Get the current gesture configuration.
180    pub fn config(&self) -> &GestureConfig {
181        &self.config
182    }
183
184    /// Get the number of active touches.
185    pub fn touch_count(&self) -> usize {
186        self.touches.len()
187    }
188
189    /// Get an active touch by ID.
190    pub fn touch(&self, id: TouchId) -> Option<&TouchPoint> {
191        self.touches.get(&id)
192    }
193
194    /// Process an event and return any recognized gesture.
195    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        // Reset gesture state when new touch starts
218        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        // Check for tap if this was the last touch
243        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            // End any active gesture
254            self.end_active_gesture()
255        } else if self.touches.len() == 1 {
256            // Went from 2 to 1 finger - end pinch/rotate
257            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        // Determine if this is more pinch or rotate
332        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            // Prefer the larger change
338            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        // Keep only recent samples (last 100ms)
455        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    /// Check if a long press has occurred.
481    /// Call this periodically (e.g., from a timer) to detect long press.
482    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    /// Reset the recognizer state.
506    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/// Pointer gesture recognizer that unifies mouse, touch, and pen input.
522#[derive(Debug)]
523pub struct PointerGestureRecognizer {
524    /// Active pointers.
525    pointers: HashMap<PointerId, PointerInfo>,
526    /// Configuration.
527    config: GestureConfig,
528    /// Primary pointer ID.
529    primary_pointer: Option<PointerId>,
530}
531
532/// Information about an active pointer.
533#[derive(Debug, Clone)]
534pub struct PointerInfo {
535    /// Pointer ID.
536    pub id: PointerId,
537    /// Pointer type.
538    pub pointer_type: PointerType,
539    /// Starting position.
540    pub start_position: Point,
541    /// Current position.
542    pub current_position: Point,
543    /// Start time.
544    pub start_time: Instant,
545    /// Is primary pointer.
546    pub is_primary: bool,
547    /// Pressure (0.0-1.0).
548    pub pressure: f32,
549}
550
551impl PointerGestureRecognizer {
552    /// Create a new pointer gesture recognizer.
553    pub fn new() -> Self {
554        Self::with_config(GestureConfig::default())
555    }
556
557    /// Create with custom config.
558    pub fn with_config(config: GestureConfig) -> Self {
559        Self {
560            pointers: HashMap::new(),
561            config,
562            primary_pointer: None,
563        }
564    }
565
566    /// Get the gesture configuration.
567    pub fn config(&self) -> &GestureConfig {
568        &self.config
569    }
570
571    /// Get the number of active pointers.
572    pub fn pointer_count(&self) -> usize {
573        self.pointers.len()
574    }
575
576    /// Get the primary pointer.
577    pub fn primary(&self) -> Option<&PointerInfo> {
578        self.primary_pointer.and_then(|id| self.pointers.get(&id))
579    }
580
581    /// Process a pointer event.
582    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    /// Reset the recognizer.
633    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    // GestureConfig tests
650    #[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    // TouchPoint tests
678    #[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    // GestureRecognizer tests
717    #[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        // Start touch
755        recognizer.process(&Event::TouchStart {
756            id: TouchId::new(1),
757            position: Point::new(100.0, 200.0),
758            pressure: 0.5,
759        });
760
761        // Move touch
762        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        // End immediately (tap)
801        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        // Move beyond pan threshold
820        let result = recognizer.process(&Event::TouchMove {
821            id: TouchId::new(1),
822            position: Point::new(150.0, 250.0), // 50px moved
823            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        // First move - starts pan
846        recognizer.process(&Event::TouchMove {
847            id: TouchId::new(1),
848            position: Point::new(150.0, 250.0),
849            pressure: 0.5,
850        });
851
852        // Second move - continues pan
853        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        // Start with two fingers 100px apart
895        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        // Move fingers apart to 200px
908        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    // PointerGestureRecognizer tests
969    #[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        // First pointer (primary)
1052        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        // Second pointer (not primary)
1062        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        // Remove primary
1100        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        // Primary should now be the remaining pointer
1110        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    // RecognizedGesture tests
1153    #[test]
1154    fn test_recognized_gesture_default() {
1155        let gesture = RecognizedGesture::default();
1156        assert!(matches!(gesture, RecognizedGesture::None));
1157    }
1158
1159    // PointerInfo tests
1160    #[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    // =========================================================================
1179    // Additional Edge Case Tests
1180    // =========================================================================
1181
1182    #[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); // Should be very short
1219    }
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        // Move without starting touch
1295        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        // End without starting touch
1309        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        // Move to start pan
1328        recognizer.process(&Event::TouchMove {
1329            id: TouchId::new(1),
1330            position: Point::new(150.0, 250.0),
1331            pressure: 0.5,
1332        });
1333
1334        // End touch
1335        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, // High threshold to prefer rotation
1354            ..Default::default()
1355        });
1356
1357        // Start with two fingers
1358        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        // Rotate (move one finger up, other down, same distance apart)
1371        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        // Start pinch
1394        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        // Move to trigger pinch
1407        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        // End one finger
1420        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        // Move with 3 touches should return None (not handled)
1459        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        // First pointer is not marked as primary, but should become primary
1538        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        // Since no primary existed, this should be set as primary
1548        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        // Small move below threshold
1562        let result = recognizer.process(&Event::TouchMove {
1563            id: TouchId::new(1),
1564            position: Point::new(102.0, 202.0), // Only ~2.8px moved
1565            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        // Tiny movement that doesn't trigger pinch/rotate
1588        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}