1use super::gesture_arena::{
6 self, DetailedReasonUint, EndGestureEvent, ExamineEventResult, MouseEvent,
7 ProcessBufferedEventsResult, ProcessNewEventResult, Reason, RecognizedGesture, TouchpadEvent,
8 VerifyEventResult,
9};
10use crate::mouse_binding;
11use crate::utils::{Position, euclidean_distance};
12use maplit::hashset;
13
14#[derive(Debug)]
16pub(super) struct InitialContender {
17 pub(super) min_movement_in_mm: f32,
19}
20
21#[derive(Debug)]
23struct FingerContactContender {
24 min_movement_in_mm: f32,
26
27 initial_position: Position,
29}
30
31#[derive(Debug)]
34struct MatchedContender {}
35
36#[derive(Debug)]
38struct Winner {
39 last_position: Position,
41}
42
43impl InitialContender {
44 #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
45 fn into_finger_contact_contender(
46 self: Box<Self>,
47 initial_position: Position,
48 ) -> Box<dyn gesture_arena::Contender> {
49 Box::new(FingerContactContender {
50 min_movement_in_mm: self.min_movement_in_mm,
51 initial_position,
52 })
53 }
54}
55
56impl gesture_arena::Contender for InitialContender {
57 fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
58 let num_contacts = event.contacts.len();
59 if num_contacts != 1 {
60 return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
61 criterion: "num_contacts",
62 min: Some(1),
63 max: Some(1),
64 actual: num_contacts,
65 }));
66 }
67
68 let num_pressed_buttons = event.pressed_buttons.len();
69 if num_pressed_buttons > 0 {
70 return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
71 criterion: "num_pressed_buttons",
72 min: None,
73 max: Some(0),
74 actual: num_pressed_buttons,
75 }));
76 }
77
78 ExamineEventResult::Contender(
79 self.into_finger_contact_contender(event.contacts[0].position),
80 )
81 }
82}
83
84impl FingerContactContender {
85 #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
86 fn into_matched_contender(self: Box<Self>) -> Box<dyn gesture_arena::MatchedContender> {
87 Box::new(MatchedContender {})
88 }
89}
90
91impl gesture_arena::Contender for FingerContactContender {
92 fn examine_event(self: Box<Self>, event: &TouchpadEvent) -> ExamineEventResult {
93 let num_contacts = event.contacts.len();
94 if num_contacts != 1 {
95 return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
96 criterion: "num_contacts",
97 min: Some(1),
98 max: Some(1),
99 actual: num_contacts,
100 }));
101 }
102
103 let num_pressed_buttons = event.pressed_buttons.len();
104 if num_pressed_buttons > 0 {
105 return ExamineEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
106 criterion: "num_pressed_buttons",
107 min: None,
108 max: Some(0),
109 actual: num_pressed_buttons,
110 }));
111 }
112
113 let distance = euclidean_distance(event.contacts[0].position, self.initial_position);
114 if distance > self.min_movement_in_mm {
115 return ExamineEventResult::MatchedContender(self.into_matched_contender());
116 }
117
118 ExamineEventResult::Contender(self)
119 }
120}
121
122impl MatchedContender {
123 #[allow(clippy::boxed_local, reason = "mass allow for https://fxbug.dev/381896734")]
124 fn into_winner(self: Box<Self>, last_position: Position) -> Box<dyn gesture_arena::Winner> {
125 Box::new(Winner { last_position })
126 }
127}
128
129impl gesture_arena::MatchedContender for MatchedContender {
130 fn verify_event(self: Box<Self>, event: &TouchpadEvent) -> VerifyEventResult {
131 let num_contacts = event.contacts.len();
132 if num_contacts != 1 {
133 return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
134 criterion: "num_contacts",
135 min: Some(1),
136 max: Some(1),
137 actual: num_contacts,
138 }));
139 }
140
141 let num_pressed_buttons = event.pressed_buttons.len();
142 if num_pressed_buttons > 0 {
143 return VerifyEventResult::Mismatch(Reason::DetailedUint(DetailedReasonUint {
144 criterion: "num_pressed_buttons",
145 min: None,
146 max: Some(0),
147 actual: num_pressed_buttons,
148 }));
149 }
150
151 VerifyEventResult::MatchedContender(self)
152 }
153
154 fn process_buffered_events(
155 self: Box<Self>,
156 events: Vec<TouchpadEvent>,
157 ) -> ProcessBufferedEventsResult {
158 let mut mouse_events: Vec<MouseEvent> = Vec::new();
159 let last_position = events[events.len() - 1].contacts[0].position.clone();
160
161 for [a, b] in events.array_windows() {
162 mouse_events.push(touchpad_event_to_mouse_motion_event(&a.contacts[0].position, b));
163 }
164
165 ProcessBufferedEventsResult {
166 generated_events: mouse_events,
167 winner: Some(self.into_winner(last_position)),
168 recognized_gesture: RecognizedGesture::Motion,
169 }
170 }
171}
172
173impl gesture_arena::Winner for Winner {
174 fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
175 match u8::try_from(event.contacts.len()).unwrap_or(u8::MAX) {
176 0 => ProcessNewEventResult::EndGesture(
177 EndGestureEvent::NoEvent,
178 Reason::DetailedUint(DetailedReasonUint {
179 criterion: "num_contacts",
180 min: Some(1),
181 max: Some(1),
182 actual: 0,
183 }),
184 ),
185 1 => {
186 let num_pressed_buttons = event.pressed_buttons.len();
187 if num_pressed_buttons > 0 {
188 ProcessNewEventResult::EndGesture(
189 EndGestureEvent::UnconsumedEvent(event),
190 Reason::DetailedUint(DetailedReasonUint {
191 criterion: "num_pressed_buttons",
192 min: None,
193 max: Some(0),
194 actual: num_pressed_buttons,
195 }),
196 )
197 } else {
198 let last_position = event.contacts[0].position.clone();
199 ProcessNewEventResult::ContinueGesture(
200 Some(touchpad_event_to_mouse_motion_event(&self.last_position, &event)),
201 Box::new(Winner { last_position }),
202 )
203 }
204 }
205 num_contacts @ 2.. => ProcessNewEventResult::EndGesture(
206 EndGestureEvent::UnconsumedEvent(event),
207 Reason::DetailedUint(DetailedReasonUint {
208 criterion: "num_contacts",
209 min: Some(1),
210 max: Some(1),
211 actual: usize::from(num_contacts),
212 }),
213 ),
214 }
215 }
216}
217
218fn touchpad_event_to_mouse_motion_event(
219 last_position: &Position,
220 event: &TouchpadEvent,
221) -> MouseEvent {
222 MouseEvent {
223 timestamp: event.timestamp,
224 mouse_data: mouse_binding::MouseEvent::new(
225 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
226 millimeters: Position {
227 x: event.contacts[0].position.x - last_position.x,
228 y: event.contacts[0].position.y - last_position.y,
229 },
230 }),
231 None,
232 None,
233 mouse_binding::MousePhase::Move,
234 hashset! {},
235 hashset! {},
236 None,
237 None,
238 ),
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::touch_binding;
246 use assert_matches::assert_matches;
247
248 use pretty_assertions::assert_eq;
249 use test_case::test_case;
250
251 fn touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
252 touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
253 }
254
255 #[test_case(TouchpadEvent{
256 timestamp: zx::MonotonicInstant::ZERO,
257 pressed_buttons: vec![1],
258 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
259 filtered_palm_contacts: vec![],
260 };"button down")]
261 #[test_case(TouchpadEvent{
262 timestamp: zx::MonotonicInstant::ZERO,
263 pressed_buttons: vec![],
264 contacts: vec![],
265 filtered_palm_contacts: vec![],
266 };"0 fingers")]
267 #[test_case(TouchpadEvent{
268 timestamp: zx::MonotonicInstant::ZERO,
269 pressed_buttons: vec![],
270 contacts: vec![
271 touch_contact(1, Position{x: 1.0, y: 1.0}),
272 touch_contact(2, Position{x: 5.0, y: 5.0}),
273 ],
274 filtered_palm_contacts: vec![],
275 };"2 fingers")]
276 #[fuchsia::test]
277 fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
278 let contender: Box<dyn gesture_arena::Contender> =
279 Box::new(InitialContender { min_movement_in_mm: 10.0 });
280
281 let got = contender.examine_event(&event);
282 assert_matches!(got, ExamineEventResult::Mismatch(_));
283 }
284
285 #[test_case(TouchpadEvent{
286 timestamp: zx::MonotonicInstant::ZERO,
287 pressed_buttons: vec![],
288 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
289 filtered_palm_contacts: vec![],
290 };"finger hold")]
291 #[test_case(TouchpadEvent{
292 timestamp: zx::MonotonicInstant::ZERO,
293 pressed_buttons: vec![],
294 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
295 filtered_palm_contacts: vec![],
296 };"finger moved")]
297 #[fuchsia::test]
298 fn initial_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
299 let contender: Box<dyn gesture_arena::Contender> =
300 Box::new(InitialContender { min_movement_in_mm: 10.0 });
301
302 let got = contender.examine_event(&event);
303 assert_matches!(got, ExamineEventResult::Contender(_));
304 }
305
306 #[test_case(TouchpadEvent{
307 timestamp: zx::MonotonicInstant::ZERO,
308 pressed_buttons: vec![1],
309 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
310 filtered_palm_contacts: vec![],
311 };"button down")]
312 #[test_case(TouchpadEvent{
313 timestamp: zx::MonotonicInstant::ZERO,
314 pressed_buttons: vec![],
315 contacts: vec![],
316 filtered_palm_contacts: vec![],
317 };"0 fingers")]
318 #[test_case(TouchpadEvent{
319 timestamp: zx::MonotonicInstant::ZERO,
320 pressed_buttons: vec![],
321 contacts: vec![
322 touch_contact(1, Position{x: 1.0, y: 1.0}),
323 touch_contact(2, Position{x: 5.0, y: 5.0}),
324 ],
325 filtered_palm_contacts: vec![],
326 };"2 fingers")]
327 #[fuchsia::test]
328 fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
329 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
330 min_movement_in_mm: 10.0,
331 initial_position: Position { x: 1.0, y: 1.0 },
332 });
333
334 let got = contender.examine_event(&event);
335 assert_matches!(got, ExamineEventResult::Mismatch(_));
336 }
337
338 #[test_case(TouchpadEvent{
339 timestamp: zx::MonotonicInstant::ZERO,
340 pressed_buttons: vec![],
341 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
342 filtered_palm_contacts: vec![],
343 };"finger hold")]
344 #[test_case(TouchpadEvent{
345 timestamp: zx::MonotonicInstant::ZERO,
346 pressed_buttons: vec![],
347 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
348 filtered_palm_contacts: vec![],
349 };"finger move less than threshold")]
350 #[fuchsia::test]
351 fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
352 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
353 min_movement_in_mm: 10.0,
354 initial_position: Position { x: 1.0, y: 1.0 },
355 });
356
357 let got = contender.examine_event(&event);
358 assert_matches!(got, ExamineEventResult::Contender(_));
359 }
360
361 #[fuchsia::test]
362 fn finger_contact_contender_examine_event_matched_contender() {
363 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
364 min_movement_in_mm: 10.0,
365 initial_position: Position { x: 1.0, y: 1.0 },
366 });
367 let event = TouchpadEvent {
368 timestamp: zx::MonotonicInstant::ZERO,
369 pressed_buttons: vec![],
370 contacts: vec![touch_contact(1, Position { x: 11.0, y: 12.0 })],
371 filtered_palm_contacts: vec![],
372 };
373 let got = contender.examine_event(&event);
374 assert_matches!(got, ExamineEventResult::MatchedContender(_));
375 }
376
377 #[test_case(TouchpadEvent{
378 timestamp: zx::MonotonicInstant::ZERO,
379 pressed_buttons: vec![1],
380 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
381 filtered_palm_contacts: vec![],
382 };"button down")]
383 #[test_case(TouchpadEvent{
384 timestamp: zx::MonotonicInstant::ZERO,
385 pressed_buttons: vec![],
386 contacts: vec![],
387 filtered_palm_contacts: vec![],
388 };"0 fingers")]
389 #[test_case(TouchpadEvent{
390 timestamp: zx::MonotonicInstant::ZERO,
391 pressed_buttons: vec![],
392 contacts: vec![
393 touch_contact(1, Position{x: 1.0, y: 1.0}),
394 touch_contact(2, Position{x: 5.0, y: 5.0}),
395 ],
396 filtered_palm_contacts: vec![],
397 };"2 fingers")]
398 #[fuchsia::test]
399 fn matched_contender_verify_event_mismatch(event: TouchpadEvent) {
400 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
401
402 let got = contender.verify_event(&event);
403 assert_matches!(got, VerifyEventResult::Mismatch(_));
404 }
405
406 #[test_case(TouchpadEvent{
407 timestamp: zx::MonotonicInstant::ZERO,
408 pressed_buttons: vec![],
409 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
410 filtered_palm_contacts: vec![],
411 };"finger hold")]
412 #[test_case(TouchpadEvent{
413 timestamp: zx::MonotonicInstant::ZERO,
414 pressed_buttons: vec![],
415 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
416 filtered_palm_contacts: vec![],
417 };"finger move")]
418 #[fuchsia::test]
419 fn matched_contender_verify_event_matched_contender(event: TouchpadEvent) {
420 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
421
422 let got = contender.verify_event(&event);
423 assert_matches!(got, VerifyEventResult::MatchedContender(_));
424 }
425
426 #[fuchsia::test]
427 fn matched_contender_process_buffered_events() {
428 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
429
430 let got = contender.process_buffered_events(vec![
431 TouchpadEvent {
432 timestamp: zx::MonotonicInstant::from_nanos(1),
433 pressed_buttons: vec![],
434 contacts: vec![touch_contact(1, Position { x: 1.0, y: 1.0 })],
435 filtered_palm_contacts: vec![],
436 },
437 TouchpadEvent {
438 timestamp: zx::MonotonicInstant::from_nanos(2),
439 pressed_buttons: vec![],
440 contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
441 filtered_palm_contacts: vec![],
442 },
443 ]);
444
445 assert_eq!(
446 got.generated_events,
447 vec![MouseEvent {
448 timestamp: zx::MonotonicInstant::from_nanos(2),
449 mouse_data: mouse_binding::MouseEvent::new(
450 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
451 millimeters: Position { x: 4.0, y: 5.0 },
452 }),
453 None,
454 None,
455 mouse_binding::MousePhase::Move,
456 hashset! {},
457 hashset! {},
458 None,
459 None,
460 ),
461 },]
462 );
463 assert_eq!(got.recognized_gesture, RecognizedGesture::Motion);
464 }
465
466 #[fuchsia::test]
467 fn winner_process_new_event_end_gesture_none() {
468 let winner: Box<dyn gesture_arena::Winner> =
469 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
470 let event = TouchpadEvent {
471 timestamp: zx::MonotonicInstant::ZERO,
472 pressed_buttons: vec![],
473 contacts: vec![],
474 filtered_palm_contacts: vec![],
475 };
476 let got = winner.process_new_event(event);
477
478 assert_matches!(got, ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, _reason));
479 }
480
481 #[test_case(
482 TouchpadEvent{
483 timestamp: zx::MonotonicInstant::ZERO,
484 pressed_buttons: vec![1],
485 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
486 filtered_palm_contacts: vec![],
487 };"button down")]
488 #[test_case(
489 TouchpadEvent{
490 timestamp: zx::MonotonicInstant::ZERO,
491 pressed_buttons: vec![],
492 contacts: vec![
493 touch_contact(1, Position{x: 1.0, y: 1.0}),
494 touch_contact(2, Position{x: 5.0, y: 5.0}),
495 ],
496 filtered_palm_contacts: vec![],
497 };"2 fingers")]
498 #[fuchsia::test]
499 fn winner_process_new_event_end_gesture_some(event: TouchpadEvent) {
500 let winner: Box<dyn gesture_arena::Winner> =
501 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
502 let got = winner.process_new_event(event);
503
504 assert_matches!(
505 got,
506 ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _reason)
507 );
508 }
509
510 #[test_case(
511 TouchpadEvent{
512 timestamp: zx::MonotonicInstant::from_nanos(2),
513 pressed_buttons: vec![],
514 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
515 filtered_palm_contacts: vec![],
516 },
517 Position {x:0.0, y:0.0}; "finger hold")]
518 #[test_case(
519 TouchpadEvent{
520 timestamp: zx::MonotonicInstant::from_nanos(2),
521 pressed_buttons: vec![],
522 contacts: vec![touch_contact(1, Position{x: 5.0, y: 6.0})],
523 filtered_palm_contacts: vec![],
524 },
525 Position {x:4.0, y:5.0};"finger moved")]
526 #[fuchsia::test]
527 fn winner_process_new_event_continue_gesture(event: TouchpadEvent, want_position: Position) {
528 let winner: Box<dyn gesture_arena::Winner> =
529 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
530 let got = winner.process_new_event(event);
531
532 match got {
536 ProcessNewEventResult::EndGesture(..) => {
537 panic!("Got {:?}, want ContinueGesture()", got);
538 }
539 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
540 pretty_assertions::assert_eq!(
541 got_mouse_event.unwrap(),
542 MouseEvent {
543 timestamp: zx::MonotonicInstant::from_nanos(2),
544 mouse_data: mouse_binding::MouseEvent {
545 location: mouse_binding::MouseLocation::Relative(
546 mouse_binding::RelativeLocation { millimeters: want_position }
547 ),
548 wheel_delta_v: None,
549 wheel_delta_h: None,
550 phase: mouse_binding::MousePhase::Move,
551 affected_buttons: hashset! {},
552 pressed_buttons: hashset! {},
553 is_precision_scroll: None,
554 wake_lease: None.into(),
555 },
556 }
557 );
558 }
559 }
560 }
561
562 #[fuchsia::test]
563 fn winner_process_new_event_continue_multiple_gestures() {
564 let mut winner: Box<dyn gesture_arena::Winner> =
565 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
566 let event = TouchpadEvent {
567 timestamp: zx::MonotonicInstant::from_nanos(2),
568 pressed_buttons: vec![],
569 contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
570 filtered_palm_contacts: vec![],
571 };
572 let got = winner.process_new_event(event);
573
574 match got {
578 ProcessNewEventResult::EndGesture(..) => {
579 panic!("Got {:?}, want ContinueGesture()", got);
580 }
581 ProcessNewEventResult::ContinueGesture(got_mouse_event, got_winner) => {
582 pretty_assertions::assert_eq!(
583 got_mouse_event.unwrap(),
584 MouseEvent {
585 timestamp: zx::MonotonicInstant::from_nanos(2),
586 mouse_data: mouse_binding::MouseEvent {
587 location: mouse_binding::MouseLocation::Relative(
588 mouse_binding::RelativeLocation {
589 millimeters: Position { x: 4.0, y: 5.0 },
590 }
591 ),
592 wheel_delta_v: None,
593 wheel_delta_h: None,
594 phase: mouse_binding::MousePhase::Move,
595 affected_buttons: hashset! {},
596 pressed_buttons: hashset! {},
597 is_precision_scroll: None,
598 wake_lease: None.into(),
599 },
600 }
601 );
602
603 winner = got_winner;
604 }
605 }
606
607 let event = TouchpadEvent {
608 timestamp: zx::MonotonicInstant::from_nanos(3),
609 pressed_buttons: vec![],
610 contacts: vec![touch_contact(1, Position { x: 7.0, y: 9.0 })],
611 filtered_palm_contacts: vec![],
612 };
613 let got = winner.process_new_event(event);
614
615 match got {
619 ProcessNewEventResult::EndGesture(..) => {
620 panic!("Got {:?}, want ContinueGesture()", got)
621 }
622 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
623 pretty_assertions::assert_eq!(
624 got_mouse_event.unwrap(),
625 MouseEvent {
626 timestamp: zx::MonotonicInstant::from_nanos(3),
627 mouse_data: mouse_binding::MouseEvent {
628 location: mouse_binding::MouseLocation::Relative(
629 mouse_binding::RelativeLocation {
630 millimeters: Position { x: 2.0, y: 3.0 },
631 }
632 ),
633 wheel_delta_v: None,
634 wheel_delta_h: None,
635 phase: mouse_binding::MousePhase::Move,
636 affected_buttons: hashset! {},
637 pressed_buttons: hashset! {},
638 is_precision_scroll: None,
639 wake_lease: None.into(),
640 },
641 }
642 );
643 }
644 }
645 }
646}