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