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)]
55#[non_exhaustive]
56pub enum GestureEvent {
57 Down { at: Point, pointer_id: i32 },
59 Move { at: Point, pointer_id: i32 },
61 Up { at: Point, pointer_id: i32 },
63 Hold { pointer_id: i32 },
68 Cancel,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub enum Promotion {
76 #[default]
80 Distance,
81 HoldOrSideways,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq)]
92pub enum GestureEffect {
93 None,
96 Begin { origin: Point, at: Point },
100 Track { at: Point },
102 Drop { at: Point },
104 Tap,
106 Abort,
108}
109
110pub fn transition(
116 phase: GesturePhase,
117 event: GestureEvent,
118 threshold: f64,
119) -> (GesturePhase, GestureEffect) {
120 transition_with(phase, event, threshold, Promotion::Distance)
121}
122
123pub fn transition_with(
129 phase: GesturePhase,
130 event: GestureEvent,
131 threshold: f64,
132 promotion: Promotion,
133) -> (GesturePhase, GestureEffect) {
134 use GestureEffect as Fx;
135 use GesturePhase as P;
136
137 match (phase, event) {
138 (P::Idle, GestureEvent::Down { at, pointer_id }) => (
140 P::Pressed {
141 origin: at,
142 pointer_id,
143 },
144 Fx::None,
145 ),
146 (p @ (P::Pressed { .. } | P::Dragging { .. }), GestureEvent::Down { .. }) => (p, Fx::None),
148
149 (
151 P::Pressed { origin, pointer_id },
152 GestureEvent::Move {
153 at,
154 pointer_id: pid,
155 },
156 ) if pid == pointer_id => {
157 let d = at - origin;
158 if (d.x * d.x + d.y * d.y).sqrt() >= threshold {
159 match promotion {
160 Promotion::Distance => {
161 (P::Dragging { origin, pointer_id }, Fx::Begin { origin, at })
162 }
163 Promotion::HoldOrSideways if d.x.abs() > d.y.abs() => {
164 (P::Dragging { origin, pointer_id }, Fx::Begin { origin, at })
165 }
166 Promotion::HoldOrSideways => (P::Idle, Fx::None),
171 }
172 } else {
173 (P::Pressed { origin, pointer_id }, Fx::None)
174 }
175 }
176 (P::Pressed { origin, pointer_id }, GestureEvent::Hold { pointer_id: pid })
180 if pid == pointer_id =>
181 {
182 (
183 P::Dragging { origin, pointer_id },
184 Fx::Begin { origin, at: origin },
185 )
186 }
187 (
188 P::Pressed { pointer_id, .. },
189 GestureEvent::Up {
190 pointer_id: pid, ..
191 },
192 ) if pid == pointer_id => (P::Idle, Fx::Tap),
193
194 (
196 P::Dragging { origin, pointer_id },
197 GestureEvent::Move {
198 at,
199 pointer_id: pid,
200 },
201 ) if pid == pointer_id => (P::Dragging { origin, pointer_id }, Fx::Track { at }),
202 (
203 P::Dragging { pointer_id, .. },
204 GestureEvent::Up {
205 at,
206 pointer_id: pid,
207 },
208 ) if pid == pointer_id => (P::Idle, Fx::Drop { at }),
209
210 (P::Dragging { .. }, GestureEvent::Cancel) => (P::Idle, Fx::Abort),
212 (P::Pressed { .. }, GestureEvent::Cancel) => (P::Idle, Fx::None),
213 (P::Idle, GestureEvent::Cancel) => (P::Idle, Fx::None),
214
215 (p, _) => (p, Fx::None),
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 const T: f64 = 8.0;
227
228 fn down(x: f64, y: f64, pid: i32) -> GestureEvent {
229 GestureEvent::Down {
230 at: Point::new(x, y),
231 pointer_id: pid,
232 }
233 }
234 fn mv(x: f64, y: f64, pid: i32) -> GestureEvent {
235 GestureEvent::Move {
236 at: Point::new(x, y),
237 pointer_id: pid,
238 }
239 }
240 fn up(x: f64, y: f64, pid: i32) -> GestureEvent {
241 GestureEvent::Up {
242 at: Point::new(x, y),
243 pointer_id: pid,
244 }
245 }
246
247 #[test]
248 fn full_drag_lifecycle() {
249 let (p, fx) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
250 assert_eq!(fx, GestureEffect::None);
251
252 let (p, fx) = transition(p, mv(3.0, 3.0, 1), T);
254 assert!(matches!(p, GesturePhase::Pressed { .. }));
255 assert_eq!(fx, GestureEffect::None);
256
257 let (p, fx) = transition(p, mv(10.0, 0.0, 1), T);
259 assert_eq!(
260 fx,
261 GestureEffect::Begin {
262 origin: Point::new(0.0, 0.0),
263 at: Point::new(10.0, 0.0)
264 }
265 );
266
267 let (p, fx) = transition(p, mv(20.0, 5.0, 1), T);
268 assert_eq!(
269 fx,
270 GestureEffect::Track {
271 at: Point::new(20.0, 5.0)
272 }
273 );
274
275 let (p, fx) = transition(p, up(20.0, 5.0, 1), T);
276 assert_eq!(p, GesturePhase::Idle);
277 assert_eq!(
278 fx,
279 GestureEffect::Drop {
280 at: Point::new(20.0, 5.0)
281 }
282 );
283 }
284
285 #[test]
286 fn release_before_threshold_is_a_tap() {
287 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
288 let (p, fx) = transition(p, up(2.0, 2.0, 1), T);
289 assert_eq!(p, GesturePhase::Idle);
290 assert_eq!(fx, GestureEffect::Tap);
291 }
292
293 #[test]
294 fn foreign_pointer_ids_are_ignored() {
295 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
296 let (p, fx) = transition(p, mv(100.0, 100.0, 2), T);
298 assert!(matches!(p, GesturePhase::Pressed { .. }));
299 assert_eq!(fx, GestureEffect::None);
300 let (p, fx) = transition(p, up(100.0, 100.0, 2), T);
301 assert!(matches!(p, GesturePhase::Pressed { .. }));
302 assert_eq!(fx, GestureEffect::None);
303 let (p, fx) = transition(p, down(50.0, 50.0, 2), T);
305 assert!(matches!(p, GesturePhase::Pressed { pointer_id: 1, .. }));
306 assert_eq!(fx, GestureEffect::None);
307 }
308
309 #[test]
310 fn cancel_paths() {
311 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
313 let (p, _) = transition(p, mv(20.0, 0.0, 1), T);
314 let (p, fx) = transition(p, GestureEvent::Cancel, T);
315 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::Abort));
316 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
318 let (p, fx) = transition(p, GestureEvent::Cancel, T);
319 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
320 }
321
322 #[test]
323 fn stray_events_while_idle_are_inert() {
324 for ev in [mv(9.0, 9.0, 1), up(9.0, 9.0, 1)] {
325 let (p, fx) = transition(GesturePhase::Idle, ev, T);
326 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
327 }
328 }
329
330 #[test]
331 fn exact_threshold_promotes() {
332 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
333 let (_, fx) = transition(p, mv(8.0, 0.0, 1), T);
334 assert!(matches!(fx, GestureEffect::Begin { .. }));
335 }
336
337 fn hold(pid: i32) -> GestureEvent {
338 GestureEvent::Hold { pointer_id: pid }
339 }
340
341 fn step_auto(p: GesturePhase, ev: GestureEvent) -> (GesturePhase, GestureEffect) {
342 transition_with(p, ev, T, Promotion::HoldOrSideways)
343 }
344
345 #[test]
346 fn hold_promotes_at_the_press_origin() {
347 let (p, _) = step_auto(GesturePhase::Idle, down(5.0, 5.0, 1));
348 let (p, fx) = step_auto(p, hold(1));
349 assert!(matches!(p, GesturePhase::Dragging { .. }));
350 assert_eq!(
351 fx,
352 GestureEffect::Begin {
353 origin: Point::new(5.0, 5.0),
354 at: Point::new(5.0, 5.0)
355 }
356 );
357 let (_, fx) = step_auto(p, mv(5.0, 40.0, 1));
359 assert_eq!(
360 fx,
361 GestureEffect::Track {
362 at: Point::new(5.0, 40.0)
363 }
364 );
365 }
366
367 #[test]
368 fn stale_or_foreign_hold_is_inert() {
369 let (p, _) = step_auto(GesturePhase::Idle, down(0.0, 0.0, 1));
371 let (p2, fx) = step_auto(p, hold(2));
372 assert_eq!((p2, fx), (p, GestureEffect::None));
373 let (p, fx) = step_auto(GesturePhase::Idle, hold(1));
375 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
376 let (p, _) = step_auto(GesturePhase::Idle, down(0.0, 0.0, 1));
378 let (p, _) = step_auto(p, hold(1));
379 let (p2, fx) = step_auto(p, hold(1));
380 assert_eq!((p2, fx), (p, GestureEffect::None));
381 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
384 let (_, fx) = transition(p, hold(1), T);
385 assert!(matches!(fx, GestureEffect::Begin { .. }));
386 }
387
388 #[test]
389 fn sideways_pull_promotes_under_auto() {
390 let (p, _) = step_auto(GesturePhase::Idle, down(0.0, 0.0, 1));
391 let (_, fx) = step_auto(p, mv(9.0, 4.0, 1));
392 assert_eq!(
393 fx,
394 GestureEffect::Begin {
395 origin: Point::new(0.0, 0.0),
396 at: Point::new(9.0, 4.0)
397 }
398 );
399 }
400
401 #[test]
402 fn vertical_pull_yields_to_scroll_under_auto() {
403 let (p, _) = step_auto(GesturePhase::Idle, down(0.0, 0.0, 1));
404 let (p, fx) = step_auto(p, mv(1.0, 5.0, 1));
406 assert!(matches!(p, GesturePhase::Pressed { .. }));
407 assert_eq!(fx, GestureEffect::None);
408 let (p, fx) = step_auto(p, mv(2.0, 12.0, 1));
410 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
411 let (p, _) = step_auto(GesturePhase::Idle, down(0.0, 0.0, 1));
413 let (p, fx) = step_auto(p, mv(10.0, 10.0, 1));
414 assert_eq!((p, fx), (GesturePhase::Idle, GestureEffect::None));
415 }
416
417 #[test]
418 fn distance_policy_promotes_vertical_pulls_unchanged() {
419 let (p, _) = transition(GesturePhase::Idle, down(0.0, 0.0, 1), T);
420 let (_, fx) = transition(p, mv(0.0, 12.0, 1), T);
421 assert!(matches!(fx, GestureEffect::Begin { .. }));
422 }
423}