use crate::core::Point;
use crate::event::Event;
mod pinch;
mod press;
mod rotate;
mod swipe;
mod tap;
pub use pinch::{PinchGesture, PinchTouch};
pub use press::{LongPressDragGesture, LongPressGesture, PanGesture};
pub use rotate::RotateGesture;
pub use swipe::{FlingGesture, SwipeGesture, TwoFingerSwipeGesture};
pub use tap::{DoubleTapGesture, TapGesture, TwoFingerTapGesture};
const DOUBLE_TAP_TIMEOUT_MS: u64 = 400;
const LONG_PRESS_MIN_MS: u64 = 500;
const SWIPE_MIN_VELOCITY: f32 = 0.5;
const MAX_STATIONARY_DISTANCE: f32 = 15.0;
const LONG_PRESS_MAX_MOVE: f32 = 10.0;
const SWIPE_MIN_DISTANCE: f32 = 30.0;
pub trait GestureRecognizer: std::fmt::Debug + Send {
fn process(&mut self, event: &Event, now_ms: u64) -> Option<Event>;
fn reset(&mut self);
}
#[derive(Debug)]
pub struct GestureEngine {
recognizers: Vec<Box<dyn GestureRecognizer>>,
last_timestamp_ms: u64,
}
impl GestureEngine {
pub fn new() -> Self {
let recognizers: Vec<Box<dyn GestureRecognizer>> = vec![
Box::new(TapGesture::new()),
Box::new(DoubleTapGesture::new()),
Box::new(LongPressGesture::new()),
Box::new(SwipeGesture::new()),
Box::new(PanGesture::new()),
Box::new(LongPressDragGesture::new()),
Box::new(FlingGesture::new()),
Box::new(TwoFingerTapGesture::new()),
Box::new(TwoFingerSwipeGesture::new()),
Box::new(PinchGesture::new()),
Box::new(RotateGesture::new()),
];
Self { recognizers, last_timestamp_ms: 0 }
}
pub fn with_recognizers(recognizers: Vec<Box<dyn GestureRecognizer>>) -> Self {
Self { recognizers, last_timestamp_ms: 0 }
}
pub fn process(&mut self, event: &Event, now_ms: u64) -> Option<Event> {
self.last_timestamp_ms = now_ms;
for rec in self.recognizers.iter_mut() {
if let Some(gesture) = rec.process(event, now_ms) {
return Some(gesture);
}
}
None
}
pub fn reset_all(&mut self) {
for rec in self.recognizers.iter_mut() {
rec.reset();
}
}
pub fn last_timestamp(&self) -> u64 {
self.last_timestamp_ms
}
}
impl Default for GestureEngine {
fn default() -> Self {
Self::new()
}
}
pub(crate) fn distance(a: Point, b: Point) -> f32 {
let dx = (a.x - b.x) as f32;
let dy = (a.y - b.y) as f32;
(dx * dx + dy * dy).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tap_recognises_quick_touch_end() {
let mut rec = TapGesture::new();
let t0 = 1000;
let t1 = 1100;
assert!(rec.process(&Event::touch_begin(50, 50, 0), t0).is_none());
let result = rec.process(&Event::touch_end(51, 51, 0), t1);
assert!(matches!(result, Some(Event::Tap { pos }) if pos.x == 51 && pos.y == 51));
}
#[test]
fn tap_ignores_long_hold() {
let mut rec = TapGesture::new();
rec.process(&Event::touch_begin(50, 50, 0), 0);
let result = rec.process(&Event::touch_end(50, 50, 0), 400);
assert!(result.is_none());
}
#[test]
fn tap_cancelled_by_excessive_movement() {
let mut rec = TapGesture::new();
rec.process(&Event::touch_begin(50, 50, 0), 0);
rec.process(&Event::touch_move(100, 100, 0), 100);
let result = rec.process(&Event::touch_end(100, 100, 0), 150);
assert!(result.is_none());
}
#[test]
fn tap_requires_matching_touch_id() {
let mut rec = TapGesture::new();
rec.process(&Event::touch_begin(10, 10, 5), 0);
let result = rec.process(&Event::touch_end(10, 10, 7), 100);
assert!(result.is_none());
}
#[test]
fn double_tap_recognises_two_quick_taps() {
let mut rec = DoubleTapGesture::new();
let t0 = 1000;
rec.process(&Event::tap(50, 50), t0);
let result = rec.process(&Event::tap(52, 52), t0 + 200);
assert!(matches!(result, Some(Event::DoubleTap { .. })));
}
#[test]
fn double_tap_too_slow_second_tap() {
let mut rec = DoubleTapGesture::new();
rec.process(&Event::tap(50, 50), 0);
let result = rec.process(&Event::tap(55, 55), 500);
assert!(result.is_none());
let result2 = rec.process(&Event::tap(55, 55), 700); assert!(matches!(result2, Some(Event::DoubleTap { .. })));
}
#[test]
fn long_press_fires_after_threshold() {
let mut rec = LongPressGesture::new();
rec.process(&Event::touch_begin(30, 30, 0), 0);
let result = rec.process(&Event::Timer { id: 0 }, 500);
assert!(matches!(result, Some(Event::LongPress { pos }) if pos.x == 30 && pos.y == 30));
}
#[test]
fn long_press_does_not_fire_before_threshold() {
let mut rec = LongPressGesture::new();
rec.process(&Event::touch_begin(30, 30, 0), 0);
let result = rec.process(&Event::Timer { id: 0 }, 300);
assert!(result.is_none());
}
#[test]
fn long_press_cancelled_on_early_release() {
let mut rec = LongPressGesture::new();
rec.process(&Event::touch_begin(30, 30, 0), 0);
rec.process(&Event::touch_end(30, 30, 0), 100);
let result = rec.process(&Event::Timer { id: 0 }, 500);
assert!(result.is_none());
}
#[test]
fn long_press_cancelled_by_excessive_move() {
let mut rec = LongPressGesture::new();
rec.process(&Event::touch_begin(30, 30, 0), 0);
rec.process(&Event::touch_move(50, 50, 0), 100); let result = rec.process(&Event::Timer { id: 0 }, 500);
assert!(result.is_none());
}
#[test]
fn swipe_recognises_rapid_motion() {
let mut rec = SwipeGesture::new();
rec.process(&Event::touch_begin(0, 0, 0), 0);
rec.process(&Event::touch_move(20, 0, 0), 20);
rec.process(&Event::touch_move(40, 0, 0), 40);
let result = rec.process(&Event::touch_end(60, 0, 0), 60);
assert!(matches!(result, Some(Event::Swipe { .. })));
}
#[test]
fn swipe_requires_minimum_distance() {
let mut rec = SwipeGesture::new();
rec.process(&Event::touch_begin(0, 0, 0), 0);
let result = rec.process(&Event::touch_end(10, 0, 0), 50);
assert!(result.is_none());
}
#[test]
fn swipe_requires_minimum_velocity() {
let mut rec = SwipeGesture::new();
rec.process(&Event::touch_begin(0, 0, 0), 0);
let result = rec.process(&Event::touch_end(40, 0, 0), 500);
assert!(result.is_none());
}
#[test]
fn pinch_recognises_two_finger_zoom() {
let mut rec = PinchGesture::new();
rec.process(&Event::touch_begin(0, 100, 0), 0);
rec.process(&Event::touch_begin(100, 100, 1), 0);
let result = rec.process(&Event::touch_move(0, 100, 0), 10);
assert!(result.is_none()); let result = rec.process(&Event::touch_move(200, 100, 1), 10);
assert!(matches!(result, Some(Event::Pinch { scale }) if (scale - 2.0).abs() < 0.001));
}
#[test]
fn pinch_resets_when_touch_lost() {
let mut rec = PinchGesture::new();
rec.process(&Event::touch_begin(0, 0, 0), 0);
rec.process(&Event::touch_begin(100, 0, 1), 0);
rec.process(&Event::touch_end(100, 0, 1), 50);
let result = rec.process(&Event::touch_move(10, 0, 0), 60);
assert!(result.is_none());
}
#[test]
fn rotate_recognises_two_finger_turn() {
let mut rec = RotateGesture::new();
rec.process(&Event::touch_begin(0, 100, 0), 0);
rec.process(&Event::touch_begin(100, 100, 1), 0);
rec.process(&Event::touch_move(0, 0, 0), 10);
rec.process(&Event::touch_move(100, 0, 1), 10);
let result = rec.process(&Event::touch_move(50, 50, 0), 20);
assert!(matches!(result, Some(Event::Rotate { .. })));
}
#[test]
fn engine_processes_chain() {
let mut engine = GestureEngine::new();
let r1 = engine.process(&Event::touch_begin(10, 10, 0), 0);
assert!(r1.is_none());
let r2 = engine.process(&Event::touch_end(10, 10, 0), 100);
assert!(matches!(r2, Some(Event::Tap { .. })));
}
#[test]
fn engine_reset_clears_all() {
let mut engine = GestureEngine::new();
engine.process(&Event::touch_begin(10, 10, 0), 0);
engine.reset_all();
let r1 = engine.process(&Event::touch_begin(20, 20, 1), 200);
assert!(r1.is_none());
let r2 = engine.process(&Event::touch_end(20, 20, 1), 250);
assert!(matches!(r2, Some(Event::Tap { .. })));
}
#[test]
fn engine_default_is_new() {
let engine = GestureEngine::default();
assert_eq!(engine.last_timestamp(), 0);
}
}