use crate::core::Point;
use crate::event::Event;
use super::{
DoubleTapGesture, FlingGesture, LongPressDragGesture, LongPressGesture, PanGesture,
PinchGesture, RotateGesture, SwipeGesture, TapGesture, TwoFingerSwipeGesture,
TwoFingerTapGesture,
};
pub(crate) const DOUBLE_TAP_TIMEOUT_MS: u64 = 400;
pub(crate) const LONG_PRESS_MIN_MS: u64 = 500;
pub(crate) const SWIPE_MIN_VELOCITY: f32 = 500.0;
pub(crate) const MAX_STATIONARY_DISTANCE: f32 = 15.0;
pub(crate) const LONG_PRESS_MAX_MOVE: f32 = 10.0;
pub(crate) const SWIPE_MIN_DISTANCE: f32 = 30.0;
pub(crate) const TAP_TIMEOUT_MS: u64 = 300;
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(LongPressDragGesture::new()),
Box::new(PanGesture::new()),
Box::new(SwipeGesture::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 process(&mut self, event: &Event, now_ms: u64) -> Option<Event> {
self.last_timestamp_ms = now_ms;
for r in &mut self.recognizers {
if let Some(gesture_event) = r.process(event, now_ms) {
return Some(gesture_event);
}
}
None
}
pub fn reset_all(&mut self) {
for r in &mut self.recognizers {
r.reset();
}
}
}
crate::impl_default_via_new!(GestureEngine);
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::*;
use crate::event::Event;
#[test]
fn gesture_engine_new() {
let engine = GestureEngine::new();
assert_eq!(engine.recognizers.len(), 11);
}
#[test]
fn gesture_engine_process_none_for_unrelated_event() {
let mut engine = GestureEngine::new();
let result =
engine.process(&Event::MousePress { pos: crate::core::Point::new(0, 0), button: 0 }, 0);
assert!(result.is_none());
}
#[test]
fn gesture_engine_reset_all_clears_state() {
let mut engine = GestureEngine::new();
engine.reset_all();
assert_eq!(engine.recognizers.len(), 11);
}
#[test]
fn every_recogniser_reports_velocity_in_pixels_per_second() {
const START: i32 = 0;
const END: i32 = 100; const ELAPSED_MS: u64 = 100; const TOUCH: u64 = 1;
const EXPECTED: std::ops::RangeInclusive<f32> = 900.0..=1100.0;
let mut swipe = SwipeGesture::new();
assert!(swipe
.process(&Event::TouchBegin { pos: Point::new(START, START), touch_id: TOUCH }, 0)
.is_none());
let swipe_event = swipe
.process(&Event::TouchEnd { pos: Point::new(END, START), touch_id: TOUCH }, ELAPSED_MS);
let swipe_velocity = match swipe_event {
Some(Event::Swipe { velocity, .. }) => velocity,
other => panic!("expected Event::Swipe over 100px in 100ms, got {other:?}"),
};
let mut fling = FlingGesture::new();
assert!(fling
.process(&Event::TouchBegin { pos: Point::new(START, START), touch_id: TOUCH }, 0)
.is_none());
for step in 1..=4u64 {
fling.process(
&Event::TouchMove {
pos: Point::new(START + (step as i32) * (END - START) / 4, START),
touch_id: TOUCH,
},
ELAPSED_MS / 4 * step,
);
}
let fling_event = fling
.process(&Event::TouchEnd { pos: Point::new(END, START), touch_id: TOUCH }, ELAPSED_MS);
let fling_velocity = match fling_event {
Some(Event::Fling { velocity, .. }) => velocity,
other => panic!("expected Event::Fling over 100px in 100ms, got {other:?}"),
};
for (name, velocity) in [("swipe", swipe_velocity), ("fling", fling_velocity.x as f32)] {
assert!(
EXPECTED.contains(&velocity),
"{name} velocity must be ~1000 px/s for 100px in 100ms, got {velocity}. \
A value near 1 means px/ms leaked through, which contradicts the \
unit documented on Event::Swipe / Event::Fling"
);
}
}
}