1use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
7use crate::utils::Position;
8use crate::{input_device, metrics, mouse_binding};
9use async_trait::async_trait;
10use fuchsia_inspect::health::Reporter;
11
12use metrics_registry::*;
13use std::cell::RefCell;
14use std::num::FpCategory;
15use std::rc::Rc;
16
17pub struct PointerSensorScaleHandler {
18 mutable_state: RefCell<MutableState>,
19
20 pub inspect_status: InputHandlerStatus,
22
23 metrics_logger: metrics::MetricsLogger,
25}
26
27struct MutableState {
28 last_move_timestamp: Option<zx::MonotonicInstant>,
30 last_scroll_timestamp: Option<zx::MonotonicInstant>,
32}
33
34const PIXELS_PER_TICK: f32 = 120.0;
37
38const SCALE_SCROLL: f32 = 2.0;
41
42#[async_trait(?Send)]
43impl UnhandledInputHandler for PointerSensorScaleHandler {
44 async fn handle_unhandled_input_event(
45 self: Rc<Self>,
46 unhandled_input_event: input_device::UnhandledInputEvent,
47 ) -> Vec<input_device::InputEvent> {
48 match unhandled_input_event.clone() {
49 input_device::UnhandledInputEvent {
50 device_event:
51 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
52 location:
53 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
54 millimeters: raw_motion,
55 }),
56 wheel_delta_v,
57 wheel_delta_h,
58 phase: phase @ mouse_binding::MousePhase::Move,
60 affected_buttons,
61 pressed_buttons,
62 is_precision_scroll,
63 }),
64 device_descriptor:
65 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
66 absolute_x_range,
67 absolute_y_range,
68 buttons,
69 counts_per_mm,
70 device_id,
71 wheel_h_range,
72 wheel_v_range,
73 }),
74 event_time,
75 trace_id,
76 } => {
77 let tracing_id = trace_id.unwrap_or_else(|| 0.into());
78 fuchsia_trace::duration!(c"input", c"pointer_sensor_scale_handler");
79 fuchsia_trace::flow_step!(c"input", c"event_in_input_pipeline", tracing_id);
80
81 self.inspect_status.count_received_event(&event_time);
82 let scaled_motion = self.scale_motion(raw_motion, event_time);
83 let input_event = input_device::InputEvent {
84 device_event: input_device::InputDeviceEvent::Mouse(
85 mouse_binding::MouseEvent {
86 location: mouse_binding::MouseLocation::Relative(
87 mouse_binding::RelativeLocation { millimeters: scaled_motion },
88 ),
89 wheel_delta_v,
90 wheel_delta_h,
91 phase,
92 affected_buttons,
93 pressed_buttons,
94 is_precision_scroll,
95 },
96 ),
97 device_descriptor: input_device::InputDeviceDescriptor::Mouse(
98 mouse_binding::MouseDeviceDescriptor {
99 absolute_x_range,
100 absolute_y_range,
101 buttons,
102 counts_per_mm,
103 device_id,
104 wheel_h_range,
105 wheel_v_range,
106 },
107 ),
108 event_time,
109 handled: input_device::Handled::No,
110 trace_id,
111 };
112 vec![input_event]
113 }
114 input_device::UnhandledInputEvent {
115 device_event:
116 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
117 location,
118 wheel_delta_v,
119 wheel_delta_h,
120 phase: phase @ mouse_binding::MousePhase::Wheel,
121 affected_buttons,
122 pressed_buttons,
123 is_precision_scroll,
124 }),
125 device_descriptor:
126 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
127 absolute_x_range,
128 absolute_y_range,
129 buttons,
130 counts_per_mm,
131 device_id,
132 wheel_h_range,
133 wheel_v_range,
134 }),
135 event_time,
136 trace_id,
137 } => {
138 fuchsia_trace::duration!(c"input", c"pointer_sensor_scale_handler");
139 if let Some(trace_id) = trace_id {
140 fuchsia_trace::flow_step!(
141 c"input",
142 c"event_in_input_pipeline",
143 trace_id.into()
144 );
145 }
146
147 self.inspect_status.count_received_event(&event_time);
148 let scaled_wheel_delta_v = self.scale_scroll(wheel_delta_v, event_time);
149 let scaled_wheel_delta_h = self.scale_scroll(wheel_delta_h, event_time);
150 let input_event = input_device::InputEvent {
151 device_event: input_device::InputDeviceEvent::Mouse(
152 mouse_binding::MouseEvent {
153 location,
154 wheel_delta_v: scaled_wheel_delta_v,
155 wheel_delta_h: scaled_wheel_delta_h,
156 phase,
157 affected_buttons,
158 pressed_buttons,
159 is_precision_scroll,
160 },
161 ),
162 device_descriptor: input_device::InputDeviceDescriptor::Mouse(
163 mouse_binding::MouseDeviceDescriptor {
164 absolute_x_range,
165 absolute_y_range,
166 buttons,
167 counts_per_mm,
168 device_id,
169 wheel_h_range,
170 wheel_v_range,
171 },
172 ),
173 event_time,
174 handled: input_device::Handled::No,
175 trace_id,
176 };
177 vec![input_event]
178 }
179 _ => vec![input_device::InputEvent::from(unhandled_input_event)],
180 }
181 }
182
183 fn set_handler_healthy(self: std::rc::Rc<Self>) {
184 self.inspect_status.health_node.borrow_mut().set_ok();
185 }
186
187 fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
188 self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
189 }
190}
191
192const MIN_PLAUSIBLE_EVENT_DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(100);
200
201const MAX_PLAUSIBLE_EVENT_DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(50);
213
214const MAX_SENSOR_COUNTS_PER_INCH: f32 = 20_000.0; const MAX_SENSOR_COUNTS_PER_MM: f32 = MAX_SENSOR_COUNTS_PER_INCH / 12.7;
216const MIN_MEASURABLE_DISTANCE_MM: f32 = 1.0 / MAX_SENSOR_COUNTS_PER_MM;
217const MAX_PLAUSIBLE_EVENT_DELAY_SECS: f32 = MAX_PLAUSIBLE_EVENT_DELAY.into_nanos() as f32 / 1E9;
218const MIN_MEASURABLE_VELOCITY_MM_PER_SEC: f32 =
219 MIN_MEASURABLE_DISTANCE_MM / MAX_PLAUSIBLE_EVENT_DELAY_SECS;
220
221const MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC: f32 = 32.0;
226const MEDIUM_SPEED_RANGE_END_MM_PER_SEC: f32 = 150.0;
227
228const NUMBNESS: f32 = 37.5;
231
232impl PointerSensorScaleHandler {
233 pub fn new(
237 input_handlers_node: &fuchsia_inspect::Node,
238 metrics_logger: metrics::MetricsLogger,
239 ) -> Rc<Self> {
240 let inspect_status = InputHandlerStatus::new(
241 input_handlers_node,
242 "pointer_sensor_scale_handler",
243 false,
244 );
245 Rc::new(Self {
246 mutable_state: RefCell::new(MutableState {
247 last_move_timestamp: None,
248 last_scroll_timestamp: None,
249 }),
250 inspect_status,
251 metrics_logger,
252 })
253 }
254
255 fn scale_low_speed(movement_mm_per_sec: f32) -> f32 {
260 const LINEAR_SCALE_FACTOR: f32 = MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC / NUMBNESS;
261 LINEAR_SCALE_FACTOR * movement_mm_per_sec
262 }
263
264 fn scale_medium_speed(movement_mm_per_sec: f32) -> f32 {
279 const QUARDRATIC_SCALE_FACTOR: f32 = 1.0 / NUMBNESS;
280 QUARDRATIC_SCALE_FACTOR * movement_mm_per_sec * movement_mm_per_sec
281 }
282
283 fn scale_high_speed(movement_mm_per_sec: f32) -> f32 {
290 const LINEAR_SCALE_FACTOR: f32 = 2.0 * (MEDIUM_SPEED_RANGE_END_MM_PER_SEC / NUMBNESS);
293
294 const Y_AT_MEDIUM_SPEED_RANGE_END_MM_PER_SEC: f32 =
296 MEDIUM_SPEED_RANGE_END_MM_PER_SEC * MEDIUM_SPEED_RANGE_END_MM_PER_SEC / NUMBNESS;
297 const OFFSET: f32 = Y_AT_MEDIUM_SPEED_RANGE_END_MM_PER_SEC
298 - LINEAR_SCALE_FACTOR * MEDIUM_SPEED_RANGE_END_MM_PER_SEC;
299
300 LINEAR_SCALE_FACTOR * movement_mm_per_sec + OFFSET
302 }
303
304 fn scale_euclidean_velocity(raw_velocity: f32) -> f32 {
308 if (0.0..MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC).contains(&raw_velocity) {
309 Self::scale_low_speed(raw_velocity)
310 } else if (MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC..MEDIUM_SPEED_RANGE_END_MM_PER_SEC)
311 .contains(&raw_velocity)
312 {
313 Self::scale_medium_speed(raw_velocity)
314 } else {
315 Self::scale_high_speed(raw_velocity)
316 }
317 }
318
319 fn scale_motion(&self, movement_mm: Position, event_time: zx::MonotonicInstant) -> Position {
321 let elapsed_time_secs =
323 match self.mutable_state.borrow_mut().last_move_timestamp.replace(event_time) {
324 Some(last_event_time) => (event_time - last_event_time)
325 .clamp(MIN_PLAUSIBLE_EVENT_DELAY, MAX_PLAUSIBLE_EVENT_DELAY),
326 None => MAX_PLAUSIBLE_EVENT_DELAY,
327 }
328 .into_nanos() as f32
329 / 1E9;
330
331 let x_mm_per_sec = movement_mm.x / elapsed_time_secs;
333 let y_mm_per_sec = movement_mm.y / elapsed_time_secs;
334
335 let euclidean_velocity =
336 f32::sqrt(x_mm_per_sec * x_mm_per_sec + y_mm_per_sec * y_mm_per_sec);
337 if euclidean_velocity < MIN_MEASURABLE_VELOCITY_MM_PER_SEC {
338 return movement_mm;
340 }
341
342 let scale_factor = Self::scale_euclidean_velocity(euclidean_velocity) / euclidean_velocity;
350
351 let scaled_movement_mm = scale_factor * movement_mm;
353
354 match (scaled_movement_mm.x.classify(), scaled_movement_mm.y.classify()) {
355 (FpCategory::Infinite | FpCategory::Nan, _)
356 | (_, FpCategory::Infinite | FpCategory::Nan) => {
357 self.metrics_logger.log_error(
366 InputPipelineErrorMetricDimensionEvent::PointerSensorScaleHandlerScaledMotionInvalid,
367 std::format!(
368 "skipped motion; scaled movement of {:?} is infinite or NaN; x is {:?}, and y is {:?}",
369 scaled_movement_mm,
370 scaled_movement_mm.x.classify(),
371 scaled_movement_mm.y.classify(),
372 ));
373 Position { x: 0.0, y: 0.0 }
374 }
375 _ => scaled_movement_mm,
376 }
377 }
378
379 fn scale_scroll(
382 &self,
383 wheel_delta: Option<mouse_binding::WheelDelta>,
384 event_time: zx::MonotonicInstant,
385 ) -> Option<mouse_binding::WheelDelta> {
386 match wheel_delta {
387 None => None,
388 Some(mouse_binding::WheelDelta {
389 raw_data: mouse_binding::RawWheelDelta::Ticks(tick),
390 ..
391 }) => Some(mouse_binding::WheelDelta {
392 raw_data: mouse_binding::RawWheelDelta::Ticks(tick),
393 physical_pixel: Some(tick as f32 * PIXELS_PER_TICK),
394 }),
395 Some(mouse_binding::WheelDelta {
396 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
397 ..
398 }) => {
399 let elapsed_time_secs =
401 match self.mutable_state.borrow_mut().last_scroll_timestamp.replace(event_time)
402 {
403 Some(last_event_time) => (event_time - last_event_time)
404 .clamp(MIN_PLAUSIBLE_EVENT_DELAY, MAX_PLAUSIBLE_EVENT_DELAY),
405 None => MAX_PLAUSIBLE_EVENT_DELAY,
406 }
407 .into_nanos() as f32
408 / 1E9;
409
410 let velocity = mm.abs() / elapsed_time_secs;
411
412 if velocity < MIN_MEASURABLE_VELOCITY_MM_PER_SEC {
413 return Some(mouse_binding::WheelDelta {
416 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
417 physical_pixel: Some(SCALE_SCROLL * mm),
418 });
419 }
420
421 let scale_factor = Self::scale_euclidean_velocity(velocity) / velocity;
422
423 let scaled_scroll_mm = SCALE_SCROLL * scale_factor * mm;
425
426 if scaled_scroll_mm.is_infinite() || scaled_scroll_mm.is_nan() {
427 self.metrics_logger.log_error(
428 InputPipelineErrorMetricDimensionEvent::PointerSensorScaleHandlerScaledScrollInvalid,
429 std::format!(
430 "skipped scroll; scaled scroll of {:?} is infinite or NaN.",
431 scaled_scroll_mm,
432 ));
433 return Some(mouse_binding::WheelDelta {
434 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
435 physical_pixel: Some(SCALE_SCROLL * mm),
436 });
437 }
438
439 Some(mouse_binding::WheelDelta {
440 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
441 physical_pixel: Some(scaled_scroll_mm),
442 })
443 }
444 }
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use crate::input_handler::InputHandler;
452 use crate::testing_utilities;
453 use assert_matches::assert_matches;
454 use maplit::hashset;
455 use std::cell::Cell;
456 use std::ops::Add;
457 use test_util::{assert_gt, assert_lt, assert_near};
458 use {fuchsia_async as fasync, fuchsia_inspect};
459
460 const COUNTS_PER_MM: f32 = 12.0;
461 const DEVICE_DESCRIPTOR: input_device::InputDeviceDescriptor =
462 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
463 device_id: 0,
464 absolute_x_range: None,
465 absolute_y_range: None,
466 wheel_v_range: None,
467 wheel_h_range: None,
468 buttons: None,
469 counts_per_mm: COUNTS_PER_MM as u32,
470 });
471
472 const SCALE_EPSILON: f32 = 1.0 / 100_000.0;
488
489 std::thread_local! {static NEXT_EVENT_TIME: Cell<i64> = Cell::new(0)}
490
491 fn make_unhandled_input_event(
492 mouse_event: mouse_binding::MouseEvent,
493 ) -> input_device::UnhandledInputEvent {
494 let event_time = NEXT_EVENT_TIME.with(|t| {
495 let old = t.get();
496 t.set(old + 1);
497 old
498 });
499 input_device::UnhandledInputEvent {
500 device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
501 device_descriptor: DEVICE_DESCRIPTOR.clone(),
502 event_time: zx::MonotonicInstant::from_nanos(event_time),
503 trace_id: None,
504 }
505 }
506
507 #[fuchsia::test]
508 async fn pointer_sensor_scale_handler_initialized_with_inspect_node() {
509 let inspector = fuchsia_inspect::Inspector::default();
510 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
511 let _handler =
512 PointerSensorScaleHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
513 diagnostics_assertions::assert_data_tree!(inspector, root: {
514 input_handlers_node: {
515 pointer_sensor_scale_handler: {
516 events_received_count: 0u64,
517 events_handled_count: 0u64,
518 last_received_timestamp_ns: 0u64,
519 "fuchsia.inspect.Health": {
520 status: "STARTING_UP",
521 start_timestamp_nanos: diagnostics_assertions::AnyProperty
524 },
525 }
526 }
527 });
528 }
529
530 #[fasync::run_singlethreaded(test)]
531 async fn pointer_sensor_scale_handler_inspect_counts_events() {
532 let inspector = fuchsia_inspect::Inspector::default();
533 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
534 let handler =
535 PointerSensorScaleHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
536
537 let event_time1 = zx::MonotonicInstant::get();
538 let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
539 let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
540
541 let input_events = vec![
542 testing_utilities::create_mouse_event(
543 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
544 None, None, None, mouse_binding::MousePhase::Wheel,
548 hashset! {},
549 hashset! {},
550 event_time1,
551 &DEVICE_DESCRIPTOR,
552 ),
553 testing_utilities::create_mouse_event(
554 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
555 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
556 }),
557 None, None, None, mouse_binding::MousePhase::Move,
561 hashset! {},
562 hashset! {},
563 event_time2,
564 &DEVICE_DESCRIPTOR,
565 ),
566 testing_utilities::create_fake_input_event(event_time2),
568 testing_utilities::create_mouse_event_with_handled(
570 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
571 None, None, None, mouse_binding::MousePhase::Wheel,
575 hashset! {},
576 hashset! {},
577 event_time3,
578 &DEVICE_DESCRIPTOR,
579 input_device::Handled::Yes,
580 ),
581 ];
582
583 for input_event in input_events {
584 let _ = handler.clone().handle_input_event(input_event).await;
585 }
586
587 let last_received_event_time: u64 = event_time2.into_nanos().try_into().unwrap();
588
589 diagnostics_assertions::assert_data_tree!(inspector, root: {
590 input_handlers_node: {
591 pointer_sensor_scale_handler: {
592 events_received_count: 2u64,
593 events_handled_count: 0u64,
594 last_received_timestamp_ns: last_received_event_time,
595 "fuchsia.inspect.Health": {
596 status: "STARTING_UP",
597 start_timestamp_nanos: diagnostics_assertions::AnyProperty
600 },
601 }
602 }
603 });
604 }
605
606 mod internal_computations {
612 use super::*;
613
614 #[fuchsia::test]
615 fn transition_from_low_to_medium_is_continuous() {
616 assert_near!(
617 PointerSensorScaleHandler::scale_low_speed(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC),
618 PointerSensorScaleHandler::scale_medium_speed(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC),
619 SCALE_EPSILON
620 );
621 }
622
623 #[fuchsia::test]
629 fn transition_from_medium_to_high_is_continuous() {
630 assert_near!(
631 PointerSensorScaleHandler::scale_medium_speed(MEDIUM_SPEED_RANGE_END_MM_PER_SEC),
632 PointerSensorScaleHandler::scale_high_speed(MEDIUM_SPEED_RANGE_END_MM_PER_SEC),
633 SCALE_EPSILON
634 );
635 }
636 }
637
638 mod motion_scaling_mm {
639 use super::*;
640
641 #[ignore]
642 #[fuchsia::test(allow_stalls = false)]
643 async fn plot_example_curve() {
644 let duration = zx::MonotonicDuration::from_millis(8);
645 for count in 1..1000 {
646 let scaled_count = get_scaled_motion_mm(
647 Position { x: count as f32 / COUNTS_PER_MM, y: 0.0 },
648 duration,
649 )
650 .await;
651 log::error!("{}, {}", count, scaled_count.x);
652 }
653 }
654
655 async fn get_scaled_motion_mm(
656 movement_mm: Position,
657 duration: zx::MonotonicDuration,
658 ) -> Position {
659 let inspector = fuchsia_inspect::Inspector::default();
660 let test_node = inspector.root().create_child("test_node");
661 let handler =
662 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
663
664 let input_event = input_device::UnhandledInputEvent {
666 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
667 location: mouse_binding::MouseLocation::Relative(Default::default()),
668 wheel_delta_v: None,
669 wheel_delta_h: None,
670 phase: mouse_binding::MousePhase::Move,
671 affected_buttons: hashset! {},
672 pressed_buttons: hashset! {},
673 is_precision_scroll: None,
674 }),
675 device_descriptor: DEVICE_DESCRIPTOR.clone(),
676 event_time: zx::MonotonicInstant::from_nanos(0),
677 trace_id: None,
678 };
679 handler.clone().handle_unhandled_input_event(input_event).await;
680
681 let input_event = input_device::UnhandledInputEvent {
683 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
684 location: mouse_binding::MouseLocation::Relative(
685 mouse_binding::RelativeLocation { millimeters: movement_mm },
686 ),
687 wheel_delta_v: None,
688 wheel_delta_h: None,
689 phase: mouse_binding::MousePhase::Move,
690 affected_buttons: hashset! {},
691 pressed_buttons: hashset! {},
692 is_precision_scroll: None,
693 }),
694 device_descriptor: DEVICE_DESCRIPTOR.clone(),
695 event_time: zx::MonotonicInstant::from_nanos(duration.into_nanos()),
696 trace_id: None,
697 };
698 let transformed_events =
699 handler.clone().handle_unhandled_input_event(input_event).await;
700
701 assert_matches!(
704 transformed_events.as_slice(),
705 [input_device::InputEvent {
706 device_event: input_device::InputDeviceEvent::Mouse(
707 mouse_binding::MouseEvent {
708 location: mouse_binding::MouseLocation::Relative(
709 mouse_binding::RelativeLocation { .. }
710 ),
711 ..
712 }
713 ),
714 ..
715 }]
716 );
717
718 if let input_device::InputEvent {
720 device_event:
721 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
722 location:
723 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
724 millimeters: movement_mm,
725 }),
726 ..
727 }),
728 ..
729 } = transformed_events[0]
730 {
731 movement_mm
732 } else {
733 unreachable!()
734 }
735 }
736
737 fn velocity_to_mm(velocity_mm_per_sec: f32, duration: zx::MonotonicDuration) -> f32 {
738 velocity_mm_per_sec * (duration.into_nanos() as f32 / 1E9)
739 }
740
741 #[fuchsia::test(allow_stalls = false)]
742 async fn low_speed_horizontal_motion_scales_linearly() {
743 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
744 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
745 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
746 assert_lt!(
747 MOTION_B_MM,
748 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
749 );
750
751 let scaled_a =
752 get_scaled_motion_mm(Position { x: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
753 let scaled_b =
754 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
755 assert_near!(scaled_b.x / scaled_a.x, 2.0, SCALE_EPSILON);
756 }
757
758 #[fuchsia::test(allow_stalls = false)]
759 async fn low_speed_vertical_motion_scales_linearly() {
760 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
761 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
762 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
763 assert_lt!(
764 MOTION_B_MM,
765 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
766 );
767
768 let scaled_a =
769 get_scaled_motion_mm(Position { x: 0.0, y: MOTION_A_MM }, TICK_DURATION).await;
770 let scaled_b =
771 get_scaled_motion_mm(Position { x: 0.0, y: MOTION_B_MM }, TICK_DURATION).await;
772 assert_near!(scaled_b.y / scaled_a.y, 2.0, SCALE_EPSILON);
773 }
774
775 #[fuchsia::test(allow_stalls = false)]
776 async fn low_speed_45degree_motion_scales_dimensions_equally() {
777 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
778 const MOTION_MM: f32 = 1.0 / COUNTS_PER_MM;
779 assert_lt!(
780 MOTION_MM,
781 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
782 );
783
784 let scaled =
785 get_scaled_motion_mm(Position { x: MOTION_MM, y: MOTION_MM }, TICK_DURATION).await;
786 assert_near!(scaled.x, scaled.y, SCALE_EPSILON);
787 }
788
789 #[fuchsia::test(allow_stalls = false)]
790 async fn medium_speed_motion_scales_quadratically() {
791 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
792 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
793 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
794 assert_gt!(
795 MOTION_A_MM,
796 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
797 );
798 assert_lt!(
799 MOTION_B_MM,
800 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
801 );
802
803 let scaled_a =
804 get_scaled_motion_mm(Position { x: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
805 let scaled_b =
806 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
807 assert_near!(scaled_b.x / scaled_a.x, 4.0, SCALE_EPSILON);
808 }
809
810 #[fuchsia::test(allow_stalls = false)]
816 async fn high_speed_motion_scaling_is_increasing() {
817 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
818 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
819 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
820 assert_gt!(
821 MOTION_A_MM,
822 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
823 );
824
825 let scaled_a =
826 get_scaled_motion_mm(Position { x: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
827 let scaled_b =
828 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
829 assert_gt!(scaled_b.x, scaled_a.x)
830 }
831
832 #[fuchsia::test(allow_stalls = false)]
833 async fn zero_motion_maps_to_zero_motion() {
834 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
835 let scaled = get_scaled_motion_mm(Position { x: 0.0, y: 0.0 }, TICK_DURATION).await;
836 assert_eq!(scaled, Position::zero())
837 }
838
839 #[fuchsia::test(allow_stalls = false)]
840 async fn zero_duration_does_not_crash() {
841 get_scaled_motion_mm(
842 Position { x: 1.0 / COUNTS_PER_MM, y: 0.0 },
843 zx::MonotonicDuration::from_millis(0),
844 )
845 .await;
846 }
847 }
848
849 mod scroll_scaling_tick {
850 use super::*;
851 use test_case::test_case;
852
853 #[test_case(mouse_binding::MouseEvent {
854 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
855 millimeters: Position::zero(),
856 }),
857 wheel_delta_v: Some(mouse_binding::WheelDelta {
858 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
859 physical_pixel: None,
860 }),
861 wheel_delta_h: None,
862 phase: mouse_binding::MousePhase::Wheel,
863 affected_buttons: hashset! {},
864 pressed_buttons: hashset! {},
865 is_precision_scroll: None,
866 } => (Some(PIXELS_PER_TICK), None); "v")]
867 #[test_case(mouse_binding::MouseEvent {
868 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
869 millimeters: Position::zero(),
870 }),
871 wheel_delta_v: None,
872 wheel_delta_h: Some(mouse_binding::WheelDelta {
873 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
874 physical_pixel: None,
875 }),
876 phase: mouse_binding::MousePhase::Wheel,
877 affected_buttons: hashset! {},
878 pressed_buttons: hashset! {},
879 is_precision_scroll: None,
880 } => (None, Some(PIXELS_PER_TICK)); "h")]
881 #[fuchsia::test(allow_stalls = false)]
882 async fn scaled(event: mouse_binding::MouseEvent) -> (Option<f32>, Option<f32>) {
883 let inspector = fuchsia_inspect::Inspector::default();
884 let test_node = inspector.root().create_child("test_node");
885 let handler =
886 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
887 let unhandled_event = make_unhandled_input_event(event);
888
889 let events = handler.clone().handle_unhandled_input_event(unhandled_event).await;
890 assert_matches!(
891 events.as_slice(),
892 [input_device::InputEvent {
893 device_event: input_device::InputDeviceEvent::Mouse(
894 mouse_binding::MouseEvent { .. }
895 ),
896 ..
897 }]
898 );
899 if let input_device::InputEvent {
900 device_event:
901 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
902 wheel_delta_v,
903 wheel_delta_h,
904 ..
905 }),
906 ..
907 } = events[0].clone()
908 {
909 match (wheel_delta_v, wheel_delta_h) {
910 (None, None) => return (None, None),
911 (None, Some(delta_h)) => return (None, delta_h.physical_pixel),
912 (Some(delta_v), None) => return (delta_v.physical_pixel, None),
913 (Some(delta_v), Some(delta_h)) => {
914 return (delta_v.physical_pixel, delta_h.physical_pixel);
915 }
916 }
917 } else {
918 unreachable!();
919 }
920 }
921 }
922
923 mod scroll_scaling_mm {
924 use super::*;
925 use pretty_assertions::assert_eq;
926
927 async fn get_scaled_scroll_mm(
928 wheel_delta_v_mm: Option<f32>,
929 wheel_delta_h_mm: Option<f32>,
930 duration: zx::MonotonicDuration,
931 ) -> (Option<mouse_binding::WheelDelta>, Option<mouse_binding::WheelDelta>) {
932 let inspector = fuchsia_inspect::Inspector::default();
933 let test_node = inspector.root().create_child("test_node");
934 let handler =
935 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
936
937 let input_event = input_device::UnhandledInputEvent {
939 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
940 location: mouse_binding::MouseLocation::Relative(Default::default()),
941 wheel_delta_v: Some(mouse_binding::WheelDelta {
942 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
943 physical_pixel: None,
944 }),
945 wheel_delta_h: None,
946 phase: mouse_binding::MousePhase::Wheel,
947 affected_buttons: hashset! {},
948 pressed_buttons: hashset! {},
949 is_precision_scroll: None,
950 }),
951 device_descriptor: DEVICE_DESCRIPTOR.clone(),
952 event_time: zx::MonotonicInstant::from_nanos(0),
953 trace_id: None,
954 };
955 handler.clone().handle_unhandled_input_event(input_event).await;
956
957 let input_event = input_device::UnhandledInputEvent {
959 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
960 location: mouse_binding::MouseLocation::Relative(Default::default()),
961 wheel_delta_v: match wheel_delta_v_mm {
962 None => None,
963 Some(delta) => Some(mouse_binding::WheelDelta {
964 raw_data: mouse_binding::RawWheelDelta::Millimeters(delta),
965 physical_pixel: None,
966 }),
967 },
968 wheel_delta_h: match wheel_delta_h_mm {
969 None => None,
970 Some(delta) => Some(mouse_binding::WheelDelta {
971 raw_data: mouse_binding::RawWheelDelta::Millimeters(delta),
972 physical_pixel: None,
973 }),
974 },
975 phase: mouse_binding::MousePhase::Wheel,
976 affected_buttons: hashset! {},
977 pressed_buttons: hashset! {},
978 is_precision_scroll: None,
979 }),
980 device_descriptor: DEVICE_DESCRIPTOR.clone(),
981 event_time: zx::MonotonicInstant::from_nanos(duration.into_nanos()),
982 trace_id: None,
983 };
984 let transformed_events =
985 handler.clone().handle_unhandled_input_event(input_event).await;
986
987 assert_eq!(transformed_events.len(), 1);
988
989 if let input_device::InputEvent {
990 device_event:
991 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
992 wheel_delta_v: delta_v,
993 wheel_delta_h: delta_h,
994 ..
995 }),
996 ..
997 } = transformed_events[0].clone()
998 {
999 return (delta_v, delta_h);
1000 } else {
1001 unreachable!()
1002 }
1003 }
1004
1005 fn velocity_to_mm(velocity_mm_per_sec: f32, duration: zx::MonotonicDuration) -> f32 {
1006 velocity_mm_per_sec * (duration.into_nanos() as f32 / 1E9)
1007 }
1008
1009 #[fuchsia::test(allow_stalls = false)]
1010 async fn low_speed_horizontal_scroll_scales_linearly() {
1011 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1012 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
1013 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
1014 assert_lt!(
1015 MOTION_B_MM,
1016 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1017 );
1018
1019 let (_, scaled_a_h) =
1020 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1021
1022 let (_, scaled_b_h) =
1023 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1024
1025 match (scaled_a_h, scaled_b_h) {
1026 (Some(a_h), Some(b_h)) => {
1027 assert_ne!(a_h.physical_pixel, None);
1028 assert_ne!(b_h.physical_pixel, None);
1029 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1030 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1031 assert_near!(
1032 b_h.physical_pixel.unwrap() / a_h.physical_pixel.unwrap(),
1033 2.0,
1034 SCALE_EPSILON
1035 );
1036 }
1037 _ => {
1038 panic!("wheel delta is none");
1039 }
1040 }
1041 }
1042
1043 #[fuchsia::test(allow_stalls = false)]
1044 async fn low_speed_vertical_scroll_scales_linearly() {
1045 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1046 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
1047 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
1048 assert_lt!(
1049 MOTION_B_MM,
1050 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1051 );
1052
1053 let (scaled_a_v, _) =
1054 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1055
1056 let (scaled_b_v, _) =
1057 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1058
1059 match (scaled_a_v, scaled_b_v) {
1060 (Some(a_v), Some(b_v)) => {
1061 assert_ne!(a_v.physical_pixel, None);
1062 assert_ne!(b_v.physical_pixel, None);
1063 assert_near!(
1064 b_v.physical_pixel.unwrap() / a_v.physical_pixel.unwrap(),
1065 2.0,
1066 SCALE_EPSILON
1067 );
1068 }
1069 _ => {
1070 panic!("wheel delta is none");
1071 }
1072 }
1073 }
1074
1075 #[fuchsia::test(allow_stalls = false)]
1076 async fn medium_speed_horizontal_scroll_scales_quadratically() {
1077 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1078 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
1079 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
1080 assert_gt!(
1081 MOTION_A_MM,
1082 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1083 );
1084 assert_lt!(
1085 MOTION_B_MM,
1086 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1087 );
1088
1089 let (_, scaled_a_h) =
1090 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1091
1092 let (_, scaled_b_h) =
1093 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1094
1095 match (scaled_a_h, scaled_b_h) {
1096 (Some(a_h), Some(b_h)) => {
1097 assert_ne!(a_h.physical_pixel, None);
1098 assert_ne!(b_h.physical_pixel, None);
1099 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1100 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1101 assert_near!(
1102 b_h.physical_pixel.unwrap() / a_h.physical_pixel.unwrap(),
1103 4.0,
1104 SCALE_EPSILON
1105 );
1106 }
1107 _ => {
1108 panic!("wheel delta is none");
1109 }
1110 }
1111 }
1112
1113 #[fuchsia::test(allow_stalls = false)]
1114 async fn medium_speed_vertical_scroll_scales_quadratically() {
1115 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1116 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
1117 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
1118 assert_gt!(
1119 MOTION_A_MM,
1120 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1121 );
1122 assert_lt!(
1123 MOTION_B_MM,
1124 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1125 );
1126
1127 let (scaled_a_v, _) =
1128 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1129
1130 let (scaled_b_v, _) =
1131 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1132
1133 match (scaled_a_v, scaled_b_v) {
1134 (Some(a_v), Some(b_v)) => {
1135 assert_ne!(a_v.physical_pixel, None);
1136 assert_ne!(b_v.physical_pixel, None);
1137 assert_near!(
1138 b_v.physical_pixel.unwrap() / a_v.physical_pixel.unwrap(),
1139 4.0,
1140 SCALE_EPSILON
1141 );
1142 }
1143 _ => {
1144 panic!("wheel delta is none");
1145 }
1146 }
1147 }
1148
1149 #[fuchsia::test(allow_stalls = false)]
1150 async fn high_speed_horizontal_scroll_scaling_is_inreasing() {
1151 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1152 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
1153 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
1154 assert_gt!(
1155 MOTION_A_MM,
1156 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1157 );
1158
1159 let (_, scaled_a_h) =
1160 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1161
1162 let (_, scaled_b_h) =
1163 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1164
1165 match (scaled_a_h, scaled_b_h) {
1166 (Some(a_h), Some(b_h)) => {
1167 assert_ne!(a_h.physical_pixel, None);
1168 assert_ne!(b_h.physical_pixel, None);
1169 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1170 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1171 assert_gt!(b_h.physical_pixel.unwrap(), a_h.physical_pixel.unwrap());
1172 }
1173 _ => {
1174 panic!("wheel delta is none");
1175 }
1176 }
1177 }
1178
1179 #[fuchsia::test(allow_stalls = false)]
1180 async fn high_speed_vertical_scroll_scaling_is_inreasing() {
1181 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1182 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
1183 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
1184 assert_gt!(
1185 MOTION_A_MM,
1186 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1187 );
1188
1189 let (scaled_a_v, _) =
1190 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1191
1192 let (scaled_b_v, _) =
1193 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1194
1195 match (scaled_a_v, scaled_b_v) {
1196 (Some(a_v), Some(b_v)) => {
1197 assert_ne!(a_v.physical_pixel, None);
1198 assert_ne!(b_v.physical_pixel, None);
1199 assert_gt!(b_v.physical_pixel.unwrap(), a_v.physical_pixel.unwrap());
1200 }
1201 _ => {
1202 panic!("wheel delta is none");
1203 }
1204 }
1205 }
1206 }
1207
1208 mod metadata_preservation {
1209 use super::*;
1210 use test_case::test_case;
1211
1212 #[test_case(mouse_binding::MouseEvent {
1213 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1214 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
1215 }),
1216 wheel_delta_v: None,
1217 wheel_delta_h: None,
1218 phase: mouse_binding::MousePhase::Move,
1219 affected_buttons: hashset! {},
1220 pressed_buttons: hashset! {},
1221 is_precision_scroll: None,
1222 }; "move event")]
1223 #[test_case(mouse_binding::MouseEvent {
1224 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1225 millimeters: Position::zero(),
1226 }),
1227 wheel_delta_v: Some(mouse_binding::WheelDelta {
1228 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1229 physical_pixel: None,
1230 }),
1231 wheel_delta_h: None,
1232 phase: mouse_binding::MousePhase::Wheel,
1233 affected_buttons: hashset! {},
1234 pressed_buttons: hashset! {},
1235 is_precision_scroll: None,
1236 }; "wheel event")]
1237 #[fuchsia::test(allow_stalls = false)]
1238 async fn does_not_consume_event(event: mouse_binding::MouseEvent) {
1239 let inspector = fuchsia_inspect::Inspector::default();
1240 let test_node = inspector.root().create_child("test_node");
1241 let handler =
1242 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1243 let input_event = make_unhandled_input_event(event);
1244 assert_matches!(
1245 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
1246 [input_device::InputEvent { handled: input_device::Handled::No, .. }]
1247 );
1248 }
1249
1250 #[test_case(mouse_binding::MouseEvent {
1253 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1254 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
1255 }),
1256 wheel_delta_v: None,
1257 wheel_delta_h: None,
1258 phase: mouse_binding::MousePhase::Move,
1259 affected_buttons: hashset! {},
1260 pressed_buttons: hashset! {},
1261 is_precision_scroll: None,
1262 }; "move event")]
1263 #[test_case(mouse_binding::MouseEvent {
1264 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1265 millimeters: Position::zero(),
1266 }),
1267 wheel_delta_v: Some(mouse_binding::WheelDelta {
1268 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1269 physical_pixel: None,
1270 }),
1271 wheel_delta_h: None,
1272 phase: mouse_binding::MousePhase::Wheel,
1273 affected_buttons: hashset! {},
1274 pressed_buttons: hashset! {},
1275 is_precision_scroll: None,
1276 }; "wheel event")]
1277 #[fuchsia::test(allow_stalls = false)]
1278 async fn preserves_event_time(event: mouse_binding::MouseEvent) {
1279 let inspector = fuchsia_inspect::Inspector::default();
1280 let test_node = inspector.root().create_child("test_node");
1281 let handler =
1282 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1283 let mut input_event = make_unhandled_input_event(event);
1284 const EVENT_TIME: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(42);
1285 input_event.event_time = EVENT_TIME;
1286
1287 let events = handler.clone().handle_unhandled_input_event(input_event).await;
1288 assert_eq!(events.len(), 1, "{events:?} should be length 1");
1289 assert_eq!(events[0].event_time, EVENT_TIME);
1290 }
1291
1292 #[test_case(
1293 mouse_binding::MouseEvent {
1294 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1295 millimeters: Position::zero(),
1296 }),
1297 wheel_delta_v: Some(mouse_binding::WheelDelta {
1298 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1299 physical_pixel: Some(1.0),
1300 }),
1301 wheel_delta_h: None,
1302 phase: mouse_binding::MousePhase::Wheel,
1303 affected_buttons: hashset! {},
1304 pressed_buttons: hashset! {},
1305 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
1306 } => matches input_device::InputEvent {
1307 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1308 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
1309 ..
1310 }),
1311 ..
1312 }; "no")]
1313 #[test_case(
1314 mouse_binding::MouseEvent {
1315 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1316 millimeters: Position::zero(),
1317 }),
1318 wheel_delta_v: Some(mouse_binding::WheelDelta {
1319 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1320 physical_pixel: Some(1.0),
1321 }),
1322 wheel_delta_h: None,
1323 phase: mouse_binding::MousePhase::Wheel,
1324 affected_buttons: hashset! {},
1325 pressed_buttons: hashset! {},
1326 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
1327 } => matches input_device::InputEvent {
1328 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1329 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
1330 ..
1331 }),
1332 ..
1333 }; "yes")]
1334 #[fuchsia::test(allow_stalls = false)]
1335 async fn preserves_is_precision_scroll(
1336 event: mouse_binding::MouseEvent,
1337 ) -> input_device::InputEvent {
1338 let inspector = fuchsia_inspect::Inspector::default();
1339 let test_node = inspector.root().create_child("test_node");
1340 let handler =
1341 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1342 let input_event = make_unhandled_input_event(event);
1343
1344 handler.clone().handle_unhandled_input_event(input_event).await[0].clone()
1345 }
1346 }
1347}