input_pipeline/gestures/
primary_tap.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::gesture_arena::{
6    self, DetailedReasonFloat, DetailedReasonInt, DetailedReasonUint, ExamineEventResult,
7    ProcessBufferedEventsResult, Reason, RecognizedGesture, TouchpadEvent, VerifyEventResult,
8    PRIMARY_BUTTON,
9};
10use crate::mouse_binding::{MouseEvent, MouseLocation, MousePhase, RelativeLocation};
11use crate::utils::{euclidean_distance, Position};
12
13use maplit::hashset;
14
15/// The initial state of this recognizer, before a tap has been detected.
16#[derive(Debug)]
17pub(super) struct InitialContender {
18    /// The maximum displacement that a detected finger can withstand to still
19    /// be considered a tap. Measured in millimeters.
20    pub(super) max_finger_displacement_in_mm: f32,
21
22    /// The maximum time that can elapse between a finger down and finger up
23    /// to be considered a tap gesture.
24    pub(super) max_time_elapsed: zx::MonotonicDuration,
25}
26
27impl InitialContender {
28    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
29    fn into_finger_contact_contender(
30        self: Box<Self>,
31        finger_down_event: TouchpadEvent,
32    ) -> Box<dyn gesture_arena::Contender> {
33        Box::new(FingerContactContender {
34            max_finger_displacement_in_mm: self.max_finger_displacement_in_mm,
35            max_time_elapsed: self.max_time_elapsed,
36            finger_down_event,
37        })
38    }
39}
40
41impl gesture_arena::Contender for InitialContender {
42    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
43        let num_contacts = event.contacts.len();
44        if num_contacts != 1 {
45            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
46                criterion: "num_contacts",
47                min: Some(1),
48                max: Some(1),
49                actual: num_contacts,
50            }));
51        }
52
53        let num_pressed_buttons = event.pressed_buttons.len();
54        match num_pressed_buttons {
55            0 => ExamineEventResult::Contender(self.into_finger_contact_contender(event.clone())),
56            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
57                criterion: "num_pressed_buttons",
58                min: Some(0),
59                max: Some(0),
60                actual: num_pressed_buttons,
61            })),
62        }
63    }
64
65    fn start_from_idle(&self) -> bool {
66        true
67    }
68}
69
70/// The state when this recognizer has detected a single finger in contact with the touchpad.
71#[derive(Debug)]
72struct FingerContactContender {
73    /// The maximum displacement that a detected finger can withstand to still
74    /// be considered a tap. Measured in millimeters.
75    max_finger_displacement_in_mm: f32,
76
77    /// The maximum time that can elapse between a finger down and finger up
78    /// to be considered a tap gesture.
79    max_time_elapsed: zx::MonotonicDuration,
80
81    /// The TouchpadEvent when a finger down was first detected.
82    finger_down_event: TouchpadEvent,
83}
84
85impl FingerContactContender {
86    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
87    fn into_matched_contender(
88        self: Box<Self>,
89        finger_up_event: TouchpadEvent,
90    ) -> Box<dyn gesture_arena::MatchedContender> {
91        Box::new(MatchedContender {
92            finger_down_event: self.finger_down_event,
93            finger_up_event,
94            max_time_elapsed: self.max_time_elapsed,
95        })
96    }
97}
98
99impl gesture_arena::Contender for FingerContactContender {
100    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
101        let elapsed_time = event.timestamp - self.finger_down_event.timestamp;
102        if elapsed_time >= self.max_time_elapsed {
103            return ExamineEventResult::Mismatch(Reason::DetailedInt(DetailedReasonInt {
104                criterion: "elapsed_time_micros",
105                min: None,
106                max: Some(self.max_time_elapsed.into_micros()),
107                actual: elapsed_time.into_micros(),
108            }));
109        }
110
111        let num_pressed_buttons = event.pressed_buttons.len();
112        if num_pressed_buttons != 0 {
113            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
114                criterion: "num_pressed_buttons",
115                min: Some(0),
116                max: Some(0),
117                actual: num_pressed_buttons,
118            }));
119        }
120
121        let num_contacts = event.contacts.len();
122        match num_contacts {
123            0 => ExamineEventResult::MatchedContender(self.into_matched_contender(event.clone())),
124            1 => {
125                let displacement_mm = euclidean_distance(
126                    position_from_event(event),
127                    position_from_event(&self.finger_down_event),
128                );
129                if displacement_mm >= self.max_finger_displacement_in_mm {
130                    return ExamineEventResult::Mismatch(Reason::DetailedFloat(
131                        DetailedReasonFloat {
132                            criterion: "displacement_mm",
133                            min: None,
134                            max: Some(self.max_finger_displacement_in_mm),
135                            actual: displacement_mm,
136                        },
137                    ));
138                }
139                ExamineEventResult::Contender(self)
140            }
141            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
142                criterion: "num_contacts",
143                min: Some(0),
144                max: Some(1),
145                actual: num_contacts,
146            })),
147        }
148    }
149}
150
151/// The state when this recognizer has detected a tap, but the gesture arena
152/// has not declared this recognizer the winner.
153#[derive(Debug)]
154struct MatchedContender {
155    /// The TouchpadEvent when a finger down was first detected.
156    finger_down_event: TouchpadEvent,
157
158    /// The TouchpadEvent when a finger up was first detected.
159    finger_up_event: TouchpadEvent,
160
161    /// The maximum time that can elapse between a finger down and finger up
162    /// to be considered a tap gesture.
163    max_time_elapsed: zx::MonotonicDuration,
164}
165
166impl gesture_arena::MatchedContender for MatchedContender {
167    fn verify_event(self: Box<Self>, event: &TouchpadEvent) -> VerifyEventResult {
168        let elapsed_time = event.timestamp - self.finger_down_event.timestamp;
169        if elapsed_time >= self.max_time_elapsed {
170            return VerifyEventResult::Mismatch(Reason::DetailedInt(DetailedReasonInt {
171                criterion: "elapsed_time_micros",
172                min: None,
173                max: Some(self.max_time_elapsed.into_micros()),
174                actual: elapsed_time.into_micros(),
175            }));
176        }
177
178        let num_contacts = event.contacts.len();
179        if num_contacts != 0 {
180            return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
181                criterion: "num_contacts",
182                min: Some(0),
183                max: Some(0),
184                actual: num_contacts,
185            }));
186        }
187
188        let num_pressed_buttons = event.pressed_buttons.len();
189        if num_pressed_buttons != 0 {
190            return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
191                criterion: "num_pressed_buttons",
192                min: Some(0),
193                max: Some(0),
194                actual: num_pressed_buttons,
195            }));
196        }
197
198        VerifyEventResult::MatchedContender(self)
199    }
200
201    fn process_buffered_events(
202        self: Box<Self>,
203        _events: Vec<TouchpadEvent>,
204    ) -> ProcessBufferedEventsResult {
205        ProcessBufferedEventsResult {
206            generated_events: vec![
207                gesture_arena::MouseEvent {
208                    timestamp: self.finger_down_event.timestamp,
209                    mouse_data: MouseEvent {
210                        location: MouseLocation::Relative(RelativeLocation {
211                            millimeters: Position::zero(),
212                        }),
213                        wheel_delta_v: None,
214                        wheel_delta_h: None,
215                        phase: MousePhase::Down,
216                        affected_buttons: hashset! {PRIMARY_BUTTON},
217                        pressed_buttons: hashset! {PRIMARY_BUTTON},
218                        is_precision_scroll: None,
219                    },
220                },
221                gesture_arena::MouseEvent {
222                    timestamp: self.finger_up_event.timestamp,
223                    mouse_data: MouseEvent {
224                        location: MouseLocation::Relative(RelativeLocation {
225                            millimeters: Position::zero(),
226                        }),
227                        wheel_delta_v: None,
228                        wheel_delta_h: None,
229                        phase: MousePhase::Up,
230                        affected_buttons: hashset! {PRIMARY_BUTTON},
231                        pressed_buttons: hashset! {},
232                        is_precision_scroll: None,
233                    },
234                },
235            ],
236            winner: None,
237            recognized_gesture: RecognizedGesture::PrimaryTap,
238        }
239    }
240}
241
242/// This function returns the position associated with a TouchpadEvent that is
243/// assumed to have a single associated TouchContact.
244fn position_from_event(event: &TouchpadEvent) -> Position {
245    event.contacts[0].position
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use crate::gestures::gesture_arena::{Contender, MatchedContender as _};
252    use crate::testing_utilities::create_touch_contact;
253    use assert_matches::assert_matches;
254    use std::any::TypeId;
255
256    const MAX_TIME_ELAPSED: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(10000);
257    const MAX_FINGER_DISPLACEMENT_IN_MM: f32 = 10.0;
258    const HALF_MOTION: f32 = MAX_FINGER_DISPLACEMENT_IN_MM / 2.0;
259
260    fn assert_finger_down_contender(result: ExamineEventResult) {
261        match result {
262            ExamineEventResult::Contender(boxed) => {
263                assert_eq!((&*boxed).as_any().type_id(), TypeId::of::<FingerContactContender>());
264            }
265            other => panic!("Expected a Contender but found {:?}", other),
266        }
267    }
268
269    fn assert_examined_matched_contender(result: ExamineEventResult) {
270        match result {
271            ExamineEventResult::MatchedContender(boxed) => {
272                assert_eq!((&*boxed).as_any().type_id(), TypeId::of::<MatchedContender>());
273            }
274            other => panic!("Expected a MatchedContender but found {:?}", other),
275        }
276    }
277
278    fn assert_verified_matched_contender(result: VerifyEventResult) {
279        match result {
280            VerifyEventResult::MatchedContender(boxed) => {
281                assert_eq!((&*boxed).as_any().type_id(), TypeId::of::<MatchedContender>());
282            }
283            other => panic!("Expected a MatchedContender but found {:?}", other),
284        }
285    }
286
287    /// Tests that an InitialContender with zero touch contacts yields a
288    /// Mismatch.
289    #[fuchsia::test]
290    fn contender_no_touch_contacts() {
291        assert_matches!(
292            Box::new(InitialContender {
293                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
294                max_time_elapsed: MAX_TIME_ELAPSED
295            })
296            .examine_event(&TouchpadEvent {
297                contacts: vec![],
298                timestamp: zx::MonotonicInstant::from_nanos(0),
299                pressed_buttons: vec![],
300                filtered_palm_contacts: vec![],
301            }),
302            ExamineEventResult::Mismatch(_)
303        );
304    }
305
306    /// Tests that an InitialContender with multiple touch contacts yields a
307    /// Mismatch.
308    #[fuchsia::test]
309    fn contender_many_touch_contacts() {
310        assert_matches!(
311            Box::new(InitialContender {
312                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
313                max_time_elapsed: MAX_TIME_ELAPSED
314            })
315            .examine_event(&TouchpadEvent {
316                contacts: vec![
317                    create_touch_contact(0, Position::zero()),
318                    create_touch_contact(1, Position::zero())
319                ],
320                timestamp: zx::MonotonicInstant::from_nanos(0),
321                pressed_buttons: vec![],
322                filtered_palm_contacts: vec![],
323            }),
324            ExamineEventResult::Mismatch(_)
325        );
326    }
327
328    /// Tests that an InitialContender with a single touch contact and one
329    /// pressed button yields a Mismatch.
330    #[fuchsia::test]
331    fn contender_single_button() {
332        assert_matches!(
333            Box::new(InitialContender {
334                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
335                max_time_elapsed: MAX_TIME_ELAPSED
336            })
337            .examine_event(&TouchpadEvent {
338                contacts: vec![create_touch_contact(0, Position::zero())],
339                timestamp: zx::MonotonicInstant::from_nanos(0),
340                pressed_buttons: vec![0],
341                filtered_palm_contacts: vec![],
342            },),
343            ExamineEventResult::Mismatch(_)
344        );
345    }
346
347    /// Tests that an InitialContender with a single touch contact and multiple
348    /// pressed button yields a Mismatch.
349    #[fuchsia::test]
350    fn contender_many_buttons() {
351        assert_matches!(
352            Box::new(InitialContender {
353                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
354                max_time_elapsed: MAX_TIME_ELAPSED
355            })
356            .examine_event(&TouchpadEvent {
357                contacts: vec![create_touch_contact(0, Position::zero())],
358                timestamp: zx::MonotonicInstant::from_nanos(0),
359                pressed_buttons: vec![0, 1],
360                filtered_palm_contacts: vec![],
361            },),
362            ExamineEventResult::Mismatch(_)
363        );
364    }
365
366    /// Tests that an InitialContender with a single touch contact and no
367    /// pressed buttons yields a FingerContactContender.
368    #[fuchsia::test]
369    fn contender_no_buttons() {
370        assert_finger_down_contender(
371            Box::new(InitialContender {
372                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
373                max_time_elapsed: MAX_TIME_ELAPSED,
374            })
375            .examine_event(&TouchpadEvent {
376                contacts: vec![create_touch_contact(0, Position::zero())],
377                timestamp: zx::MonotonicInstant::from_nanos(0),
378                pressed_buttons: vec![],
379                filtered_palm_contacts: vec![],
380            }),
381        );
382    }
383
384    /// Tests that a FingerContactContender with an event whose timestamp exceeds
385    /// the elapsed threshold yields a Mismatch.
386    #[fuchsia::test]
387    fn finger_down_contender_too_long() {
388        assert_matches!(
389            Box::new(FingerContactContender {
390                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
391                max_time_elapsed: MAX_TIME_ELAPSED,
392                finger_down_event: TouchpadEvent {
393                    contacts: vec![create_touch_contact(0, Position::zero())],
394                    timestamp: zx::MonotonicInstant::from_nanos(0),
395                    pressed_buttons: vec![],
396                    filtered_palm_contacts: vec![],
397                },
398            })
399            .examine_event(&TouchpadEvent {
400                contacts: vec![],
401                timestamp: MAX_TIME_ELAPSED + zx::MonotonicInstant::from_nanos(1),
402                pressed_buttons: vec![],
403                filtered_palm_contacts: vec![],
404            }),
405            ExamineEventResult::Mismatch(_)
406        );
407    }
408
409    /// Tests that a FingerContactContender with zero touch contacts yields a
410    /// MatchedContender.
411    #[fuchsia::test]
412    fn finger_down_contender_no_touch_contacts() {
413        assert_examined_matched_contender(
414            Box::new(FingerContactContender {
415                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
416                max_time_elapsed: MAX_TIME_ELAPSED,
417                finger_down_event: TouchpadEvent {
418                    contacts: vec![create_touch_contact(0, Position::zero())],
419                    timestamp: zx::MonotonicInstant::from_nanos(0),
420                    pressed_buttons: vec![],
421                    filtered_palm_contacts: vec![],
422                },
423            })
424            .examine_event(&TouchpadEvent {
425                contacts: vec![],
426                timestamp: zx::MonotonicInstant::from_nanos(0),
427                pressed_buttons: vec![],
428                filtered_palm_contacts: vec![],
429            }),
430        );
431    }
432
433    /// Tests that a FingerContactContender with multiple touch contacts yields a
434    /// Mismatch.
435    #[fuchsia::test]
436    fn finger_down_contender_many_touch_contacts() {
437        assert_matches!(
438            Box::new(FingerContactContender {
439                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
440                max_time_elapsed: MAX_TIME_ELAPSED,
441                finger_down_event: TouchpadEvent {
442                    contacts: vec![create_touch_contact(0, Position::zero())],
443                    timestamp: zx::MonotonicInstant::from_nanos(0),
444                    pressed_buttons: vec![],
445                    filtered_palm_contacts: vec![],
446                },
447            })
448            .examine_event(&TouchpadEvent {
449                contacts: vec![
450                    create_touch_contact(0, Position::zero()),
451                    create_touch_contact(1, Position::zero())
452                ],
453                timestamp: zx::MonotonicInstant::from_nanos(0),
454                pressed_buttons: vec![],
455                filtered_palm_contacts: vec![],
456            }),
457            ExamineEventResult::Mismatch(_)
458        );
459    }
460
461    /// Tests that a FingerContactContender with a single touch contact and
462    /// too much displacement yields a Mismatch.
463    #[fuchsia::test]
464    fn finger_down_contender_large_displacement() {
465        assert_matches!(
466            Box::new(FingerContactContender {
467                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
468                max_time_elapsed: MAX_TIME_ELAPSED,
469                finger_down_event: TouchpadEvent {
470                    contacts: vec![create_touch_contact(0, Position::zero())],
471                    timestamp: zx::MonotonicInstant::from_nanos(0),
472                    pressed_buttons: vec![],
473                    filtered_palm_contacts: vec![],
474                },
475            })
476            .examine_event(&TouchpadEvent {
477                contacts: vec![create_touch_contact(
478                    0,
479                    Position { x: MAX_FINGER_DISPLACEMENT_IN_MM, y: MAX_FINGER_DISPLACEMENT_IN_MM }
480                )],
481                timestamp: zx::MonotonicInstant::from_nanos(0),
482                pressed_buttons: vec![],
483                filtered_palm_contacts: vec![],
484            }),
485            ExamineEventResult::Mismatch(_)
486        );
487    }
488
489    /// Tests that a FingerContactContender with a single touch contact,
490    /// no displacement, and no pressed buttons yields a
491    /// FingerContactContender.
492    #[fuchsia::test]
493    fn finger_down_contender_no_buttons_no_displacement() {
494        assert_finger_down_contender(
495            Box::new(FingerContactContender {
496                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
497                max_time_elapsed: MAX_TIME_ELAPSED,
498                finger_down_event: TouchpadEvent {
499                    contacts: vec![create_touch_contact(0, Position::zero())],
500                    timestamp: zx::MonotonicInstant::from_nanos(0),
501                    pressed_buttons: vec![],
502                    filtered_palm_contacts: vec![],
503                },
504            })
505            .examine_event(&TouchpadEvent {
506                contacts: vec![create_touch_contact(0, Position::zero())],
507                timestamp: zx::MonotonicInstant::from_nanos(0),
508                pressed_buttons: vec![],
509                filtered_palm_contacts: vec![],
510            }),
511        );
512    }
513
514    /// Tests that a FingerContactContender with a single touch contact,
515    /// acceptable displacement, and no pressed buttons yields a
516    /// FingerContactContender.
517    #[fuchsia::test]
518    fn finger_down_contender_no_buttons_some_displacement() {
519        assert_finger_down_contender(
520            Box::new(FingerContactContender {
521                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
522                max_time_elapsed: MAX_TIME_ELAPSED,
523                finger_down_event: TouchpadEvent {
524                    contacts: vec![create_touch_contact(0, Position::zero())],
525                    timestamp: zx::MonotonicInstant::from_nanos(0),
526                    pressed_buttons: vec![],
527                    filtered_palm_contacts: vec![],
528                },
529            })
530            .examine_event(&TouchpadEvent {
531                contacts: vec![create_touch_contact(
532                    0,
533                    Position { x: HALF_MOTION, y: HALF_MOTION },
534                )],
535                timestamp: zx::MonotonicInstant::from_nanos(0),
536                pressed_buttons: vec![],
537                filtered_palm_contacts: vec![],
538            }),
539        );
540    }
541
542    /// Tests that a FingerContactContender with a single touch contact,
543    /// acceptable displacement, and one pressed button yields a
544    /// Mismatch.
545    #[fuchsia::test]
546    fn finger_down_contender_single_button() {
547        assert_matches!(
548            Box::new(FingerContactContender {
549                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
550                max_time_elapsed: MAX_TIME_ELAPSED,
551                finger_down_event: TouchpadEvent {
552                    contacts: vec![create_touch_contact(0, Position::zero())],
553                    timestamp: zx::MonotonicInstant::from_nanos(0),
554                    pressed_buttons: vec![],
555                    filtered_palm_contacts: vec![],
556                },
557            })
558            .examine_event(&TouchpadEvent {
559                contacts: vec![create_touch_contact(
560                    0,
561                    Position { x: HALF_MOTION, y: HALF_MOTION }
562                )],
563                timestamp: zx::MonotonicInstant::from_nanos(0),
564                pressed_buttons: vec![0],
565                filtered_palm_contacts: vec![],
566            }),
567            ExamineEventResult::Mismatch(_)
568        );
569    }
570
571    /// Tests that a FingerContactContender with a single touch contact,
572    /// acceptable displacement, and multiple pressed buttons yields a
573    /// Mismatch.
574    #[fuchsia::test]
575    fn finger_down_contender_many_buttons() {
576        assert_matches!(
577            Box::new(FingerContactContender {
578                max_finger_displacement_in_mm: MAX_FINGER_DISPLACEMENT_IN_MM,
579                max_time_elapsed: MAX_TIME_ELAPSED,
580                finger_down_event: TouchpadEvent {
581                    contacts: vec![create_touch_contact(0, Position::zero())],
582                    timestamp: zx::MonotonicInstant::from_nanos(0),
583                    pressed_buttons: vec![],
584                    filtered_palm_contacts: vec![],
585                },
586            })
587            .examine_event(&TouchpadEvent {
588                contacts: vec![create_touch_contact(
589                    0,
590                    Position { x: HALF_MOTION, y: HALF_MOTION }
591                )],
592                timestamp: zx::MonotonicInstant::from_nanos(0),
593                pressed_buttons: vec![0, 1],
594                filtered_palm_contacts: vec![],
595            }),
596            ExamineEventResult::Mismatch(_)
597        );
598    }
599
600    /// Tests that a MatchedContender with one button pressed yields a Mismatch.
601    #[fuchsia::test]
602    fn matched_contender_one_button() {
603        assert_matches!(
604            Box::new(MatchedContender {
605                finger_down_event: TouchpadEvent {
606                    contacts: vec![create_touch_contact(0, Position::zero())],
607                    timestamp: zx::MonotonicInstant::from_nanos(0),
608                    pressed_buttons: vec![],
609                    filtered_palm_contacts: vec![],
610                },
611                finger_up_event: TouchpadEvent {
612                    contacts: vec![],
613                    timestamp: zx::MonotonicInstant::from_nanos(0),
614                    pressed_buttons: vec![],
615                    filtered_palm_contacts: vec![],
616                },
617                max_time_elapsed: MAX_TIME_ELAPSED,
618            })
619            .verify_event(&TouchpadEvent {
620                contacts: vec![],
621                timestamp: zx::MonotonicInstant::from_nanos(0),
622                pressed_buttons: vec![0],
623                filtered_palm_contacts: vec![],
624            }),
625            VerifyEventResult::Mismatch(_)
626        );
627    }
628
629    /// Tests that a MatchedContender with multiple buttons pressed yields a
630    /// Mismatch.
631    #[fuchsia::test]
632    fn matched_contender_many_buttons() {
633        assert_matches!(
634            Box::new(MatchedContender {
635                finger_down_event: TouchpadEvent {
636                    contacts: vec![create_touch_contact(0, Position::zero())],
637                    timestamp: zx::MonotonicInstant::from_nanos(0),
638                    pressed_buttons: vec![],
639                    filtered_palm_contacts: vec![],
640                },
641                finger_up_event: TouchpadEvent {
642                    contacts: vec![],
643                    timestamp: zx::MonotonicInstant::from_nanos(0),
644                    pressed_buttons: vec![],
645                    filtered_palm_contacts: vec![],
646                },
647                max_time_elapsed: MAX_TIME_ELAPSED,
648            })
649            .verify_event(&TouchpadEvent {
650                contacts: vec![],
651                timestamp: zx::MonotonicInstant::from_nanos(0),
652                pressed_buttons: vec![0, 1],
653                filtered_palm_contacts: vec![],
654            }),
655            VerifyEventResult::Mismatch(_)
656        );
657    }
658
659    /// Tests that a MatchedContender with one touch contact yields a Mismatch.
660    #[fuchsia::test]
661    fn matched_contender_single_touch_contact() {
662        assert_matches!(
663            Box::new(MatchedContender {
664                finger_down_event: TouchpadEvent {
665                    contacts: vec![create_touch_contact(0, Position::zero())],
666                    timestamp: zx::MonotonicInstant::from_nanos(0),
667                    pressed_buttons: vec![],
668                    filtered_palm_contacts: vec![],
669                },
670                finger_up_event: TouchpadEvent {
671                    contacts: vec![],
672                    timestamp: zx::MonotonicInstant::from_nanos(0),
673                    pressed_buttons: vec![],
674                    filtered_palm_contacts: vec![],
675                },
676                max_time_elapsed: MAX_TIME_ELAPSED,
677            })
678            .verify_event(&TouchpadEvent {
679                contacts: vec![create_touch_contact(0, Position::zero())],
680                timestamp: zx::MonotonicInstant::from_nanos(0),
681                pressed_buttons: vec![],
682                filtered_palm_contacts: vec![],
683            }),
684            VerifyEventResult::Mismatch(_)
685        );
686    }
687
688    /// Tests that a MatchedContender with multiple touch contacts yields a
689    /// Mismatch.
690    #[fuchsia::test]
691    fn matched_contender_many_touch_contacts() {
692        assert_matches!(
693            Box::new(MatchedContender {
694                finger_down_event: TouchpadEvent {
695                    contacts: vec![create_touch_contact(0, Position::zero())],
696                    timestamp: zx::MonotonicInstant::from_nanos(0),
697                    pressed_buttons: vec![0],
698                    filtered_palm_contacts: vec![],
699                },
700                finger_up_event: TouchpadEvent {
701                    contacts: vec![],
702                    timestamp: zx::MonotonicInstant::from_nanos(0),
703                    pressed_buttons: vec![],
704                    filtered_palm_contacts: vec![],
705                },
706                max_time_elapsed: MAX_TIME_ELAPSED,
707            })
708            .verify_event(&TouchpadEvent {
709                contacts: vec![
710                    create_touch_contact(0, Position::zero()),
711                    create_touch_contact(1, Position::zero())
712                ],
713                timestamp: zx::MonotonicInstant::from_nanos(0),
714                pressed_buttons: vec![],
715                filtered_palm_contacts: vec![],
716            }),
717            VerifyEventResult::Mismatch(_)
718        );
719    }
720
721    /// Tests that a MatchedContender with an event whose timestamp exceeds
722    /// the elapsed threshold yields a Mismatch.
723    #[fuchsia::test]
724    fn matched_contender_no_contacts_no_buttons_too_long() {
725        assert_matches!(
726            Box::new(MatchedContender {
727                finger_down_event: TouchpadEvent {
728                    contacts: vec![create_touch_contact(0, Position::zero())],
729                    timestamp: zx::MonotonicInstant::from_nanos(0),
730                    pressed_buttons: vec![],
731                    filtered_palm_contacts: vec![],
732                },
733                finger_up_event: TouchpadEvent {
734                    contacts: vec![],
735                    timestamp: zx::MonotonicInstant::from_nanos(0),
736                    pressed_buttons: vec![],
737                    filtered_palm_contacts: vec![],
738                },
739                max_time_elapsed: MAX_TIME_ELAPSED,
740            })
741            .verify_event(&TouchpadEvent {
742                contacts: vec![],
743                timestamp: MAX_TIME_ELAPSED + zx::MonotonicInstant::from_nanos(1),
744                pressed_buttons: vec![],
745                filtered_palm_contacts: vec![],
746            }),
747            VerifyEventResult::Mismatch(_)
748        );
749    }
750
751    /// Tests that a MatchedContender with no buttons or touch contacts
752    /// yields a MatchedContender.
753    #[fuchsia::test]
754    fn matched_contender_no_contacts_no_buttons() {
755        assert_verified_matched_contender(
756            Box::new(MatchedContender {
757                finger_down_event: TouchpadEvent {
758                    contacts: vec![create_touch_contact(0, Position::zero())],
759                    timestamp: zx::MonotonicInstant::from_nanos(0),
760                    pressed_buttons: vec![],
761                    filtered_palm_contacts: vec![],
762                },
763                finger_up_event: TouchpadEvent {
764                    contacts: vec![],
765                    timestamp: zx::MonotonicInstant::from_nanos(0),
766                    pressed_buttons: vec![],
767                    filtered_palm_contacts: vec![],
768                },
769                max_time_elapsed: MAX_TIME_ELAPSED,
770            })
771            .verify_event(&TouchpadEvent {
772                contacts: vec![],
773                timestamp: zx::MonotonicInstant::from_nanos(0),
774                pressed_buttons: vec![],
775                filtered_palm_contacts: vec![],
776            }),
777        );
778    }
779
780    /// Tests that a MatchedContender processes buffered events by
781    /// returning mouse down and mouse up events.
782    #[fuchsia::test]
783    fn matched_contender_process_buffered_events() {
784        let timestamp = zx::MonotonicInstant::from_nanos(0);
785        let ProcessBufferedEventsResult { generated_events, winner, recognized_gesture } =
786            Box::new(MatchedContender {
787                finger_down_event: TouchpadEvent {
788                    contacts: vec![create_touch_contact(0, Position::zero())],
789                    timestamp,
790                    pressed_buttons: vec![],
791                    filtered_palm_contacts: vec![],
792                },
793                finger_up_event: TouchpadEvent {
794                    contacts: vec![],
795                    timestamp,
796                    pressed_buttons: vec![],
797                    filtered_palm_contacts: vec![],
798                },
799                max_time_elapsed: MAX_TIME_ELAPSED,
800            })
801            .process_buffered_events(vec![]);
802
803        assert_eq!(
804            generated_events,
805            [
806                gesture_arena::MouseEvent {
807                    timestamp: timestamp,
808                    mouse_data: MouseEvent {
809                        location: MouseLocation::Relative(RelativeLocation {
810                            millimeters: Position { x: 0.0, y: 0.0 }
811                        }),
812                        wheel_delta_v: None,
813                        wheel_delta_h: None,
814                        phase: MousePhase::Down,
815                        affected_buttons: hashset! {PRIMARY_BUTTON},
816                        pressed_buttons: hashset! {PRIMARY_BUTTON},
817                        is_precision_scroll: None,
818                    },
819                },
820                gesture_arena::MouseEvent {
821                    timestamp: timestamp,
822                    mouse_data: MouseEvent {
823                        location: MouseLocation::Relative(RelativeLocation {
824                            millimeters: Position { x: 0.0, y: 0.0 }
825                        }),
826                        wheel_delta_v: None,
827                        wheel_delta_h: None,
828                        phase: MousePhase::Up,
829                        affected_buttons: hashset! {PRIMARY_BUTTON},
830                        pressed_buttons: hashset! {},
831                        is_precision_scroll: None,
832                    },
833                }
834            ]
835        );
836        assert_matches!(winner, None);
837        assert_eq!(recognized_gesture, RecognizedGesture::PrimaryTap);
838    }
839}