input_pipeline/gestures/
motion.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, DetailedReasonUint, EndGestureEvent, ExamineEventResult, MouseEvent,
7    ProcessBufferedEventsResult, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
8    VerifyEventResult,
9};
10use crate::mouse_binding;
11use crate::utils::{Position, euclidean_distance};
12use maplit::hashset;
13
14/// The initial state of this recognizer, before a finger contact has been detected.
15#[derive(Debug)]
16pub(super) struct InitialContender {
17    /// The minimum movement in millimeters on surface to recognize as a motion.
18    pub(super) min_movement_in_mm: f32,
19}
20
21/// The state when this recognizer has detected a finger contact, before finger movement > threshold.
22#[derive(Debug)]
23struct FingerContactContender {
24    /// The minimum movement in millimeters on surface to recognize as a motion.
25    min_movement_in_mm: f32,
26
27    /// The initial contact position on touchpad surface.
28    initial_position: Position,
29}
30
31/// The state when this recognizer has detected a finger contact and a movement > threshold, but the
32/// gesture arena has not declared this recognizer the winner.
33#[derive(Debug)]
34struct MatchedContender {}
35
36/// The state when this recognizer has won the contest.
37#[derive(Debug)]
38struct Winner {
39    /// The last contact position on touchpad surface.
40    last_position: Position,
41}
42
43impl InitialContender {
44    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
45    fn into_finger_contact_contender(
46        self: Box<Self>,
47        initial_position: Position,
48    ) -> Box<dyn gesture_arena::Contender> {
49        Box::new(FingerContactContender {
50            min_movement_in_mm: self.min_movement_in_mm,
51            initial_position,
52        })
53    }
54}
55
56impl gesture_arena::Contender for InitialContender {
57    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
58        let num_contacts = event.contacts.len();
59        if num_contacts != 1 {
60            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
61                criterion: "num_contacts",
62                min: Some(1),
63                max: Some(1),
64                actual: num_contacts,
65            }));
66        }
67
68        let num_pressed_buttons = event.pressed_buttons.len();
69        if num_pressed_buttons > 0 {
70            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
71                criterion: "num_pressed_buttons",
72                min: None,
73                max: Some(0),
74                actual: num_pressed_buttons,
75            }));
76        }
77
78        ExamineEventResult::Contender(
79            self.into_finger_contact_contender(event.contacts[0].position),
80        )
81    }
82}
83
84impl FingerContactContender {
85    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
86    fn into_matched_contender(self: Box<Self>) -> Box<dyn gesture_arena::MatchedContender> {
87        Box::new(MatchedContender {})
88    }
89}
90
91impl gesture_arena::Contender for FingerContactContender {
92    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
93        let num_contacts = event.contacts.len();
94        if num_contacts != 1 {
95            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
96                criterion: "num_contacts",
97                min: Some(1),
98                max: Some(1),
99                actual: num_contacts,
100            }));
101        }
102
103        let num_pressed_buttons = event.pressed_buttons.len();
104        if num_pressed_buttons > 0 {
105            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
106                criterion: "num_pressed_buttons",
107                min: None,
108                max: Some(0),
109                actual: num_pressed_buttons,
110            }));
111        }
112
113        let distance = euclidean_distance(event.contacts[0].position, self.initial_position);
114        if distance > self.min_movement_in_mm {
115            return ExamineEventResult::MatchedContender(self.into_matched_contender());
116        }
117
118        ExamineEventResult::Contender(self)
119    }
120}
121
122impl MatchedContender {
123    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
124    fn into_winner(self: Box<Self>, last_position: Position) -> Box<dyn gesture_arena::Winner> {
125        Box::new(Winner { last_position })
126    }
127}
128
129impl gesture_arena::MatchedContender for MatchedContender {
130    fn verify_event(self: Box<Self>, event: &TouchpadEvent) -> VerifyEventResult {
131        let num_contacts = event.contacts.len();
132        if num_contacts != 1 {
133            return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
134                criterion: "num_contacts",
135                min: Some(1),
136                max: Some(1),
137                actual: num_contacts,
138            }));
139        }
140
141        let num_pressed_buttons = event.pressed_buttons.len();
142        if num_pressed_buttons > 0 {
143            return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
144                criterion: "num_pressed_buttons",
145                min: None,
146                max: Some(0),
147                actual: num_pressed_buttons,
148            }));
149        }
150
151        VerifyEventResult::MatchedContender(self)
152    }
153
154    fn process_buffered_events(
155        self: Box<Self>,
156        events: Vec<TouchpadEvent>,
157    ) -> ProcessBufferedEventsResult {
158        let mut mouse_events: Vec<MouseEvent> = Vec::new();
159        let last_position = events[events.len() - 1].contacts[0].position.clone();
160
161        for pair in events.windows(2) {
162            mouse_events.push(touchpad_event_to_mouse_motion_event(
163                &pair[0].contacts[0].position,
164                &pair[1],
165            ));
166        }
167
168        ProcessBufferedEventsResult {
169            generated_events: mouse_events,
170            winner: Some(self.into_winner(last_position)),
171            recognized_gesture: RecognizedGesture::Motion,
172        }
173    }
174}
175
176impl gesture_arena::Winner for Winner {
177    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
178        match u8::try_from(event.contacts.len()).unwrap_or(u8::MAX) {
179            0 => ProcessNewEventResult::EndGesture(
180                EndGestureEvent::NoEvent,
181                Reason::DetailedUint(DetailedReasonUint {
182                    criterion: "num_contacts",
183                    min: Some(1),
184                    max: Some(1),
185                    actual: 0,
186                }),
187            ),
188            1 => {
189                let num_pressed_buttons = event.pressed_buttons.len();
190                if num_pressed_buttons > 0 {
191                    ProcessNewEventResult::EndGesture(
192                        EndGestureEvent::UnconsumedEvent(event),
193                        Reason::DetailedUint(DetailedReasonUint {
194                            criterion: "num_pressed_buttons",
195                            min: None,
196                            max: Some(0),
197                            actual: num_pressed_buttons,
198                        }),
199                    )
200                } else {
201                    let last_position = event.contacts[0].position.clone();
202                    ProcessNewEventResult::ContinueGesture(
203                        Some(touchpad_event_to_mouse_motion_event(&self.last_position, &event)),
204                        Box::new(Winner { last_position }),
205                    )
206                }
207            }
208            num_contacts @ 2.. => ProcessNewEventResult::EndGesture(
209                EndGestureEvent::UnconsumedEvent(event),
210                Reason::DetailedUint(DetailedReasonUint {
211                    criterion: "num_contacts",
212                    min: Some(1),
213                    max: Some(1),
214                    actual: usize::from(num_contacts),
215                }),
216            ),
217        }
218    }
219}
220
221fn touchpad_event_to_mouse_motion_event(
222    last_position: &Position,
223    event: &TouchpadEvent,
224) -> MouseEvent {
225    MouseEvent {
226        timestamp: event.timestamp,
227        mouse_data: mouse_binding::MouseEvent::new(
228            mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
229                millimeters: Position {
230                    x: event.contacts[0].position.x - last_position.x,
231                    y: event.contacts[0].position.y - last_position.y,
232                },
233            }),
234            /* wheel_delta_v= */ None,
235            /* wheel_delta_h= */ None,
236            mouse_binding::MousePhase::Move,
237            /* affected_buttons= */ hashset! {},
238            /* pressed_buttons= */ hashset! {},
239            /* is_precision_scroll= */ None,
240            /* wake_lease= */ None,
241        ),
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use crate::touch_binding;
249    use assert_matches::assert_matches;
250
251    use pretty_assertions::assert_eq;
252    use test_case::test_case;
253
254    fn touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
255        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
256    }
257
258    #[test_case(TouchpadEvent{
259        timestamp: zx::MonotonicInstant::ZERO,
260        pressed_buttons: vec![1],
261        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
262        filtered_palm_contacts: vec![],
263    };"button down")]
264    #[test_case(TouchpadEvent{
265        timestamp: zx::MonotonicInstant::ZERO,
266        pressed_buttons: vec![],
267        contacts: vec![],
268        filtered_palm_contacts: vec![],
269    };"0 fingers")]
270    #[test_case(TouchpadEvent{
271        timestamp: zx::MonotonicInstant::ZERO,
272        pressed_buttons: vec![],
273        contacts: vec![
274            touch_contact(1, Position{x: 1.0, y: 1.0}),
275            touch_contact(2, Position{x: 5.0, y: 5.0}),
276        ],
277        filtered_palm_contacts: vec![],
278    };"2 fingers")]
279    #[fuchsia::test]
280    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
281        let contender: Box<dyn gesture_arena::Contender> =
282            Box::new(InitialContender { min_movement_in_mm: 10.0 });
283
284        let got = contender.examine_event(&event);
285        assert_matches!(got, ExamineEventResult::Mismatch(_));
286    }
287
288    #[test_case(TouchpadEvent{
289        timestamp: zx::MonotonicInstant::ZERO,
290        pressed_buttons: vec![],
291        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
292        filtered_palm_contacts: vec![],
293    };"finger hold")]
294    #[test_case(TouchpadEvent{
295        timestamp: zx::MonotonicInstant::ZERO,
296        pressed_buttons: vec![],
297        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
298        filtered_palm_contacts: vec![],
299    };"finger moved")]
300    #[fuchsia::test]
301    fn initial_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
302        let contender: Box<dyn gesture_arena::Contender> =
303            Box::new(InitialContender { min_movement_in_mm: 10.0 });
304
305        let got = contender.examine_event(&event);
306        assert_matches!(got, ExamineEventResult::Contender(_));
307    }
308
309    #[test_case(TouchpadEvent{
310        timestamp: zx::MonotonicInstant::ZERO,
311        pressed_buttons: vec![1],
312        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
313        filtered_palm_contacts: vec![],
314    };"button down")]
315    #[test_case(TouchpadEvent{
316        timestamp: zx::MonotonicInstant::ZERO,
317        pressed_buttons: vec![],
318        contacts: vec![],
319        filtered_palm_contacts: vec![],
320    };"0 fingers")]
321    #[test_case(TouchpadEvent{
322        timestamp: zx::MonotonicInstant::ZERO,
323        pressed_buttons: vec![],
324        contacts: vec![
325            touch_contact(1, Position{x: 1.0, y: 1.0}),
326            touch_contact(2, Position{x: 5.0, y: 5.0}),
327            ],
328        filtered_palm_contacts: vec![],
329    };"2 fingers")]
330    #[fuchsia::test]
331    fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
332        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
333            min_movement_in_mm: 10.0,
334            initial_position: Position { x: 1.0, y: 1.0 },
335        });
336
337        let got = contender.examine_event(&event);
338        assert_matches!(got, ExamineEventResult::Mismatch(_));
339    }
340
341    #[test_case(TouchpadEvent{
342        timestamp: zx::MonotonicInstant::ZERO,
343        pressed_buttons: vec![],
344        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
345        filtered_palm_contacts: vec![],
346    };"finger hold")]
347    #[test_case(TouchpadEvent{
348        timestamp: zx::MonotonicInstant::ZERO,
349        pressed_buttons: vec![],
350        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
351        filtered_palm_contacts: vec![],
352    };"finger move less than threshold")]
353    #[fuchsia::test]
354    fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
355        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
356            min_movement_in_mm: 10.0,
357            initial_position: Position { x: 1.0, y: 1.0 },
358        });
359
360        let got = contender.examine_event(&event);
361        assert_matches!(got, ExamineEventResult::Contender(_));
362    }
363
364    #[fuchsia::test]
365    fn finger_contact_contender_examine_event_matched_contender() {
366        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
367            min_movement_in_mm: 10.0,
368            initial_position: Position { x: 1.0, y: 1.0 },
369        });
370        let event = TouchpadEvent {
371            timestamp: zx::MonotonicInstant::ZERO,
372            pressed_buttons: vec![],
373            contacts: vec![touch_contact(1, Position { x: 11.0, y: 12.0 })],
374            filtered_palm_contacts: vec![],
375        };
376        let got = contender.examine_event(&event);
377        assert_matches!(got, ExamineEventResult::MatchedContender(_));
378    }
379
380    #[test_case(TouchpadEvent{
381        timestamp: zx::MonotonicInstant::ZERO,
382        pressed_buttons: vec![1],
383        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
384        filtered_palm_contacts: vec![],
385    };"button down")]
386    #[test_case(TouchpadEvent{
387        timestamp: zx::MonotonicInstant::ZERO,
388        pressed_buttons: vec![],
389        contacts: vec![],
390        filtered_palm_contacts: vec![],
391    };"0 fingers")]
392    #[test_case(TouchpadEvent{
393        timestamp: zx::MonotonicInstant::ZERO,
394        pressed_buttons: vec![],
395        contacts: vec![
396            touch_contact(1, Position{x: 1.0, y: 1.0}),
397            touch_contact(2, Position{x: 5.0, y: 5.0}),
398        ],
399        filtered_palm_contacts: vec![],
400    };"2 fingers")]
401    #[fuchsia::test]
402    fn matched_contender_verify_event_mismatch(event: TouchpadEvent) {
403        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
404
405        let got = contender.verify_event(&event);
406        assert_matches!(got, VerifyEventResult::Mismatch(_));
407    }
408
409    #[test_case(TouchpadEvent{
410        timestamp: zx::MonotonicInstant::ZERO,
411        pressed_buttons: vec![],
412        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
413        filtered_palm_contacts: vec![],
414    };"finger hold")]
415    #[test_case(TouchpadEvent{
416        timestamp: zx::MonotonicInstant::ZERO,
417        pressed_buttons: vec![],
418        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
419        filtered_palm_contacts: vec![],
420    };"finger move")]
421    #[fuchsia::test]
422    fn matched_contender_verify_event_matched_contender(event: TouchpadEvent) {
423        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
424
425        let got = contender.verify_event(&event);
426        assert_matches!(got, VerifyEventResult::MatchedContender(_));
427    }
428
429    #[fuchsia::test]
430    fn matched_contender_process_buffered_events() {
431        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
432
433        let got = contender.process_buffered_events(vec![
434            TouchpadEvent {
435                timestamp: zx::MonotonicInstant::from_nanos(1),
436                pressed_buttons: vec![],
437                contacts: vec![touch_contact(1, Position { x: 1.0, y: 1.0 })],
438                filtered_palm_contacts: vec![],
439            },
440            TouchpadEvent {
441                timestamp: zx::MonotonicInstant::from_nanos(2),
442                pressed_buttons: vec![],
443                contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
444                filtered_palm_contacts: vec![],
445            },
446        ]);
447
448        assert_eq!(
449            got.generated_events,
450            vec![MouseEvent {
451                timestamp: zx::MonotonicInstant::from_nanos(2),
452                mouse_data: mouse_binding::MouseEvent::new(
453                    mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
454                        millimeters: Position { x: 4.0, y: 5.0 },
455                    }),
456                    /* wheel_delta_v= */ None,
457                    /* wheel_delta_h= */ None,
458                    mouse_binding::MousePhase::Move,
459                    /* affected_buttons= */ hashset! {},
460                    /* pressed_buttons= */ hashset! {},
461                    /* is_precision_scroll= */ None,
462                    /* wake_lease= */ None,
463                ),
464            },]
465        );
466        assert_eq!(got.recognized_gesture, RecognizedGesture::Motion);
467    }
468
469    #[fuchsia::test]
470    fn winner_process_new_event_end_gesture_none() {
471        let winner: Box<dyn gesture_arena::Winner> =
472            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
473        let event = TouchpadEvent {
474            timestamp: zx::MonotonicInstant::ZERO,
475            pressed_buttons: vec![],
476            contacts: vec![],
477            filtered_palm_contacts: vec![],
478        };
479        let got = winner.process_new_event(event);
480
481        assert_matches!(got, ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, _reason));
482    }
483
484    #[test_case(
485        TouchpadEvent{
486            timestamp: zx::MonotonicInstant::ZERO,
487            pressed_buttons: vec![1],
488            contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
489            filtered_palm_contacts: vec![],
490        };"button down")]
491    #[test_case(
492        TouchpadEvent{
493            timestamp: zx::MonotonicInstant::ZERO,
494            pressed_buttons: vec![],
495            contacts: vec![
496                touch_contact(1, Position{x: 1.0, y: 1.0}),
497                touch_contact(2, Position{x: 5.0, y: 5.0}),
498            ],
499            filtered_palm_contacts: vec![],
500        };"2 fingers")]
501    #[fuchsia::test]
502    fn winner_process_new_event_end_gesture_some(event: TouchpadEvent) {
503        let winner: Box<dyn gesture_arena::Winner> =
504            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
505        let got = winner.process_new_event(event);
506
507        assert_matches!(
508            got,
509            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _reason)
510        );
511    }
512
513    #[test_case(
514        TouchpadEvent{
515            timestamp: zx::MonotonicInstant::from_nanos(2),
516            pressed_buttons: vec![],
517            contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
518            filtered_palm_contacts: vec![],
519        },
520        Position {x:0.0, y:0.0}; "finger hold")]
521    #[test_case(
522        TouchpadEvent{
523            timestamp: zx::MonotonicInstant::from_nanos(2),
524            pressed_buttons: vec![],
525            contacts: vec![touch_contact(1, Position{x: 5.0, y: 6.0})],
526            filtered_palm_contacts: vec![],
527        },
528        Position {x:4.0, y:5.0};"finger moved")]
529    #[fuchsia::test]
530    fn winner_process_new_event_continue_gesture(event: TouchpadEvent, want_position: Position) {
531        let winner: Box<dyn gesture_arena::Winner> =
532            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
533        let got = winner.process_new_event(event);
534
535        // This not able to use `assert_eq` or `assert_matches` because:
536        // - assert_matches: floating point is not allow in match.
537        // - assert_eq: `ContinueGesture` has Box dyn type.
538        match got {
539            ProcessNewEventResult::EndGesture(..) => {
540                panic!("Got {:?}, want ContinueGesture()", got);
541            }
542            ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
543                pretty_assertions::assert_eq!(
544                    got_mouse_event.unwrap(),
545                    MouseEvent {
546                        timestamp: zx::MonotonicInstant::from_nanos(2),
547                        mouse_data: mouse_binding::MouseEvent {
548                            location: mouse_binding::MouseLocation::Relative(
549                                mouse_binding::RelativeLocation { millimeters: want_position }
550                            ),
551                            wheel_delta_v: None,
552                            wheel_delta_h: None,
553                            phase: mouse_binding::MousePhase::Move,
554                            affected_buttons: hashset! {},
555                            pressed_buttons: hashset! {},
556                            is_precision_scroll: None,
557                            wake_lease: None.into(),
558                        },
559                    }
560                );
561            }
562        }
563    }
564
565    #[fuchsia::test]
566    fn winner_process_new_event_continue_multiple_gestures() {
567        let mut winner: Box<dyn gesture_arena::Winner> =
568            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
569        let event = TouchpadEvent {
570            timestamp: zx::MonotonicInstant::from_nanos(2),
571            pressed_buttons: vec![],
572            contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
573            filtered_palm_contacts: vec![],
574        };
575        let got = winner.process_new_event(event);
576
577        // This not able to use `assert_eq` or `assert_matches` because:
578        // - assert_matches: floating point is not allow in match.
579        // - assert_eq: `ContinueGesture` has Box dyn type.
580        match got {
581            ProcessNewEventResult::EndGesture(..) => {
582                panic!("Got {:?}, want ContinueGesture()", got);
583            }
584            ProcessNewEventResult::ContinueGesture(got_mouse_event, got_winner) => {
585                pretty_assertions::assert_eq!(
586                    got_mouse_event.unwrap(),
587                    MouseEvent {
588                        timestamp: zx::MonotonicInstant::from_nanos(2),
589                        mouse_data: mouse_binding::MouseEvent {
590                            location: mouse_binding::MouseLocation::Relative(
591                                mouse_binding::RelativeLocation {
592                                    millimeters: Position { x: 4.0, y: 5.0 },
593                                }
594                            ),
595                            wheel_delta_v: None,
596                            wheel_delta_h: None,
597                            phase: mouse_binding::MousePhase::Move,
598                            affected_buttons: hashset! {},
599                            pressed_buttons: hashset! {},
600                            is_precision_scroll: None,
601                            wake_lease: None.into(),
602                        },
603                    }
604                );
605
606                winner = got_winner;
607            }
608        }
609
610        let event = TouchpadEvent {
611            timestamp: zx::MonotonicInstant::from_nanos(3),
612            pressed_buttons: vec![],
613            contacts: vec![touch_contact(1, Position { x: 7.0, y: 9.0 })],
614            filtered_palm_contacts: vec![],
615        };
616        let got = winner.process_new_event(event);
617
618        // This not able to use `assert_eq` or `assert_matches` because:
619        // - assert_matches: floating point is not allow in match.
620        // - assert_eq: `ContinueGesture` has Box dyn type.
621        match got {
622            ProcessNewEventResult::EndGesture(..) => {
623                panic!("Got {:?}, want ContinueGesture()", got)
624            }
625            ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
626                pretty_assertions::assert_eq!(
627                    got_mouse_event.unwrap(),
628                    MouseEvent {
629                        timestamp: zx::MonotonicInstant::from_nanos(3),
630                        mouse_data: mouse_binding::MouseEvent {
631                            location: mouse_binding::MouseLocation::Relative(
632                                mouse_binding::RelativeLocation {
633                                    millimeters: Position { x: 2.0, y: 3.0 },
634                                }
635                            ),
636                            wheel_delta_v: None,
637                            wheel_delta_h: None,
638                            phase: mouse_binding::MousePhase::Move,
639                            affected_buttons: hashset! {},
640                            pressed_buttons: hashset! {},
641                            is_precision_scroll: None,
642                            wake_lease: None.into(),
643                        },
644                    }
645                );
646            }
647        }
648    }
649}