Skip to main content

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 [a, b] in events.array_windows() {
162            mouse_events.push(touchpad_event_to_mouse_motion_event(&a.contacts[0].position, b));
163        }
164
165        ProcessBufferedEventsResult {
166            generated_events: mouse_events,
167            winner: Some(self.into_winner(last_position)),
168            recognized_gesture: RecognizedGesture::Motion,
169        }
170    }
171}
172
173impl gesture_arena::Winner for Winner {
174    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
175        match u8::try_from(event.contacts.len()).unwrap_or(u8::MAX) {
176            0 => ProcessNewEventResult::EndGesture(
177                EndGestureEvent::NoEvent,
178                Reason::DetailedUint(DetailedReasonUint {
179                    criterion: "num_contacts",
180                    min: Some(1),
181                    max: Some(1),
182                    actual: 0,
183                }),
184            ),
185            1 => {
186                let num_pressed_buttons = event.pressed_buttons.len();
187                if num_pressed_buttons > 0 {
188                    ProcessNewEventResult::EndGesture(
189                        EndGestureEvent::UnconsumedEvent(event),
190                        Reason::DetailedUint(DetailedReasonUint {
191                            criterion: "num_pressed_buttons",
192                            min: None,
193                            max: Some(0),
194                            actual: num_pressed_buttons,
195                        }),
196                    )
197                } else {
198                    let last_position = event.contacts[0].position.clone();
199                    ProcessNewEventResult::ContinueGesture(
200                        Some(touchpad_event_to_mouse_motion_event(&self.last_position, &event)),
201                        Box::new(Winner { last_position }),
202                    )
203                }
204            }
205            num_contacts @ 2.. => ProcessNewEventResult::EndGesture(
206                EndGestureEvent::UnconsumedEvent(event),
207                Reason::DetailedUint(DetailedReasonUint {
208                    criterion: "num_contacts",
209                    min: Some(1),
210                    max: Some(1),
211                    actual: usize::from(num_contacts),
212                }),
213            ),
214        }
215    }
216}
217
218fn touchpad_event_to_mouse_motion_event(
219    last_position: &Position,
220    event: &TouchpadEvent,
221) -> MouseEvent {
222    MouseEvent {
223        timestamp: event.timestamp,
224        mouse_data: mouse_binding::MouseEvent::new(
225            mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
226                millimeters: Position {
227                    x: event.contacts[0].position.x - last_position.x,
228                    y: event.contacts[0].position.y - last_position.y,
229                },
230            }),
231            /* wheel_delta_v= */ None,
232            /* wheel_delta_h= */ None,
233            mouse_binding::MousePhase::Move,
234            /* affected_buttons= */ hashset! {},
235            /* pressed_buttons= */ hashset! {},
236            /* is_precision_scroll= */ None,
237            /* wake_lease= */ None,
238        ),
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use crate::touch_binding;
246    use assert_matches::assert_matches;
247
248    use pretty_assertions::assert_eq;
249    use test_case::test_case;
250
251    fn touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
252        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
253    }
254
255    #[test_case(TouchpadEvent{
256        timestamp: zx::MonotonicInstant::ZERO,
257        pressed_buttons: vec![1],
258        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
259        filtered_palm_contacts: vec![],
260    };"button down")]
261    #[test_case(TouchpadEvent{
262        timestamp: zx::MonotonicInstant::ZERO,
263        pressed_buttons: vec![],
264        contacts: vec![],
265        filtered_palm_contacts: vec![],
266    };"0 fingers")]
267    #[test_case(TouchpadEvent{
268        timestamp: zx::MonotonicInstant::ZERO,
269        pressed_buttons: vec![],
270        contacts: vec![
271            touch_contact(1, Position{x: 1.0, y: 1.0}),
272            touch_contact(2, Position{x: 5.0, y: 5.0}),
273        ],
274        filtered_palm_contacts: vec![],
275    };"2 fingers")]
276    #[fuchsia::test]
277    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
278        let contender: Box<dyn gesture_arena::Contender> =
279            Box::new(InitialContender { min_movement_in_mm: 10.0 });
280
281        let got = contender.examine_event(&event);
282        assert_matches!(got, ExamineEventResult::Mismatch(_));
283    }
284
285    #[test_case(TouchpadEvent{
286        timestamp: zx::MonotonicInstant::ZERO,
287        pressed_buttons: vec![],
288        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
289        filtered_palm_contacts: vec![],
290    };"finger hold")]
291    #[test_case(TouchpadEvent{
292        timestamp: zx::MonotonicInstant::ZERO,
293        pressed_buttons: vec![],
294        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
295        filtered_palm_contacts: vec![],
296    };"finger moved")]
297    #[fuchsia::test]
298    fn initial_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
299        let contender: Box<dyn gesture_arena::Contender> =
300            Box::new(InitialContender { min_movement_in_mm: 10.0 });
301
302        let got = contender.examine_event(&event);
303        assert_matches!(got, ExamineEventResult::Contender(_));
304    }
305
306    #[test_case(TouchpadEvent{
307        timestamp: zx::MonotonicInstant::ZERO,
308        pressed_buttons: vec![1],
309        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
310        filtered_palm_contacts: vec![],
311    };"button down")]
312    #[test_case(TouchpadEvent{
313        timestamp: zx::MonotonicInstant::ZERO,
314        pressed_buttons: vec![],
315        contacts: vec![],
316        filtered_palm_contacts: vec![],
317    };"0 fingers")]
318    #[test_case(TouchpadEvent{
319        timestamp: zx::MonotonicInstant::ZERO,
320        pressed_buttons: vec![],
321        contacts: vec![
322            touch_contact(1, Position{x: 1.0, y: 1.0}),
323            touch_contact(2, Position{x: 5.0, y: 5.0}),
324            ],
325        filtered_palm_contacts: vec![],
326    };"2 fingers")]
327    #[fuchsia::test]
328    fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
329        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
330            min_movement_in_mm: 10.0,
331            initial_position: Position { x: 1.0, y: 1.0 },
332        });
333
334        let got = contender.examine_event(&event);
335        assert_matches!(got, ExamineEventResult::Mismatch(_));
336    }
337
338    #[test_case(TouchpadEvent{
339        timestamp: zx::MonotonicInstant::ZERO,
340        pressed_buttons: vec![],
341        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
342        filtered_palm_contacts: vec![],
343    };"finger hold")]
344    #[test_case(TouchpadEvent{
345        timestamp: zx::MonotonicInstant::ZERO,
346        pressed_buttons: vec![],
347        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
348        filtered_palm_contacts: vec![],
349    };"finger move less than threshold")]
350    #[fuchsia::test]
351    fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
352        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
353            min_movement_in_mm: 10.0,
354            initial_position: Position { x: 1.0, y: 1.0 },
355        });
356
357        let got = contender.examine_event(&event);
358        assert_matches!(got, ExamineEventResult::Contender(_));
359    }
360
361    #[fuchsia::test]
362    fn finger_contact_contender_examine_event_matched_contender() {
363        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
364            min_movement_in_mm: 10.0,
365            initial_position: Position { x: 1.0, y: 1.0 },
366        });
367        let event = TouchpadEvent {
368            timestamp: zx::MonotonicInstant::ZERO,
369            pressed_buttons: vec![],
370            contacts: vec![touch_contact(1, Position { x: 11.0, y: 12.0 })],
371            filtered_palm_contacts: vec![],
372        };
373        let got = contender.examine_event(&event);
374        assert_matches!(got, ExamineEventResult::MatchedContender(_));
375    }
376
377    #[test_case(TouchpadEvent{
378        timestamp: zx::MonotonicInstant::ZERO,
379        pressed_buttons: vec![1],
380        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
381        filtered_palm_contacts: vec![],
382    };"button down")]
383    #[test_case(TouchpadEvent{
384        timestamp: zx::MonotonicInstant::ZERO,
385        pressed_buttons: vec![],
386        contacts: vec![],
387        filtered_palm_contacts: vec![],
388    };"0 fingers")]
389    #[test_case(TouchpadEvent{
390        timestamp: zx::MonotonicInstant::ZERO,
391        pressed_buttons: vec![],
392        contacts: vec![
393            touch_contact(1, Position{x: 1.0, y: 1.0}),
394            touch_contact(2, Position{x: 5.0, y: 5.0}),
395        ],
396        filtered_palm_contacts: vec![],
397    };"2 fingers")]
398    #[fuchsia::test]
399    fn matched_contender_verify_event_mismatch(event: TouchpadEvent) {
400        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
401
402        let got = contender.verify_event(&event);
403        assert_matches!(got, VerifyEventResult::Mismatch(_));
404    }
405
406    #[test_case(TouchpadEvent{
407        timestamp: zx::MonotonicInstant::ZERO,
408        pressed_buttons: vec![],
409        contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
410        filtered_palm_contacts: vec![],
411    };"finger hold")]
412    #[test_case(TouchpadEvent{
413        timestamp: zx::MonotonicInstant::ZERO,
414        pressed_buttons: vec![],
415        contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
416        filtered_palm_contacts: vec![],
417    };"finger move")]
418    #[fuchsia::test]
419    fn matched_contender_verify_event_matched_contender(event: TouchpadEvent) {
420        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
421
422        let got = contender.verify_event(&event);
423        assert_matches!(got, VerifyEventResult::MatchedContender(_));
424    }
425
426    #[fuchsia::test]
427    fn matched_contender_process_buffered_events() {
428        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
429
430        let got = contender.process_buffered_events(vec![
431            TouchpadEvent {
432                timestamp: zx::MonotonicInstant::from_nanos(1),
433                pressed_buttons: vec![],
434                contacts: vec![touch_contact(1, Position { x: 1.0, y: 1.0 })],
435                filtered_palm_contacts: vec![],
436            },
437            TouchpadEvent {
438                timestamp: zx::MonotonicInstant::from_nanos(2),
439                pressed_buttons: vec![],
440                contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
441                filtered_palm_contacts: vec![],
442            },
443        ]);
444
445        assert_eq!(
446            got.generated_events,
447            vec![MouseEvent {
448                timestamp: zx::MonotonicInstant::from_nanos(2),
449                mouse_data: mouse_binding::MouseEvent::new(
450                    mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
451                        millimeters: Position { x: 4.0, y: 5.0 },
452                    }),
453                    /* wheel_delta_v= */ None,
454                    /* wheel_delta_h= */ None,
455                    mouse_binding::MousePhase::Move,
456                    /* affected_buttons= */ hashset! {},
457                    /* pressed_buttons= */ hashset! {},
458                    /* is_precision_scroll= */ None,
459                    /* wake_lease= */ None,
460                ),
461            },]
462        );
463        assert_eq!(got.recognized_gesture, RecognizedGesture::Motion);
464    }
465
466    #[fuchsia::test]
467    fn winner_process_new_event_end_gesture_none() {
468        let winner: Box<dyn gesture_arena::Winner> =
469            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
470        let event = TouchpadEvent {
471            timestamp: zx::MonotonicInstant::ZERO,
472            pressed_buttons: vec![],
473            contacts: vec![],
474            filtered_palm_contacts: vec![],
475        };
476        let got = winner.process_new_event(event);
477
478        assert_matches!(got, ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, _reason));
479    }
480
481    #[test_case(
482        TouchpadEvent{
483            timestamp: zx::MonotonicInstant::ZERO,
484            pressed_buttons: vec![1],
485            contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
486            filtered_palm_contacts: vec![],
487        };"button down")]
488    #[test_case(
489        TouchpadEvent{
490            timestamp: zx::MonotonicInstant::ZERO,
491            pressed_buttons: vec![],
492            contacts: vec![
493                touch_contact(1, Position{x: 1.0, y: 1.0}),
494                touch_contact(2, Position{x: 5.0, y: 5.0}),
495            ],
496            filtered_palm_contacts: vec![],
497        };"2 fingers")]
498    #[fuchsia::test]
499    fn winner_process_new_event_end_gesture_some(event: TouchpadEvent) {
500        let winner: Box<dyn gesture_arena::Winner> =
501            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
502        let got = winner.process_new_event(event);
503
504        assert_matches!(
505            got,
506            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _reason)
507        );
508    }
509
510    #[test_case(
511        TouchpadEvent{
512            timestamp: zx::MonotonicInstant::from_nanos(2),
513            pressed_buttons: vec![],
514            contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
515            filtered_palm_contacts: vec![],
516        },
517        Position {x:0.0, y:0.0}; "finger hold")]
518    #[test_case(
519        TouchpadEvent{
520            timestamp: zx::MonotonicInstant::from_nanos(2),
521            pressed_buttons: vec![],
522            contacts: vec![touch_contact(1, Position{x: 5.0, y: 6.0})],
523            filtered_palm_contacts: vec![],
524        },
525        Position {x:4.0, y:5.0};"finger moved")]
526    #[fuchsia::test]
527    fn winner_process_new_event_continue_gesture(event: TouchpadEvent, want_position: Position) {
528        let winner: Box<dyn gesture_arena::Winner> =
529            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
530        let got = winner.process_new_event(event);
531
532        // This not able to use `assert_eq` or `assert_matches` because:
533        // - assert_matches: floating point is not allow in match.
534        // - assert_eq: `ContinueGesture` has Box dyn type.
535        match got {
536            ProcessNewEventResult::EndGesture(..) => {
537                panic!("Got {:?}, want ContinueGesture()", got);
538            }
539            ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
540                pretty_assertions::assert_eq!(
541                    got_mouse_event.unwrap(),
542                    MouseEvent {
543                        timestamp: zx::MonotonicInstant::from_nanos(2),
544                        mouse_data: mouse_binding::MouseEvent {
545                            location: mouse_binding::MouseLocation::Relative(
546                                mouse_binding::RelativeLocation { millimeters: want_position }
547                            ),
548                            wheel_delta_v: None,
549                            wheel_delta_h: None,
550                            phase: mouse_binding::MousePhase::Move,
551                            affected_buttons: hashset! {},
552                            pressed_buttons: hashset! {},
553                            is_precision_scroll: None,
554                            wake_lease: None.into(),
555                        },
556                    }
557                );
558            }
559        }
560    }
561
562    #[fuchsia::test]
563    fn winner_process_new_event_continue_multiple_gestures() {
564        let mut winner: Box<dyn gesture_arena::Winner> =
565            Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
566        let event = TouchpadEvent {
567            timestamp: zx::MonotonicInstant::from_nanos(2),
568            pressed_buttons: vec![],
569            contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
570            filtered_palm_contacts: vec![],
571        };
572        let got = winner.process_new_event(event);
573
574        // This not able to use `assert_eq` or `assert_matches` because:
575        // - assert_matches: floating point is not allow in match.
576        // - assert_eq: `ContinueGesture` has Box dyn type.
577        match got {
578            ProcessNewEventResult::EndGesture(..) => {
579                panic!("Got {:?}, want ContinueGesture()", got);
580            }
581            ProcessNewEventResult::ContinueGesture(got_mouse_event, got_winner) => {
582                pretty_assertions::assert_eq!(
583                    got_mouse_event.unwrap(),
584                    MouseEvent {
585                        timestamp: zx::MonotonicInstant::from_nanos(2),
586                        mouse_data: mouse_binding::MouseEvent {
587                            location: mouse_binding::MouseLocation::Relative(
588                                mouse_binding::RelativeLocation {
589                                    millimeters: Position { x: 4.0, y: 5.0 },
590                                }
591                            ),
592                            wheel_delta_v: None,
593                            wheel_delta_h: None,
594                            phase: mouse_binding::MousePhase::Move,
595                            affected_buttons: hashset! {},
596                            pressed_buttons: hashset! {},
597                            is_precision_scroll: None,
598                            wake_lease: None.into(),
599                        },
600                    }
601                );
602
603                winner = got_winner;
604            }
605        }
606
607        let event = TouchpadEvent {
608            timestamp: zx::MonotonicInstant::from_nanos(3),
609            pressed_buttons: vec![],
610            contacts: vec![touch_contact(1, Position { x: 7.0, y: 9.0 })],
611            filtered_palm_contacts: vec![],
612        };
613        let got = winner.process_new_event(event);
614
615        // This not able to use `assert_eq` or `assert_matches` because:
616        // - assert_matches: floating point is not allow in match.
617        // - assert_eq: `ContinueGesture` has Box dyn type.
618        match got {
619            ProcessNewEventResult::EndGesture(..) => {
620                panic!("Got {:?}, want ContinueGesture()", got)
621            }
622            ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
623                pretty_assertions::assert_eq!(
624                    got_mouse_event.unwrap(),
625                    MouseEvent {
626                        timestamp: zx::MonotonicInstant::from_nanos(3),
627                        mouse_data: mouse_binding::MouseEvent {
628                            location: mouse_binding::MouseLocation::Relative(
629                                mouse_binding::RelativeLocation {
630                                    millimeters: Position { x: 2.0, y: 3.0 },
631                                }
632                            ),
633                            wheel_delta_v: None,
634                            wheel_delta_h: None,
635                            phase: mouse_binding::MousePhase::Move,
636                            affected_buttons: hashset! {},
637                            pressed_buttons: hashset! {},
638                            is_precision_scroll: None,
639                            wake_lease: None.into(),
640                        },
641                    }
642                );
643            }
644        }
645    }
646}