use super::types::Point;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GesturePhase {
Idle,
Pressed {
origin: Point,
pointer_id: i32,
},
Dragging {
origin: Point,
pointer_id: i32,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GestureEvent {
Down { at: Point, pointer_id: i32 },
Move { at: Point, pointer_id: i32 },
Up { at: Point, pointer_id: i32 },
Cancel,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GestureEffect {
None,
Begin { origin: Point, at: Point },
Track { at: Point },
Drop { at: Point },
Tap,
Abort,
}
pub fn transition(
phase: GesturePhase,
event: GestureEvent,
threshold: f64,
) -> (GesturePhase, GestureEffect) {
use GestureEffect as Fx;
use GesturePhase as P;
match (phase, event) {
(P::Idle, GestureEvent::Down { at, pointer_id }) => (
P::Pressed {
origin: at,
pointer_id,
},
Fx::None,
),
(p @ (P::Pressed { .. } | P::Dragging { .. }), GestureEvent::Down { .. }) => (p, Fx::None),
(
P::Pressed { origin, pointer_id },
GestureEvent::Move {
at,
pointer_id: pid,
},
) if pid == pointer_id => {
let d = at - origin;
if (d.x * d.x + d.y * d.y).sqrt() >= threshold {
(P::Dragging { origin, pointer_id }, Fx::Begin { origin, at })
} else {
(P::Pressed { origin, pointer_id }, Fx::None)
}
}
(
P::Pressed { pointer_id, .. },
GestureEvent::Up {
pointer_id: pid, ..
},
) if pid == pointer_id => (P::Idle, Fx::Tap),
(
P::Dragging { origin, pointer_id },
GestureEvent::Move {
at,
pointer_id: pid,
},
) if pid == pointer_id => (P::Dragging { origin, pointer_id }, Fx::Track { at }),
(
P::Dragging { pointer_id, .. },
GestureEvent::Up {
at,
pointer_id: pid,
},
) if pid == pointer_id => (P::Idle, Fx::Drop { at }),
(P::Dragging { .. }, GestureEvent::Cancel) => (P::Idle, Fx::Abort),
(P::Pressed { .. }, GestureEvent::Cancel) => (P::Idle, Fx::None),
(P::Idle, GestureEvent::Cancel) => (P::Idle, Fx::None),
(p, _) => (p, Fx::None),
}
}
#[cfg(test)]
mod tests {
use super::*;
const T: f64 = 8.0;
fn down(x: f64, y: f64, pid: i32) -> GestureEvent {
GestureEvent::Down {
at: Point::new(x, y),
pointer_id: pid,
}
}
fn mv(x: f64, y: f64, pid: i32) -> GestureEvent {
GestureEvent::Move {
at: Point::new(x, y),
pointer_id: pid,
}
}
fn up(x: f64, y: f64, pid: i32) -> GestureEvent {
GestureEvent::Up {
at: Point::new(x, y),
pointer_id: pid,
}
}
#[test]
fn full_drag_lifecycle() {
let (p, fx) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
assert_eq!(fx, GestureEffect::None);
let (p, fx) = transition(p, mv(3.0, 3.0, 1), T);
assert!(matches!(p, GesturePhase::Pressed { .. }));
assert_eq!(fx, GestureEffect::None);
let (p, fx) = transition(p, mv(10.0, 0.0, 1), T);
assert_eq!(
fx,
GestureEffect::Begin {
origin: Point::new(0.0, 0.0),
at: Point::new(10.0, 0.0)
}
);
let (p, fx) = transition(p, mv(20.0, 5.0, 1), T);
assert_eq!(
fx,
GestureEffect::Track {
at: Point::new(20.0, 5.0)
}
);
let (p, fx) = transition(p, up(20.0, 5.0, 1), T);
assert_eq!(p, GesturePhase::Idle);
assert_eq!(
fx,
GestureEffect::Drop {
at: Point::new(20.0, 5.0)
}
);
}
#[test]
fn release_before_threshold_is_a_tap() {
let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
let (p, fx) = transition(p, up(2.0, 2.0, 1), T);
assert_eq!(p, GesturePhase::Idle);
assert_eq!(fx, GestureEffect::Tap);
}
#[test]
fn foreign_pointer_ids_are_ignored() {
let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
let (p, fx) = transition(p, mv(100.0, 100.0, 2), T);
assert!(matches!(p, GesturePhase::Pressed { .. }));
assert_eq!(fx, GestureEffect::None);
let (p, fx) = transition(p, up(100.0, 100.0, 2), T);
assert!(matches!(p, GesturePhase::Pressed { .. }));
assert_eq!(fx, GestureEffect::None);
let (p, fx) = transition(p, down(50.0, 50.0, 2), T);
assert!(matches!(p, GesturePhase::Pressed { pointer_id: 1, .. }));
assert_eq!(fx, GestureEffect::None);
}
#[test]
fn cancel_paths() {
let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
let (p, _) = transition(p, mv(20.0, 0.0, 1), T);
let (p, fx) = transition(p, GestureEvent::Cancel, T);
assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::Abort));
let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
let (p, fx) = transition(p, GestureEvent::Cancel, T);
assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
}
#[test]
fn stray_events_while_idle_are_inert() {
for ev in [mv(9.0, 9.0, 1), up(9.0, 9.0, 1)] {
let (p, fx) = transition(GesturePhase::Idle, ev, T);
assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
}
}
#[test]
fn exact_threshold_promotes() {
let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
let (_, fx) = transition(p, mv(8.0, 0.0, 1), T);
assert!(matches!(fx, GestureEffect::Begin { .. }));
}
}