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