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::{MovementDetail, movement_from_events};
11use crate::mouse_binding::{self, MouseButton};
12use crate::utils::{Position, euclidean_distance};
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            /* wake_lease= */ None,
502        ),
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use crate::touch_binding;
510    use assert_matches::assert_matches;
511    use test_case::test_case;
512
513    fn make_touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
514        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
515    }
516
517    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM: f32 = 10.0;
518    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM: f32 = 20.0;
519    const BUTTON_CHANGE_STATE_TIMEOUT: zx::MonotonicDuration =
520        zx::MonotonicDuration::from_seconds(1);
521
522    #[test_case(TouchpadEvent{
523        timestamp: zx::MonotonicInstant::ZERO,
524        pressed_buttons: vec![],
525        contacts: vec![],
526        filtered_palm_contacts: vec![],
527    };"0 fingers")]
528    #[test_case(TouchpadEvent{
529        timestamp: zx::MonotonicInstant::ZERO,
530        pressed_buttons: vec![],
531        contacts: vec![
532            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
533            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
534        ],
535        filtered_palm_contacts: vec![],
536    };"2 fingers")]
537    #[fuchsia::test]
538    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
539        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
540            spurious_to_intentional_motion_threshold_mm:
541                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
542            spurious_to_intentional_motion_threshold_button_change_mm:
543                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
544            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
545        });
546
547        let got = contender.examine_event(&event);
548        assert_matches!(got, ExamineEventResult::Mismatch(_));
549    }
550
551    #[fuchsia::test]
552    fn initial_contender_examine_event_finger_contact_contender() {
553        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
554            spurious_to_intentional_motion_threshold_mm:
555                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
556            spurious_to_intentional_motion_threshold_button_change_mm:
557                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
558            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
559        });
560        let event = TouchpadEvent {
561            timestamp: zx::MonotonicInstant::ZERO,
562            pressed_buttons: vec![],
563            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
564            filtered_palm_contacts: vec![],
565        };
566
567        let got = contender.examine_event(&event);
568        assert_matches!(got, ExamineEventResult::Contender(_));
569    }
570
571    #[fuchsia::test]
572    fn initial_contender_examine_event_matched_contender() {
573        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
574            spurious_to_intentional_motion_threshold_mm:
575                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
576            spurious_to_intentional_motion_threshold_button_change_mm:
577                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
578            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
579        });
580        let event = TouchpadEvent {
581            timestamp: zx::MonotonicInstant::ZERO,
582            pressed_buttons: vec![1],
583            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
584            filtered_palm_contacts: vec![],
585        };
586
587        let got = contender.examine_event(&event);
588        assert_matches!(got, ExamineEventResult::MatchedContender(_));
589    }
590
591    #[test_case(TouchpadEvent{
592      timestamp: zx::MonotonicInstant::ZERO,
593      pressed_buttons: vec![],
594      contacts: vec![],
595      filtered_palm_contacts: vec![],
596    };"0 fingers")]
597    #[test_case(TouchpadEvent{
598      timestamp: zx::MonotonicInstant::ZERO,
599      pressed_buttons: vec![],
600      contacts: vec![
601          make_touch_contact(1, Position{x: 1.0, y: 1.0}),
602          make_touch_contact(2, Position{x: 5.0, y: 5.0}),
603      ],
604      filtered_palm_contacts: vec![],
605    };"2 fingers")]
606    #[test_case(TouchpadEvent{
607      timestamp: zx::MonotonicInstant::ZERO,
608      pressed_buttons: vec![],
609      contacts: vec![
610          make_touch_contact(1, Position{x: 10.0, y: 1.0}),
611      ],
612      filtered_palm_contacts: vec![],
613    };"1 fingers move more than threshold")]
614    #[fuchsia::test]
615    fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
616        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
617            spurious_to_intentional_motion_threshold_mm:
618                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
619            spurious_to_intentional_motion_threshold_button_change_mm:
620                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
621            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
622            initial_position: Position { x: 0.0, y: 0.0 },
623        });
624
625        let got = contender.examine_event(&event);
626        assert_matches!(got, ExamineEventResult::Mismatch(_));
627    }
628
629    #[test_case(TouchpadEvent{
630      timestamp: zx::MonotonicInstant::ZERO,
631      pressed_buttons: vec![],
632      contacts: vec![
633          make_touch_contact(1, Position{x: 9.0, y: 1.0}),
634      ],
635      filtered_palm_contacts: vec![],
636    };"1 fingers move less than threshold")]
637    #[test_case(TouchpadEvent{
638      timestamp: zx::MonotonicInstant::ZERO,
639      pressed_buttons: vec![],
640      contacts: vec![
641          make_touch_contact(1, Position{x: 0.0, y: 0.0}),
642      ],
643      filtered_palm_contacts: vec![],
644    };"1 fingers stay")]
645    #[fuchsia::test]
646    fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
647        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
648            spurious_to_intentional_motion_threshold_mm:
649                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
650            spurious_to_intentional_motion_threshold_button_change_mm:
651                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
652            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
653            initial_position: Position { x: 0.0, y: 0.0 },
654        });
655
656        let got = contender.examine_event(&event);
657        assert_matches!(got, ExamineEventResult::Contender(_));
658    }
659
660    #[fuchsia::test]
661    fn finger_contact_contender_examine_event_matched_contender() {
662        let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
663            spurious_to_intentional_motion_threshold_mm:
664                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
665            spurious_to_intentional_motion_threshold_button_change_mm:
666                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
667            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
668            initial_position: Position { x: 0.0, y: 0.0 },
669        });
670        let event = TouchpadEvent {
671            timestamp: zx::MonotonicInstant::ZERO,
672            pressed_buttons: vec![1],
673            contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
674            filtered_palm_contacts: vec![],
675        };
676
677        let got = contender.examine_event(&event);
678        assert_matches!(got, ExamineEventResult::MatchedContender(_));
679    }
680
681    #[fuchsia::test]
682    fn matched_contender_process_buffered_events() {
683        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {
684            spurious_to_intentional_motion_threshold_mm:
685                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
686            spurious_to_intentional_motion_threshold_button_change_mm:
687                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
688            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
689            pressed_event: TouchpadEvent {
690                timestamp: zx::MonotonicInstant::from_nanos(41),
691                pressed_buttons: vec![1],
692                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
693                filtered_palm_contacts: vec![],
694            },
695        });
696        let events = vec![
697            TouchpadEvent {
698                timestamp: zx::MonotonicInstant::ZERO,
699                pressed_buttons: vec![],
700                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
701                filtered_palm_contacts: vec![],
702            },
703            TouchpadEvent {
704                timestamp: zx::MonotonicInstant::from_nanos(41),
705                pressed_buttons: vec![1],
706                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
707                filtered_palm_contacts: vec![],
708            },
709        ];
710
711        let got = contender.process_buffered_events(events);
712
713        assert_matches!(got, ProcessBufferedEventsResult{
714          generated_events,
715          winner: Some(winner),
716          recognized_gesture: RecognizedGesture::OneButtonDown,
717        } => {
718          pretty_assertions::assert_eq!(generated_events, vec![
719            MouseEvent {
720              timestamp:zx::MonotonicInstant::from_nanos(41),
721              mouse_data: mouse_binding::MouseEvent::new(
722                  mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
723                      millimeters: Position { x: 0.0, y: 0.0 },
724                  }),
725                  /* wheel_delta_v= */ None,
726                  /* wheel_delta_h= */ None,
727                  mouse_binding::MousePhase::Down,
728                  /* affected_buttons= */ hashset!{1},
729                  /* pressed_buttons= */ hashset!{1},
730                  /* is_precision_scroll= */ None,
731                  /* wake_lease= */ None,
732              ),
733            }
734          ]);
735          pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonDownWinner");
736        });
737    }
738
739    #[test_case(TouchpadEvent{
740        timestamp: zx::MonotonicInstant::from_nanos(41),
741        pressed_buttons: vec![],
742        contacts: vec![
743            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
744            ],
745        filtered_palm_contacts: vec![],
746    };"button release")]
747    #[test_case(TouchpadEvent{
748        timestamp: zx::MonotonicInstant::from_nanos(41),
749        pressed_buttons: vec![],
750        contacts: vec![
751            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
752        ],
753        filtered_palm_contacts: vec![],
754    };"move less than threshold in edge state")]
755    #[test_case(TouchpadEvent{
756        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
757        pressed_buttons: vec![],
758        contacts: vec![
759            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
760        ],
761        filtered_palm_contacts: vec![],
762    };"move less than threshold out of edge state")]
763    #[test_case(TouchpadEvent{
764        timestamp: zx::MonotonicInstant::from_nanos(41),
765        pressed_buttons: vec![],
766        contacts: vec![
767            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
768        ],
769        filtered_palm_contacts: vec![],
770    };"move more than threshold in edge state and release button")]
771    #[test_case(TouchpadEvent{
772        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
773        pressed_buttons: vec![],
774        contacts: vec![
775            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
776        ],
777        filtered_palm_contacts: vec![],
778    };"move more than threshold out of edge state and release button")]
779    #[fuchsia::test]
780    fn button_down_winner_button_up(event: TouchpadEvent) {
781        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
782            spurious_to_intentional_motion_threshold_mm:
783                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
784            spurious_to_intentional_motion_threshold_button_change_mm:
785                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
786            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
787            pressed_event: TouchpadEvent {
788                timestamp: zx::MonotonicInstant::ZERO,
789                pressed_buttons: vec![1],
790                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
791                filtered_palm_contacts: vec![],
792            },
793        });
794
795        let got = winner.process_new_event(event);
796        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner) => {
797            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
798                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
799                    millimeters: Position { x: 0.0, y: 0.0 },
800                }),
801                /* wheel_delta_v= */ None,
802                /* wheel_delta_h= */ None,
803                mouse_binding::MousePhase::Up,
804                /* affected_buttons= */ hashset!{1},
805                /* pressed_buttons= */ hashset!{},
806                /* is_precision_scroll= */ None,
807                /* wake_lease= */ None,
808            ));
809            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
810        });
811    }
812
813    #[test_case(TouchpadEvent{
814        timestamp: zx::MonotonicInstant::from_nanos(41),
815        pressed_buttons: vec![1],
816        contacts: vec![
817            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
818        ],
819        filtered_palm_contacts: vec![],
820    };"move less than threshold in edge state")]
821    #[test_case(TouchpadEvent{
822        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
823        pressed_buttons: vec![1],
824        contacts: vec![
825            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
826        ],
827        filtered_palm_contacts: vec![],
828    };"move less than threshold out of edge state")]
829    #[test_case(TouchpadEvent{
830        timestamp: zx::MonotonicInstant::from_nanos(41),
831        pressed_buttons: vec![1],
832        contacts: vec![
833            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
834            make_touch_contact(2, Position{x: 1.0, y: 2.0}),
835        ],
836        filtered_palm_contacts: vec![],
837    };"add more finger")]
838    #[fuchsia::test]
839    fn button_down_winner_continue(event: TouchpadEvent) {
840        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
841            spurious_to_intentional_motion_threshold_mm:
842                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
843            spurious_to_intentional_motion_threshold_button_change_mm:
844                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
845            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
846            pressed_event: TouchpadEvent {
847                timestamp: zx::MonotonicInstant::ZERO,
848                pressed_buttons: vec![1],
849                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
850                filtered_palm_contacts: vec![],
851            },
852        });
853
854        let got = winner.process_new_event(event);
855        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
856            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonDownWinner");
857        });
858    }
859
860    #[test_case(TouchpadEvent{
861        timestamp: zx::MonotonicInstant::from_nanos(41),
862        pressed_buttons: vec![1],
863        contacts: vec![
864            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
865        ],
866        filtered_palm_contacts: vec![],
867    };"move more than threshold in edge state")]
868    #[test_case(TouchpadEvent{
869        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
870        pressed_buttons: vec![1],
871        contacts: vec![
872            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
873        ],
874        filtered_palm_contacts: vec![],
875    };"move more than threshold out of edge state")]
876    #[fuchsia::test]
877    fn button_down_winner_drag_winner_continue(event: TouchpadEvent) {
878        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
879            spurious_to_intentional_motion_threshold_mm:
880                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
881            spurious_to_intentional_motion_threshold_button_change_mm:
882                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
883            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
884            pressed_event: TouchpadEvent {
885                timestamp: zx::MonotonicInstant::ZERO,
886                pressed_buttons: vec![1],
887                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
888                filtered_palm_contacts: vec![],
889            },
890        });
891
892        let got = winner.process_new_event(event);
893        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
894            pretty_assertions::assert_eq!(mouse_data.phase, mouse_binding::MousePhase::Move);
895            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
896        });
897    }
898
899    #[test_case(TouchpadEvent{
900        timestamp: zx::MonotonicInstant::from_nanos(41),
901        pressed_buttons: vec![],
902        contacts: vec![
903            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
904        ],
905        filtered_palm_contacts: vec![],
906    };"button release")]
907    #[test_case(TouchpadEvent{
908        timestamp: zx::MonotonicInstant::from_nanos(41),
909        pressed_buttons: vec![],
910        contacts: vec![
911            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
912        ],
913        filtered_palm_contacts: vec![],
914    };"move and button release")]
915    #[fuchsia::test]
916    fn drag_winner_button_up(event: TouchpadEvent) {
917        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
918            spurious_to_intentional_motion_threshold_button_change_mm:
919                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
920            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
921            last_event: TouchpadEvent {
922                timestamp: zx::MonotonicInstant::ZERO,
923                pressed_buttons: vec![1],
924                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
925                filtered_palm_contacts: vec![],
926            },
927        });
928
929        let got = winner.process_new_event(event);
930        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner) => {
931            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
932                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
933                    millimeters: Position { x: 0.0, y: 0.0 },
934                }),
935                /* wheel_delta_v= */ None,
936                /* wheel_delta_h= */ None,
937                mouse_binding::MousePhase::Up,
938                /* affected_buttons= */ hashset!{1},
939                /* pressed_buttons= */ hashset!{},
940                /* is_precision_scroll= */ None,
941                /* wake_lease= */ None,
942            ));
943            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
944        });
945    }
946
947    #[fuchsia::test]
948    fn drag_winner_continue() {
949        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
950            spurious_to_intentional_motion_threshold_button_change_mm:
951                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
952            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
953            last_event: TouchpadEvent {
954                timestamp: zx::MonotonicInstant::ZERO,
955                pressed_buttons: vec![1],
956                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
957                filtered_palm_contacts: vec![],
958            },
959        });
960
961        let event = TouchpadEvent {
962            timestamp: zx::MonotonicInstant::from_nanos(41),
963            pressed_buttons: vec![1],
964            contacts: vec![make_touch_contact(1, Position { x: 19.0, y: 1.0 })],
965            filtered_palm_contacts: vec![],
966        };
967
968        let got = winner.process_new_event(event);
969        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
970            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
971                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
972                    millimeters: Position { x: 19.0, y: 1.0 },
973                }),
974                /* wheel_delta_v= */ None,
975                /* wheel_delta_h= */ None,
976                mouse_binding::MousePhase::Move,
977                /* affected_buttons= */ hashset!{},
978                /* pressed_buttons= */ hashset!{1},
979                /* is_precision_scroll= */ None,
980                /* wake_lease= */ None,
981            ));
982            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
983        });
984    }
985
986    #[fuchsia::test]
987    fn drag_winner_continue_2_finger() {
988        let mut winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
989            spurious_to_intentional_motion_threshold_button_change_mm:
990                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
991            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
992            last_event: TouchpadEvent {
993                timestamp: zx::MonotonicInstant::ZERO,
994                pressed_buttons: vec![1],
995                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
996                filtered_palm_contacts: vec![],
997            },
998        });
999
1000        let event = TouchpadEvent {
1001            timestamp: zx::MonotonicInstant::from_nanos(41),
1002            pressed_buttons: vec![1],
1003            contacts: vec![
1004                make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1005                make_touch_contact(2, Position { x: 0.0, y: 5.0 }),
1006            ],
1007            filtered_palm_contacts: vec![],
1008        };
1009
1010        let got = winner.process_new_event(event);
1011        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
1012            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
1013                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1014                    millimeters: Position { x: 0.0, y: 0.0 },
1015                }),
1016                /* wheel_delta_v= */ None,
1017                /* wheel_delta_h= */ None,
1018                mouse_binding::MousePhase::Move,
1019                /* affected_buttons= */ hashset!{},
1020                /* pressed_buttons= */ hashset!{1},
1021                /* is_precision_scroll= */ None,
1022                /* wake_lease= */ None,
1023            ));
1024            winner = got_winner;
1025            pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
1026        });
1027
1028        let event = TouchpadEvent {
1029            timestamp: zx::MonotonicInstant::from_nanos(41),
1030            pressed_buttons: vec![1],
1031            contacts: vec![
1032                make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1033                make_touch_contact(2, Position { x: 19.0, y: 5.0 }),
1034            ],
1035            filtered_palm_contacts: vec![],
1036        };
1037
1038        let got = winner.process_new_event(event);
1039        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(MouseEvent {mouse_data, ..}), got_winner)=>{
1040            pretty_assertions::assert_eq!(mouse_data, mouse_binding::MouseEvent::new(
1041                mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1042                    millimeters: Position { x: 19.0, y: 0.0 },
1043                }),
1044                /* wheel_delta_v= */ None,
1045                /* wheel_delta_h= */ None,
1046                mouse_binding::MousePhase::Move,
1047                /* affected_buttons= */ hashset!{},
1048                /* pressed_buttons= */ hashset!{1},
1049                /* is_precision_scroll= */ None,
1050                /* wake_lease= */ None,
1051            ));
1052            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::DragWinner");
1053        });
1054    }
1055
1056    #[fuchsia::test]
1057    fn button_up_winner_continue() {
1058        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1059            spurious_to_intentional_motion_threshold_button_change_mm:
1060                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1061            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1062            button_up_event: TouchpadEvent {
1063                timestamp: zx::MonotonicInstant::ZERO,
1064                pressed_buttons: vec![],
1065                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
1066                filtered_palm_contacts: vec![],
1067            },
1068        });
1069
1070        let event = TouchpadEvent {
1071            timestamp: zx::MonotonicInstant::from_nanos(41),
1072            pressed_buttons: vec![],
1073            contacts: vec![make_touch_contact(1, Position { x: 10.0, y: 1.0 })],
1074            filtered_palm_contacts: vec![],
1075        };
1076
1077        let got = winner.process_new_event(event);
1078        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1079            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::one_finger_button::ButtonUpWinner");
1080        });
1081    }
1082
1083    #[test_case(TouchpadEvent{
1084        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1_001),
1085        pressed_buttons: vec![],
1086        contacts: vec![
1087            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1088        ],
1089        filtered_palm_contacts: vec![],
1090    };"timeout")]
1091    #[test_case(TouchpadEvent{
1092        timestamp: zx::MonotonicInstant::from_nanos(41),
1093        pressed_buttons: vec![1],
1094        contacts: vec![
1095            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1096        ],
1097        filtered_palm_contacts: vec![],
1098    };"button down")]
1099    #[test_case(TouchpadEvent{
1100        timestamp: zx::MonotonicInstant::from_nanos(41),
1101        pressed_buttons: vec![],
1102        contacts: vec![
1103            make_touch_contact(1, Position{x: 21.0, y: 0.0}),
1104        ],
1105        filtered_palm_contacts: vec![],
1106    };"move more than threshold")]
1107    #[test_case(TouchpadEvent{
1108        timestamp: zx::MonotonicInstant::from_nanos(41),
1109        pressed_buttons: vec![],
1110        contacts: vec![
1111            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1112            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1113        ],
1114        filtered_palm_contacts: vec![],
1115    };"more contacts")]
1116    #[test_case(TouchpadEvent{
1117        timestamp: zx::MonotonicInstant::from_nanos(41),
1118        pressed_buttons: vec![],
1119        contacts: vec![],
1120        filtered_palm_contacts: vec![],
1121    };"no contact")]
1122    #[fuchsia::test]
1123    fn button_up_winner_end(event: TouchpadEvent) {
1124        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1125            spurious_to_intentional_motion_threshold_button_change_mm:
1126                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1127            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1128            button_up_event: TouchpadEvent {
1129                timestamp: zx::MonotonicInstant::ZERO,
1130                pressed_buttons: vec![],
1131                contacts: vec![make_touch_contact(1, Position { x: 0.0, y: 0.0 })],
1132                filtered_palm_contacts: vec![],
1133            },
1134        });
1135
1136        let got = winner.process_new_event(event);
1137        assert_matches!(
1138            got,
1139            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _)
1140        );
1141    }
1142}