1use super::types::Point;
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub enum GesturePhase {
30 Idle,
32 Pressed {
35 origin: Point,
37 pointer_id: i32,
39 },
40 Dragging {
42 origin: Point,
44 pointer_id: i32,
46 },
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51pub enum GestureEvent {
52 Down { at: Point, pointer_id: i32 },
54 Move { at: Point, pointer_id: i32 },
56 Up { at: Point, pointer_id: i32 },
58 Cancel,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq)]
64pub enum GestureEffect {
65 None,
68 Begin { origin: Point, at: Point },
72 Track { at: Point },
74 Drop { at: Point },
76 Tap,
78 Abort,
80}
81
82pub fn transition(
87 phase: GesturePhase,
88 event: GestureEvent,
89 threshold: f64,
90) -> (GesturePhase, GestureEffect) {
91 use GestureEffect as Fx;
92 use GesturePhase as P;
93
94 match (phase, event) {
95 (P::Idle, GestureEvent::Down { at, pointer_id }) => (
97 P::Pressed {
98 origin: at,
99 pointer_id,
100 },
101 Fx::None,
102 ),
103 (p @ (P::Pressed { .. } | P::Dragging { .. }), GestureEvent::Down { .. }) => (p, Fx::None),
105
106 (
108 P::Pressed { origin, pointer_id },
109 GestureEvent::Move {
110 at,
111 pointer_id: pid,
112 },
113 ) if pid == pointer_id => {
114 let d = at - origin;
115 if (d.x * d.x + d.y * d.y).sqrt() >= threshold {
116 (P::Dragging { origin, pointer_id }, Fx::Begin { origin, at })
117 } else {
118 (P::Pressed { origin, pointer_id }, Fx::None)
119 }
120 }
121 (
122 P::Pressed { pointer_id, .. },
123 GestureEvent::Up {
124 pointer_id: pid, ..
125 },
126 ) if pid == pointer_id => (P::Idle, Fx::Tap),
127
128 (
130 P::Dragging { origin, pointer_id },
131 GestureEvent::Move {
132 at,
133 pointer_id: pid,
134 },
135 ) if pid == pointer_id => (P::Dragging { origin, pointer_id }, Fx::Track { at }),
136 (
137 P::Dragging { pointer_id, .. },
138 GestureEvent::Up {
139 at,
140 pointer_id: pid,
141 },
142 ) if pid == pointer_id => (P::Idle, Fx::Drop { at }),
143
144 (P::Dragging { .. }, GestureEvent::Cancel) => (P::Idle, Fx::Abort),
146 (P::Pressed { .. }, GestureEvent::Cancel) => (P::Idle, Fx::None),
147 (P::Idle, GestureEvent::Cancel) => (P::Idle, Fx::None),
148
149 (p, _) => (p, Fx::None),
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 const T: f64 = 8.0;
161
162 fn down(x: f64, y: f64, pid: i32) -> GestureEvent {
163 GestureEvent::Down {
164 at: Point::new(x, y),
165 pointer_id: pid,
166 }
167 }
168 fn mv(x: f64, y: f64, pid: i32) -> GestureEvent {
169 GestureEvent::Move {
170 at: Point::new(x, y),
171 pointer_id: pid,
172 }
173 }
174 fn up(x: f64, y: f64, pid: i32) -> GestureEvent {
175 GestureEvent::Up {
176 at: Point::new(x, y),
177 pointer_id: pid,
178 }
179 }
180
181 #[test]
182 fn full_drag_lifecycle() {
183 let (p, fx) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
184 assert_eq!(fx, GestureEffect::None);
185
186 let (p, fx) = transition(p, mv(3.0, 3.0, 1), T);
188 assert!(matches!(p, GesturePhase::Pressed { .. }));
189 assert_eq!(fx, GestureEffect::None);
190
191 let (p, fx) = transition(p, mv(10.0, 0.0, 1), T);
193 assert_eq!(
194 fx,
195 GestureEffect::Begin {
196 origin: Point::new(0.0, 0.0),
197 at: Point::new(10.0, 0.0)
198 }
199 );
200
201 let (p, fx) = transition(p, mv(20.0, 5.0, 1), T);
202 assert_eq!(
203 fx,
204 GestureEffect::Track {
205 at: Point::new(20.0, 5.0)
206 }
207 );
208
209 let (p, fx) = transition(p, up(20.0, 5.0, 1), T);
210 assert_eq!(p, GesturePhase::Idle);
211 assert_eq!(
212 fx,
213 GestureEffect::Drop {
214 at: Point::new(20.0, 5.0)
215 }
216 );
217 }
218
219 #[test]
220 fn release_before_threshold_is_a_tap() {
221 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
222 let (p, fx) = transition(p, up(2.0, 2.0, 1), T);
223 assert_eq!(p, GesturePhase::Idle);
224 assert_eq!(fx, GestureEffect::Tap);
225 }
226
227 #[test]
228 fn foreign_pointer_ids_are_ignored() {
229 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
230 let (p, fx) = transition(p, mv(100.0, 100.0, 2), T);
232 assert!(matches!(p, GesturePhase::Pressed { .. }));
233 assert_eq!(fx, GestureEffect::None);
234 let (p, fx) = transition(p, up(100.0, 100.0, 2), T);
235 assert!(matches!(p, GesturePhase::Pressed { .. }));
236 assert_eq!(fx, GestureEffect::None);
237 let (p, fx) = transition(p, down(50.0, 50.0, 2), T);
239 assert!(matches!(p, GesturePhase::Pressed { pointer_id: 1, .. }));
240 assert_eq!(fx, GestureEffect::None);
241 }
242
243 #[test]
244 fn cancel_paths() {
245 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
247 let (p, _) = transition(p, mv(20.0, 0.0, 1), T);
248 let (p, fx) = transition(p, GestureEvent::Cancel, T);
249 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::Abort));
250 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
252 let (p, fx) = transition(p, GestureEvent::Cancel, T);
253 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
254 }
255
256 #[test]
257 fn stray_events_while_idle_are_inert() {
258 for ev in [mv(9.0, 9.0, 1), up(9.0, 9.0, 1)] {
259 let (p, fx) = transition(GesturePhase::Idle, ev, T);
260 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
261 }
262 }
263
264 #[test]
265 fn exact_threshold_promotes() {
266 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
267 let (_, fx) = transition(p, mv(8.0, 0.0, 1), T);
268 assert!(matches!(fx, GestureEffect::Begin { .. }));
269 }
270}