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 pair in events.windows(2) {
162 mouse_events.push(touchpad_event_to_mouse_motion_event(
163 &pair[0].contacts[0].position,
164 &pair[1],
165 ));
166 }
167
168 ProcessBufferedEventsResult {
169 generated_events: mouse_events,
170 winner: Some(self.into_winner(last_position)),
171 recognized_gesture: RecognizedGesture::Motion,
172 }
173 }
174}
175
176impl gesture_arena::Winner for Winner {
177 fn process_new_event(self: Box<Self>, event: TouchpadEvent) -> ProcessNewEventResult {
178 match u8::try_from(event.contacts.len()).unwrap_or(u8::MAX) {
179 0 => ProcessNewEventResult::EndGesture(
180 EndGestureEvent::NoEvent,
181 Reason::DetailedUint(DetailedReasonUint {
182 criterion: "num_contacts",
183 min: Some(1),
184 max: Some(1),
185 actual: 0,
186 }),
187 ),
188 1 => {
189 let num_pressed_buttons = event.pressed_buttons.len();
190 if num_pressed_buttons > 0 {
191 ProcessNewEventResult::EndGesture(
192 EndGestureEvent::UnconsumedEvent(event),
193 Reason::DetailedUint(DetailedReasonUint {
194 criterion: "num_pressed_buttons",
195 min: None,
196 max: Some(0),
197 actual: num_pressed_buttons,
198 }),
199 )
200 } else {
201 let last_position = event.contacts[0].position.clone();
202 ProcessNewEventResult::ContinueGesture(
203 Some(touchpad_event_to_mouse_motion_event(&self.last_position, &event)),
204 Box::new(Winner { last_position }),
205 )
206 }
207 }
208 num_contacts @ 2.. => ProcessNewEventResult::EndGesture(
209 EndGestureEvent::UnconsumedEvent(event),
210 Reason::DetailedUint(DetailedReasonUint {
211 criterion: "num_contacts",
212 min: Some(1),
213 max: Some(1),
214 actual: usize::from(num_contacts),
215 }),
216 ),
217 }
218 }
219}
220
221fn touchpad_event_to_mouse_motion_event(
222 last_position: &Position,
223 event: &TouchpadEvent,
224) -> MouseEvent {
225 MouseEvent {
226 timestamp: event.timestamp,
227 mouse_data: mouse_binding::MouseEvent::new(
228 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
229 millimeters: Position {
230 x: event.contacts[0].position.x - last_position.x,
231 y: event.contacts[0].position.y - last_position.y,
232 },
233 }),
234 None,
235 None,
236 mouse_binding::MousePhase::Move,
237 hashset! {},
238 hashset! {},
239 None,
240 None,
241 ),
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::touch_binding;
249 use assert_matches::assert_matches;
250
251 use pretty_assertions::assert_eq;
252 use test_case::test_case;
253
254 fn touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
255 touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
256 }
257
258 #[test_case(TouchpadEvent{
259 timestamp: zx::MonotonicInstant::ZERO,
260 pressed_buttons: vec![1],
261 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
262 filtered_palm_contacts: vec![],
263 };"button down")]
264 #[test_case(TouchpadEvent{
265 timestamp: zx::MonotonicInstant::ZERO,
266 pressed_buttons: vec![],
267 contacts: vec![],
268 filtered_palm_contacts: vec![],
269 };"0 fingers")]
270 #[test_case(TouchpadEvent{
271 timestamp: zx::MonotonicInstant::ZERO,
272 pressed_buttons: vec![],
273 contacts: vec![
274 touch_contact(1, Position{x: 1.0, y: 1.0}),
275 touch_contact(2, Position{x: 5.0, y: 5.0}),
276 ],
277 filtered_palm_contacts: vec![],
278 };"2 fingers")]
279 #[fuchsia::test]
280 fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
281 let contender: Box<dyn gesture_arena::Contender> =
282 Box::new(InitialContender { min_movement_in_mm: 10.0 });
283
284 let got = contender.examine_event(&event);
285 assert_matches!(got, ExamineEventResult::Mismatch(_));
286 }
287
288 #[test_case(TouchpadEvent{
289 timestamp: zx::MonotonicInstant::ZERO,
290 pressed_buttons: vec![],
291 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
292 filtered_palm_contacts: vec![],
293 };"finger hold")]
294 #[test_case(TouchpadEvent{
295 timestamp: zx::MonotonicInstant::ZERO,
296 pressed_buttons: vec![],
297 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
298 filtered_palm_contacts: vec![],
299 };"finger moved")]
300 #[fuchsia::test]
301 fn initial_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
302 let contender: Box<dyn gesture_arena::Contender> =
303 Box::new(InitialContender { min_movement_in_mm: 10.0 });
304
305 let got = contender.examine_event(&event);
306 assert_matches!(got, ExamineEventResult::Contender(_));
307 }
308
309 #[test_case(TouchpadEvent{
310 timestamp: zx::MonotonicInstant::ZERO,
311 pressed_buttons: vec![1],
312 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
313 filtered_palm_contacts: vec![],
314 };"button down")]
315 #[test_case(TouchpadEvent{
316 timestamp: zx::MonotonicInstant::ZERO,
317 pressed_buttons: vec![],
318 contacts: vec![],
319 filtered_palm_contacts: vec![],
320 };"0 fingers")]
321 #[test_case(TouchpadEvent{
322 timestamp: zx::MonotonicInstant::ZERO,
323 pressed_buttons: vec![],
324 contacts: vec![
325 touch_contact(1, Position{x: 1.0, y: 1.0}),
326 touch_contact(2, Position{x: 5.0, y: 5.0}),
327 ],
328 filtered_palm_contacts: vec![],
329 };"2 fingers")]
330 #[fuchsia::test]
331 fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
332 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
333 min_movement_in_mm: 10.0,
334 initial_position: Position { x: 1.0, y: 1.0 },
335 });
336
337 let got = contender.examine_event(&event);
338 assert_matches!(got, ExamineEventResult::Mismatch(_));
339 }
340
341 #[test_case(TouchpadEvent{
342 timestamp: zx::MonotonicInstant::ZERO,
343 pressed_buttons: vec![],
344 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
345 filtered_palm_contacts: vec![],
346 };"finger hold")]
347 #[test_case(TouchpadEvent{
348 timestamp: zx::MonotonicInstant::ZERO,
349 pressed_buttons: vec![],
350 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
351 filtered_palm_contacts: vec![],
352 };"finger move less than threshold")]
353 #[fuchsia::test]
354 fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
355 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
356 min_movement_in_mm: 10.0,
357 initial_position: Position { x: 1.0, y: 1.0 },
358 });
359
360 let got = contender.examine_event(&event);
361 assert_matches!(got, ExamineEventResult::Contender(_));
362 }
363
364 #[fuchsia::test]
365 fn finger_contact_contender_examine_event_matched_contender() {
366 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
367 min_movement_in_mm: 10.0,
368 initial_position: Position { x: 1.0, y: 1.0 },
369 });
370 let event = TouchpadEvent {
371 timestamp: zx::MonotonicInstant::ZERO,
372 pressed_buttons: vec![],
373 contacts: vec![touch_contact(1, Position { x: 11.0, y: 12.0 })],
374 filtered_palm_contacts: vec![],
375 };
376 let got = contender.examine_event(&event);
377 assert_matches!(got, ExamineEventResult::MatchedContender(_));
378 }
379
380 #[test_case(TouchpadEvent{
381 timestamp: zx::MonotonicInstant::ZERO,
382 pressed_buttons: vec![1],
383 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
384 filtered_palm_contacts: vec![],
385 };"button down")]
386 #[test_case(TouchpadEvent{
387 timestamp: zx::MonotonicInstant::ZERO,
388 pressed_buttons: vec![],
389 contacts: vec![],
390 filtered_palm_contacts: vec![],
391 };"0 fingers")]
392 #[test_case(TouchpadEvent{
393 timestamp: zx::MonotonicInstant::ZERO,
394 pressed_buttons: vec![],
395 contacts: vec![
396 touch_contact(1, Position{x: 1.0, y: 1.0}),
397 touch_contact(2, Position{x: 5.0, y: 5.0}),
398 ],
399 filtered_palm_contacts: vec![],
400 };"2 fingers")]
401 #[fuchsia::test]
402 fn matched_contender_verify_event_mismatch(event: TouchpadEvent) {
403 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
404
405 let got = contender.verify_event(&event);
406 assert_matches!(got, VerifyEventResult::Mismatch(_));
407 }
408
409 #[test_case(TouchpadEvent{
410 timestamp: zx::MonotonicInstant::ZERO,
411 pressed_buttons: vec![],
412 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
413 filtered_palm_contacts: vec![],
414 };"finger hold")]
415 #[test_case(TouchpadEvent{
416 timestamp: zx::MonotonicInstant::ZERO,
417 pressed_buttons: vec![],
418 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
419 filtered_palm_contacts: vec![],
420 };"finger move")]
421 #[fuchsia::test]
422 fn matched_contender_verify_event_matched_contender(event: TouchpadEvent) {
423 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
424
425 let got = contender.verify_event(&event);
426 assert_matches!(got, VerifyEventResult::MatchedContender(_));
427 }
428
429 #[fuchsia::test]
430 fn matched_contender_process_buffered_events() {
431 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
432
433 let got = contender.process_buffered_events(vec![
434 TouchpadEvent {
435 timestamp: zx::MonotonicInstant::from_nanos(1),
436 pressed_buttons: vec![],
437 contacts: vec![touch_contact(1, Position { x: 1.0, y: 1.0 })],
438 filtered_palm_contacts: vec![],
439 },
440 TouchpadEvent {
441 timestamp: zx::MonotonicInstant::from_nanos(2),
442 pressed_buttons: vec![],
443 contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
444 filtered_palm_contacts: vec![],
445 },
446 ]);
447
448 assert_eq!(
449 got.generated_events,
450 vec![MouseEvent {
451 timestamp: zx::MonotonicInstant::from_nanos(2),
452 mouse_data: mouse_binding::MouseEvent::new(
453 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
454 millimeters: Position { x: 4.0, y: 5.0 },
455 }),
456 None,
457 None,
458 mouse_binding::MousePhase::Move,
459 hashset! {},
460 hashset! {},
461 None,
462 None,
463 ),
464 },]
465 );
466 assert_eq!(got.recognized_gesture, RecognizedGesture::Motion);
467 }
468
469 #[fuchsia::test]
470 fn winner_process_new_event_end_gesture_none() {
471 let winner: Box<dyn gesture_arena::Winner> =
472 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
473 let event = TouchpadEvent {
474 timestamp: zx::MonotonicInstant::ZERO,
475 pressed_buttons: vec![],
476 contacts: vec![],
477 filtered_palm_contacts: vec![],
478 };
479 let got = winner.process_new_event(event);
480
481 assert_matches!(got, ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, _reason));
482 }
483
484 #[test_case(
485 TouchpadEvent{
486 timestamp: zx::MonotonicInstant::ZERO,
487 pressed_buttons: vec![1],
488 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
489 filtered_palm_contacts: vec![],
490 };"button down")]
491 #[test_case(
492 TouchpadEvent{
493 timestamp: zx::MonotonicInstant::ZERO,
494 pressed_buttons: vec![],
495 contacts: vec![
496 touch_contact(1, Position{x: 1.0, y: 1.0}),
497 touch_contact(2, Position{x: 5.0, y: 5.0}),
498 ],
499 filtered_palm_contacts: vec![],
500 };"2 fingers")]
501 #[fuchsia::test]
502 fn winner_process_new_event_end_gesture_some(event: TouchpadEvent) {
503 let winner: Box<dyn gesture_arena::Winner> =
504 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
505 let got = winner.process_new_event(event);
506
507 assert_matches!(
508 got,
509 ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _reason)
510 );
511 }
512
513 #[test_case(
514 TouchpadEvent{
515 timestamp: zx::MonotonicInstant::from_nanos(2),
516 pressed_buttons: vec![],
517 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
518 filtered_palm_contacts: vec![],
519 },
520 Position {x:0.0, y:0.0}; "finger hold")]
521 #[test_case(
522 TouchpadEvent{
523 timestamp: zx::MonotonicInstant::from_nanos(2),
524 pressed_buttons: vec![],
525 contacts: vec![touch_contact(1, Position{x: 5.0, y: 6.0})],
526 filtered_palm_contacts: vec![],
527 },
528 Position {x:4.0, y:5.0};"finger moved")]
529 #[fuchsia::test]
530 fn winner_process_new_event_continue_gesture(event: TouchpadEvent, want_position: Position) {
531 let winner: Box<dyn gesture_arena::Winner> =
532 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
533 let got = winner.process_new_event(event);
534
535 match got {
539 ProcessNewEventResult::EndGesture(..) => {
540 panic!("Got {:?}, want ContinueGesture()", got);
541 }
542 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
543 pretty_assertions::assert_eq!(
544 got_mouse_event.unwrap(),
545 MouseEvent {
546 timestamp: zx::MonotonicInstant::from_nanos(2),
547 mouse_data: mouse_binding::MouseEvent {
548 location: mouse_binding::MouseLocation::Relative(
549 mouse_binding::RelativeLocation { millimeters: want_position }
550 ),
551 wheel_delta_v: None,
552 wheel_delta_h: None,
553 phase: mouse_binding::MousePhase::Move,
554 affected_buttons: hashset! {},
555 pressed_buttons: hashset! {},
556 is_precision_scroll: None,
557 wake_lease: None.into(),
558 },
559 }
560 );
561 }
562 }
563 }
564
565 #[fuchsia::test]
566 fn winner_process_new_event_continue_multiple_gestures() {
567 let mut winner: Box<dyn gesture_arena::Winner> =
568 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
569 let event = TouchpadEvent {
570 timestamp: zx::MonotonicInstant::from_nanos(2),
571 pressed_buttons: vec![],
572 contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
573 filtered_palm_contacts: vec![],
574 };
575 let got = winner.process_new_event(event);
576
577 match got {
581 ProcessNewEventResult::EndGesture(..) => {
582 panic!("Got {:?}, want ContinueGesture()", got);
583 }
584 ProcessNewEventResult::ContinueGesture(got_mouse_event, got_winner) => {
585 pretty_assertions::assert_eq!(
586 got_mouse_event.unwrap(),
587 MouseEvent {
588 timestamp: zx::MonotonicInstant::from_nanos(2),
589 mouse_data: mouse_binding::MouseEvent {
590 location: mouse_binding::MouseLocation::Relative(
591 mouse_binding::RelativeLocation {
592 millimeters: Position { x: 4.0, y: 5.0 },
593 }
594 ),
595 wheel_delta_v: None,
596 wheel_delta_h: None,
597 phase: mouse_binding::MousePhase::Move,
598 affected_buttons: hashset! {},
599 pressed_buttons: hashset! {},
600 is_precision_scroll: None,
601 wake_lease: None.into(),
602 },
603 }
604 );
605
606 winner = got_winner;
607 }
608 }
609
610 let event = TouchpadEvent {
611 timestamp: zx::MonotonicInstant::from_nanos(3),
612 pressed_buttons: vec![],
613 contacts: vec![touch_contact(1, Position { x: 7.0, y: 9.0 })],
614 filtered_palm_contacts: vec![],
615 };
616 let got = winner.process_new_event(event);
617
618 match got {
622 ProcessNewEventResult::EndGesture(..) => {
623 panic!("Got {:?}, want ContinueGesture()", got)
624 }
625 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
626 pretty_assertions::assert_eq!(
627 got_mouse_event.unwrap(),
628 MouseEvent {
629 timestamp: zx::MonotonicInstant::from_nanos(3),
630 mouse_data: mouse_binding::MouseEvent {
631 location: mouse_binding::MouseLocation::Relative(
632 mouse_binding::RelativeLocation {
633 millimeters: Position { x: 2.0, y: 3.0 },
634 }
635 ),
636 wheel_delta_v: None,
637 wheel_delta_h: None,
638 phase: mouse_binding::MousePhase::Move,
639 affected_buttons: hashset! {},
640 pressed_buttons: hashset! {},
641 is_precision_scroll: None,
642 wake_lease: None.into(),
643 },
644 }
645 );
646 }
647 }
648 }
649}