input_pipeline/gestures/
secondary_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,
7    ProcessBufferedEventsResult, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
8    VerifyEventResult,
9};
10use super::utils::{movement_from_events, MovementDetail};
11use crate::mouse_binding::{MouseButton, MouseEvent, MouseLocation, MousePhase, RelativeLocation};
12use crate::utils::{euclidean_distance, Position};
13
14use maplit::hashset;
15use std::collections::HashSet;
16
17/// The initial state of this recognizer, before a secondary tap has been
18/// detected.
19#[derive(Debug)]
20pub(super) struct InitialContender {
21    /// The maximum displacement that a detected finger can withstand to still
22    /// be considered a secondary click. Measured in millimeters.
23    pub(super) spurious_to_intentional_motion_threshold_mm: f32,
24
25    /// Use a larger threshold to detect motion on the edge of contact-button down,
26    /// button down-button up
27    pub(super) spurious_to_intentional_motion_threshold_button_change_mm: f32,
28
29    /// The timeout of the edge of contact-button down, button down-button up,
30    /// The recognizer will leave edge state either timeout or motion detected.
31    pub(super) button_change_state_timeout: zx::MonotonicDuration,
32}
33
34/// The state when this recognizer has detected a single finger down.
35#[derive(Debug)]
36struct OneFingerContactContender {
37    /// The TouchpadEvent when a finger down was first detected.
38    one_finger_contact_event: TouchpadEvent,
39
40    /// The maximum displacement that a detected finger can withstand to still
41    /// be considered a tap. Measured in millimeters.
42    spurious_to_intentional_motion_threshold_mm: f32,
43
44    /// Use a larger threshold to detect motion on the edge of contact-button down,
45    /// button down-button up
46    spurious_to_intentional_motion_threshold_button_change_mm: f32,
47
48    /// The timeout of the edge of contact-button down, button down-button up,
49    /// The recognizer will leave edge state either timeout or motion detected.
50    button_change_state_timeout: zx::MonotonicDuration,
51}
52
53/// The state when this recognizer has detected two fingers down.
54#[derive(Debug)]
55struct TwoFingerContactContender {
56    /// The TouchpadEvent when two fingers were first detected.
57    two_finger_contact_event: TouchpadEvent,
58
59    /// The maximum displacement that a detected finger can withstand to still
60    /// be considered a tap. Measured in millimeters.
61    spurious_to_intentional_motion_threshold_mm: f32,
62
63    /// Use a larger threshold to detect motion on the edge of contact-button down,
64    /// button down-button up
65    spurious_to_intentional_motion_threshold_button_change_mm: f32,
66
67    /// The timeout of the edge of contact-button down, button down-button up,
68    /// The recognizer will leave edge state either timeout or motion detected.
69    button_change_state_timeout: zx::MonotonicDuration,
70}
71
72/// The state when this recognizer has detected a secondary button down, but the
73/// gesture arena has not declared this recognizer the winner.
74#[derive(Debug)]
75struct MatchedContender {
76    /// The TouchpadEvent when two fingers and button were first detected.
77    pressed_event: TouchpadEvent,
78
79    /// The maximum displacement that a detected finger can withstand to still
80    /// be considered a tap. Measured in millimeters.
81    spurious_to_intentional_motion_threshold_mm: f32,
82
83    /// Use a larger threshold to detect motion on the edge of contact-button down,
84    /// button down-button up
85    spurious_to_intentional_motion_threshold_button_change_mm: f32,
86
87    /// The timeout of the edge of contact-button down, button down-button up,
88    /// The recognizer will leave edge state either timeout or motion detected.
89    button_change_state_timeout: zx::MonotonicDuration,
90}
91
92/// The state when this recognizer has won the contest.
93#[derive(Debug)]
94struct ButtonDownWinner {
95    /// The TouchpadEvent when two fingers and button were first detected.
96    pressed_event: TouchpadEvent,
97
98    /// The threshold to detect motion.
99    spurious_to_intentional_motion_threshold_mm: f32,
100
101    /// Use a larger threshold to detect motion on the edge of contact-button down,
102    /// button down-button up
103    spurious_to_intentional_motion_threshold_button_change_mm: f32,
104
105    /// The timeout of the edge of contact-button down, button down-button up,
106    /// The recognizer will leave edge state either timeout or motion detected.
107    button_change_state_timeout: zx::MonotonicDuration,
108}
109
110/// The state when ButtonDownWinner got motion more than threshold to recognize as
111/// drag gesture.
112#[derive(Debug)]
113struct DragWinner {
114    /// The last TouchpadEvent.
115    last_event: TouchpadEvent,
116
117    /// Use a larger threshold to detect motion on the edge of contact-button down,
118    /// button down-button up
119    spurious_to_intentional_motion_threshold_button_change_mm: f32,
120
121    /// The timeout of the edge of contact-button down, button down-button up,
122    /// The recognizer will leave edge state either timeout or motion detected.
123    button_change_state_timeout: zx::MonotonicDuration,
124}
125
126/// The state when ButtonDownWinner / DragWinner got button up, this winner is
127/// used to discard tailing movement from button up.
128#[derive(Debug)]
129struct ButtonUpWinner {
130    /// The button up event.
131    button_up_event: TouchpadEvent,
132
133    /// Use a larger threshold to detect motion on the edge of contact-button down,
134    /// button down-button up
135    spurious_to_intentional_motion_threshold_button_change_mm: f32,
136
137    /// The timeout of the edge of contact-button down, button down-button up,
138    /// The recognizer will leave edge state either timeout or motion detected.
139    button_change_state_timeout: zx::MonotonicDuration,
140}
141
142impl InitialContender {
143    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
144    fn into_one_finger_contact_contender(
145        self: Box<Self>,
146        one_finger_contact_event: TouchpadEvent,
147    ) -> Box<dyn gesture_arena::Contender> {
148        Box::new(OneFingerContactContender {
149            one_finger_contact_event,
150            spurious_to_intentional_motion_threshold_mm: self
151                .spurious_to_intentional_motion_threshold_mm,
152            spurious_to_intentional_motion_threshold_button_change_mm: self
153                .spurious_to_intentional_motion_threshold_button_change_mm,
154            button_change_state_timeout: self.button_change_state_timeout,
155        })
156    }
157
158    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
159    fn into_two_finger_contacts_contender(
160        self: Box<Self>,
161        two_finger_contact_event: TouchpadEvent,
162    ) -> Box<dyn gesture_arena::Contender> {
163        Box::new(TwoFingerContactContender {
164            two_finger_contact_event,
165            spurious_to_intentional_motion_threshold_mm: self
166                .spurious_to_intentional_motion_threshold_mm,
167            spurious_to_intentional_motion_threshold_button_change_mm: self
168                .spurious_to_intentional_motion_threshold_button_change_mm,
169            button_change_state_timeout: self.button_change_state_timeout,
170        })
171    }
172
173    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
174    fn into_matched_contender(
175        self: Box<Self>,
176        pressed_event: TouchpadEvent,
177    ) -> Box<dyn gesture_arena::MatchedContender> {
178        Box::new(MatchedContender {
179            pressed_event,
180            spurious_to_intentional_motion_threshold_mm: self
181                .spurious_to_intentional_motion_threshold_mm,
182            spurious_to_intentional_motion_threshold_button_change_mm: self
183                .spurious_to_intentional_motion_threshold_button_change_mm,
184            button_change_state_timeout: self.button_change_state_timeout,
185        })
186    }
187}
188
189impl gesture_arena::Contender for InitialContender {
190    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
191        let num_pressed_buttons = event.pressed_buttons.len();
192
193        let num_contacts = event.contacts.len();
194        match num_contacts {
195            1 => {
196                if num_pressed_buttons != 0 {
197                    return ExamineEventResult::Mismatch(Reason::DetailedUint(
198                        DetailedReasonUint {
199                            criterion: "num_pressed_buttons",
200                            min: Some(0),
201                            max: Some(0),
202                            actual: num_pressed_buttons,
203                        },
204                    ));
205                }
206
207                ExamineEventResult::Contender(self.into_one_finger_contact_contender(event.clone()))
208            }
209            2 => {
210                match num_pressed_buttons {
211                    0 => ExamineEventResult::Contender(
212                        self.into_two_finger_contacts_contender(event.clone()),
213                    ),
214                    // Currently we don't have hardware with more than 1 button in
215                    // touchpad, ignore this case.
216                    _ => ExamineEventResult::MatchedContender(
217                        self.into_matched_contender(event.clone()),
218                    ),
219                }
220            }
221            0 | _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
222                criterion: "num_contacts",
223                min: Some(1),
224                max: Some(2),
225                actual: num_contacts,
226            })),
227        }
228    }
229}
230
231impl OneFingerContactContender {
232    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
233    fn into_two_finger_contacts_contender(
234        self: Box<Self>,
235        two_finger_contact_event: TouchpadEvent,
236    ) -> Box<dyn gesture_arena::Contender> {
237        Box::new(TwoFingerContactContender {
238            two_finger_contact_event,
239            spurious_to_intentional_motion_threshold_mm: self
240                .spurious_to_intentional_motion_threshold_mm,
241            spurious_to_intentional_motion_threshold_button_change_mm: self
242                .spurious_to_intentional_motion_threshold_button_change_mm,
243            button_change_state_timeout: self.button_change_state_timeout,
244        })
245    }
246}
247
248impl gesture_arena::Contender for OneFingerContactContender {
249    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
250        let num_pressed_buttons = event.pressed_buttons.len();
251        if num_pressed_buttons > 0 {
252            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
253                criterion: "num_pressed_buttons",
254                min: None,
255                max: Some(0),
256                actual: num_pressed_buttons,
257            }));
258        }
259
260        let num_contacts = event.contacts.len();
261
262        match num_contacts {
263            1 => {
264                let displacement_mm = euclidean_distance(
265                    position_from_event(event, 0),
266                    position_from_event(&self.one_finger_contact_event, 0),
267                );
268                if displacement_mm >= self.spurious_to_intentional_motion_threshold_mm {
269                    return ExamineEventResult::Mismatch(Reason::DetailedFloat(
270                        DetailedReasonFloat {
271                            criterion: "displacement_mm",
272                            min: None,
273                            max: Some(self.spurious_to_intentional_motion_threshold_mm),
274                            actual: displacement_mm,
275                        },
276                    ));
277                }
278                ExamineEventResult::Contender(self)
279            }
280            2 => {
281                let displacement_mm = euclidean_distance(
282                    position_from_event(event, 0),
283                    position_from_event(&self.one_finger_contact_event, 0),
284                );
285                if displacement_mm >= self.spurious_to_intentional_motion_threshold_mm {
286                    return ExamineEventResult::Mismatch(Reason::DetailedFloat(
287                        DetailedReasonFloat {
288                            criterion: "displacement_mm",
289                            min: None,
290                            max: Some(self.spurious_to_intentional_motion_threshold_mm),
291                            actual: displacement_mm,
292                        },
293                    ));
294                }
295                ExamineEventResult::Contender(
296                    self.into_two_finger_contacts_contender(event.clone()),
297                )
298            }
299            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
300                criterion: "num_contacts",
301                min: Some(1),
302                max: Some(2),
303                actual: num_contacts,
304            })),
305        }
306    }
307}
308
309impl TwoFingerContactContender {
310    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
311    fn into_matched_contender(
312        self: Box<Self>,
313        pressed_event: TouchpadEvent,
314    ) -> Box<dyn gesture_arena::MatchedContender> {
315        Box::new(MatchedContender {
316            pressed_event,
317            spurious_to_intentional_motion_threshold_mm: self
318                .spurious_to_intentional_motion_threshold_mm,
319            spurious_to_intentional_motion_threshold_button_change_mm: self
320                .spurious_to_intentional_motion_threshold_button_change_mm,
321            button_change_state_timeout: self.button_change_state_timeout,
322        })
323    }
324}
325
326impl gesture_arena::Contender for TwoFingerContactContender {
327    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
328        let num_contacts = event.contacts.len();
329        if num_contacts != 2 {
330            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
331                criterion: "num_contacts",
332                min: Some(2),
333                max: Some(2),
334                actual: num_contacts,
335            }));
336        }
337
338        // both touch contact should not move > threshold.
339        let MovementDetail { euclidean_distance, movement: _ } =
340            movement_from_events(&self.two_finger_contact_event, &event);
341        if euclidean_distance >= self.spurious_to_intentional_motion_threshold_mm {
342            return ExamineEventResult::Mismatch(Reason::DetailedFloat(DetailedReasonFloat {
343                criterion: "displacement_mm",
344                min: None,
345                max: Some(self.spurious_to_intentional_motion_threshold_mm),
346                actual: euclidean_distance,
347            }));
348        }
349
350        let num_pressed_buttons = event.pressed_buttons.len();
351        match num_pressed_buttons {
352            // No button down, keep matching.
353            0 => ExamineEventResult::Contender(self),
354            // If button down, into matched.
355            // Currently we don't have hardware with more than 1 button in
356            // touchpad, ignore this case.
357            _ => ExamineEventResult::MatchedContender(self.into_matched_contender(event.clone())),
358        }
359    }
360}
361
362impl MatchedContender {
363    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
364    fn into_button_down_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
365        Box::new(ButtonDownWinner {
366            pressed_event: self.pressed_event,
367            spurious_to_intentional_motion_threshold_mm: self
368                .spurious_to_intentional_motion_threshold_mm,
369            spurious_to_intentional_motion_threshold_button_change_mm: self
370                .spurious_to_intentional_motion_threshold_button_change_mm,
371            button_change_state_timeout: self.button_change_state_timeout,
372        })
373    }
374}
375
376impl gesture_arena::MatchedContender for MatchedContender {
377    fn verify_event(self: Box<Self>, _event: &TouchpadEvent) -> VerifyEventResult {
378        // This verify_event expected not call because all other recognizers
379        // should exit on 2 finger button down.
380
381        log::error!("Unexpected MatchedContender::verify_event() called");
382
383        VerifyEventResult::MatchedContender(self)
384    }
385
386    fn process_buffered_events(
387        self: Box<Self>,
388        _events: Vec<TouchpadEvent>,
389    ) -> ProcessBufferedEventsResult {
390        // all small motion before button down are ignored.
391        ProcessBufferedEventsResult {
392            generated_events: vec![touchpad_event_to_mouse_down_event(&self.pressed_event)],
393            winner: Some(self.into_button_down_winner()),
394            recognized_gesture: RecognizedGesture::SecondaryButtonDown,
395        }
396    }
397}
398
399impl ButtonDownWinner {
400    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
401    fn into_drag_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
402        Box::new(DragWinner {
403            last_event: self.pressed_event,
404            spurious_to_intentional_motion_threshold_button_change_mm: self
405                .spurious_to_intentional_motion_threshold_button_change_mm,
406            button_change_state_timeout: self.button_change_state_timeout,
407        })
408    }
409
410    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
411    fn into_button_up(
412        self: Box<Self>,
413        button_up_event: TouchpadEvent,
414    ) -> Box<dyn gesture_arena::Winner> {
415        Box::new(ButtonUpWinner {
416            button_up_event,
417            spurious_to_intentional_motion_threshold_button_change_mm: self
418                .spurious_to_intentional_motion_threshold_button_change_mm,
419            button_change_state_timeout: self.button_change_state_timeout,
420        })
421    }
422}
423
424impl gesture_arena::Winner for ButtonDownWinner {
425    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
426        let motion_threshold =
427            if event.timestamp - self.pressed_event.timestamp > self.button_change_state_timeout {
428                self.spurious_to_intentional_motion_threshold_mm
429            } else {
430                self.spurious_to_intentional_motion_threshold_button_change_mm
431            };
432
433        // Check for drag (button held, with sufficient contact movement).
434        let MovementDetail { euclidean_distance, movement: _ } =
435            movement_from_events(&self.pressed_event, &event);
436        if euclidean_distance >= motion_threshold {
437            let drag_winner = self.into_drag_winner();
438            return drag_winner.process_new_event(event);
439        }
440
441        let num_pressed_buttons = event.pressed_buttons.len();
442        match num_pressed_buttons {
443            // All small motion before button up, and motion in button up event
444            // are ignored.
445            0 => ProcessNewEventResult::ContinueGesture(
446                Some(touchpad_event_to_mouse_up_event(&event)),
447                self.into_button_up(event),
448            ),
449            1 => ProcessNewEventResult::ContinueGesture(None, self),
450            // Also wait for the button release to complete the click or drag gesture.
451            // this should never happens unless there is a touchpad has more than 1 button.
452            _ => ProcessNewEventResult::ContinueGesture(None, self),
453        }
454    }
455}
456
457impl DragWinner {
458    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
459    fn into_drag_winner(
460        self: Box<Self>,
461        last_event: TouchpadEvent,
462    ) -> Box<dyn gesture_arena::Winner> {
463        Box::new(DragWinner {
464            spurious_to_intentional_motion_threshold_button_change_mm: self
465                .spurious_to_intentional_motion_threshold_button_change_mm,
466            button_change_state_timeout: self.button_change_state_timeout,
467            last_event,
468        })
469    }
470
471    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
472    fn into_button_up(
473        self: Box<Self>,
474        button_up_event: TouchpadEvent,
475    ) -> Box<dyn gesture_arena::Winner> {
476        Box::new(ButtonUpWinner {
477            spurious_to_intentional_motion_threshold_button_change_mm: self
478                .spurious_to_intentional_motion_threshold_button_change_mm,
479            button_change_state_timeout: self.button_change_state_timeout,
480            button_up_event,
481        })
482    }
483}
484
485impl gesture_arena::Winner for DragWinner {
486    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
487        let num_pressed_buttons = event.pressed_buttons.len();
488        match num_pressed_buttons {
489            // Motion in button up event is ignored.
490            0 => ProcessNewEventResult::ContinueGesture(
491                Some(touchpad_event_to_mouse_up_event(&event)),
492                self.into_button_up(event),
493            ),
494            1 | _ => {
495                // More than 2 button should never happens unless there is a touchpad has
496                // more than 1 button. Just treat this same with 1 button down.
497                ProcessNewEventResult::ContinueGesture(
498                    Some(touchpad_event_to_mouse_drag_event(&self.last_event, &event)),
499                    self.into_drag_winner(event),
500                )
501            }
502        }
503    }
504}
505
506impl gesture_arena::Winner for ButtonUpWinner {
507    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
508        // Fingers leave or add to surface should end the ButtonUpWinner.
509        let num_contacts = event.contacts.len();
510        let num_contacts_button_up = self.button_up_event.contacts.len();
511        if num_contacts != num_contacts_button_up {
512            return ProcessNewEventResult::EndGesture(
513                EndGestureEvent::UnconsumedEvent(event),
514                Reason::DetailedUint(DetailedReasonUint {
515                    criterion: "num_contacts",
516                    min: Some(num_contacts_button_up as u64),
517                    max: Some(num_contacts_button_up as u64),
518                    actual: num_contacts,
519                }),
520            );
521        }
522
523        // Button change should end the ButtonUpWinner.
524        let num_pressed_buttons = event.pressed_buttons.len();
525        if num_pressed_buttons != 0 {
526            return ProcessNewEventResult::EndGesture(
527                EndGestureEvent::UnconsumedEvent(event),
528                Reason::DetailedUint(DetailedReasonUint {
529                    criterion: "num_buttons",
530                    min: Some(0),
531                    max: Some(0),
532                    actual: num_pressed_buttons,
533                }),
534            );
535        }
536
537        // Events after timeout should not be discarded by ButtonUpWinner.
538        if event.timestamp - self.button_up_event.timestamp > self.button_change_state_timeout {
539            return ProcessNewEventResult::EndGesture(
540                EndGestureEvent::UnconsumedEvent(event),
541                Reason::Basic("button_up_timeout"),
542            );
543        }
544
545        // Events move more than threshold should end the ButtonUpWinner.
546        let MovementDetail { euclidean_distance, movement: _ } =
547            movement_from_events(&self.button_up_event, &event);
548        if euclidean_distance > self.spurious_to_intentional_motion_threshold_button_change_mm {
549            return ProcessNewEventResult::EndGesture(
550                EndGestureEvent::UnconsumedEvent(event),
551                Reason::DetailedFloat(DetailedReasonFloat {
552                    criterion: "displacement_mm",
553                    min: None,
554                    max: Some(self.spurious_to_intentional_motion_threshold_button_change_mm),
555                    actual: euclidean_distance,
556                }),
557            );
558        }
559
560        // Discard this event.
561        ProcessNewEventResult::ContinueGesture(None, self)
562    }
563}
564
565/// This function returns the position associated with the touch contact at the
566/// given index from a TouchpadEvent.
567fn position_from_event(event: &TouchpadEvent, index: usize) -> Position {
568    event.contacts[index].position
569}
570
571fn touchpad_event_to_mouse_down_event(event: &TouchpadEvent) -> gesture_arena::MouseEvent {
572    make_mouse_event(
573        event.timestamp,
574        Position::zero(),
575        MousePhase::Down,
576        hashset! {gesture_arena::SECONDARY_BUTTON},
577        hashset! {gesture_arena::SECONDARY_BUTTON},
578    )
579}
580
581fn touchpad_event_to_mouse_up_event(event: &TouchpadEvent) -> gesture_arena::MouseEvent {
582    make_mouse_event(
583        event.timestamp,
584        Position::zero(),
585        MousePhase::Up,
586        hashset! {gesture_arena::SECONDARY_BUTTON},
587        hashset! {},
588    )
589}
590
591fn touchpad_event_to_mouse_drag_event(
592    last_event: &TouchpadEvent,
593    event: &TouchpadEvent,
594) -> gesture_arena::MouseEvent {
595    let MovementDetail { movement, euclidean_distance: _ } =
596        movement_from_events(last_event, event);
597    make_mouse_event(
598        event.timestamp,
599        movement,
600        MousePhase::Move,
601        hashset! {},
602        hashset! {gesture_arena::SECONDARY_BUTTON},
603    )
604}
605
606fn make_mouse_event(
607    timestamp: zx::MonotonicInstant,
608    movement_in_mm: Position,
609    phase: MousePhase,
610    affected_buttons: HashSet<MouseButton>,
611    pressed_buttons: HashSet<MouseButton>,
612) -> gesture_arena::MouseEvent {
613    gesture_arena::MouseEvent {
614        timestamp,
615        mouse_data: MouseEvent::new(
616            MouseLocation::Relative(RelativeLocation { millimeters: movement_in_mm }),
617            /* wheel_delta_v= */ None,
618            /* wheel_delta_h= */ None,
619            phase,
620            affected_buttons,
621            pressed_buttons,
622            /* is_precision_scroll= */ None,
623        ),
624    }
625}
626
627#[cfg(test)]
628mod test {
629    use super::*;
630    use crate::touch_binding;
631    use assert_matches::assert_matches;
632    use test_case::test_case;
633
634    fn make_touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
635        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
636    }
637
638    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM: f32 = 10.0;
639    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM: f32 = 20.0;
640    const BUTTON_CHANGE_STATE_TIMEOUT: zx::MonotonicDuration =
641        zx::MonotonicDuration::from_seconds(1);
642
643    #[test_case(TouchpadEvent{
644        timestamp: zx::MonotonicInstant::ZERO,
645        pressed_buttons: vec![],
646        contacts: vec![],
647        filtered_palm_contacts: vec![],
648    };"0 fingers")]
649    #[test_case(TouchpadEvent{
650        timestamp: zx::MonotonicInstant::ZERO,
651        pressed_buttons: vec![1],
652        contacts: vec![
653            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
654        ],
655        filtered_palm_contacts: vec![],
656    };"1 fingers button down")]
657    #[test_case(TouchpadEvent{
658        timestamp: zx::MonotonicInstant::ZERO,
659        pressed_buttons: vec![],
660        contacts: vec![
661            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
662            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
663            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
664        ],
665        filtered_palm_contacts: vec![],
666    };"3 fingers")]
667    #[fuchsia::test]
668    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
669        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
670            spurious_to_intentional_motion_threshold_mm:
671                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
672            spurious_to_intentional_motion_threshold_button_change_mm:
673                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
674            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
675        });
676
677        let got = contender.examine_event(&event);
678        assert_matches!(got, ExamineEventResult::Mismatch(_));
679    }
680
681    #[test_case(TouchpadEvent{
682        timestamp: zx::MonotonicInstant::ZERO,
683        pressed_buttons: vec![],
684        contacts: vec![
685            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
686        ],
687        filtered_palm_contacts: vec![],
688    }, "input_pipeline_lib_test::gestures::secondary_button::OneFingerContactContender";"1 fingers")]
689    #[test_case(TouchpadEvent{
690        timestamp: zx::MonotonicInstant::ZERO,
691        pressed_buttons: vec![],
692        contacts: vec![
693            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
694            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
695        ],
696        filtered_palm_contacts: vec![],
697    }, "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender";"2 fingers")]
698    #[fuchsia::test]
699    fn initial_contender_examine_event_contender(event: TouchpadEvent, contender_name: &str) {
700        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
701            spurious_to_intentional_motion_threshold_mm:
702                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
703            spurious_to_intentional_motion_threshold_button_change_mm:
704                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
705            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
706        });
707
708        let got = contender.examine_event(&event);
709        assert_matches!(got, ExamineEventResult::Contender(contender) => {
710            pretty_assertions::assert_eq!(contender.get_type_name(), contender_name);
711        });
712    }
713
714    #[fuchsia::test]
715    fn initial_contender_examine_event_matched_contender() {
716        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
717            spurious_to_intentional_motion_threshold_mm:
718                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
719            spurious_to_intentional_motion_threshold_button_change_mm:
720                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
721            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
722        });
723        let event = TouchpadEvent {
724            timestamp: zx::MonotonicInstant::ZERO,
725            pressed_buttons: vec![1],
726            contacts: vec![
727                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
728                make_touch_contact(2, Position { x: 1.0, y: 5.0 }),
729            ],
730            filtered_palm_contacts: vec![],
731        };
732
733        let got = contender.examine_event(&event);
734        assert_matches!(got, ExamineEventResult::MatchedContender(_));
735    }
736
737    #[test_case(TouchpadEvent{
738        timestamp: zx::MonotonicInstant::ZERO,
739        pressed_buttons: vec![],
740        contacts: vec![],
741        filtered_palm_contacts: vec![],
742    };"0 fingers")]
743    #[test_case(TouchpadEvent{
744        timestamp: zx::MonotonicInstant::ZERO,
745        pressed_buttons: vec![1],
746        contacts: vec![
747            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
748        ],
749        filtered_palm_contacts: vec![],
750    };"1 fingers button down")]
751    #[test_case(TouchpadEvent{
752        timestamp: zx::MonotonicInstant::ZERO,
753        pressed_buttons: vec![],
754        contacts: vec![
755            make_touch_contact(1, Position{x: 1.0, y: 12.0}),
756        ],
757        filtered_palm_contacts: vec![],
758    };"1 fingers move more than threshold")]
759    #[test_case(TouchpadEvent{
760        timestamp: zx::MonotonicInstant::ZERO,
761        pressed_buttons: vec![],
762        contacts: vec![
763            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
764            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
765            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
766        ],
767        filtered_palm_contacts: vec![],
768    };"3 fingers")]
769    #[fuchsia::test]
770    fn one_finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
771        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
772            one_finger_contact_event: TouchpadEvent {
773                timestamp: zx::MonotonicInstant::ZERO,
774                pressed_buttons: vec![],
775                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
776                filtered_palm_contacts: vec![],
777            },
778            spurious_to_intentional_motion_threshold_mm:
779                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
780            spurious_to_intentional_motion_threshold_button_change_mm:
781                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
782            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
783        });
784
785        let got = contender.examine_event(&event);
786        assert_matches!(got, ExamineEventResult::Mismatch(_));
787    }
788
789    #[test_case(TouchpadEvent{
790        timestamp: zx::MonotonicInstant::ZERO,
791        pressed_buttons: vec![],
792        contacts: vec![
793            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
794        ],
795        filtered_palm_contacts: vec![],
796    };"1 fingers hold")]
797    #[test_case(TouchpadEvent{
798        timestamp: zx::MonotonicInstant::ZERO,
799        pressed_buttons: vec![],
800        contacts: vec![
801            make_touch_contact(1, Position{x: 1.0, y: 10.0}),
802        ],
803        filtered_palm_contacts: vec![],
804    };"1 fingers move less than threshold")]
805    #[fuchsia::test]
806    fn one_finger_contact_contender_examine_event_contender(event: TouchpadEvent) {
807        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
808            one_finger_contact_event: TouchpadEvent {
809                timestamp: zx::MonotonicInstant::ZERO,
810                pressed_buttons: vec![],
811                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
812                filtered_palm_contacts: vec![],
813            },
814            spurious_to_intentional_motion_threshold_mm:
815                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
816            spurious_to_intentional_motion_threshold_button_change_mm:
817                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
818            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
819        });
820
821        let got = contender.examine_event(&event);
822        assert_matches!(got, ExamineEventResult::Contender(c) => {
823            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::OneFingerContactContender");
824        });
825    }
826
827    #[fuchsia::test]
828    fn one_finger_contact_contender_examine_event_two_finger_contact_contender() {
829        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
830            one_finger_contact_event: TouchpadEvent {
831                timestamp: zx::MonotonicInstant::ZERO,
832                pressed_buttons: vec![],
833                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
834                filtered_palm_contacts: vec![],
835            },
836            spurious_to_intentional_motion_threshold_mm:
837                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
838            spurious_to_intentional_motion_threshold_button_change_mm:
839                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
840            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
841        });
842
843        let event = TouchpadEvent {
844            timestamp: zx::MonotonicInstant::ZERO,
845            pressed_buttons: vec![],
846            contacts: vec![
847                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
848                make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
849            ],
850            filtered_palm_contacts: vec![],
851        };
852        let got = contender.examine_event(&event);
853        assert_matches!(got, ExamineEventResult::Contender(c) => {
854            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender");
855        });
856    }
857
858    #[test_case(TouchpadEvent{
859        timestamp: zx::MonotonicInstant::ZERO,
860        pressed_buttons: vec![],
861        contacts: vec![],
862        filtered_palm_contacts: vec![],
863    };"0 fingers")]
864    #[test_case(TouchpadEvent{
865        timestamp: zx::MonotonicInstant::ZERO,
866        pressed_buttons: vec![],
867        contacts: vec![
868            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
869        ],
870        filtered_palm_contacts: vec![],
871    };"1 fingers")]
872    #[test_case(TouchpadEvent{
873        timestamp: zx::MonotonicInstant::ZERO,
874        pressed_buttons: vec![],
875        contacts: vec![
876            make_touch_contact(1, Position{x: 1.0, y: 12.0}),
877            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
878        ],
879        filtered_palm_contacts: vec![],
880    };"2 fingers move more than threshold")]
881    #[test_case(TouchpadEvent{
882        timestamp: zx::MonotonicInstant::ZERO,
883        pressed_buttons: vec![],
884        contacts: vec![
885            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
886            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
887            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
888        ],
889        filtered_palm_contacts: vec![],
890    };"3 fingers")]
891    #[fuchsia::test]
892    fn two_finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
893        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
894            two_finger_contact_event: TouchpadEvent {
895                timestamp: zx::MonotonicInstant::ZERO,
896                pressed_buttons: vec![],
897                contacts: vec![
898                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
899                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
900                ],
901                filtered_palm_contacts: vec![],
902            },
903            spurious_to_intentional_motion_threshold_mm:
904                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
905            spurious_to_intentional_motion_threshold_button_change_mm:
906                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
907            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
908        });
909
910        let got = contender.examine_event(&event);
911        assert_matches!(got, ExamineEventResult::Mismatch(_));
912    }
913
914    #[test_case(TouchpadEvent{
915        timestamp: zx::MonotonicInstant::ZERO,
916        pressed_buttons: vec![],
917        contacts: vec![
918            make_touch_contact(1, Position{x: 1.0, y: 9.0}),
919            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
920        ],
921        filtered_palm_contacts: vec![],
922    };"2 fingers move less than threshold")]
923    #[test_case(TouchpadEvent{
924        timestamp: zx::MonotonicInstant::ZERO,
925        pressed_buttons: vec![],
926        contacts: vec![
927            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
928            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
929        ],
930        filtered_palm_contacts: vec![],
931    };"2 fingers hold")]
932    #[fuchsia::test]
933    fn two_finger_contact_contender_examine_event_contender(event: TouchpadEvent) {
934        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
935            two_finger_contact_event: TouchpadEvent {
936                timestamp: zx::MonotonicInstant::ZERO,
937                pressed_buttons: vec![],
938                contacts: vec![
939                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
940                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
941                ],
942                filtered_palm_contacts: vec![],
943            },
944            spurious_to_intentional_motion_threshold_mm:
945                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
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        });
950
951        let got = contender.examine_event(&event);
952        assert_matches!(got, ExamineEventResult::Contender(c) => {
953            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender");
954        });
955    }
956
957    #[fuchsia::test]
958    fn two_finger_contact_contender_examine_event_matched_contender() {
959        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
960            two_finger_contact_event: TouchpadEvent {
961                timestamp: zx::MonotonicInstant::ZERO,
962                pressed_buttons: vec![],
963                contacts: vec![
964                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
965                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
966                ],
967                filtered_palm_contacts: vec![],
968            },
969            spurious_to_intentional_motion_threshold_mm:
970                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
971            spurious_to_intentional_motion_threshold_button_change_mm:
972                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
973            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
974        });
975
976        let event = TouchpadEvent {
977            timestamp: zx::MonotonicInstant::ZERO,
978            pressed_buttons: vec![1],
979            contacts: vec![
980                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
981                make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
982            ],
983            filtered_palm_contacts: vec![],
984        };
985        let got = contender.examine_event(&event);
986        assert_matches!(got, ExamineEventResult::MatchedContender(_));
987    }
988
989    #[fuchsia::test]
990    fn matched_contender_process_buffered_events() {
991        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {
992            spurious_to_intentional_motion_threshold_mm:
993                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
994            spurious_to_intentional_motion_threshold_button_change_mm:
995                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
996            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
997            pressed_event: TouchpadEvent {
998                timestamp: zx::MonotonicInstant::from_nanos(41),
999                pressed_buttons: vec![1],
1000                contacts: vec![
1001                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1002                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1003                ],
1004                filtered_palm_contacts: vec![],
1005            },
1006        });
1007        let events = vec![
1008            TouchpadEvent {
1009                timestamp: zx::MonotonicInstant::ZERO,
1010                pressed_buttons: vec![],
1011                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
1012                filtered_palm_contacts: vec![],
1013            },
1014            // Small movement will be ignored.
1015            TouchpadEvent {
1016                timestamp: zx::MonotonicInstant::ZERO,
1017                pressed_buttons: vec![],
1018                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 9.0 })],
1019                filtered_palm_contacts: vec![],
1020            },
1021            TouchpadEvent {
1022                timestamp: zx::MonotonicInstant::from_nanos(21),
1023                pressed_buttons: vec![],
1024                contacts: vec![
1025                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1026                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1027                ],
1028                filtered_palm_contacts: vec![],
1029            },
1030            // Small movement will be ignored.
1031            TouchpadEvent {
1032                timestamp: zx::MonotonicInstant::from_nanos(21),
1033                pressed_buttons: vec![],
1034                contacts: vec![
1035                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1036                    make_touch_contact(2, Position { x: 5.0, y: 14.0 }),
1037                ],
1038                filtered_palm_contacts: vec![],
1039            },
1040            TouchpadEvent {
1041                timestamp: zx::MonotonicInstant::from_nanos(41),
1042                pressed_buttons: vec![1],
1043                contacts: vec![
1044                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1045                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1046                ],
1047                filtered_palm_contacts: vec![],
1048            },
1049        ];
1050
1051        let got = contender.process_buffered_events(events);
1052
1053        assert_matches!(got, ProcessBufferedEventsResult{
1054          generated_events,
1055          winner: Some(winner),
1056          recognized_gesture: RecognizedGesture::SecondaryButtonDown,
1057        } => {
1058          pretty_assertions::assert_eq!(generated_events, vec![
1059            gesture_arena::MouseEvent {
1060              timestamp:zx::MonotonicInstant::from_nanos(41),
1061              mouse_data: MouseEvent::new(
1062                  MouseLocation::Relative(RelativeLocation {
1063                      millimeters: Position { x: 0.0, y: 0.0 },
1064                  }),
1065                  /* wheel_delta_v= */ None,
1066                  /* wheel_delta_h= */ None,
1067                  MousePhase::Down,
1068                  /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1069                  /* pressed_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1070                  /* is_precision_scroll= */ None,
1071              ),
1072            }
1073          ]);
1074          pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1075        });
1076    }
1077
1078    #[test_case(TouchpadEvent{
1079        timestamp: zx::MonotonicInstant::from_nanos(41),
1080        pressed_buttons: vec![],
1081        contacts: vec![
1082            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1083        ],
1084        filtered_palm_contacts: vec![],
1085    };"1 finger button release")]
1086    #[test_case(TouchpadEvent{
1087        timestamp: zx::MonotonicInstant::from_nanos(41),
1088        pressed_buttons: vec![],
1089        contacts: vec![
1090            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1091            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1092        ],
1093        filtered_palm_contacts: vec![],
1094    };"2 finger button release")]
1095    #[test_case(TouchpadEvent{
1096        timestamp: zx::MonotonicInstant::from_nanos(41),
1097        pressed_buttons: vec![],
1098        contacts: vec![
1099            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1100            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1101            make_touch_contact(2, Position{x: 15.0, y: 15.0}),
1102        ],
1103        filtered_palm_contacts: vec![],
1104    };"3 finger button release")]
1105    #[test_case(TouchpadEvent{
1106        timestamp: zx::MonotonicInstant::from_nanos(41),
1107        pressed_buttons: vec![],
1108        contacts: vec![
1109            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1110            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1111        ],
1112        filtered_palm_contacts: vec![],
1113    };"move less than threshold in button change state")]
1114    #[test_case(TouchpadEvent{
1115        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1116        pressed_buttons: vec![],
1117        contacts: vec![
1118            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
1119            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1120        ],
1121        filtered_palm_contacts: vec![],
1122    };"move less than threshold out of button change state")]
1123    #[test_case(TouchpadEvent{
1124        timestamp: zx::MonotonicInstant::from_nanos(41),
1125        pressed_buttons: vec![],
1126        contacts: vec![
1127            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1128            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1129        ],
1130        filtered_palm_contacts: vec![],
1131    };"move more than threshold in button change state")]
1132    #[test_case(TouchpadEvent{
1133        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1134        pressed_buttons: vec![],
1135        contacts: vec![
1136            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1137            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1138        ],
1139        filtered_palm_contacts: vec![],
1140    };"move more than threshold out of button change state")]
1141    #[fuchsia::test]
1142    fn button_down_winner_button_up(event: TouchpadEvent) {
1143        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1144            spurious_to_intentional_motion_threshold_mm:
1145                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1146            spurious_to_intentional_motion_threshold_button_change_mm:
1147                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1148            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1149            pressed_event: TouchpadEvent {
1150                timestamp: zx::MonotonicInstant::ZERO,
1151                pressed_buttons: vec![1],
1152                contacts: vec![
1153                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1154                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1155                ],
1156                filtered_palm_contacts: vec![],
1157            },
1158        });
1159
1160        let got = winner.process_new_event(event);
1161        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1162            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1163                MouseLocation::Relative(RelativeLocation {
1164                    millimeters: Position { x: 0.0, y: 0.0 },
1165                }),
1166                /* wheel_delta_v= */ None,
1167                /* wheel_delta_h= */ None,
1168                MousePhase::Up,
1169                /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1170                /* pressed_buttons= */ hashset!{},
1171                /* is_precision_scroll= */ None,
1172            ));
1173            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1174        });
1175    }
1176
1177    #[test_case(TouchpadEvent{
1178        timestamp: zx::MonotonicInstant::from_nanos(41),
1179        pressed_buttons: vec![1],
1180        contacts: vec![
1181            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
1182            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1183        ],
1184        filtered_palm_contacts: vec![],
1185    };"move less than threshold in button change state")]
1186    #[test_case(TouchpadEvent{
1187        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1188        pressed_buttons: vec![1],
1189        contacts: vec![
1190            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
1191            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1192        ],
1193        filtered_palm_contacts: vec![],
1194    };"move less than threshold out of button change state")]
1195    #[fuchsia::test]
1196    fn button_down_winner_continue(event: TouchpadEvent) {
1197        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1198            spurious_to_intentional_motion_threshold_mm:
1199                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1200            spurious_to_intentional_motion_threshold_button_change_mm:
1201                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1202            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1203            pressed_event: TouchpadEvent {
1204                timestamp: zx::MonotonicInstant::ZERO,
1205                pressed_buttons: vec![1],
1206                contacts: vec![
1207                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1208                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1209                ],
1210                filtered_palm_contacts: vec![],
1211            },
1212        });
1213
1214        let got = winner.process_new_event(event);
1215        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1216            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1217        });
1218    }
1219
1220    #[test_case(TouchpadEvent{
1221        timestamp: zx::MonotonicInstant::from_nanos(41),
1222        pressed_buttons: vec![1],
1223        contacts: vec![
1224            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1225            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1226        ],
1227        filtered_palm_contacts: vec![],
1228    };"move more than threshold in button change state")]
1229    #[test_case(TouchpadEvent{
1230        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1231        pressed_buttons: vec![1],
1232        contacts: vec![
1233            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1234            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1235        ],
1236        filtered_palm_contacts: vec![],
1237    };"move more than threshold out of button change state")]
1238    #[fuchsia::test]
1239    fn button_down_winner_drag_winner_continue(event: TouchpadEvent) {
1240        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1241            spurious_to_intentional_motion_threshold_mm:
1242                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1243            spurious_to_intentional_motion_threshold_button_change_mm:
1244                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1245            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1246            pressed_event: TouchpadEvent {
1247                timestamp: zx::MonotonicInstant::ZERO,
1248                pressed_buttons: vec![1],
1249                contacts: vec![
1250                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1251                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1252                ],
1253                filtered_palm_contacts: vec![],
1254            },
1255        });
1256
1257        let got = winner.process_new_event(event);
1258        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1259            pretty_assertions::assert_eq!(mouse_data.phase, MousePhase::Move);
1260            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1261        });
1262    }
1263
1264    #[test_case(TouchpadEvent{
1265      timestamp: zx::MonotonicInstant::from_nanos(41),
1266      pressed_buttons: vec![],
1267      contacts: vec![
1268          make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1269          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1270      ],
1271      filtered_palm_contacts: vec![],
1272    };"button release")]
1273    #[test_case(TouchpadEvent{
1274        timestamp: zx::MonotonicInstant::from_nanos(41),
1275        pressed_buttons: vec![],
1276        contacts: vec![
1277            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1278        ],
1279        filtered_palm_contacts: vec![],
1280      };"1 finger button release")]
1281    #[test_case(TouchpadEvent{
1282      timestamp: zx::MonotonicInstant::from_nanos(41),
1283      pressed_buttons: vec![],
1284      contacts: vec![
1285          make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1286          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1287      ],
1288      filtered_palm_contacts: vec![],
1289    };"large move and button release")]
1290    #[fuchsia::test]
1291    fn drag_winner_button_up(event: TouchpadEvent) {
1292        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1293            spurious_to_intentional_motion_threshold_button_change_mm:
1294                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1295            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1296            last_event: TouchpadEvent {
1297                timestamp: zx::MonotonicInstant::ZERO,
1298                pressed_buttons: vec![1],
1299                contacts: vec![
1300                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1301                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1302                ],
1303                filtered_palm_contacts: vec![],
1304            },
1305        });
1306
1307        let got = winner.process_new_event(event);
1308        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1309            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1310                MouseLocation::Relative(RelativeLocation {
1311                    millimeters: Position { x: 0.0, y: 0.0 },
1312                }),
1313                /* wheel_delta_v= */ None,
1314                /* wheel_delta_h= */ None,
1315                MousePhase::Up,
1316                /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1317                /* pressed_buttons= */ hashset!{},
1318                /* is_precision_scroll= */ None,
1319            ));
1320            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1321        });
1322    }
1323
1324    #[test_case(TouchpadEvent{
1325        timestamp: zx::MonotonicInstant::from_nanos(41),
1326        pressed_buttons: vec![1],
1327        contacts: vec![
1328            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1329            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1330        ],
1331        filtered_palm_contacts: vec![],
1332    };"2 finger")]
1333    #[test_case(TouchpadEvent{
1334        timestamp: zx::MonotonicInstant::from_nanos(41),
1335        pressed_buttons: vec![1],
1336        contacts: vec![
1337            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1338        ],
1339        filtered_palm_contacts: vec![],
1340    };"1 finger")]
1341    #[fuchsia::test]
1342    fn drag_winner_continue(event: TouchpadEvent) {
1343        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1344            spurious_to_intentional_motion_threshold_button_change_mm:
1345                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1346            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1347            last_event: TouchpadEvent {
1348                timestamp: zx::MonotonicInstant::ZERO,
1349                pressed_buttons: vec![1],
1350                contacts: vec![
1351                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1352                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1353                ],
1354                filtered_palm_contacts: vec![],
1355            },
1356        });
1357
1358        let got = winner.process_new_event(event);
1359        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1360            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1361                MouseLocation::Relative(RelativeLocation {
1362                    millimeters: Position { x: 19.0, y: 1.0 },
1363                }),
1364                /* wheel_delta_v= */ None,
1365                /* wheel_delta_h= */ None,
1366                MousePhase::Move,
1367                /* affected_buttons= */ hashset!{},
1368                /* pressed_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1369                /* is_precision_scroll= */ None,
1370            ));
1371            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1372        });
1373    }
1374
1375    #[fuchsia::test]
1376    fn button_up_winner_continue() {
1377        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1378            spurious_to_intentional_motion_threshold_button_change_mm:
1379                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1380            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1381            button_up_event: TouchpadEvent {
1382                timestamp: zx::MonotonicInstant::ZERO,
1383                pressed_buttons: vec![],
1384                contacts: vec![
1385                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1386                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1387                ],
1388                filtered_palm_contacts: vec![],
1389            },
1390        });
1391
1392        let event = TouchpadEvent {
1393            timestamp: zx::MonotonicInstant::from_nanos(41),
1394            pressed_buttons: vec![],
1395            contacts: vec![
1396                make_touch_contact(1, Position { x: 10.0, y: 1.0 }),
1397                make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1398            ],
1399            filtered_palm_contacts: vec![],
1400        };
1401
1402        let got = winner.process_new_event(event);
1403        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1404            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1405        });
1406    }
1407
1408    #[test_case(TouchpadEvent{
1409        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1_001),
1410        pressed_buttons: vec![],
1411        contacts: vec![
1412            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1413        ],
1414        filtered_palm_contacts: vec![],
1415    };"timeout")]
1416    #[test_case(TouchpadEvent{
1417        timestamp: zx::MonotonicInstant::from_nanos(41),
1418        pressed_buttons: vec![1],
1419        contacts: vec![
1420            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1421        ],
1422        filtered_palm_contacts: vec![],
1423    };"button down")]
1424    #[test_case(TouchpadEvent{
1425        timestamp: zx::MonotonicInstant::from_nanos(41),
1426        pressed_buttons: vec![],
1427        contacts: vec![
1428            make_touch_contact(1, Position{x: 21.0, y: 0.0}),
1429        ],
1430        filtered_palm_contacts: vec![],
1431    };"move more than threshold")]
1432    #[test_case(TouchpadEvent{
1433        timestamp: zx::MonotonicInstant::from_nanos(41),
1434        pressed_buttons: vec![],
1435        contacts: vec![
1436            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1437            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1438            make_touch_contact(3, Position{x: 15.0, y: 15.0}),
1439        ],
1440        filtered_palm_contacts: vec![],
1441    };"more contacts")]
1442    #[test_case(TouchpadEvent{
1443      timestamp: zx::MonotonicInstant::from_nanos(41),
1444      pressed_buttons: vec![],
1445      contacts: vec![
1446          make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1447      ],
1448      filtered_palm_contacts: vec![],
1449    };"less contacts")]
1450    #[test_case(TouchpadEvent{
1451        timestamp: zx::MonotonicInstant::from_nanos(41),
1452        pressed_buttons: vec![],
1453        contacts: vec![],
1454        filtered_palm_contacts: vec![],
1455    };"no contact")]
1456    #[fuchsia::test]
1457    fn button_up_winner_end(event: TouchpadEvent) {
1458        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1459            spurious_to_intentional_motion_threshold_button_change_mm:
1460                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1461            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1462            button_up_event: TouchpadEvent {
1463                timestamp: zx::MonotonicInstant::ZERO,
1464                pressed_buttons: vec![],
1465                contacts: vec![
1466                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1467                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1468                ],
1469                filtered_palm_contacts: vec![],
1470            },
1471        });
1472
1473        let got = winner.process_new_event(event);
1474        assert_matches!(
1475            got,
1476            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _)
1477        );
1478    }
1479}