input_pipeline/gestures/
one_finger_button.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, DetailedReasonUint, EndGestureEvent, ExamineEventResult, MouseEvent,
7    ProcessBufferedEventsResult, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
8    VerifyEventResult,
9};
10use super::utils::{movement_from_events, MovementDetail};
11use crate::mouse_binding::{self, MouseButton};
12use crate::utils::{euclidean_distance, Position};
13
14use maplit::hashset;
15use std::collections::HashSet;
16
17/// The initial state of this recognizer, before a click has been detected.
18#[derive(Debug)]
19pub(super) struct InitialContender {
20    /// The threshold to detect motion.
21    pub(super) spurious_to_intentional_motion_threshold_mm: f32,
22
23    /// Use a larger threshold to detect motion on the edge of contact-button down,
24    /// button down-button up
25    pub(super) spurious_to_intentional_motion_threshold_button_change_mm: f32,
26
27    /// The timeout of the edge of contact-button down, button down-button up,
28    /// The recognizer will leave edge state either timeout or motion detected.
29    pub(super) button_change_state_timeout: zx::MonotonicDuration,
30}
31
32/// The state when this recognizer has detected a single finger, but not a
33/// button press or release.
34#[derive(Debug)]
35struct FingerContactContender {
36    /// The threshold to detect motion.
37    spurious_to_intentional_motion_threshold_mm: f32,
38
39    /// Use a larger threshold to detect motion on the edge of contact-button down,
40    /// button down-button up
41    spurious_to_intentional_motion_threshold_button_change_mm: f32,
42
43    /// The timeout of the edge of contact-button down, button down-button up,
44    /// The recognizer will leave edge state either timeout or motion detected.
45    button_change_state_timeout: zx::MonotonicDuration,
46
47    /// The position of the initial single touch contact.
48    initial_position: Position,
49}
50
51/// The state when this recognizer has detected a button down, but the gesture arena
52/// has not declared this recognizer the winner.
53#[derive(Debug)]
54struct MatchedContender {
55    /// The threshold to detect motion.
56    spurious_to_intentional_motion_threshold_mm: f32,
57
58    /// Use a larger threshold to detect motion on the edge of contact-button down,
59    /// button down-button up
60    spurious_to_intentional_motion_threshold_button_change_mm: f32,
61
62    /// The timeout of the edge of contact-button down, button down-button up,
63    /// The recognizer will leave edge state either timeout or motion detected.
64    button_change_state_timeout: zx::MonotonicDuration,
65
66    /// The TouchpadEvent when a button was first pressed.
67    pressed_event: TouchpadEvent,
68}
69
70/// The state when this recognizer has won the contest.
71#[derive(Debug)]
72struct ButtonDownWinner {
73    /// The threshold to detect motion.
74    spurious_to_intentional_motion_threshold_mm: f32,
75
76    /// Use a larger threshold to detect motion on the edge of contact-button down,
77    /// button down-button up
78    spurious_to_intentional_motion_threshold_button_change_mm: f32,
79
80    /// The timeout of the edge of contact-button down, button down-button up,
81    /// The recognizer will leave edge state either timeout or motion detected.
82    button_change_state_timeout: zx::MonotonicDuration,
83
84    /// The TouchpadEvent when a button was first pressed.
85    pressed_event: TouchpadEvent,
86}
87
88/// The state when ButtonDownWinner got motion more than threshold to recognize as
89/// drag gesture.
90#[derive(Debug)]
91struct DragWinner {
92    /// Use a larger threshold to detect motion on the edge of contact-button down,
93    /// button down-button up
94    spurious_to_intentional_motion_threshold_button_change_mm: f32,
95
96    /// The timeout of the edge of contact-button down, button down-button up,
97    /// The recognizer will leave edge state either timeout or motion detected.
98    button_change_state_timeout: zx::MonotonicDuration,
99
100    /// The last TouchpadEvent.
101    last_event: TouchpadEvent,
102}
103
104/// The state when ButtonDownWinner / DragWinner got button up, this winner is
105/// used to discard tailing movement from button up.
106#[derive(Debug)]
107struct ButtonUpWinner {
108    /// Use a larger threshold to detect motion on the edge of contact-button down,
109    /// button down-button up
110    spurious_to_intentional_motion_threshold_button_change_mm: f32,
111
112    /// The timeout of the edge of contact-button down, button down-button up,
113    /// The recognizer will leave edge state either timeout or motion detected.
114    button_change_state_timeout: zx::MonotonicDuration,
115
116    /// The button up event.
117    button_up_event: TouchpadEvent,
118}
119
120impl InitialContender {
121    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
122    fn into_finger_contact_contender(
123        self: Box<Self>,
124        initial_position: Position,
125    ) -> Box<dyn gesture_arena::Contender> {
126        Box::new(FingerContactContender {
127            spurious_to_intentional_motion_threshold_mm: self
128                .spurious_to_intentional_motion_threshold_mm,
129            spurious_to_intentional_motion_threshold_button_change_mm: self
130                .spurious_to_intentional_motion_threshold_button_change_mm,
131            button_change_state_timeout: self.button_change_state_timeout,
132            initial_position,
133        })
134    }
135
136    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
137    fn into_matched_contender(
138        self: Box<Self>,
139        pressed_event: TouchpadEvent,
140    ) -> Box<dyn gesture_arena::MatchedContender> {
141        Box::new(MatchedContender {
142            spurious_to_intentional_motion_threshold_mm: self
143                .spurious_to_intentional_motion_threshold_mm,
144            spurious_to_intentional_motion_threshold_button_change_mm: self
145                .spurious_to_intentional_motion_threshold_button_change_mm,
146            button_change_state_timeout: self.button_change_state_timeout,
147            pressed_event,
148        })
149    }
150}
151
152impl gesture_arena::Contender for InitialContender {
153    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
154        let num_contacts = event.contacts.len();
155        if num_contacts != 1 {
156            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
157                criterion: "num_contacts",
158                min: Some(1),
159                max: Some(1),
160                actual: num_contacts,
161            }));
162        }
163
164        let num_pressed_buttons = event.pressed_buttons.len();
165        match num_pressed_buttons {
166            0 => ExamineEventResult::Contender(
167                self.into_finger_contact_contender(position_from_event(event)),
168            ),
169            1 => ExamineEventResult::MatchedContender(self.into_matched_contender(event.clone())),
170            // More than one button is a Mismatch for now, but in practice
171            // we do not expect to receive more than one button from the
172            // touchpad driver.
173            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
174                criterion: "num_pressed_buttons",
175                min: Some(0),
176                max: Some(1),
177                actual: num_pressed_buttons,
178            })),
179        }
180    }
181}
182
183impl FingerContactContender {
184    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
185    fn into_matched_contender(
186        self: Box<Self>,
187        pressed_event: TouchpadEvent,
188    ) -> Box<dyn gesture_arena::MatchedContender> {
189        Box::new(MatchedContender {
190            spurious_to_intentional_motion_threshold_mm: self
191                .spurious_to_intentional_motion_threshold_mm,
192            spurious_to_intentional_motion_threshold_button_change_mm: self
193                .spurious_to_intentional_motion_threshold_button_change_mm,
194            button_change_state_timeout: self.button_change_state_timeout,
195            pressed_event,
196        })
197    }
198}
199
200impl gesture_arena::Contender for FingerContactContender {
201    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
202        let num_contacts = event.contacts.len();
203        if num_contacts != 1 {
204            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
205                criterion: "num_contacts",
206                min: Some(1),
207                max: Some(1),
208                actual: num_contacts,
209            }));
210        }
211
212        let displacement_mm = euclidean_distance(position_from_event(event), self.initial_position);
213        if displacement_mm >= self.spurious_to_intentional_motion_threshold_mm {
214            return ExamineEventResult::Mismatch(Reason::DetailedFloat(DetailedReasonFloat {
215                criterion: "displacement_mm",
216                min: None,
217                max: Some(self.spurious_to_intentional_motion_threshold_mm),
218                actual: displacement_mm,
219            }));
220        }
221
222        let num_pressed_buttons = event.pressed_buttons.len();
223        match num_pressed_buttons {
224            0 => ExamineEventResult::Contender(self),
225            1 => ExamineEventResult::MatchedContender(self.into_matched_contender(event.clone())),
226            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
227                criterion: "num_pressed_buttons",
228                min: Some(0),
229                max: Some(1),
230                actual: num_pressed_buttons,
231            })),
232        }
233    }
234}
235
236impl MatchedContender {
237    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
238    fn into_button_down_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
239        Box::new(ButtonDownWinner {
240            spurious_to_intentional_motion_threshold_mm: self
241                .spurious_to_intentional_motion_threshold_mm,
242            spurious_to_intentional_motion_threshold_button_change_mm: self
243                .spurious_to_intentional_motion_threshold_button_change_mm,
244            button_change_state_timeout: self.button_change_state_timeout,
245            pressed_event: self.pressed_event,
246        })
247    }
248}
249
250impl gesture_arena::MatchedContender for MatchedContender {
251    fn verify_event(self: Box<Self>, _event: &TouchpadEvent) -> VerifyEventResult {
252        // This verify_event expected not call because all other recognizers
253        // should exit on 1 finger button down.
254
255        log::error!("Unexpected MatchedContender::verify_event() called");
256
257        VerifyEventResult::MatchedContender(self)
258    }
259
260    fn process_buffered_events(
261        self: Box<Self>,
262        _events: Vec<TouchpadEvent>,
263    ) -> ProcessBufferedEventsResult {
264        // all small motion before button down are ignored.
265        ProcessBufferedEventsResult {
266            generated_events: vec![touchpad_event_to_mouse_down_event(&self.pressed_event)],
267            winner: Some(self.into_button_down_winner()),
268            recognized_gesture: RecognizedGesture::OneButtonDown,
269        }
270    }
271}
272
273impl ButtonDownWinner {
274    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
275    fn into_drag_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
276        Box::new(DragWinner {
277            spurious_to_intentional_motion_threshold_button_change_mm: self
278                .spurious_to_intentional_motion_threshold_button_change_mm,
279            button_change_state_timeout: self.button_change_state_timeout,
280            last_event: self.pressed_event,
281        })
282    }
283
284    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
285    fn into_button_up(
286        self: Box<Self>,
287        button_up_event: TouchpadEvent,
288    ) -> Box<dyn gesture_arena::Winner> {
289        Box::new(ButtonUpWinner {
290            spurious_to_intentional_motion_threshold_button_change_mm: self
291                .spurious_to_intentional_motion_threshold_button_change_mm,
292            button_change_state_timeout: self.button_change_state_timeout,
293            button_up_event,
294        })
295    }
296}
297
298impl gesture_arena::Winner for ButtonDownWinner {
299    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
300        let motion_threshold =
301            if event.timestamp - self.pressed_event.timestamp > self.button_change_state_timeout {
302                self.spurious_to_intentional_motion_threshold_mm
303            } else {
304                self.spurious_to_intentional_motion_threshold_button_change_mm
305            };
306
307        let num_pressed_buttons = event.pressed_buttons.len();
308        match num_pressed_buttons {
309            // All small motion before button up, and motion in button up event
310            // are ignored.
311            0 => ProcessNewEventResult::ContinueGesture(
312                Some(touchpad_event_to_mouse_up_event(&event)),
313                self.into_button_up(event),
314            ),
315            1 => {
316                // Check for drag (button held, with sufficient contact movement).
317                let MovementDetail { euclidean_distance, movement: _ } =
318                    movement_from_events(&self.pressed_event, &event);
319
320                if euclidean_distance > motion_threshold {
321                    let drag_winner = self.into_drag_winner();
322                    return drag_winner.process_new_event(event);
323                }
324                ProcessNewEventResult::ContinueGesture(None, self)
325            }
326            // Also wait for the button release to complete the click or drag gesture.
327            // this should never happens unless there is a touchpad has more than 1 button.
328            _ => ProcessNewEventResult::ContinueGesture(None, self),
329        }
330    }
331}
332
333impl DragWinner {
334    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
335    fn into_drag_winner(
336        self: Box<Self>,
337        last_event: TouchpadEvent,
338    ) -> Box<dyn gesture_arena::Winner> {
339        Box::new(DragWinner {
340            spurious_to_intentional_motion_threshold_button_change_mm: self
341                .spurious_to_intentional_motion_threshold_button_change_mm,
342            button_change_state_timeout: self.button_change_state_timeout,
343            last_event,
344        })
345    }
346
347    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
348    fn into_button_up(
349        self: Box<Self>,
350        button_up_event: TouchpadEvent,
351    ) -> Box<dyn gesture_arena::Winner> {
352        Box::new(ButtonUpWinner {
353            spurious_to_intentional_motion_threshold_button_change_mm: self
354                .spurious_to_intentional_motion_threshold_button_change_mm,
355            button_change_state_timeout: self.button_change_state_timeout,
356            button_up_event,
357        })
358    }
359}
360
361impl gesture_arena::Winner for DragWinner {
362    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
363        let num_pressed_buttons = event.pressed_buttons.len();
364        match num_pressed_buttons {
365            // TODO(https://fxbug.dev/42175500): may want to handle contact > 1 with different logic.
366            // Motion in button up event is ignored.
367            0 => ProcessNewEventResult::ContinueGesture(
368                Some(touchpad_event_to_mouse_up_event(&event)),
369                self.into_button_up(event),
370            ),
371            _ => {
372                // More than 2 button should never happens unless there is a touchpad has
373                // more than 1 button. Just treat this same with 1 button down.
374                ProcessNewEventResult::ContinueGesture(
375                    Some(touchpad_event_to_mouse_drag_event(&self.last_event, &event)),
376                    self.into_drag_winner(event),
377                )
378            }
379        }
380    }
381}
382
383impl gesture_arena::Winner for ButtonUpWinner {
384    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
385        // Fingers leave or add to surface should end the ButtonUpWinner.
386        let num_contacts = event.contacts.len();
387        if num_contacts != 1 {
388            return ProcessNewEventResult::EndGesture(
389                EndGestureEvent::UnconsumedEvent(event),
390                Reason::DetailedUint(DetailedReasonUint {
391                    criterion: "num_contacts",
392                    min: Some(1),
393                    max: Some(1),
394                    actual: num_contacts,
395                }),
396            );
397        }
398
399        // Button change should end the ButtonUpWinner.
400        let num_pressed_buttons = event.pressed_buttons.len();
401        if num_pressed_buttons != 0 {
402            return ProcessNewEventResult::EndGesture(
403                EndGestureEvent::UnconsumedEvent(event),
404                Reason::DetailedUint(DetailedReasonUint {
405                    criterion: "num_buttons",
406                    min: Some(0),
407                    max: Some(0),
408                    actual: num_pressed_buttons,
409                }),
410            );
411        }
412
413        // Events after timeout should not be discarded by ButtonUpWinner.
414        if event.timestamp - self.button_up_event.timestamp > self.button_change_state_timeout {
415            return ProcessNewEventResult::EndGesture(
416                EndGestureEvent::UnconsumedEvent(event),
417                Reason::Basic("button_up_timeout"),
418            );
419        }
420
421        // Events move more than threshold should end the ButtonUpWinner.
422        let MovementDetail { euclidean_distance, movement: _ } =
423            movement_from_events(&self.button_up_event, &event);
424        if euclidean_distance > self.spurious_to_intentional_motion_threshold_button_change_mm {
425            return ProcessNewEventResult::EndGesture(
426                EndGestureEvent::UnconsumedEvent(event),
427                Reason::DetailedFloat(DetailedReasonFloat {
428                    criterion: "displacement_mm",
429                    min: None,
430                    max: Some(self.spurious_to_intentional_motion_threshold_button_change_mm),
431                    actual: euclidean_distance,
432                }),
433            );
434        }
435
436        // Discard this event.
437        ProcessNewEventResult::ContinueGesture(None, self)
438    }
439}
440
441/// This function returns the position associated with a TouchpadEvent that is
442/// assumed to have a single associated TouchContact.
443fn position_from_event(event: &TouchpadEvent) -> Position {
444    event.contacts[0].position
445}
446
447fn touchpad_event_to_mouse_down_event(event: &TouchpadEvent) -> MouseEvent {
448    make_mouse_event(
449        event.timestamp,
450        Position::zero(),
451        mouse_binding::MousePhase::Down,
452        hashset! {1},
453        hashset! {1},
454    )
455}
456
457fn touchpad_event_to_mouse_up_event(event: &TouchpadEvent) -> MouseEvent {
458    make_mouse_event(
459        event.timestamp,
460        Position::zero(),
461        mouse_binding::MousePhase::Up,
462        hashset! {1},
463        hashset! {},
464    )
465}
466
467fn touchpad_event_to_mouse_drag_event(
468    last_event: &TouchpadEvent,
469    event: &TouchpadEvent,
470) -> MouseEvent {
471    let MovementDetail { movement, euclidean_distance: _ } =
472        movement_from_events(last_event, event);
473    make_mouse_event(
474        event.timestamp,
475        movement,
476        mouse_binding::MousePhase::Move,
477        hashset! {},
478        hashset! {1},
479    )
480}
481
482fn make_mouse_event(
483    timestamp: zx::MonotonicInstant,
484    movement_in_mm: Position,
485    phase: mouse_binding::MousePhase,
486    affected_buttons: HashSet<MouseButton>,
487    pressed_buttons: HashSet<MouseButton>,
488) -> MouseEvent {
489    MouseEvent {
490        timestamp,
491        mouse_data: mouse_binding::MouseEvent::new(
492            mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
493                millimeters: movement_in_mm,
494            }),
495            /* wheel_delta_v= */ None,
496            /* wheel_delta_h= */ None,
497            phase,
498            affected_buttons,
499            pressed_buttons,
500            /* is_precision_scroll= */ None,
501        ),
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use crate::touch_binding;
509    use assert_matches::assert_matches;
510    use test_case::test_case;
511
512    fn make_touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
513        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
514    }
515
516    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM: f32 = 10.0;
517    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM: f32 = 20.0;
518    const BUTTON_CHANGE_STATE_TIMEOUT: zx::MonotonicDuration =
519        zx::MonotonicDuration::from_seconds(1);
520
521    #[test_case(TouchpadEvent{
522        timestamp: zx::MonotonicInstant::ZERO,
523        pressed_buttons: vec![],
524        contacts: vec![],
525        filtered_palm_contacts: vec![],
526    };"0 fingers")]
527    #[test_case(TouchpadEvent{
528        timestamp: zx::MonotonicInstant::ZERO,
529        pressed_buttons: vec![],
530        contacts: vec![
531            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
532            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
533        ],
534        filtered_palm_contacts: vec![],
535    };"2 fingers")]
536    #[fuchsia::test]
537    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
538        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
539            spurious_to_intentional_motion_threshold_mm:
540                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
541            spurious_to_intentional_motion_threshold_button_change_mm:
542                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
543            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
544        });
545
546        let got = contender.examine_event(&event);
547        assert_matches!(got, ExamineEventResult::Mismatch(_));
548    }
549
550    #[fuchsia::test]
551    fn initial_contender_examine_event_finger_contact_contender() {
552        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
553            spurious_to_intentional_motion_threshold_mm:
554                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
555            spurious_to_intentional_motion_threshold_button_change_mm:
556                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
557            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
558        });
559        let event = TouchpadEvent {
560            timestamp: zx::MonotonicInstant::ZERO,
561            pressed_buttons: vec![],
562            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
563            filtered_palm_contacts: vec![],
564        };
565
566        let got = contender.examine_event(&event);
567        assert_matches!(got, ExamineEventResult::Contender(_));
568    }
569
570    #[fuchsia::test]
571    fn initial_contender_examine_event_matched_contender() {
572        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
573            spurious_to_intentional_motion_threshold_mm:
574                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
575            spurious_to_intentional_motion_threshold_button_change_mm:
576                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
577            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
578        });
579        let event = TouchpadEvent {
580            timestamp: zx::MonotonicInstant::ZERO,
581            pressed_buttons: vec![1],
582            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
583            filtered_palm_contacts: vec![],
584        };
585
586        let got = contender.examine_event(&event);
587        assert_matches!(got, ExamineEventResult::MatchedContender(_));
588    }
589
590    #[test_case(TouchpadEvent{
591      timestamp: zx::MonotonicInstant::ZERO,
592      pressed_buttons: vec![],
593      contacts: vec![],
594      filtered_palm_contacts: vec![],
595    };"0 fingers")]
596    #[test_case(TouchpadEvent{
597      timestamp: zx::MonotonicInstant::ZERO,
598      pressed_buttons: vec![],
599      contacts: vec![
600          make_touch_contact(1, Position{x: 1.0, y: 1.0}),
601          make_touch_contact(2, Position{x: 5.0, y: 5.0}),
602      ],
603      filtered_palm_contacts: vec![],
604    };"2 fingers")]
605    #[test_case(TouchpadEvent{
606      timestamp: zx::MonotonicInstant::ZERO,
607      pressed_buttons: vec![],
608      contacts: vec![
609          make_touch_contact(1, Position{x: 10.0, y: 1.0}),
610      ],
611      filtered_palm_contacts: vec![],
612    };"1 fingers move more than threshold")]
613    #[fuchsia::test]
614    fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
615        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
616            spurious_to_intentional_motion_threshold_mm:
617                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
618            spurious_to_intentional_motion_threshold_button_change_mm:
619                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
620            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
621            initial_position: Position { x: 0.0, y: 0.0 },
622        });
623
624        let got = contender.examine_event(&event);
625        assert_matches!(got, ExamineEventResult::Mismatch(_));
626    }
627
628    #[test_case(TouchpadEvent{
629      timestamp: zx::MonotonicInstant::ZERO,
630      pressed_buttons: vec![],
631      contacts: vec![
632          make_touch_contact(1, Position{x: 9.0, y: 1.0}),
633      ],
634      filtered_palm_contacts: vec![],
635    };"1 fingers move less than threshold")]
636    #[test_case(TouchpadEvent{
637      timestamp: zx::MonotonicInstant::ZERO,
638      pressed_buttons: vec![],
639      contacts: vec![
640          make_touch_contact(1, Position{x: 0.0, y: 0.0}),
641      ],
642      filtered_palm_contacts: vec![],
643    };"1 fingers stay")]
644    #[fuchsia::test]
645    fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
646        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
647            spurious_to_intentional_motion_threshold_mm:
648                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
649            spurious_to_intentional_motion_threshold_button_change_mm:
650                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
651            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
652            initial_position: Position { x: 0.0, y: 0.0 },
653        });
654
655        let got = contender.examine_event(&event);
656        assert_matches!(got, ExamineEventResult::Contender(_));
657    }
658
659    #[fuchsia::test]
660    fn finger_contact_contender_examine_event_matched_contender() {
661        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
662            spurious_to_intentional_motion_threshold_mm:
663                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
664            spurious_to_intentional_motion_threshold_button_change_mm:
665                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
666            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
667            initial_position: Position { x: 0.0, y: 0.0 },
668        });
669        let event = TouchpadEvent {
670            timestamp: zx::MonotonicInstant::ZERO,
671            pressed_buttons: vec![1],
672            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
673            filtered_palm_contacts: vec![],
674        };
675
676        let got = contender.examine_event(&event);
677        assert_matches!(got, ExamineEventResult::MatchedContender(_));
678    }
679
680    #[fuchsia::test]
681    fn matched_contender_process_buffered_events() {
682        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {
683            spurious_to_intentional_motion_threshold_mm:
684                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
685            spurious_to_intentional_motion_threshold_button_change_mm:
686                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
687            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
688            pressed_event: TouchpadEvent {
689                timestamp: zx::MonotonicInstant::from_nanos(41),
690                pressed_buttons: vec![1],
691                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
692                filtered_palm_contacts: vec![],
693            },
694        });
695        let events = vec![
696            TouchpadEvent {
697                timestamp: zx::MonotonicInstant::ZERO,
698                pressed_buttons: vec![],
699                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
700                filtered_palm_contacts: vec![],
701            },
702            TouchpadEvent {
703                timestamp: zx::MonotonicInstant::from_nanos(41),
704                pressed_buttons: vec![1],
705                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
706                filtered_palm_contacts: vec![],
707            },
708        ];
709
710        let got = contender.process_buffered_events(events);
711
712        assert_matches!(got, ProcessBufferedEventsResult{
713          generated_events,
714          winner: Some(winner),
715          recognized_gesture: RecognizedGesture::OneButtonDown,
716        } => {
717          pretty_assertions::assert_eq!(generated_events, vec![
718            MouseEvent {
719              timestamp:zx::MonotonicInstant::from_nanos(41),
720              mouse_data: mouse_binding::MouseEvent::new(
721                  mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
722                      millimeters: Position { x: 0.0, y: 0.0 },
723                  }),
724                  /* wheel_delta_v= */ None,
725                  /* wheel_delta_h= */ None,
726                  mouse_binding::MousePhase::Down,
727                  /* affected_buttons= */ hashset!{1},
728                  /* pressed_buttons= */ hashset!{1},
729                  /* is_precision_scroll= */ None,
730              ),
731            }
732          ]);
733          pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonDownWinner");
734        });
735    }
736
737    #[test_case(TouchpadEvent{
738        timestamp: zx::MonotonicInstant::from_nanos(41),
739        pressed_buttons: vec![],
740        contacts: vec![
741            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
742            ],
743        filtered_palm_contacts: vec![],
744    };"button release")]
745    #[test_case(TouchpadEvent{
746        timestamp: zx::MonotonicInstant::from_nanos(41),
747        pressed_buttons: vec![],
748        contacts: vec![
749            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
750        ],
751        filtered_palm_contacts: vec![],
752    };"move less than threshold in edge state")]
753    #[test_case(TouchpadEvent{
754        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
755        pressed_buttons: vec![],
756        contacts: vec![
757            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
758        ],
759        filtered_palm_contacts: vec![],
760    };"move less than threshold out of edge state")]
761    #[test_case(TouchpadEvent{
762        timestamp: zx::MonotonicInstant::from_nanos(41),
763        pressed_buttons: vec![],
764        contacts: vec![
765            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
766        ],
767        filtered_palm_contacts: vec![],
768    };"move more than threshold in edge state and release button")]
769    #[test_case(TouchpadEvent{
770        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
771        pressed_buttons: vec![],
772        contacts: vec![
773            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
774        ],
775        filtered_palm_contacts: vec![],
776    };"move more than threshold out of edge state and release button")]
777    #[fuchsia::test]
778    fn button_down_winner_button_up(event: TouchpadEvent) {
779        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
780            spurious_to_intentional_motion_threshold_mm:
781                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
782            spurious_to_intentional_motion_threshold_button_change_mm:
783                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
784            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
785            pressed_event: TouchpadEvent {
786                timestamp: zx::MonotonicInstant::ZERO,
787                pressed_buttons: vec![1],
788                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
789                filtered_palm_contacts: vec![],
790            },
791        });
792
793        let got = winner.process_new_event(event);
794        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner) => {
795            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
796                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
797                    millimeters: Position { x: 0.0, y: 0.0 },
798                }),
799                /* wheel_delta_v= */ None,
800                /* wheel_delta_h= */ None,
801                mouse_binding::MousePhase::Up,
802                /* affected_buttons= */ hashset!{1},
803                /* pressed_buttons= */ hashset!{},
804                /* is_precision_scroll= */ None,
805            ));
806            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
807        });
808    }
809
810    #[test_case(TouchpadEvent{
811        timestamp: zx::MonotonicInstant::from_nanos(41),
812        pressed_buttons: vec![1],
813        contacts: vec![
814            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
815        ],
816        filtered_palm_contacts: vec![],
817    };"move less than threshold in edge state")]
818    #[test_case(TouchpadEvent{
819        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
820        pressed_buttons: vec![1],
821        contacts: vec![
822            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
823        ],
824        filtered_palm_contacts: vec![],
825    };"move less than threshold out of edge state")]
826    #[test_case(TouchpadEvent{
827        timestamp: zx::MonotonicInstant::from_nanos(41),
828        pressed_buttons: vec![1],
829        contacts: vec![
830            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
831            make_touch_contact(2, Position{x: 1.0, y: 2.0}),
832        ],
833        filtered_palm_contacts: vec![],
834    };"add more finger")]
835    #[fuchsia::test]
836    fn button_down_winner_continue(event: TouchpadEvent) {
837        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
838            spurious_to_intentional_motion_threshold_mm:
839                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
840            spurious_to_intentional_motion_threshold_button_change_mm:
841                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
842            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
843            pressed_event: TouchpadEvent {
844                timestamp: zx::MonotonicInstant::ZERO,
845                pressed_buttons: vec![1],
846                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
847                filtered_palm_contacts: vec![],
848            },
849        });
850
851        let got = winner.process_new_event(event);
852        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
853            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonDownWinner");
854        });
855    }
856
857    #[test_case(TouchpadEvent{
858        timestamp: zx::MonotonicInstant::from_nanos(41),
859        pressed_buttons: vec![1],
860        contacts: vec![
861            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
862        ],
863        filtered_palm_contacts: vec![],
864    };"move more than threshold in edge state")]
865    #[test_case(TouchpadEvent{
866        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
867        pressed_buttons: vec![1],
868        contacts: vec![
869            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
870        ],
871        filtered_palm_contacts: vec![],
872    };"move more than threshold out of edge state")]
873    #[fuchsia::test]
874    fn button_down_winner_drag_winner_continue(event: TouchpadEvent) {
875        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
876            spurious_to_intentional_motion_threshold_mm:
877                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
878            spurious_to_intentional_motion_threshold_button_change_mm:
879                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
880            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
881            pressed_event: TouchpadEvent {
882                timestamp: zx::MonotonicInstant::ZERO,
883                pressed_buttons: vec![1],
884                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
885                filtered_palm_contacts: vec![],
886            },
887        });
888
889        let got = winner.process_new_event(event);
890        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
891            pretty_assertions::assert_eq!(mouse_data.phase, mouse_binding::MousePhase::Move);
892            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
893        });
894    }
895
896    #[test_case(TouchpadEvent{
897        timestamp: zx::MonotonicInstant::from_nanos(41),
898        pressed_buttons: vec![],
899        contacts: vec![
900            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
901        ],
902        filtered_palm_contacts: vec![],
903    };"button release")]
904    #[test_case(TouchpadEvent{
905        timestamp: zx::MonotonicInstant::from_nanos(41),
906        pressed_buttons: vec![],
907        contacts: vec![
908            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
909        ],
910        filtered_palm_contacts: vec![],
911    };"move and button release")]
912    #[fuchsia::test]
913    fn drag_winner_button_up(event: TouchpadEvent) {
914        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
915            spurious_to_intentional_motion_threshold_button_change_mm:
916                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
917            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
918            last_event: TouchpadEvent {
919                timestamp: zx::MonotonicInstant::ZERO,
920                pressed_buttons: vec![1],
921                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
922                filtered_palm_contacts: vec![],
923            },
924        });
925
926        let got = winner.process_new_event(event);
927        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner) => {
928            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
929                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
930                    millimeters: Position { x: 0.0, y: 0.0 },
931                }),
932                /* wheel_delta_v= */ None,
933                /* wheel_delta_h= */ None,
934                mouse_binding::MousePhase::Up,
935                /* affected_buttons= */ hashset!{1},
936                /* pressed_buttons= */ hashset!{},
937                /* is_precision_scroll= */ None,
938            ));
939            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
940        });
941    }
942
943    #[fuchsia::test]
944    fn drag_winner_continue() {
945        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
946            spurious_to_intentional_motion_threshold_button_change_mm:
947                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
948            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
949            last_event: TouchpadEvent {
950                timestamp: zx::MonotonicInstant::ZERO,
951                pressed_buttons: vec![1],
952                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
953                filtered_palm_contacts: vec![],
954            },
955        });
956
957        let event = TouchpadEvent {
958            timestamp: zx::MonotonicInstant::from_nanos(41),
959            pressed_buttons: vec![1],
960            contacts: vec![make_touch_contact(1, Position { x: 19.0, y: 1.0 })],
961            filtered_palm_contacts: vec![],
962        };
963
964        let got = winner.process_new_event(event);
965        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
966            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
967                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
968                    millimeters: Position { x: 19.0, y: 1.0 },
969                }),
970                /* wheel_delta_v= */ None,
971                /* wheel_delta_h= */ None,
972                mouse_binding::MousePhase::Move,
973                /* affected_buttons= */ hashset!{},
974                /* pressed_buttons= */ hashset!{1},
975                /* is_precision_scroll= */ None,
976            ));
977            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
978        });
979    }
980
981    #[fuchsia::test]
982    fn drag_winner_continue_2_finger() {
983        let mut winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
984            spurious_to_intentional_motion_threshold_button_change_mm:
985                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
986            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
987            last_event: TouchpadEvent {
988                timestamp: zx::MonotonicInstant::ZERO,
989                pressed_buttons: vec![1],
990                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
991                filtered_palm_contacts: vec![],
992            },
993        });
994
995        let event = TouchpadEvent {
996            timestamp: zx::MonotonicInstant::from_nanos(41),
997            pressed_buttons: vec![1],
998            contacts: vec![
999                make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1000                make_touch_contact(2, Position { x: 0.0, y: 5.0 }),
1001            ],
1002            filtered_palm_contacts: vec![],
1003        };
1004
1005        let got = winner.process_new_event(event);
1006        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
1007            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
1008                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1009                    millimeters: Position { x: 0.0, y: 0.0 },
1010                }),
1011                /* wheel_delta_v= */ None,
1012                /* wheel_delta_h= */ None,
1013                mouse_binding::MousePhase::Move,
1014                /* affected_buttons= */ hashset!{},
1015                /* pressed_buttons= */ hashset!{1},
1016                /* is_precision_scroll= */ None,
1017            ));
1018            winner = got_winner;
1019            pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
1020        });
1021
1022        let event = TouchpadEvent {
1023            timestamp: zx::MonotonicInstant::from_nanos(41),
1024            pressed_buttons: vec![1],
1025            contacts: vec![
1026                make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1027                make_touch_contact(2, Position { x: 19.0, y: 5.0 }),
1028            ],
1029            filtered_palm_contacts: vec![],
1030        };
1031
1032        let got = winner.process_new_event(event);
1033        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
1034            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
1035                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1036                    millimeters: Position { x: 19.0, y: 0.0 },
1037                }),
1038                /* wheel_delta_v= */ None,
1039                /* wheel_delta_h= */ None,
1040                mouse_binding::MousePhase::Move,
1041                /* affected_buttons= */ hashset!{},
1042                /* pressed_buttons= */ hashset!{1},
1043                /* is_precision_scroll= */ None,
1044            ));
1045            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
1046        });
1047    }
1048
1049    #[fuchsia::test]
1050    fn button_up_winner_continue() {
1051        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1052            spurious_to_intentional_motion_threshold_button_change_mm:
1053                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1054            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1055            button_up_event: TouchpadEvent {
1056                timestamp: zx::MonotonicInstant::ZERO,
1057                pressed_buttons: vec![],
1058                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
1059                filtered_palm_contacts: vec![],
1060            },
1061        });
1062
1063        let event = TouchpadEvent {
1064            timestamp: zx::MonotonicInstant::from_nanos(41),
1065            pressed_buttons: vec![],
1066            contacts: vec![make_touch_contact(1, Position { x: 10.0, y: 1.0 })],
1067            filtered_palm_contacts: vec![],
1068        };
1069
1070        let got = winner.process_new_event(event);
1071        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1072            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
1073        });
1074    }
1075
1076    #[test_case(TouchpadEvent{
1077        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1_001),
1078        pressed_buttons: vec![],
1079        contacts: vec![
1080            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1081        ],
1082        filtered_palm_contacts: vec![],
1083    };"timeout")]
1084    #[test_case(TouchpadEvent{
1085        timestamp: zx::MonotonicInstant::from_nanos(41),
1086        pressed_buttons: vec![1],
1087        contacts: vec![
1088            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1089        ],
1090        filtered_palm_contacts: vec![],
1091    };"button down")]
1092    #[test_case(TouchpadEvent{
1093        timestamp: zx::MonotonicInstant::from_nanos(41),
1094        pressed_buttons: vec![],
1095        contacts: vec![
1096            make_touch_contact(1, Position{x: 21.0, y: 0.0}),
1097        ],
1098        filtered_palm_contacts: vec![],
1099    };"move more than threshold")]
1100    #[test_case(TouchpadEvent{
1101        timestamp: zx::MonotonicInstant::from_nanos(41),
1102        pressed_buttons: vec![],
1103        contacts: vec![
1104            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1105            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1106        ],
1107        filtered_palm_contacts: vec![],
1108    };"more contacts")]
1109    #[test_case(TouchpadEvent{
1110        timestamp: zx::MonotonicInstant::from_nanos(41),
1111        pressed_buttons: vec![],
1112        contacts: vec![],
1113        filtered_palm_contacts: vec![],
1114    };"no contact")]
1115    #[fuchsia::test]
1116    fn button_up_winner_end(event: TouchpadEvent) {
1117        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1118            spurious_to_intentional_motion_threshold_button_change_mm:
1119                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1120            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1121            button_up_event: TouchpadEvent {
1122                timestamp: zx::MonotonicInstant::ZERO,
1123                pressed_buttons: vec![],
1124                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
1125                filtered_palm_contacts: vec![],
1126            },
1127        });
1128
1129        let got = winner.process_new_event(event);
1130        assert_matches!(
1131            got,
1132            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _)
1133        );
1134    }
1135}