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::{euclidean_distance, Position};
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 ),
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::touch_binding;
248 use assert_matches::assert_matches;
249
250 use pretty_assertions::assert_eq;
251 use test_case::test_case;
252
253 fn touch_contact(id: u32, position: Position) -> touch_binding::TouchContact {
254 touch_binding::TouchContact { id, position, pressure: None, contact_size: None }
255 }
256
257 #[test_case(TouchpadEvent{
258 timestamp: zx::MonotonicInstant::ZERO,
259 pressed_buttons: vec![1],
260 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
261 filtered_palm_contacts: vec![],
262 };"button down")]
263 #[test_case(TouchpadEvent{
264 timestamp: zx::MonotonicInstant::ZERO,
265 pressed_buttons: vec![],
266 contacts: vec![],
267 filtered_palm_contacts: vec![],
268 };"0 fingers")]
269 #[test_case(TouchpadEvent{
270 timestamp: zx::MonotonicInstant::ZERO,
271 pressed_buttons: vec![],
272 contacts: vec![
273 touch_contact(1, Position{x: 1.0, y: 1.0}),
274 touch_contact(2, Position{x: 5.0, y: 5.0}),
275 ],
276 filtered_palm_contacts: vec![],
277 };"2 fingers")]
278 #[fuchsia::test]
279 fn initial_contender_examine_event_mismatch(event: TouchpadEvent) {
280 let contender: Box<dyn gesture_arena::Contender> =
281 Box::new(InitialContender { min_movement_in_mm: 10.0 });
282
283 let got = contender.examine_event(&event);
284 assert_matches!(got, ExamineEventResult::Mismatch(_));
285 }
286
287 #[test_case(TouchpadEvent{
288 timestamp: zx::MonotonicInstant::ZERO,
289 pressed_buttons: vec![],
290 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
291 filtered_palm_contacts: vec![],
292 };"finger hold")]
293 #[test_case(TouchpadEvent{
294 timestamp: zx::MonotonicInstant::ZERO,
295 pressed_buttons: vec![],
296 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
297 filtered_palm_contacts: vec![],
298 };"finger moved")]
299 #[fuchsia::test]
300 fn initial_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
301 let contender: Box<dyn gesture_arena::Contender> =
302 Box::new(InitialContender { min_movement_in_mm: 10.0 });
303
304 let got = contender.examine_event(&event);
305 assert_matches!(got, ExamineEventResult::Contender(_));
306 }
307
308 #[test_case(TouchpadEvent{
309 timestamp: zx::MonotonicInstant::ZERO,
310 pressed_buttons: vec![1],
311 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
312 filtered_palm_contacts: vec![],
313 };"button down")]
314 #[test_case(TouchpadEvent{
315 timestamp: zx::MonotonicInstant::ZERO,
316 pressed_buttons: vec![],
317 contacts: vec![],
318 filtered_palm_contacts: vec![],
319 };"0 fingers")]
320 #[test_case(TouchpadEvent{
321 timestamp: zx::MonotonicInstant::ZERO,
322 pressed_buttons: vec![],
323 contacts: vec![
324 touch_contact(1, Position{x: 1.0, y: 1.0}),
325 touch_contact(2, Position{x: 5.0, y: 5.0}),
326 ],
327 filtered_palm_contacts: vec![],
328 };"2 fingers")]
329 #[fuchsia::test]
330 fn finger_contact_contender_examine_event_mismatch(event: TouchpadEvent) {
331 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
332 min_movement_in_mm: 10.0,
333 initial_position: Position { x: 1.0, y: 1.0 },
334 });
335
336 let got = contender.examine_event(&event);
337 assert_matches!(got, ExamineEventResult::Mismatch(_));
338 }
339
340 #[test_case(TouchpadEvent{
341 timestamp: zx::MonotonicInstant::ZERO,
342 pressed_buttons: vec![],
343 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
344 filtered_palm_contacts: vec![],
345 };"finger hold")]
346 #[test_case(TouchpadEvent{
347 timestamp: zx::MonotonicInstant::ZERO,
348 pressed_buttons: vec![],
349 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
350 filtered_palm_contacts: vec![],
351 };"finger move less than threshold")]
352 #[fuchsia::test]
353 fn finger_contact_contender_examine_event_finger_contact_contender(event: TouchpadEvent) {
354 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
355 min_movement_in_mm: 10.0,
356 initial_position: Position { x: 1.0, y: 1.0 },
357 });
358
359 let got = contender.examine_event(&event);
360 assert_matches!(got, ExamineEventResult::Contender(_));
361 }
362
363 #[fuchsia::test]
364 fn finger_contact_contender_examine_event_matched_contender() {
365 let contender: Box<dyn gesture_arena::Contender> = Box::new(FingerContactContender {
366 min_movement_in_mm: 10.0,
367 initial_position: Position { x: 1.0, y: 1.0 },
368 });
369 let event = TouchpadEvent {
370 timestamp: zx::MonotonicInstant::ZERO,
371 pressed_buttons: vec![],
372 contacts: vec![touch_contact(1, Position { x: 11.0, y: 12.0 })],
373 filtered_palm_contacts: vec![],
374 };
375 let got = contender.examine_event(&event);
376 assert_matches!(got, ExamineEventResult::MatchedContender(_));
377 }
378
379 #[test_case(TouchpadEvent{
380 timestamp: zx::MonotonicInstant::ZERO,
381 pressed_buttons: vec![1],
382 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
383 filtered_palm_contacts: vec![],
384 };"button down")]
385 #[test_case(TouchpadEvent{
386 timestamp: zx::MonotonicInstant::ZERO,
387 pressed_buttons: vec![],
388 contacts: vec![],
389 filtered_palm_contacts: vec![],
390 };"0 fingers")]
391 #[test_case(TouchpadEvent{
392 timestamp: zx::MonotonicInstant::ZERO,
393 pressed_buttons: vec![],
394 contacts: vec![
395 touch_contact(1, Position{x: 1.0, y: 1.0}),
396 touch_contact(2, Position{x: 5.0, y: 5.0}),
397 ],
398 filtered_palm_contacts: vec![],
399 };"2 fingers")]
400 #[fuchsia::test]
401 fn matched_contender_verify_event_mismatch(event: TouchpadEvent) {
402 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
403
404 let got = contender.verify_event(&event);
405 assert_matches!(got, VerifyEventResult::Mismatch(_));
406 }
407
408 #[test_case(TouchpadEvent{
409 timestamp: zx::MonotonicInstant::ZERO,
410 pressed_buttons: vec![],
411 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
412 filtered_palm_contacts: vec![],
413 };"finger hold")]
414 #[test_case(TouchpadEvent{
415 timestamp: zx::MonotonicInstant::ZERO,
416 pressed_buttons: vec![],
417 contacts: vec![touch_contact(1, Position{x: 5.0, y: 5.0})],
418 filtered_palm_contacts: vec![],
419 };"finger move")]
420 #[fuchsia::test]
421 fn matched_contender_verify_event_matched_contender(event: TouchpadEvent) {
422 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
423
424 let got = contender.verify_event(&event);
425 assert_matches!(got, VerifyEventResult::MatchedContender(_));
426 }
427
428 #[fuchsia::test]
429 fn matched_contender_process_buffered_events() {
430 let contender: Box<dyn gesture_arena::MatchedContender> = Box::new(MatchedContender {});
431
432 let got = contender.process_buffered_events(vec![
433 TouchpadEvent {
434 timestamp: zx::MonotonicInstant::from_nanos(1),
435 pressed_buttons: vec![],
436 contacts: vec![touch_contact(1, Position { x: 1.0, y: 1.0 })],
437 filtered_palm_contacts: vec![],
438 },
439 TouchpadEvent {
440 timestamp: zx::MonotonicInstant::from_nanos(2),
441 pressed_buttons: vec![],
442 contacts: vec![touch_contact(1, Position { x: 5.0, y: 6.0 })],
443 filtered_palm_contacts: vec![],
444 },
445 ]);
446
447 assert_eq!(
448 got.generated_events,
449 vec![MouseEvent {
450 timestamp: zx::MonotonicInstant::from_nanos(2),
451 mouse_data: mouse_binding::MouseEvent::new(
452 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
453 millimeters: Position { x: 4.0, y: 5.0 },
454 }),
455 None,
456 None,
457 mouse_binding::MousePhase::Move,
458 hashset! {},
459 hashset! {},
460 None,
461 ),
462 },]
463 );
464 assert_eq!(got.recognized_gesture, RecognizedGesture::Motion);
465 }
466
467 #[fuchsia::test]
468 fn winner_process_new_event_end_gesture_none() {
469 let winner: Box<dyn gesture_arena::Winner> =
470 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
471 let event = TouchpadEvent {
472 timestamp: zx::MonotonicInstant::ZERO,
473 pressed_buttons: vec![],
474 contacts: vec![],
475 filtered_palm_contacts: vec![],
476 };
477 let got = winner.process_new_event(event);
478
479 assert_matches!(got, ProcessNewEventResult::EndGesture(EndGestureEvent::NoEvent, _reason));
480 }
481
482 #[test_case(
483 TouchpadEvent{
484 timestamp: zx::MonotonicInstant::ZERO,
485 pressed_buttons: vec![1],
486 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
487 filtered_palm_contacts: vec![],
488 };"button down")]
489 #[test_case(
490 TouchpadEvent{
491 timestamp: zx::MonotonicInstant::ZERO,
492 pressed_buttons: vec![],
493 contacts: vec![
494 touch_contact(1, Position{x: 1.0, y: 1.0}),
495 touch_contact(2, Position{x: 5.0, y: 5.0}),
496 ],
497 filtered_palm_contacts: vec![],
498 };"2 fingers")]
499 #[fuchsia::test]
500 fn winner_process_new_event_end_gesture_some(event: TouchpadEvent) {
501 let winner: Box<dyn gesture_arena::Winner> =
502 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
503 let got = winner.process_new_event(event);
504
505 assert_matches!(
506 got,
507 ProcessNewEventResult::EndGesture(EndGestureEvent::UnconsumedEvent(_), _reason)
508 );
509 }
510
511 #[test_case(
512 TouchpadEvent{
513 timestamp: zx::MonotonicInstant::from_nanos(2),
514 pressed_buttons: vec![],
515 contacts: vec![touch_contact(1, Position{x: 1.0, y: 1.0})],
516 filtered_palm_contacts: vec![],
517 },
518 Position {x:0.0, y:0.0}; "finger hold")]
519 #[test_case(
520 TouchpadEvent{
521 timestamp: zx::MonotonicInstant::from_nanos(2),
522 pressed_buttons: vec![],
523 contacts: vec![touch_contact(1, Position{x: 5.0, y: 6.0})],
524 filtered_palm_contacts: vec![],
525 },
526 Position {x:4.0, y:5.0};"finger moved")]
527 #[fuchsia::test]
528 fn winner_process_new_event_continue_gesture(event: TouchpadEvent, want_position: Position) {
529 let winner: Box<dyn gesture_arena::Winner> =
530 Box::new(Winner { last_position: Position { x: 1.0, y: 1.0 } });
531 let got = winner.process_new_event(event);
532
533 match got {
537 ProcessNewEventResult::EndGesture(..) => {
538 panic!("Got {:?}, want ContinueGesture()", got);
539 }
540 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
541 pretty_assertions::assert_eq!(
542 got_mouse_event.unwrap(),
543 MouseEvent {
544 timestamp: zx::MonotonicInstant::from_nanos(2),
545 mouse_data: mouse_binding::MouseEvent {
546 location: mouse_binding::MouseLocation::Relative(
547 mouse_binding::RelativeLocation { millimeters: want_position }
548 ),
549 wheel_delta_v: None,
550 wheel_delta_h: None,
551 phase: mouse_binding::MousePhase::Move,
552 affected_buttons: hashset! {},
553 pressed_buttons: hashset! {},
554 is_precision_scroll: None,
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 },
599 }
600 );
601
602 winner = got_winner;
603 }
604 }
605
606 let event = TouchpadEvent {
607 timestamp: zx::MonotonicInstant::from_nanos(3),
608 pressed_buttons: vec![],
609 contacts: vec![touch_contact(1, Position { x: 7.0, y: 9.0 })],
610 filtered_palm_contacts: vec![],
611 };
612 let got = winner.process_new_event(event);
613
614 match got {
618 ProcessNewEventResult::EndGesture(..) => {
619 panic!("Got {:?}, want ContinueGesture()", got)
620 }
621 ProcessNewEventResult::ContinueGesture(got_mouse_event, _) => {
622 pretty_assertions::assert_eq!(
623 got_mouse_event.unwrap(),
624 MouseEvent {
625 timestamp: zx::MonotonicInstant::from_nanos(3),
626 mouse_data: mouse_binding::MouseEvent {
627 location: mouse_binding::MouseLocation::Relative(
628 mouse_binding::RelativeLocation {
629 millimeters: Position { x: 2.0, y: 3.0 },
630 }
631 ),
632 wheel_delta_v: None,
633 wheel_delta_h: None,
634 phase: mouse_binding::MousePhase::Move,
635 affected_buttons: hashset! {},
636 pressed_buttons: hashset! {},
637 is_precision_scroll: None,
638 },
639 }
640 );
641 }
642 }
643 }
644}