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 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            /* wake_lease= */ None,
624        ),
625    }
626}
627
628#[cfg(test)]
629mod test {
630    use super::*;
631    use crate::touch_binding;
632    use assert_matches::assert_matches;
633    use test_case::test_case;
634
635    fn make_touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
636        touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
637    }
638
639    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM: f32 = 10.0;
640    const SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM: f32 = 20.0;
641    const BUTTON_CHANGE_STATE_TIMEOUT: zx::MonotonicDuration =
642        zx::MonotonicDuration::from_seconds(1);
643
644    #[test_case(TouchpadEvent{
645        timestamp: zx::MonotonicInstant::ZERO,
646        pressed_buttons: vec![],
647        contacts: vec![],
648        filtered_palm_contacts: vec![],
649    };"0 fingers")]
650    #[test_case(TouchpadEvent{
651        timestamp: zx::MonotonicInstant::ZERO,
652        pressed_buttons: vec![1],
653        contacts: vec![
654            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
655        ],
656        filtered_palm_contacts: vec![],
657    };"1 fingers button down")]
658    #[test_case(TouchpadEvent{
659        timestamp: zx::MonotonicInstant::ZERO,
660        pressed_buttons: vec![],
661        contacts: vec![
662            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
663            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
664            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
665        ],
666        filtered_palm_contacts: vec![],
667    };"3 fingers")]
668    #[fuchsia::test]
669    fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
670        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
671            spurious_to_intentional_motion_threshold_mm:
672                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
673            spurious_to_intentional_motion_threshold_button_change_mm:
674                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
675            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
676        });
677
678        let got = contender.examine_event(&event);
679        assert_matches!(got, ExamineEventResult::Mismatch(_));
680    }
681
682    #[test_case(TouchpadEvent{
683        timestamp: zx::MonotonicInstant::ZERO,
684        pressed_buttons: vec![],
685        contacts: vec![
686            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
687        ],
688        filtered_palm_contacts: vec![],
689    }, "input_pipeline_lib_test::gestures::secondary_button::OneFingerContactContender";"1 fingers")]
690    #[test_case(TouchpadEvent{
691        timestamp: zx::MonotonicInstant::ZERO,
692        pressed_buttons: vec![],
693        contacts: vec![
694            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
695            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
696        ],
697        filtered_palm_contacts: vec![],
698    }, "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender";"2 fingers")]
699    #[fuchsia::test]
700    fn initial_contender_examine_event_contender(event: TouchpadEvent, contender_name: &str) {
701        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
702            spurious_to_intentional_motion_threshold_mm:
703                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
704            spurious_to_intentional_motion_threshold_button_change_mm:
705                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
706            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
707        });
708
709        let got = contender.examine_event(&event);
710        assert_matches!(got, ExamineEventResult::Contender(contender) => {
711            pretty_assertions::assert_eq!(contender.get_type_name(), contender_name);
712        });
713    }
714
715    #[fuchsia::test]
716    fn initial_contender_examine_event_matched_contender() {
717        let contender: Box<dyn gesture_arena::Contender> = Box::new(InitialContender {
718            spurious_to_intentional_motion_threshold_mm:
719                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
720            spurious_to_intentional_motion_threshold_button_change_mm:
721                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
722            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
723        });
724        let event = TouchpadEvent {
725            timestamp: zx::MonotonicInstant::ZERO,
726            pressed_buttons: vec![1],
727            contacts: vec![
728                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
729                make_touch_contact(2, Position { x: 1.0, y: 5.0 }),
730            ],
731            filtered_palm_contacts: vec![],
732        };
733
734        let got = contender.examine_event(&event);
735        assert_matches!(got, ExamineEventResult::MatchedContender(_));
736    }
737
738    #[test_case(TouchpadEvent{
739        timestamp: zx::MonotonicInstant::ZERO,
740        pressed_buttons: vec![],
741        contacts: vec![],
742        filtered_palm_contacts: vec![],
743    };"0 fingers")]
744    #[test_case(TouchpadEvent{
745        timestamp: zx::MonotonicInstant::ZERO,
746        pressed_buttons: vec![1],
747        contacts: vec![
748            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
749        ],
750        filtered_palm_contacts: vec![],
751    };"1 fingers button down")]
752    #[test_case(TouchpadEvent{
753        timestamp: zx::MonotonicInstant::ZERO,
754        pressed_buttons: vec![],
755        contacts: vec![
756            make_touch_contact(1, Position{x: 1.0, y: 12.0}),
757        ],
758        filtered_palm_contacts: vec![],
759    };"1 fingers move more than threshold")]
760    #[test_case(TouchpadEvent{
761        timestamp: zx::MonotonicInstant::ZERO,
762        pressed_buttons: vec![],
763        contacts: vec![
764            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
765            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
766            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
767        ],
768        filtered_palm_contacts: vec![],
769    };"3 fingers")]
770    #[fuchsia::test]
771    fn one_finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
772        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
773            one_finger_contact_event: TouchpadEvent {
774                timestamp: zx::MonotonicInstant::ZERO,
775                pressed_buttons: vec![],
776                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
777                filtered_palm_contacts: vec![],
778            },
779            spurious_to_intentional_motion_threshold_mm:
780                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
781            spurious_to_intentional_motion_threshold_button_change_mm:
782                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
783            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
784        });
785
786        let got = contender.examine_event(&event);
787        assert_matches!(got, ExamineEventResult::Mismatch(_));
788    }
789
790    #[test_case(TouchpadEvent{
791        timestamp: zx::MonotonicInstant::ZERO,
792        pressed_buttons: vec![],
793        contacts: vec![
794            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
795        ],
796        filtered_palm_contacts: vec![],
797    };"1 fingers hold")]
798    #[test_case(TouchpadEvent{
799        timestamp: zx::MonotonicInstant::ZERO,
800        pressed_buttons: vec![],
801        contacts: vec![
802            make_touch_contact(1, Position{x: 1.0, y: 10.0}),
803        ],
804        filtered_palm_contacts: vec![],
805    };"1 fingers move less than threshold")]
806    #[fuchsia::test]
807    fn one_finger_contact_contender_examine_event_contender(event: TouchpadEvent) {
808        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
809            one_finger_contact_event: TouchpadEvent {
810                timestamp: zx::MonotonicInstant::ZERO,
811                pressed_buttons: vec![],
812                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
813                filtered_palm_contacts: vec![],
814            },
815            spurious_to_intentional_motion_threshold_mm:
816                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
817            spurious_to_intentional_motion_threshold_button_change_mm:
818                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
819            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
820        });
821
822        let got = contender.examine_event(&event);
823        assert_matches!(got, ExamineEventResult::Contender(c) => {
824            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::OneFingerContactContender");
825        });
826    }
827
828    #[fuchsia::test]
829    fn one_finger_contact_contender_examine_event_two_finger_contact_contender() {
830        let contender: Box<dyn gesture_arena::Contender> = Box::new(OneFingerContactContender {
831            one_finger_contact_event: TouchpadEvent {
832                timestamp: zx::MonotonicInstant::ZERO,
833                pressed_buttons: vec![],
834                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
835                filtered_palm_contacts: vec![],
836            },
837            spurious_to_intentional_motion_threshold_mm:
838                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
839            spurious_to_intentional_motion_threshold_button_change_mm:
840                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
841            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
842        });
843
844        let event = TouchpadEvent {
845            timestamp: zx::MonotonicInstant::ZERO,
846            pressed_buttons: vec![],
847            contacts: vec![
848                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
849                make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
850            ],
851            filtered_palm_contacts: vec![],
852        };
853        let got = contender.examine_event(&event);
854        assert_matches!(got, ExamineEventResult::Contender(c) => {
855            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender");
856        });
857    }
858
859    #[test_case(TouchpadEvent{
860        timestamp: zx::MonotonicInstant::ZERO,
861        pressed_buttons: vec![],
862        contacts: vec![],
863        filtered_palm_contacts: vec![],
864    };"0 fingers")]
865    #[test_case(TouchpadEvent{
866        timestamp: zx::MonotonicInstant::ZERO,
867        pressed_buttons: vec![],
868        contacts: vec![
869            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
870        ],
871        filtered_palm_contacts: vec![],
872    };"1 fingers")]
873    #[test_case(TouchpadEvent{
874        timestamp: zx::MonotonicInstant::ZERO,
875        pressed_buttons: vec![],
876        contacts: vec![
877            make_touch_contact(1, Position{x: 1.0, y: 12.0}),
878            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
879        ],
880        filtered_palm_contacts: vec![],
881    };"2 fingers move more than threshold")]
882    #[test_case(TouchpadEvent{
883        timestamp: zx::MonotonicInstant::ZERO,
884        pressed_buttons: vec![],
885        contacts: vec![
886            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
887            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
888            make_touch_contact(3, Position{x: 10.0, y: 10.0}),
889        ],
890        filtered_palm_contacts: vec![],
891    };"3 fingers")]
892    #[fuchsia::test]
893    fn two_finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
894        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
895            two_finger_contact_event: TouchpadEvent {
896                timestamp: zx::MonotonicInstant::ZERO,
897                pressed_buttons: vec![],
898                contacts: vec![
899                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
900                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
901                ],
902                filtered_palm_contacts: vec![],
903            },
904            spurious_to_intentional_motion_threshold_mm:
905                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
906            spurious_to_intentional_motion_threshold_button_change_mm:
907                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
908            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
909        });
910
911        let got = contender.examine_event(&event);
912        assert_matches!(got, ExamineEventResult::Mismatch(_));
913    }
914
915    #[test_case(TouchpadEvent{
916        timestamp: zx::MonotonicInstant::ZERO,
917        pressed_buttons: vec![],
918        contacts: vec![
919            make_touch_contact(1, Position{x: 1.0, y: 9.0}),
920            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
921        ],
922        filtered_palm_contacts: vec![],
923    };"2 fingers move less than threshold")]
924    #[test_case(TouchpadEvent{
925        timestamp: zx::MonotonicInstant::ZERO,
926        pressed_buttons: vec![],
927        contacts: vec![
928            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
929            make_touch_contact(2, Position{x: 5.0, y: 5.0}),
930        ],
931        filtered_palm_contacts: vec![],
932    };"2 fingers hold")]
933    #[fuchsia::test]
934    fn two_finger_contact_contender_examine_event_contender(event: TouchpadEvent) {
935        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
936            two_finger_contact_event: TouchpadEvent {
937                timestamp: zx::MonotonicInstant::ZERO,
938                pressed_buttons: vec![],
939                contacts: vec![
940                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
941                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
942                ],
943                filtered_palm_contacts: vec![],
944            },
945            spurious_to_intentional_motion_threshold_mm:
946                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
947            spurious_to_intentional_motion_threshold_button_change_mm:
948                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
949            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
950        });
951
952        let got = contender.examine_event(&event);
953        assert_matches!(got, ExamineEventResult::Contender(c) => {
954            pretty_assertions::assert_eq!(c.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::TwoFingerContactContender");
955        });
956    }
957
958    #[fuchsia::test]
959    fn two_finger_contact_contender_examine_event_matched_contender() {
960        let contender: Box<dyn gesture_arena::Contender> = Box::new(TwoFingerContactContender {
961            two_finger_contact_event: TouchpadEvent {
962                timestamp: zx::MonotonicInstant::ZERO,
963                pressed_buttons: vec![],
964                contacts: vec![
965                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
966                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
967                ],
968                filtered_palm_contacts: vec![],
969            },
970            spurious_to_intentional_motion_threshold_mm:
971                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
972            spurious_to_intentional_motion_threshold_button_change_mm:
973                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
974            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
975        });
976
977        let event = TouchpadEvent {
978            timestamp: zx::MonotonicInstant::ZERO,
979            pressed_buttons: vec![1],
980            contacts: vec![
981                make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
982                make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
983            ],
984            filtered_palm_contacts: vec![],
985        };
986        let got = contender.examine_event(&event);
987        assert_matches!(got, ExamineEventResult::MatchedContender(_));
988    }
989
990    #[fuchsia::test]
991    fn matched_contender_process_buffered_events() {
992        let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {
993            spurious_to_intentional_motion_threshold_mm:
994                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
995            spurious_to_intentional_motion_threshold_button_change_mm:
996                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
997            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
998            pressed_event: TouchpadEvent {
999                timestamp: zx::MonotonicInstant::from_nanos(41),
1000                pressed_buttons: vec![1],
1001                contacts: vec![
1002                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1003                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1004                ],
1005                filtered_palm_contacts: vec![],
1006            },
1007        });
1008        let events = vec![
1009            TouchpadEvent {
1010                timestamp: zx::MonotonicInstant::ZERO,
1011                pressed_buttons: vec![],
1012                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 1.0 })],
1013                filtered_palm_contacts: vec![],
1014            },
1015            // Small movement will be ignored.
1016            TouchpadEvent {
1017                timestamp: zx::MonotonicInstant::ZERO,
1018                pressed_buttons: vec![],
1019                contacts: vec![make_touch_contact(1, Position { x: 1.0, y: 9.0 })],
1020                filtered_palm_contacts: vec![],
1021            },
1022            TouchpadEvent {
1023                timestamp: zx::MonotonicInstant::from_nanos(21),
1024                pressed_buttons: vec![],
1025                contacts: vec![
1026                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1027                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1028                ],
1029                filtered_palm_contacts: vec![],
1030            },
1031            // Small movement will be ignored.
1032            TouchpadEvent {
1033                timestamp: zx::MonotonicInstant::from_nanos(21),
1034                pressed_buttons: vec![],
1035                contacts: vec![
1036                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1037                    make_touch_contact(2, Position { x: 5.0, y: 14.0 }),
1038                ],
1039                filtered_palm_contacts: vec![],
1040            },
1041            TouchpadEvent {
1042                timestamp: zx::MonotonicInstant::from_nanos(41),
1043                pressed_buttons: vec![1],
1044                contacts: vec![
1045                    make_touch_contact(1, Position { x: 1.0, y: 9.0 }),
1046                    make_touch_contact(2, Position { x: 5.0, y: 5.0 }),
1047                ],
1048                filtered_palm_contacts: vec![],
1049            },
1050        ];
1051
1052        let got = contender.process_buffered_events(events);
1053
1054        assert_matches!(got, ProcessBufferedEventsResult{
1055          generated_events,
1056          winner: Some(winner),
1057          recognized_gesture: RecognizedGesture::SecondaryButtonDown,
1058        } => {
1059          pretty_assertions::assert_eq!(generated_events, vec![
1060            gesture_arena::MouseEvent {
1061              timestamp:zx::MonotonicInstant::from_nanos(41),
1062              mouse_data: MouseEvent::new(
1063                  MouseLocation::Relative(RelativeLocation {
1064                      millimeters: Position { x: 0.0, y: 0.0 },
1065                  }),
1066                  /* wheel_delta_v= */ None,
1067                  /* wheel_delta_h= */ None,
1068                  MousePhase::Down,
1069                  /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1070                  /* pressed_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1071                  /* is_precision_scroll= */ None,
1072            /* wake_lease= */ None,
1073
1074              ),
1075            }
1076          ]);
1077          pretty_assertions::assert_eq!(winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1078        });
1079    }
1080
1081    #[test_case(TouchpadEvent{
1082        timestamp: zx::MonotonicInstant::from_nanos(41),
1083        pressed_buttons: vec![],
1084        contacts: vec![
1085            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1086        ],
1087        filtered_palm_contacts: vec![],
1088    };"1 finger button release")]
1089    #[test_case(TouchpadEvent{
1090        timestamp: zx::MonotonicInstant::from_nanos(41),
1091        pressed_buttons: vec![],
1092        contacts: vec![
1093            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1094            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1095        ],
1096        filtered_palm_contacts: vec![],
1097    };"2 finger button release")]
1098    #[test_case(TouchpadEvent{
1099        timestamp: zx::MonotonicInstant::from_nanos(41),
1100        pressed_buttons: vec![],
1101        contacts: vec![
1102            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1103            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1104            make_touch_contact(2, Position{x: 15.0, y: 15.0}),
1105        ],
1106        filtered_palm_contacts: vec![],
1107    };"3 finger button release")]
1108    #[test_case(TouchpadEvent{
1109        timestamp: zx::MonotonicInstant::from_nanos(41),
1110        pressed_buttons: vec![],
1111        contacts: vec![
1112            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1113            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1114        ],
1115        filtered_palm_contacts: vec![],
1116    };"move less than threshold in button change state")]
1117    #[test_case(TouchpadEvent{
1118        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1119        pressed_buttons: vec![],
1120        contacts: vec![
1121            make_touch_contact(1, Position{x: 9.0, y: 1.0}),
1122            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1123        ],
1124        filtered_palm_contacts: vec![],
1125    };"move less than threshold out of button change state")]
1126    #[test_case(TouchpadEvent{
1127        timestamp: zx::MonotonicInstant::from_nanos(41),
1128        pressed_buttons: vec![],
1129        contacts: vec![
1130            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1131            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1132        ],
1133        filtered_palm_contacts: vec![],
1134    };"move more than threshold in button change state")]
1135    #[test_case(TouchpadEvent{
1136        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1137        pressed_buttons: vec![],
1138        contacts: vec![
1139            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1140            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1141        ],
1142        filtered_palm_contacts: vec![],
1143    };"move more than threshold out of button change state")]
1144    #[fuchsia::test]
1145    fn button_down_winner_button_up(event: TouchpadEvent) {
1146        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1147            spurious_to_intentional_motion_threshold_mm:
1148                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1149            spurious_to_intentional_motion_threshold_button_change_mm:
1150                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1151            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1152            pressed_event: TouchpadEvent {
1153                timestamp: zx::MonotonicInstant::ZERO,
1154                pressed_buttons: vec![1],
1155                contacts: vec![
1156                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1157                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1158                ],
1159                filtered_palm_contacts: vec![],
1160            },
1161        });
1162
1163        let got = winner.process_new_event(event);
1164        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1165            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1166                MouseLocation::Relative(RelativeLocation {
1167                    millimeters: Position { x: 0.0, y: 0.0 },
1168                }),
1169                /* wheel_delta_v= */ None,
1170                /* wheel_delta_h= */ None,
1171                MousePhase::Up,
1172                /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1173                /* pressed_buttons= */ hashset!{},
1174                /* is_precision_scroll= */ None,
1175            /* wake_lease= */ None,
1176
1177            ));
1178            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1179        });
1180    }
1181
1182    #[test_case(TouchpadEvent{
1183        timestamp: zx::MonotonicInstant::from_nanos(41),
1184        pressed_buttons: vec![1],
1185        contacts: vec![
1186            make_touch_contact(1, Position{x: 20.0, y: 1.0}),
1187            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1188        ],
1189        filtered_palm_contacts: vec![],
1190    };"move less than threshold in button change state")]
1191    #[test_case(TouchpadEvent{
1192        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1193        pressed_buttons: vec![1],
1194        contacts: vec![
1195            make_touch_contact(1, Position{x: 10.0, y: 1.0}),
1196            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1197        ],
1198        filtered_palm_contacts: vec![],
1199    };"move less than threshold out of button change state")]
1200    #[fuchsia::test]
1201    fn button_down_winner_continue(event: TouchpadEvent) {
1202        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1203            spurious_to_intentional_motion_threshold_mm:
1204                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1205            spurious_to_intentional_motion_threshold_button_change_mm:
1206                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1207            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1208            pressed_event: TouchpadEvent {
1209                timestamp: zx::MonotonicInstant::ZERO,
1210                pressed_buttons: vec![1],
1211                contacts: vec![
1212                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1213                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1214                ],
1215                filtered_palm_contacts: vec![],
1216            },
1217        });
1218
1219        let got = winner.process_new_event(event);
1220        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1221            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonDownWinner");
1222        });
1223    }
1224
1225    #[test_case(TouchpadEvent{
1226        timestamp: zx::MonotonicInstant::from_nanos(41),
1227        pressed_buttons: vec![1],
1228        contacts: vec![
1229            make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1230            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1231        ],
1232        filtered_palm_contacts: vec![],
1233    };"move more than threshold in button change state")]
1234    #[test_case(TouchpadEvent{
1235        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1500),
1236        pressed_buttons: vec![1],
1237        contacts: vec![
1238            make_touch_contact(1, Position{x: 11.0, y: 1.0}),
1239            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1240        ],
1241        filtered_palm_contacts: vec![],
1242    };"move more than threshold out of button change state")]
1243    #[fuchsia::test]
1244    fn button_down_winner_drag_winner_continue(event: TouchpadEvent) {
1245        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonDownWinner {
1246            spurious_to_intentional_motion_threshold_mm:
1247                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_MM,
1248            spurious_to_intentional_motion_threshold_button_change_mm:
1249                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1250            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1251            pressed_event: TouchpadEvent {
1252                timestamp: zx::MonotonicInstant::ZERO,
1253                pressed_buttons: vec![1],
1254                contacts: vec![
1255                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1256                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1257                ],
1258                filtered_palm_contacts: vec![],
1259            },
1260        });
1261
1262        let got = winner.process_new_event(event);
1263        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1264            pretty_assertions::assert_eq!(mouse_data.phase, MousePhase::Move);
1265            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1266        });
1267    }
1268
1269    #[test_case(TouchpadEvent{
1270      timestamp: zx::MonotonicInstant::from_nanos(41),
1271      pressed_buttons: vec![],
1272      contacts: vec![
1273          make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1274          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1275      ],
1276      filtered_palm_contacts: vec![],
1277    };"button release")]
1278    #[test_case(TouchpadEvent{
1279        timestamp: zx::MonotonicInstant::from_nanos(41),
1280        pressed_buttons: vec![],
1281        contacts: vec![
1282            make_touch_contact(1, Position{x: 1.0, y: 1.0}),
1283        ],
1284        filtered_palm_contacts: vec![],
1285      };"1 finger button release")]
1286    #[test_case(TouchpadEvent{
1287      timestamp: zx::MonotonicInstant::from_nanos(41),
1288      pressed_buttons: vec![],
1289      contacts: vec![
1290          make_touch_contact(1, Position{x: 21.0, y: 1.0}),
1291          make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1292      ],
1293      filtered_palm_contacts: vec![],
1294    };"large move and button release")]
1295    #[fuchsia::test]
1296    fn drag_winner_button_up(event: TouchpadEvent) {
1297        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1298            spurious_to_intentional_motion_threshold_button_change_mm:
1299                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1300            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1301            last_event: TouchpadEvent {
1302                timestamp: zx::MonotonicInstant::ZERO,
1303                pressed_buttons: vec![1],
1304                contacts: vec![
1305                    make_touch_contact(1, Position { x: 1.0, y: 1.0 }),
1306                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1307                ],
1308                filtered_palm_contacts: vec![],
1309            },
1310        });
1311
1312        let got = winner.process_new_event(event);
1313        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner) => {
1314            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1315                MouseLocation::Relative(RelativeLocation {
1316                    millimeters: Position { x: 0.0, y: 0.0 },
1317                }),
1318                /* wheel_delta_v= */ None,
1319                /* wheel_delta_h= */ None,
1320                MousePhase::Up,
1321                /* affected_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1322                /* pressed_buttons= */ hashset!{},
1323                /* is_precision_scroll= */ None,
1324            /* wake_lease= */ None,
1325
1326            ));
1327            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1328        });
1329    }
1330
1331    #[test_case(TouchpadEvent{
1332        timestamp: zx::MonotonicInstant::from_nanos(41),
1333        pressed_buttons: vec![1],
1334        contacts: vec![
1335            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1336            make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1337        ],
1338        filtered_palm_contacts: vec![],
1339    };"2 finger")]
1340    #[test_case(TouchpadEvent{
1341        timestamp: zx::MonotonicInstant::from_nanos(41),
1342        pressed_buttons: vec![1],
1343        contacts: vec![
1344            make_touch_contact(1, Position{x: 19.0, y: 1.0}),
1345        ],
1346        filtered_palm_contacts: vec![],
1347    };"1 finger")]
1348    #[fuchsia::test]
1349    fn drag_winner_continue(event: TouchpadEvent) {
1350        let winner: Box<dyn gesture_arena::Winner> = Box::new(DragWinner {
1351            spurious_to_intentional_motion_threshold_button_change_mm:
1352                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1353            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1354            last_event: TouchpadEvent {
1355                timestamp: zx::MonotonicInstant::ZERO,
1356                pressed_buttons: vec![1],
1357                contacts: vec![
1358                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1359                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1360                ],
1361                filtered_palm_contacts: vec![],
1362            },
1363        });
1364
1365        let got = winner.process_new_event(event);
1366        assert_matches!(got, ProcessNewEventResult::ContinueGesture(Some(gesture_arena::MouseEvent {mouse_data, ..}), got_winner)=>{
1367            pretty_assertions::assert_eq!(mouse_data, MouseEvent::new(
1368                MouseLocation::Relative(RelativeLocation {
1369                    millimeters: Position { x: 19.0, y: 1.0 },
1370                }),
1371                /* wheel_delta_v= */ None,
1372                /* wheel_delta_h= */ None,
1373                MousePhase::Move,
1374                /* affected_buttons= */ hashset!{},
1375                /* pressed_buttons= */ hashset!{gesture_arena::SECONDARY_BUTTON},
1376                /* is_precision_scroll= */ None,
1377            /* wake_lease= */ None,
1378
1379            ));
1380            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::DragWinner");
1381        });
1382    }
1383
1384    #[fuchsia::test]
1385    fn button_up_winner_continue() {
1386        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1387            spurious_to_intentional_motion_threshold_button_change_mm:
1388                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1389            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1390            button_up_event: TouchpadEvent {
1391                timestamp: zx::MonotonicInstant::ZERO,
1392                pressed_buttons: vec![],
1393                contacts: vec![
1394                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1395                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1396                ],
1397                filtered_palm_contacts: vec![],
1398            },
1399        });
1400
1401        let event = TouchpadEvent {
1402            timestamp: zx::MonotonicInstant::from_nanos(41),
1403            pressed_buttons: vec![],
1404            contacts: vec![
1405                make_touch_contact(1, Position { x: 10.0, y: 1.0 }),
1406                make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1407            ],
1408            filtered_palm_contacts: vec![],
1409        };
1410
1411        let got = winner.process_new_event(event);
1412        assert_matches!(got, ProcessNewEventResult::ContinueGesture(None, got_winner)=>{
1413            pretty_assertions::assert_eq!(got_winner.get_type_name(), "input_pipeline_lib_test::gestures::secondary_button::ButtonUpWinner");
1414        });
1415    }
1416
1417    #[test_case(TouchpadEvent{
1418        timestamp: zx::MonotonicInstant::ZERO + zx::MonotonicDuration::from_millis(1_001),
1419        pressed_buttons: vec![],
1420        contacts: vec![
1421            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1422        ],
1423        filtered_palm_contacts: vec![],
1424    };"timeout")]
1425    #[test_case(TouchpadEvent{
1426        timestamp: zx::MonotonicInstant::from_nanos(41),
1427        pressed_buttons: vec![1],
1428        contacts: vec![
1429            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1430        ],
1431        filtered_palm_contacts: vec![],
1432    };"button down")]
1433    #[test_case(TouchpadEvent{
1434        timestamp: zx::MonotonicInstant::from_nanos(41),
1435        pressed_buttons: vec![],
1436        contacts: vec![
1437            make_touch_contact(1, Position{x: 21.0, y: 0.0}),
1438        ],
1439        filtered_palm_contacts: vec![],
1440    };"move more than threshold")]
1441    #[test_case(TouchpadEvent{
1442        timestamp: zx::MonotonicInstant::from_nanos(41),
1443        pressed_buttons: vec![],
1444        contacts: vec![
1445            make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1446            make_touch_contact(2, Position{x: 10.0, y: 10.0}),
1447            make_touch_contact(3, Position{x: 15.0, y: 15.0}),
1448        ],
1449        filtered_palm_contacts: vec![],
1450    };"more contacts")]
1451    #[test_case(TouchpadEvent{
1452      timestamp: zx::MonotonicInstant::from_nanos(41),
1453      pressed_buttons: vec![],
1454      contacts: vec![
1455          make_touch_contact(1, Position{x: 0.0, y: 0.0}),
1456      ],
1457      filtered_palm_contacts: vec![],
1458    };"less contacts")]
1459    #[test_case(TouchpadEvent{
1460        timestamp: zx::MonotonicInstant::from_nanos(41),
1461        pressed_buttons: vec![],
1462        contacts: vec![],
1463        filtered_palm_contacts: vec![],
1464    };"no contact")]
1465    #[fuchsia::test]
1466    fn button_up_winner_end(event: TouchpadEvent) {
1467        let winner: Box<dyn gesture_arena::Winner> = Box::new(ButtonUpWinner {
1468            spurious_to_intentional_motion_threshold_button_change_mm:
1469                SPURIOUS_TO_INTENTIONAL_MOTION_THRESHOLD_BUTTON_CHANGE_MM,
1470            button_change_state_timeout: BUTTON_CHANGE_STATE_TIMEOUT,
1471            button_up_event: TouchpadEvent {
1472                timestamp: zx::MonotonicInstant::ZERO,
1473                pressed_buttons: vec![],
1474                contacts: vec![
1475                    make_touch_contact(1, Position { x: 0.0, y: 0.0 }),
1476                    make_touch_contact(2, Position { x: 10.0, y: 10.0 }),
1477                ],
1478                filtered_palm_contacts: vec![],
1479            },
1480        });
1481
1482        let got = winner.process_new_event(event);
1483        assert_matches!(
1484            got,
1485            ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _)
1486        );
1487    }
1488}