use std::time::{Duration, Instant};
use winit::event::TouchPhase;
use crate::axis_lock::GAP;
pub(crate) const GLIDE_AFTER: Duration = Duration::from_millis(100);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum Finger {
#[default]
Up,
Down,
Lifted(Instant),
Glide,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Kind {
Line,
Pixel,
}
#[derive(Debug, Default)]
pub(crate) struct Gesture {
last: Option<(Kind, Instant)>,
owed: bool,
touch: bool,
finger: Finger,
}
impl Gesture {
pub(crate) fn begins(&mut self, kind: Kind, started: bool, now: Instant) -> bool {
self.note(kind, started, now);
std::mem::take(&mut self.owed)
}
pub(crate) fn note(&mut self, kind: Kind, started: bool, now: Instant) {
let begins = match self.last {
Some((k, t)) => k != kind || now.saturating_duration_since(t) > GAP,
None => true,
};
self.last = Some((kind, now));
self.owed |= begins;
self.touch = started || (self.touch && !begins);
}
pub(crate) fn phase(&mut self, phase: TouchPhase, now: Instant) -> bool {
let (finger, down) = match (phase, self.finger) {
(TouchPhase::Started, Finger::Lifted(t))
if now.saturating_duration_since(t) <= GLIDE_AFTER =>
{
(Finger::Glide, false)
}
(TouchPhase::Started, Finger::Down) => (Finger::Down, false),
(TouchPhase::Started, _) => (Finger::Down, true),
(TouchPhase::Ended | TouchPhase::Cancelled, Finger::Down) => {
(Finger::Lifted(now), false)
}
(TouchPhase::Ended | TouchPhase::Cancelled, Finger::Glide) => (Finger::Up, false),
(_, f) => (f, false),
};
self.finger = finger;
if down {
self.last = None;
}
down
}
pub(crate) fn pointer_moved(&mut self) {
match self.last {
Some((Kind::Line, _)) => self.last = None,
Some((Kind::Pixel, _)) if !self.touch => self.last = None,
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
const FRAME: Duration = Duration::from_millis(8);
#[test]
fn a_swipe_and_its_momentum_are_one_gesture() {
let mut g = Gesture::default();
let mut t = Instant::now();
assert!(g.begins(Kind::Pixel, true, t), "a finger down");
for i in 0..50 {
t += FRAME;
assert!(!g.begins(Kind::Pixel, i == 20, t));
}
g.pointer_moved();
t += FRAME;
assert!(!g.begins(Kind::Pixel, false, t));
t += GAP + FRAME;
assert!(g.begins(Kind::Pixel, true, t), "a pause begins the next");
}
#[test]
fn a_spun_wheel_is_one_gesture_until_the_pointer_moves() {
let mut g = Gesture::default();
let mut t = Instant::now();
assert!(g.begins(Kind::Line, false, t));
t += Duration::from_millis(40);
assert!(!g.begins(Kind::Line, false, t));
g.pointer_moved();
t += Duration::from_millis(40);
assert!(g.begins(Kind::Line, false, t), "the hand aimed anew");
t += Duration::from_millis(40);
assert!(g.begins(Kind::Pixel, true, t), "another device");
t += Duration::from_millis(40);
assert!(g.begins(Kind::Line, false, t));
t += GAP + FRAME;
assert!(g.begins(Kind::Line, false, t));
}
fn event(g: &mut Gesture, phase: TouchPhase, t: Instant) -> bool {
g.phase(phase, t);
g.begins(Kind::Pixel, phase == TouchPhase::Started, t)
}
#[test]
fn a_finger_down_mid_glide_begins_a_gesture() {
use TouchPhase::*;
let mut g = Gesture::default();
let mut t = Instant::now();
assert!(event(&mut g, Started, t), "a finger down");
for _ in 0..10 {
t += FRAME;
assert!(!event(&mut g, Moved, t));
}
t += FRAME;
assert!(!event(&mut g, Ended, t), "the lift");
t += FRAME;
assert!(!event(&mut g, Started, t), "the glide starting");
for _ in 0..10 {
t += FRAME;
assert!(!event(&mut g, Moved, t));
}
t += FRAME;
assert!(!event(&mut g, Ended, t));
t += FRAME;
assert!(event(&mut g, Started, t), "a new swipe, no pause");
t += FRAME;
assert!(!event(&mut g, Started, t), "`MayBegin` then `Began`");
assert!(!event(&mut g, Moved, t + FRAME));
}
#[test]
fn a_finger_down_on_an_unended_glide_begins_a_gesture() {
use TouchPhase::*;
let mut g = Gesture::default();
let mut t = Instant::now();
event(&mut g, Started, t);
t += FRAME;
event(&mut g, Ended, t);
t += FRAME;
assert!(!event(&mut g, Started, t));
t += FRAME;
assert!(!event(&mut g, Moved, t));
t += FRAME;
assert!(event(&mut g, Started, t));
}
#[test]
fn a_finger_down_after_a_glideless_lift_begins_a_gesture() {
use TouchPhase::*;
let mut g = Gesture::default();
let mut t = Instant::now();
event(&mut g, Started, t);
t += FRAME;
event(&mut g, Ended, t);
t += GLIDE_AFTER + FRAME;
assert!(event(&mut g, Started, t));
}
#[test]
fn an_undispatched_first_event_leaves_the_beginning_owed() {
let mut g = Gesture::default();
let mut t = Instant::now();
assert!(g.begins(Kind::Pixel, true, t));
t += GAP + FRAME;
g.note(Kind::Pixel, true, t);
t += FRAME;
assert!(g.begins(Kind::Pixel, false, t));
t += FRAME;
assert!(!g.begins(Kind::Pixel, false, t));
}
#[test]
fn pixels_no_touch_began_end_when_the_pointer_moves() {
let mut g = Gesture::default();
let mut t = Instant::now();
assert!(g.begins(Kind::Pixel, false, t));
t += FRAME;
assert!(!g.begins(Kind::Pixel, false, t), "spun on: one gesture");
g.pointer_moved();
t += FRAME;
assert!(g.begins(Kind::Pixel, false, t), "the hand aimed anew");
t += FRAME;
assert!(!g.begins(Kind::Pixel, true, t));
g.pointer_moved();
t += FRAME;
assert!(!g.begins(Kind::Pixel, false, t), "a swipe keeps its target");
t += GAP + FRAME;
assert!(g.begins(Kind::Pixel, false, t));
g.pointer_moved();
t += FRAME;
assert!(g.begins(Kind::Pixel, false, t));
}
}