Skip to main content

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::{MovementDetail, movement_from_events};
11use crate::mouse_binding::{MouseButton, MouseEvent, MouseLocation, MousePhase, RelativeLocation};
12use crate::utils::{Position, euclidean_distance};
13
14use sorted_vec_map::SortedVecSet;
15
16/// The initial state of this recognizer, before a secondary tap has been
17/// detected.
18#[derive(Debug)]
19pub(super) struct InitialContender {
20    /// The maximum displacement that a detected finger can withstand to still
21    /// be considered a secondary click. Measured in millimeters.
22    pub(super) spurious_to_intentional_motion_threshold_mm: f32,
23
24    /// Use a larger threshold to detect motion on the edge of contact-button down,
25    /// button down-button up
26    pub(super) spurious_to_intentional_motion_threshold_button_change_mm: f32,
27
28    /// The timeout of the edge of contact-button down, button down-button up,
29    /// The recognizer will leave edge state either timeout or motion detected.
30    pub(super) button_change_state_timeout: zx::MonotonicDuration,
31}
32
33/// The state when this recognizer has detected a single finger down.
34#[derive(Debug)]
35struct OneFingerContactContender {
36    /// The TouchpadEvent when a finger down was first detected.
37    one_finger_contact_event: TouchpadEvent,
38
39    /// The maximum displacement that a detected finger can withstand to still
40    /// be considered a tap. Measured in millimeters.
41    spurious_to_intentional_motion_threshold_mm: f32,
42
43    /// Use a larger threshold to detect motion on the edge of contact-button down,
44    /// button down-button up
45    spurious_to_intentional_motion_threshold_button_change_mm: f32,
46
47    /// The timeout of the edge of contact-button down, button down-button up,
48    /// The recognizer will leave edge state either timeout or motion detected.
49    button_change_state_timeout: zx::MonotonicDuration,
50}
51
52/// The state when this recognizer has detected two fingers down.
53#[derive(Debug)]
54struct TwoFingerContactContender {
55    /// The TouchpadEvent when two fingers were first detected.
56    two_finger_contact_event: TouchpadEvent,
57
58    /// The maximum displacement that a detected finger can withstand to still
59    /// be considered a tap. Measured in millimeters.
60    spurious_to_intentional_motion_threshold_mm: f32,
61
62    /// Use a larger threshold to detect motion on the edge of contact-button down,
63    /// button down-button up
64    spurious_to_intentional_motion_threshold_button_change_mm: f32,
65
66    /// The timeout of the edge of contact-button down, button down-button up,
67    /// The recognizer will leave edge state either timeout or motion detected.
68    button_change_state_timeout: zx::MonotonicDuration,
69}
70
71/// The state when this recognizer has detected a secondary button down, but the
72/// gesture arena has not declared this recognizer the winner.
73#[derive(Debug)]
74struct MatchedContender {
75    /// The TouchpadEvent when two fingers and button were first detected.
76    pressed_event: TouchpadEvent,
77
78    /// The maximum displacement that a detected finger can withstand to still
79    /// be considered a tap. Measured in millimeters.
80    spurious_to_intentional_motion_threshold_mm: f32,
81
82    /// Use a larger threshold to detect motion on the edge of contact-button down,
83    /// button down-button up
84    spurious_to_intentional_motion_threshold_button_change_mm: f32,
85
86    /// The timeout of the edge of contact-button down, button down-button up,
87    /// The recognizer will leave edge state either timeout or motion detected.
88    button_change_state_timeout: zx::MonotonicDuration,
89}
90
91/// The state when this recognizer has won the contest.
92#[derive(Debug)]
93struct ButtonDownWinner {
94    /// The TouchpadEvent when two fingers and button were first detected.
95    pressed_event: TouchpadEvent,
96
97    /// The threshold to detect motion.
98    spurious_to_intentional_motion_threshold_mm: f32,
99
100    /// Use a larger threshold to detect motion on the edge of contact-button down,
101    /// button down-button up
102    spurious_to_intentional_motion_threshold_button_change_mm: f32,
103
104    /// The timeout of the edge of contact-button down, button down-button up,
105    /// The recognizer will leave edge state either timeout or motion detected.
106    button_change_state_timeout: zx::MonotonicDuration,
107}
108
109/// The state when ButtonDownWinner got motion more than threshold to recognize as
110/// drag gesture.
111#[derive(Debug)]
112struct DragWinner {
113    /// The last TouchpadEvent.
114    last_event: TouchpadEvent,
115
116    /// Use a larger threshold to detect motion on the edge of contact-button down,
117    /// button down-button up
118    spurious_to_intentional_motion_threshold_button_change_mm: f32,
119
120    /// The timeout of the edge of contact-button down, button down-button up,
121    /// The recognizer will leave edge state either timeout or motion detected.
122    button_change_state_timeout: zx::MonotonicDuration,
123}
124
125/// The state when ButtonDownWinner / DragWinner got button up, this winner is
126/// used to discard tailing movement from button up.
127#[derive(Debug)]
128struct ButtonUpWinner {
129    /// The button up event.
130    button_up_event: TouchpadEvent,
131
132    /// Use a larger threshold to detect motion on the edge of contact-button down,
133    /// button down-button up
134    spurious_to_intentional_motion_threshold_button_change_mm: f32,
135
136    /// The timeout of the edge of contact-button down, button down-button up,
137    /// The recognizer will leave edge state either timeout or motion detected.
138    button_change_state_timeout: zx::MonotonicDuration,
139}
140
141impl InitialContender {
142    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
143    fn into_one_finger_contact_contender(
144        self: Box<Self>,
145        one_finger_contact_event: TouchpadEvent,
146    ) -> Box<dyn gesture_arena::Contender> {
147        Box::new(OneFingerContactContender {
148            one_finger_contact_event,
149            spurious_to_intentional_motion_threshold_mm: self
150                .spurious_to_intentional_motion_threshold_mm,
151            spurious_to_intentional_motion_threshold_button_change_mm: self
152                .spurious_to_intentional_motion_threshold_button_change_mm,
153            button_change_state_timeout: self.button_change_state_timeout,
154        })
155    }
156
157    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
158    fn into_two_finger_contacts_contender(
159        self: Box<Self>,
160        two_finger_contact_event: TouchpadEvent,
161    ) -> Box<dyn gesture_arena::Contender> {
162        Box::new(TwoFingerContactContender {
163            two_finger_contact_event,
164            spurious_to_intentional_motion_threshold_mm: self
165                .spurious_to_intentional_motion_threshold_mm,
166            spurious_to_intentional_motion_threshold_button_change_mm: self
167                .spurious_to_intentional_motion_threshold_button_change_mm,
168            button_change_state_timeout: self.button_change_state_timeout,
169        })
170    }
171
172    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
173    fn into_matched_contender(
174        self: Box<Self>,
175        pressed_event: TouchpadEvent,
176    ) -> Box<dyn gesture_arena::MatchedContender> {
177        Box::new(MatchedContender {
178            pressed_event,
179            spurious_to_intentional_motion_threshold_mm: self
180                .spurious_to_intentional_motion_threshold_mm,
181            spurious_to_intentional_motion_threshold_button_change_mm: self
182                .spurious_to_intentional_motion_threshold_button_change_mm,
183            button_change_state_timeout: self.button_change_state_timeout,
184        })
185    }
186}
187
188impl gesture_arena::Contender for InitialContender {
189    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
190        let num_pressed_buttons = event.pressed_buttons.len();
191
192        let num_contacts = event.contacts.len();
193        match num_contacts {
194            1 => {
195                if num_pressed_buttons != 0 {
196                    return ExamineEventResult::Mismatch(Reason::DetailedUint(
197                        DetailedReasonUint {
198                            criterion: "num_pressed_buttons",
199                            min: Some(0),
200                            max: Some(0),
201                            actual: num_pressed_buttons,
202                        },
203                    ));
204                }
205
206                ExamineEventResult::Contender(self.into_one_finger_contact_contender(event.clone()))
207            }
208            2 => {
209                match num_pressed_buttons {
210                    0 => ExamineEventResult::Contender(
211                        self.into_two_finger_contacts_contender(event.clone()),
212                    ),
213                    // Currently we don't have hardware with more than 1 button in
214                    // touchpad, ignore this case.
215                    _ => ExamineEventResult::MatchedContender(
216                        self.into_matched_contender(event.clone()),
217                    ),
218                }
219            }
220            0 | _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
221                criterion: "num_contacts",
222                min: Some(1),
223                max: Some(2),
224                actual: num_contacts,
225            })),
226        }
227    }
228}
229
230impl OneFingerContactContender {
231    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
232    fn into_two_finger_contacts_contender(
233        self: Box<Self>,
234        two_finger_contact_event: TouchpadEvent,
235    ) -> Box<dyn gesture_arena::Contender> {
236        Box::new(TwoFingerContactContender {
237            two_finger_contact_event,
238            spurious_to_intentional_motion_threshold_mm: self
239                .spurious_to_intentional_motion_threshold_mm,
240            spurious_to_intentional_motion_threshold_button_change_mm: self
241                .spurious_to_intentional_motion_threshold_button_change_mm,
242            button_change_state_timeout: self.button_change_state_timeout,
243        })
244    }
245}
246
247impl gesture_arena::Contender for OneFingerContactContender {
248    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
249        let num_pressed_buttons = event.pressed_buttons.len();
250        if num_pressed_buttons > 0 {
251            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
252                criterion: "num_pressed_buttons",
253                min: None,
254                max: Some(0),
255                actual: num_pressed_buttons,
256            }));
257        }
258
259        let num_contacts = event.contacts.len();
260
261        match num_contacts {
262            1 => {
263                let displacement_mm = euclidean_distance(
264                    position_from_event(event, 0),
265                    position_from_event(&self.one_finger_contact_event, 0),
266                );
267                if displacement_mm >= self.spurious_to_intentional_motion_threshold_mm {
268                    return ExamineEventResult::Mismatch(Reason::DetailedFloat(
269                        DetailedReasonFloat {
270                            criterion: "displacement_mm",
271                            min: None,
272                            max: Some(self.spurious_to_intentional_motion_threshold_mm),
273                            actual: displacement_mm,
274                        },
275                    ));
276                }
277                ExamineEventResult::Contender(self)
278            }
279            2 => {
280                let displacement_mm = euclidean_distance(
281                    position_from_event(event, 0),
282                    position_from_event(&self.one_finger_contact_event, 0),
283                );
284                if displacement_mm >= self.spurious_to_intentional_motion_threshold_mm {
285                    return ExamineEventResult::Mismatch(Reason::DetailedFloat(
286                        DetailedReasonFloat {
287                            criterion: "displacement_mm",
288                            min: None,
289                            max: Some(self.spurious_to_intentional_motion_threshold_mm),
290                            actual: displacement_mm,
291                        },
292                    ));
293                }
294                ExamineEventResult::Contender(
295                    self.into_two_finger_contacts_contender(event.clone()),
296                )
297            }
298            _ => ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
299                criterion: "num_contacts",
300                min: Some(1),
301                max: Some(2),
302                actual: num_contacts,
303            })),
304        }
305    }
306}
307
308impl TwoFingerContactContender {
309    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
310    fn into_matched_contender(
311        self: Box<Self>,
312        pressed_event: TouchpadEvent,
313    ) -> Box<dyn gesture_arena::MatchedContender> {
314        Box::new(MatchedContender {
315            pressed_event,
316            spurious_to_intentional_motion_threshold_mm: self
317                .spurious_to_intentional_motion_threshold_mm,
318            spurious_to_intentional_motion_threshold_button_change_mm: self
319                .spurious_to_intentional_motion_threshold_button_change_mm,
320            button_change_state_timeout: self.button_change_state_timeout,
321        })
322    }
323}
324
325impl gesture_arena::Contender for TwoFingerContactContender {
326    fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
327        let num_contacts = event.contacts.len();
328        if num_contacts != 2 {
329            return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
330                criterion: "num_contacts",
331                min: Some(2),
332                max: Some(2),
333                actual: num_contacts,
334            }));
335        }
336
337        // both touch contact should not move > threshold.
338        let MovementDetail { euclidean_distance, movement: _ } =
339            movement_from_events(&self.two_finger_contact_event, &event);
340        if euclidean_distance >= self.spurious_to_intentional_motion_threshold_mm {
341            return ExamineEventResult::Mismatch(Reason::DetailedFloat(DetailedReasonFloat {
342                criterion: "displacement_mm",
343                min: None,
344                max: Some(self.spurious_to_intentional_motion_threshold_mm),
345                actual: euclidean_distance,
346            }));
347        }
348
349        let num_pressed_buttons = event.pressed_buttons.len();
350        match num_pressed_buttons {
351            // No button down, keep matching.
352            0 => ExamineEventResult::Contender(self),
353            // If button down, into matched.
354            // Currently we don't have hardware with more than 1 button in
355            // touchpad, ignore this case.
356            _ => ExamineEventResult::MatchedContender(self.into_matched_contender(event.clone())),
357        }
358    }
359}
360
361impl MatchedContender {
362    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
363    fn into_button_down_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
364        Box::new(ButtonDownWinner {
365            pressed_event: self.pressed_event,
366            spurious_to_intentional_motion_threshold_mm: self
367                .spurious_to_intentional_motion_threshold_mm,
368            spurious_to_intentional_motion_threshold_button_change_mm: self
369                .spurious_to_intentional_motion_threshold_button_change_mm,
370            button_change_state_timeout: self.button_change_state_timeout,
371        })
372    }
373}
374
375impl gesture_arena::MatchedContender for MatchedContender {
376    fn verify_event(self: Box<Self>, _event: &TouchpadEvent) -> VerifyEventResult {
377        // This verify_event expected not call because all other recognizers
378        // should exit on 2 finger button down.
379
380        log::error!("Unexpected MatchedContender::verify_event() called");
381
382        VerifyEventResult::MatchedContender(self)
383    }
384
385    fn process_buffered_events(
386        self: Box<Self>,
387        _events: Vec<TouchpadEvent>,
388    ) -> ProcessBufferedEventsResult {
389        // all small motion before button down are ignored.
390        ProcessBufferedEventsResult {
391            generated_events: vec![touchpad_event_to_mouse_down_event(&self.pressed_event)],
392            winner: Some(self.into_button_down_winner()),
393            recognized_gesture: RecognizedGesture::SecondaryButtonDown,
394        }
395    }
396}
397
398impl ButtonDownWinner {
399    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
400    fn into_drag_winner(self: Box<Self>) -> Box<dyn gesture_arena::Winner> {
401        Box::new(DragWinner {
402            last_event: self.pressed_event,
403            spurious_to_intentional_motion_threshold_button_change_mm: self
404                .spurious_to_intentional_motion_threshold_button_change_mm,
405            button_change_state_timeout: self.button_change_state_timeout,
406        })
407    }
408
409    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
410    fn into_button_up(
411        self: Box<Self>,
412        button_up_event: TouchpadEvent,
413    ) -> Box<dyn gesture_arena::Winner> {
414        Box::new(ButtonUpWinner {
415            button_up_event,
416            spurious_to_intentional_motion_threshold_button_change_mm: self
417                .spurious_to_intentional_motion_threshold_button_change_mm,
418            button_change_state_timeout: self.button_change_state_timeout,
419        })
420    }
421}
422
423impl gesture_arena::Winner for ButtonDownWinner {
424    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
425        let motion_threshold =
426            if event.timestamp - self.pressed_event.timestamp > self.button_change_state_timeout {
427                self.spurious_to_intentional_motion_threshold_mm
428            } else {
429                self.spurious_to_intentional_motion_threshold_button_change_mm
430            };
431
432        // Check for drag (button held, with sufficient contact movement).
433        let MovementDetail { euclidean_distance, movement: _ } =
434            movement_from_events(&self.pressed_event, &event);
435        if euclidean_distance >= motion_threshold {
436            let drag_winner = self.into_drag_winner();
437            return drag_winner.process_new_event(event);
438        }
439
440        let num_pressed_buttons = event.pressed_buttons.len();
441        match num_pressed_buttons {
442            // All small motion before button up, and motion in button up event
443            // are ignored.
444            0 => ProcessNewEventResult::ContinueGesture(
445                Some(touchpad_event_to_mouse_up_event(&event)),
446                self.into_button_up(event),
447            ),
448            1 => ProcessNewEventResult::ContinueGesture(None, self),
449            // Also wait for the button release to complete the click or drag gesture.
450            // this should never happens unless there is a touchpad has more than 1 button.
451            _ => ProcessNewEventResult::ContinueGesture(None, self),
452        }
453    }
454}
455
456impl DragWinner {
457    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
458    fn into_drag_winner(
459        self: Box<Self>,
460        last_event: TouchpadEvent,
461    ) -> Box<dyn gesture_arena::Winner> {
462        Box::new(DragWinner {
463            spurious_to_intentional_motion_threshold_button_change_mm: self
464                .spurious_to_intentional_motion_threshold_button_change_mm,
465            button_change_state_timeout: self.button_change_state_timeout,
466            last_event,
467        })
468    }
469
470    #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
471    fn into_button_up(
472        self: Box<Self>,
473        button_up_event: TouchpadEvent,
474    ) -> Box<dyn gesture_arena::Winner> {
475        Box::new(ButtonUpWinner {
476            spurious_to_intentional_motion_threshold_button_change_mm: self
477                .spurious_to_intentional_motion_threshold_button_change_mm,
478            button_change_state_timeout: self.button_change_state_timeout,
479            button_up_event,
480        })
481    }
482}
483
484impl gesture_arena::Winner for DragWinner {
485    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
486        let num_pressed_buttons = event.pressed_buttons.len();
487        match num_pressed_buttons {
488            // Motion in button up event is ignored.
489            0 => ProcessNewEventResult::ContinueGesture(
490                Some(touchpad_event_to_mouse_up_event(&event)),
491                self.into_button_up(event),
492            ),
493            1 | _ => {
494                // More than 2 button should never happens unless there is a touchpad has
495                // more than 1 button. Just treat this same with 1 button down.
496                ProcessNewEventResult::ContinueGesture(
497                    Some(touchpad_event_to_mouse_drag_event(&self.last_event, &event)),
498                    self.into_drag_winner(event),
499                )
500            }
501        }
502    }
503}
504
505impl gesture_arena::Winner for ButtonUpWinner {
506    fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
507        // Fingers leave or add to surface should end the ButtonUpWinner.
508        let num_contacts = event.contacts.len();
509        let num_contacts_button_up = self.button_up_event.contacts.len();
510        if num_contacts != num_contacts_button_up {
511            return ProcessNewEventResult::EndGesture(
512                EndGestureEvent::UnconsumedEvent(event),
513                Reason::DetailedUint(DetailedReasonUint {
514                    criterion: "num_contacts",
515                    min: Some(num_contacts_button_up as u64),
516                    max: Some(num_contacts_button_up as u64),
517                    actual: num_contacts,
518                }),
519            );
520        }
521
522        // Button change should end the ButtonUpWinner.
523        let num_pressed_buttons = event.pressed_buttons.len();
524        if num_pressed_buttons != 0 {
525            return ProcessNewEventResult::EndGesture(
526                EndGestureEvent::UnconsumedEvent(event),
527                Reason::DetailedUint(DetailedReasonUint {
528                    criterion: "num_buttons",
529                    min: Some(0),
530                    max: Some(0),
531                    actual: num_pressed_buttons,
532                }),
533            );
534        }
535
536        // Events after timeout should not be discarded by ButtonUpWinner.
537        if event.timestamp - self.button_up_event.timestamp > self.button_change_state_timeout {
538            return ProcessNewEventResult::EndGesture(
539                EndGestureEvent::UnconsumedEvent(event),
540                Reason::Basic("button_up_timeout"),
541            );
542        }
543
544        // Events move more than threshold should end the ButtonUpWinner.
545        let MovementDetail { euclidean_distance, movement: _ } =
546            movement_from_events(&self.button_up_event, &event);
547        if euclidean_distance > self.spurious_to_intentional_motion_threshold_button_change_mm {
548            return ProcessNewEventResult::EndGesture(
549                EndGestureEvent::UnconsumedEvent(event),
550                Reason::DetailedFloat(DetailedReasonFloat {
551                    criterion: "displacement_mm",
552                    min: None,
553                    max: Some(self.spurious_to_intentional_motion_threshold_button_change_mm),
554                    actual: euclidean_distance,
555                }),
556            );
557        }
558
559        // Discard this event.
560        ProcessNewEventResult::ContinueGesture(None, self)
561    }
562}
563
564/// This function returns the position associated with the touch contact at the
565/// given index from a TouchpadEvent.
566fn position_from_event(event: &TouchpadEvent, index: usize) -> Position {
567    event.contacts[index].position
568}
569
570fn touchpad_event_to_mouse_down_event(event: &TouchpadEvent) -> gesture_arena::MouseEvent {
571    make_mouse_event(
572        event.timestamp,
573        Position::zero(),
574        MousePhase::Down,
575        SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
576        SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
577    )
578}
579
580fn touchpad_event_to_mouse_up_event(event: &TouchpadEvent) -> gesture_arena::MouseEvent {
581    make_mouse_event(
582        event.timestamp,
583        Position::zero(),
584        MousePhase::Up,
585        SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
586        SortedVecSet::new(),
587    )
588}
589
590fn touchpad_event_to_mouse_drag_event(
591    last_event: &TouchpadEvent,
592    event: &TouchpadEvent,
593) -> gesture_arena::MouseEvent {
594    let MovementDetail { movement, euclidean_distance: _ } =
595        movement_from_events(last_event, event);
596    make_mouse_event(
597        event.timestamp,
598        movement,
599        MousePhase::Move,
600        SortedVecSet::new(),
601        SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
602    )
603}
604
605fn make_mouse_event(
606    timestamp: zx::MonotonicInstant,
607    movement_in_mm: Position,
608    phase: MousePhase,
609    affected_buttons: SortedVecSet<MouseButton>,
610    pressed_buttons: SortedVecSet<MouseButton>,
611) -> gesture_arena::MouseEvent {
612    gesture_arena::MouseEvent {
613        timestamp,
614        mouse_data: MouseEvent::new(
615            MouseLocation::Relative(RelativeLocation { millimeters: movement_in_mm }),
616            /* wheel_delta_v= */ None,
617            /* wheel_delta_h= */ None,
618            phase,
619            affected_buttons,
620            pressed_buttons,
621            /* is_precision_scroll= */ None,
622            /* wake_lease= */ 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= */ SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
1069                  /* pressed_buttons= */ SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
1070                  /* is_precision_scroll= */ None,
1071            /* wake_lease= */ None,
1072
1073              ),
1074            }
1075          ]);
1076          pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1077        });
1078    }
1079
1080    #[test_case(TouchpadEvent{
1081        timestamp: zx::MonotonicInstant::from_nanos(41),
1082        pressed_buttons: vec![],
1083        contacts: vec![
1084            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1085        ],
1086        filtered_palm_contacts: vec![],
1087    };"1 finger button release")]
1088    #[test_case(TouchpadEvent{
1089        timestamp: zx::MonotonicInstant::from_nanos(41),
1090        pressed_buttons: vec![],
1091        contacts: vec![
1092            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1093            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1094        ],
1095        filtered_palm_contacts: vec![],
1096    };"2 finger button release")]
1097    #[test_case(TouchpadEvent{
1098        timestamp: zx::MonotonicInstant::from_nanos(41),
1099        pressed_buttons: vec![],
1100        contacts: vec![
1101            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1102            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1103            make_touch_contact(2, Position{x: 15.0, y: 15.0}),
1104        ],
1105        filtered_palm_contacts: vec![],
1106    };"3 finger button release")]
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: 19.0, y: 1.0}),
1112            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1113        ],
1114        filtered_palm_contacts: vec![],
1115    };"move less than threshold in button change state")]
1116    #[test_case(TouchpadEvent{
1117        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1118        pressed_buttons: vec![],
1119        contacts: vec![
1120            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
1121            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1122        ],
1123        filtered_palm_contacts: vec![],
1124    };"move less than threshold out of button change state")]
1125    #[test_case(TouchpadEvent{
1126        timestamp: zx::MonotonicInstant::from_nanos(41),
1127        pressed_buttons: vec![],
1128        contacts: vec![
1129            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1130            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1131        ],
1132        filtered_palm_contacts: vec![],
1133    };"move more than threshold in button change state")]
1134    #[test_case(TouchpadEvent{
1135        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1136        pressed_buttons: vec![],
1137        contacts: vec![
1138            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1139            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1140        ],
1141        filtered_palm_contacts: vec![],
1142    };"move more than threshold out of button change state")]
1143    #[fuchsia::test]
1144    fn button_down_winner_button_up(event: TouchpadEvent) {
1145        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1146            spurious_to_intentional_motion_threshold_mm:
1147                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1148            spurious_to_intentional_motion_threshold_button_change_mm:
1149                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1150            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1151            pressed_event: TouchpadEvent {
1152                timestamp: zx::MonotonicInstant::ZERO,
1153                pressed_buttons: vec![1],
1154                contacts: vec![
1155                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1156                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1157                ],
1158                filtered_palm_contacts: vec![],
1159            },
1160        });
1161
1162        let got = winner.process_new_event(event);
1163        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1164            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1165                MouseLocation::Relative(RelativeLocation {
1166                    millimeters: Position { x: 0.0, y: 0.0 },
1167                }),
1168                /* wheel_delta_v= */ None,
1169                /* wheel_delta_h= */ None,
1170                MousePhase::Up,
1171                /* affected_buttons= */ SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
1172                /* pressed_buttons= */ SortedVecSet::new(),
1173                /* is_precision_scroll= */ None,
1174            /* wake_lease= */ None,
1175
1176            ));
1177            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1178        });
1179    }
1180
1181    #[test_case(TouchpadEvent{
1182        timestamp: zx::MonotonicInstant::from_nanos(41),
1183        pressed_buttons: vec![1],
1184        contacts: vec![
1185            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
1186            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1187        ],
1188        filtered_palm_contacts: vec![],
1189    };"move less than threshold in button change state")]
1190    #[test_case(TouchpadEvent{
1191        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1192        pressed_buttons: vec![1],
1193        contacts: vec![
1194            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
1195            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1196        ],
1197        filtered_palm_contacts: vec![],
1198    };"move less than threshold out of button change state")]
1199    #[fuchsia::test]
1200    fn button_down_winner_continue(event: TouchpadEvent) {
1201        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1202            spurious_to_intentional_motion_threshold_mm:
1203                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1204            spurious_to_intentional_motion_threshold_button_change_mm:
1205                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1206            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1207            pressed_event: TouchpadEvent {
1208                timestamp: zx::MonotonicInstant::ZERO,
1209                pressed_buttons: vec![1],
1210                contacts: vec![
1211                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1212                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1213                ],
1214                filtered_palm_contacts: vec![],
1215            },
1216        });
1217
1218        let got = winner.process_new_event(event);
1219        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1220            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1221        });
1222    }
1223
1224    #[test_case(TouchpadEvent{
1225        timestamp: zx::MonotonicInstant::from_nanos(41),
1226        pressed_buttons: vec![1],
1227        contacts: vec![
1228            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1229            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1230        ],
1231        filtered_palm_contacts: vec![],
1232    };"move more than threshold in button change state")]
1233    #[test_case(TouchpadEvent{
1234        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1235        pressed_buttons: vec![1],
1236        contacts: vec![
1237            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1238            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1239        ],
1240        filtered_palm_contacts: vec![],
1241    };"move more than threshold out of button change state")]
1242    #[fuchsia::test]
1243    fn button_down_winner_drag_winner_continue(event: TouchpadEvent) {
1244        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1245            spurious_to_intentional_motion_threshold_mm:
1246                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1247            spurious_to_intentional_motion_threshold_button_change_mm:
1248                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1249            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1250            pressed_event: TouchpadEvent {
1251                timestamp: zx::MonotonicInstant::ZERO,
1252                pressed_buttons: vec![1],
1253                contacts: vec![
1254                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1255                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1256                ],
1257                filtered_palm_contacts: vec![],
1258            },
1259        });
1260
1261        let got = winner.process_new_event(event);
1262        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1263            pretty_assertions::assert_eq!(mouse_data.phase, MousePhase::Move);
1264            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1265        });
1266    }
1267
1268    #[test_case(TouchpadEvent{
1269      timestamp: zx::MonotonicInstant::from_nanos(41),
1270      pressed_buttons: vec![],
1271      contacts: vec![
1272          make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1273          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1274      ],
1275      filtered_palm_contacts: vec![],
1276    };"button release")]
1277    #[test_case(TouchpadEvent{
1278        timestamp: zx::MonotonicInstant::from_nanos(41),
1279        pressed_buttons: vec![],
1280        contacts: vec![
1281            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1282        ],
1283        filtered_palm_contacts: vec![],
1284      };"1 finger button release")]
1285    #[test_case(TouchpadEvent{
1286      timestamp: zx::MonotonicInstant::from_nanos(41),
1287      pressed_buttons: vec![],
1288      contacts: vec![
1289          make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1290          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1291      ],
1292      filtered_palm_contacts: vec![],
1293    };"large move and button release")]
1294    #[fuchsia::test]
1295    fn drag_winner_button_up(event: TouchpadEvent) {
1296        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1297            spurious_to_intentional_motion_threshold_button_change_mm:
1298                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1299            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1300            last_event: TouchpadEvent {
1301                timestamp: zx::MonotonicInstant::ZERO,
1302                pressed_buttons: vec![1],
1303                contacts: vec![
1304                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1305                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1306                ],
1307                filtered_palm_contacts: vec![],
1308            },
1309        });
1310
1311        let got = winner.process_new_event(event);
1312        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1313            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1314                MouseLocation::Relative(RelativeLocation {
1315                    millimeters: Position { x: 0.0, y: 0.0 },
1316                }),
1317                /* wheel_delta_v= */ None,
1318                /* wheel_delta_h= */ None,
1319                MousePhase::Up,
1320                /* affected_buttons= */ SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
1321                /* pressed_buttons= */ SortedVecSet::new(),
1322                /* is_precision_scroll= */ None,
1323            /* wake_lease= */ None,
1324
1325            ));
1326            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1327        });
1328    }
1329
1330    #[test_case(TouchpadEvent{
1331        timestamp: zx::MonotonicInstant::from_nanos(41),
1332        pressed_buttons: vec![1],
1333        contacts: vec![
1334            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1335            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1336        ],
1337        filtered_palm_contacts: vec![],
1338    };"2 finger")]
1339    #[test_case(TouchpadEvent{
1340        timestamp: zx::MonotonicInstant::from_nanos(41),
1341        pressed_buttons: vec![1],
1342        contacts: vec![
1343            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1344        ],
1345        filtered_palm_contacts: vec![],
1346    };"1 finger")]
1347    #[fuchsia::test]
1348    fn drag_winner_continue(event: TouchpadEvent) {
1349        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1350            spurious_to_intentional_motion_threshold_button_change_mm:
1351                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1352            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1353            last_event: TouchpadEvent {
1354                timestamp: zx::MonotonicInstant::ZERO,
1355                pressed_buttons: vec![1],
1356                contacts: vec![
1357                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1358                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1359                ],
1360                filtered_palm_contacts: vec![],
1361            },
1362        });
1363
1364        let got = winner.process_new_event(event);
1365        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1366            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1367                MouseLocation::Relative(RelativeLocation {
1368                    millimeters: Position { x: 19.0, y: 1.0 },
1369                }),
1370                /* wheel_delta_v= */ None,
1371                /* wheel_delta_h= */ None,
1372                MousePhase::Move,
1373                /* affected_buttons= */ SortedVecSet::new(),
1374                /* pressed_buttons= */ SortedVecSet::from(vec![gesture_arena::SECONDARY_BUTTON]),
1375                /* is_precision_scroll= */ None,
1376            /* wake_lease= */ None,
1377
1378            ));
1379            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1380        });
1381    }
1382
1383    #[fuchsia::test]
1384    fn button_up_winner_continue() {
1385        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1386            spurious_to_intentional_motion_threshold_button_change_mm:
1387                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1388            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1389            button_up_event: TouchpadEvent {
1390                timestamp: zx::MonotonicInstant::ZERO,
1391                pressed_buttons: vec![],
1392                contacts: vec![
1393                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1394                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1395                ],
1396                filtered_palm_contacts: vec![],
1397            },
1398        });
1399
1400        let event = TouchpadEvent {
1401            timestamp: zx::MonotonicInstant::from_nanos(41),
1402            pressed_buttons: vec![],
1403            contacts: vec![
1404                make_touch_contact(1, Position { x: 10.0, y: 1.0 }),
1405                make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1406            ],
1407            filtered_palm_contacts: vec![],
1408        };
1409
1410        let got = winner.process_new_event(event);
1411        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1412            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1413        });
1414    }
1415
1416    #[test_case(TouchpadEvent{
1417        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1_001),
1418        pressed_buttons: vec![],
1419        contacts: vec![
1420            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1421        ],
1422        filtered_palm_contacts: vec![],
1423    };"timeout")]
1424    #[test_case(TouchpadEvent{
1425        timestamp: zx::MonotonicInstant::from_nanos(41),
1426        pressed_buttons: vec![1],
1427        contacts: vec![
1428            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1429        ],
1430        filtered_palm_contacts: vec![],
1431    };"button down")]
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: 21.0, y: 0.0}),
1437        ],
1438        filtered_palm_contacts: vec![],
1439    };"move more than threshold")]
1440    #[test_case(TouchpadEvent{
1441        timestamp: zx::MonotonicInstant::from_nanos(41),
1442        pressed_buttons: vec![],
1443        contacts: vec![
1444            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1445            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1446            make_touch_contact(3, Position{x: 15.0, y: 15.0}),
1447        ],
1448        filtered_palm_contacts: vec![],
1449    };"more contacts")]
1450    #[test_case(TouchpadEvent{
1451      timestamp: zx::MonotonicInstant::from_nanos(41),
1452      pressed_buttons: vec![],
1453      contacts: vec![
1454          make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1455      ],
1456      filtered_palm_contacts: vec![],
1457    };"less contacts")]
1458    #[test_case(TouchpadEvent{
1459        timestamp: zx::MonotonicInstant::from_nanos(41),
1460        pressed_buttons: vec![],
1461        contacts: vec![],
1462        filtered_palm_contacts: vec![],
1463    };"no contact")]
1464    #[fuchsia::test]
1465    fn button_up_winner_end(event: TouchpadEvent) {
1466        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1467            spurious_to_intentional_motion_threshold_button_change_mm:
1468                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1469            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1470            button_up_event: TouchpadEvent {
1471                timestamp: zx::MonotonicInstant::ZERO,
1472                pressed_buttons: vec![],
1473                contacts: vec![
1474                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1475                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1476                ],
1477                filtered_palm_contacts: vec![],
1478            },
1479        });
1480
1481        let got = winner.process_new_event(event);
1482        assert_matches!(
1483            got,
1484            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _)
1485        );
1486    }
1487}