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!("input", "pointer_sensor_scale_handler");
49 match unhandled_input_event {
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!("input", "pointer_sensor_scale_handler[processing]");
80 let tracing_id = trace_id.unwrap_or_else(|| 0.into());
81 fuchsia_trace::flow_step!("input", "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!("input", "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 _ => {
185 log::warn!("Unhandled input event: {:?}", unhandled_input_event.get_event_type());
187 vec![input_device::InputEvent::from(unhandled_input_event)]
188 }
189 }
190 }
191
192 fn set_handler_healthy(self: std::rc::Rc<Self>) {
193 self.inspect_status.health_node.borrow_mut().set_ok();
194 }
195
196 fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
197 self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
198 }
199
200 fn get_name(&self) -> &'static str {
201 "PointerSensorScaleHandler"
202 }
203
204 fn interest(&self) -> Vec<input_device::InputEventType> {
205 vec![input_device::InputEventType::Mouse]
206 }
207}
208
209const MIN_PLAUSIBLE_EVENT_DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(100);
217
218const MAX_PLAUSIBLE_EVENT_DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(50);
230
231const MAX_SENSOR_COUNTS_PER_INCH: f32 = 20_000.0; const MAX_SENSOR_COUNTS_PER_MM: f32 = MAX_SENSOR_COUNTS_PER_INCH / 12.7;
233const MIN_MEASURABLE_DISTANCE_MM: f32 = 1.0 / MAX_SENSOR_COUNTS_PER_MM;
234const MAX_PLAUSIBLE_EVENT_DELAY_SECS: f32 = MAX_PLAUSIBLE_EVENT_DELAY.into_nanos() as f32 / 1E9;
235const MIN_MEASURABLE_VELOCITY_MM_PER_SEC: f32 =
236 MIN_MEASURABLE_DISTANCE_MM / MAX_PLAUSIBLE_EVENT_DELAY_SECS;
237
238const MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC: f32 = 32.0;
243const MEDIUM_SPEED_RANGE_END_MM_PER_SEC: f32 = 150.0;
244
245const NUMBNESS: f32 = 37.5;
248
249impl PointerSensorScaleHandler {
250 pub fn new(
254 input_handlers_node: &fuchsia_inspect::Node,
255 metrics_logger: metrics::MetricsLogger,
256 ) -> Rc<Self> {
257 let inspect_status = InputHandlerStatus::new(
258 input_handlers_node,
259 "pointer_sensor_scale_handler",
260 false,
261 );
262 Rc::new(Self {
263 mutable_state: RefCell::new(MutableState {
264 last_move_timestamp: None,
265 last_scroll_timestamp: None,
266 }),
267 inspect_status,
268 metrics_logger,
269 })
270 }
271
272 fn scale_low_speed(movement_mm_per_sec: f32) -> f32 {
277 const LINEAR_SCALE_FACTOR: f32 = MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC / NUMBNESS;
278 LINEAR_SCALE_FACTOR * movement_mm_per_sec
279 }
280
281 fn scale_medium_speed(movement_mm_per_sec: f32) -> f32 {
296 const QUARDRATIC_SCALE_FACTOR: f32 = 1.0 / NUMBNESS;
297 QUARDRATIC_SCALE_FACTOR * movement_mm_per_sec * movement_mm_per_sec
298 }
299
300 fn scale_high_speed(movement_mm_per_sec: f32) -> f32 {
307 const LINEAR_SCALE_FACTOR: f32 = 2.0 * (MEDIUM_SPEED_RANGE_END_MM_PER_SEC / NUMBNESS);
310
311 const Y_AT_MEDIUM_SPEED_RANGE_END_MM_PER_SEC: f32 =
313 MEDIUM_SPEED_RANGE_END_MM_PER_SEC * MEDIUM_SPEED_RANGE_END_MM_PER_SEC / NUMBNESS;
314 const OFFSET: f32 = Y_AT_MEDIUM_SPEED_RANGE_END_MM_PER_SEC
315 - LINEAR_SCALE_FACTOR * MEDIUM_SPEED_RANGE_END_MM_PER_SEC;
316
317 LINEAR_SCALE_FACTOR * movement_mm_per_sec + OFFSET
319 }
320
321 fn scale_euclidean_velocity(raw_velocity: f32) -> f32 {
325 if (0.0..MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC).contains(&raw_velocity) {
326 Self::scale_low_speed(raw_velocity)
327 } else if (MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC..MEDIUM_SPEED_RANGE_END_MM_PER_SEC)
328 .contains(&raw_velocity)
329 {
330 Self::scale_medium_speed(raw_velocity)
331 } else {
332 Self::scale_high_speed(raw_velocity)
333 }
334 }
335
336 fn scale_motion(&self, movement_mm: Position, event_time: zx::MonotonicInstant) -> Position {
338 let elapsed_time_secs =
340 match self.mutable_state.borrow_mut().last_move_timestamp.replace(event_time) {
341 Some(last_event_time) => (event_time - last_event_time)
342 .clamp(MIN_PLAUSIBLE_EVENT_DELAY, MAX_PLAUSIBLE_EVENT_DELAY),
343 None => MAX_PLAUSIBLE_EVENT_DELAY,
344 }
345 .into_nanos() as f32
346 / 1E9;
347
348 let x_mm_per_sec = movement_mm.x / elapsed_time_secs;
350 let y_mm_per_sec = movement_mm.y / elapsed_time_secs;
351
352 let euclidean_velocity =
353 f32::sqrt(x_mm_per_sec * x_mm_per_sec + y_mm_per_sec * y_mm_per_sec);
354 if euclidean_velocity < MIN_MEASURABLE_VELOCITY_MM_PER_SEC {
355 return movement_mm;
357 }
358
359 let scale_factor = Self::scale_euclidean_velocity(euclidean_velocity) / euclidean_velocity;
367
368 let scaled_movement_mm = scale_factor * movement_mm;
370
371 match (scaled_movement_mm.x.classify(), scaled_movement_mm.y.classify()) {
372 (FpCategory::Infinite | FpCategory::Nan, _)
373 | (_, FpCategory::Infinite | FpCategory::Nan) => {
374 self.metrics_logger.log_error(
383 InputPipelineErrorMetricDimensionEvent::PointerSensorScaleHandlerScaledMotionInvalid,
384 std::format!(
385 "skipped motion; scaled movement of {:?} is infinite or NaN; x is {:?}, and y is {:?}",
386 scaled_movement_mm,
387 scaled_movement_mm.x.classify(),
388 scaled_movement_mm.y.classify(),
389 ));
390 Position { x: 0.0, y: 0.0 }
391 }
392 _ => scaled_movement_mm,
393 }
394 }
395
396 fn scale_scroll(
399 &self,
400 wheel_delta: Option<mouse_binding::WheelDelta>,
401 event_time: zx::MonotonicInstant,
402 ) -> Option<mouse_binding::WheelDelta> {
403 match wheel_delta {
404 None => None,
405 Some(mouse_binding::WheelDelta {
406 raw_data: mouse_binding::RawWheelDelta::Ticks(tick),
407 ..
408 }) => Some(mouse_binding::WheelDelta {
409 raw_data: mouse_binding::RawWheelDelta::Ticks(tick),
410 physical_pixel: Some(tick as f32 * PIXELS_PER_TICK),
411 }),
412 Some(mouse_binding::WheelDelta {
413 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
414 ..
415 }) => {
416 let elapsed_time_secs =
418 match self.mutable_state.borrow_mut().last_scroll_timestamp.replace(event_time)
419 {
420 Some(last_event_time) => (event_time - last_event_time)
421 .clamp(MIN_PLAUSIBLE_EVENT_DELAY, MAX_PLAUSIBLE_EVENT_DELAY),
422 None => MAX_PLAUSIBLE_EVENT_DELAY,
423 }
424 .into_nanos() as f32
425 / 1E9;
426
427 let velocity = mm.abs() / elapsed_time_secs;
428
429 if velocity < MIN_MEASURABLE_VELOCITY_MM_PER_SEC {
430 return Some(mouse_binding::WheelDelta {
433 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
434 physical_pixel: Some(SCALE_SCROLL * mm),
435 });
436 }
437
438 let scale_factor = Self::scale_euclidean_velocity(velocity) / velocity;
439
440 let scaled_scroll_mm = SCALE_SCROLL * scale_factor * mm;
442
443 if scaled_scroll_mm.is_infinite() || scaled_scroll_mm.is_nan() {
444 self.metrics_logger.log_error(
445 InputPipelineErrorMetricDimensionEvent::PointerSensorScaleHandlerScaledScrollInvalid,
446 std::format!(
447 "skipped scroll; scaled scroll of {:?} is infinite or NaN.",
448 scaled_scroll_mm,
449 ));
450 return Some(mouse_binding::WheelDelta {
451 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
452 physical_pixel: Some(SCALE_SCROLL * mm),
453 });
454 }
455
456 Some(mouse_binding::WheelDelta {
457 raw_data: mouse_binding::RawWheelDelta::Millimeters(mm),
458 physical_pixel: Some(scaled_scroll_mm),
459 })
460 }
461 }
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use crate::input_handler::InputHandler;
469 use crate::testing_utilities;
470 use assert_matches::assert_matches;
471 use maplit::hashset;
472 use std::cell::Cell;
473 use std::ops::Add;
474 use test_util::{assert_gt, assert_lt, assert_near};
475 use {fuchsia_async as fasync, fuchsia_inspect};
476
477 const COUNTS_PER_MM: f32 = 12.0;
478 const DEVICE_DESCRIPTOR: input_device::InputDeviceDescriptor =
479 input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor {
480 device_id: 0,
481 absolute_x_range: None,
482 absolute_y_range: None,
483 wheel_v_range: None,
484 wheel_h_range: None,
485 buttons: None,
486 counts_per_mm: COUNTS_PER_MM as u32,
487 });
488
489 const SCALE_EPSILON: f32 = 1.0 / 100_000.0;
505
506 std::thread_local! {static NEXT_EVENT_TIME: Cell<i64> = Cell::new(0)}
507
508 fn make_unhandled_input_event(
509 mouse_event: mouse_binding::MouseEvent,
510 ) -> input_device::UnhandledInputEvent {
511 let event_time = NEXT_EVENT_TIME.with(|t| {
512 let old = t.get();
513 t.set(old + 1);
514 old
515 });
516 input_device::UnhandledInputEvent {
517 device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
518 device_descriptor: DEVICE_DESCRIPTOR.clone(),
519 event_time: zx::MonotonicInstant::from_nanos(event_time),
520 trace_id: None,
521 }
522 }
523
524 #[fuchsia::test]
525 async fn pointer_sensor_scale_handler_initialized_with_inspect_node() {
526 let inspector = fuchsia_inspect::Inspector::default();
527 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
528 let _handler =
529 PointerSensorScaleHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
530 diagnostics_assertions::assert_data_tree!(inspector, root: {
531 input_handlers_node: {
532 pointer_sensor_scale_handler: {
533 events_received_count: 0u64,
534 events_handled_count: 0u64,
535 last_received_timestamp_ns: 0u64,
536 "fuchsia.inspect.Health": {
537 status: "STARTING_UP",
538 start_timestamp_nanos: diagnostics_assertions::AnyProperty
541 },
542 }
543 }
544 });
545 }
546
547 #[fasync::run_singlethreaded(test)]
548 async fn pointer_sensor_scale_handler_inspect_counts_events() {
549 let inspector = fuchsia_inspect::Inspector::default();
550 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
551 let handler =
552 PointerSensorScaleHandler::new(&fake_handlers_node, metrics::MetricsLogger::default());
553
554 let event_time1 = zx::MonotonicInstant::get();
555 let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
556 let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
557
558 let input_events = vec![
559 testing_utilities::create_mouse_event(
560 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
561 None, None, None, mouse_binding::MousePhase::Wheel,
565 hashset! {},
566 hashset! {},
567 event_time1,
568 &DEVICE_DESCRIPTOR,
569 ),
570 testing_utilities::create_mouse_event(
571 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
572 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
573 }),
574 None, None, None, mouse_binding::MousePhase::Move,
578 hashset! {},
579 hashset! {},
580 event_time2,
581 &DEVICE_DESCRIPTOR,
582 ),
583 testing_utilities::create_fake_input_event(event_time2),
585 testing_utilities::create_mouse_event_with_handled(
587 mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }),
588 None, None, None, mouse_binding::MousePhase::Wheel,
592 hashset! {},
593 hashset! {},
594 event_time3,
595 &DEVICE_DESCRIPTOR,
596 input_device::Handled::Yes,
597 ),
598 ];
599
600 for input_event in input_events {
601 let _ = handler.clone().handle_input_event(input_event).await;
602 }
603
604 let last_received_event_time: u64 = event_time2.into_nanos().try_into().unwrap();
605
606 diagnostics_assertions::assert_data_tree!(inspector, root: {
607 input_handlers_node: {
608 pointer_sensor_scale_handler: {
609 events_received_count: 2u64,
610 events_handled_count: 0u64,
611 last_received_timestamp_ns: last_received_event_time,
612 "fuchsia.inspect.Health": {
613 status: "STARTING_UP",
614 start_timestamp_nanos: diagnostics_assertions::AnyProperty
617 },
618 }
619 }
620 });
621 }
622
623 mod internal_computations {
629 use super::*;
630
631 #[fuchsia::test]
632 fn transition_from_low_to_medium_is_continuous() {
633 assert_near!(
634 PointerSensorScaleHandler::scale_low_speed(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC),
635 PointerSensorScaleHandler::scale_medium_speed(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC),
636 SCALE_EPSILON
637 );
638 }
639
640 #[fuchsia::test]
646 fn transition_from_medium_to_high_is_continuous() {
647 assert_near!(
648 PointerSensorScaleHandler::scale_medium_speed(MEDIUM_SPEED_RANGE_END_MM_PER_SEC),
649 PointerSensorScaleHandler::scale_high_speed(MEDIUM_SPEED_RANGE_END_MM_PER_SEC),
650 SCALE_EPSILON
651 );
652 }
653 }
654
655 mod motion_scaling_mm {
656 use super::*;
657
658 #[ignore]
659 #[fuchsia::test(allow_stalls = false)]
660 async fn plot_example_curve() {
661 let duration = zx::MonotonicDuration::from_millis(8);
662 for count in 1..1000 {
663 let scaled_count = get_scaled_motion_mm(
664 Position { x: count as f32 / COUNTS_PER_MM, y: 0.0 },
665 duration,
666 )
667 .await;
668 log::error!("{}, {}", count, scaled_count.x);
669 }
670 }
671
672 async fn get_scaled_motion_mm(
673 movement_mm: Position,
674 duration: zx::MonotonicDuration,
675 ) -> Position {
676 let inspector = fuchsia_inspect::Inspector::default();
677 let test_node = inspector.root().create_child("test_node");
678 let handler =
679 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
680
681 let input_event = input_device::UnhandledInputEvent {
683 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
684 location: mouse_binding::MouseLocation::Relative(Default::default()),
685 wheel_delta_v: None,
686 wheel_delta_h: None,
687 phase: mouse_binding::MousePhase::Move,
688 affected_buttons: hashset! {},
689 pressed_buttons: hashset! {},
690 is_precision_scroll: None,
691 wake_lease: None.into(),
692 }),
693 device_descriptor: DEVICE_DESCRIPTOR.clone(),
694 event_time: zx::MonotonicInstant::from_nanos(0),
695 trace_id: None,
696 };
697 handler.clone().handle_unhandled_input_event(input_event).await;
698
699 let input_event = input_device::UnhandledInputEvent {
701 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
702 location: mouse_binding::MouseLocation::Relative(
703 mouse_binding::RelativeLocation { millimeters: movement_mm },
704 ),
705 wheel_delta_v: None,
706 wheel_delta_h: None,
707 phase: mouse_binding::MousePhase::Move,
708 affected_buttons: hashset! {},
709 pressed_buttons: hashset! {},
710 is_precision_scroll: None,
711 wake_lease: None.into(),
712 }),
713 device_descriptor: DEVICE_DESCRIPTOR.clone(),
714 event_time: zx::MonotonicInstant::from_nanos(duration.into_nanos()),
715 trace_id: None,
716 };
717 let transformed_events =
718 handler.clone().handle_unhandled_input_event(input_event).await;
719
720 assert_matches!(
723 transformed_events.as_slice(),
724 [input_device::InputEvent {
725 device_event: input_device::InputDeviceEvent::Mouse(
726 mouse_binding::MouseEvent {
727 location: mouse_binding::MouseLocation::Relative(
728 mouse_binding::RelativeLocation { .. }
729 ),
730 ..
731 }
732 ),
733 ..
734 }]
735 );
736
737 if let input_device::InputEvent {
739 device_event:
740 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
741 location:
742 mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
743 millimeters: movement_mm,
744 }),
745 ..
746 }),
747 ..
748 } = transformed_events[0]
749 {
750 movement_mm
751 } else {
752 unreachable!()
753 }
754 }
755
756 fn velocity_to_mm(velocity_mm_per_sec: f32, duration: zx::MonotonicDuration) -> f32 {
757 velocity_mm_per_sec * (duration.into_nanos() as f32 / 1E9)
758 }
759
760 #[fuchsia::test(allow_stalls = false)]
761 async fn low_speed_horizontal_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: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
772 let scaled_b =
773 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
774 assert_near!(scaled_b.x / scaled_a.x, 2.0, SCALE_EPSILON);
775 }
776
777 #[fuchsia::test(allow_stalls = false)]
778 async fn low_speed_vertical_motion_scales_linearly() {
779 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
780 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
781 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
782 assert_lt!(
783 MOTION_B_MM,
784 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
785 );
786
787 let scaled_a =
788 get_scaled_motion_mm(Position { x: 0.0, y: MOTION_A_MM }, TICK_DURATION).await;
789 let scaled_b =
790 get_scaled_motion_mm(Position { x: 0.0, y: MOTION_B_MM }, TICK_DURATION).await;
791 assert_near!(scaled_b.y / scaled_a.y, 2.0, SCALE_EPSILON);
792 }
793
794 #[fuchsia::test(allow_stalls = false)]
795 async fn low_speed_45degree_motion_scales_dimensions_equally() {
796 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
797 const MOTION_MM: f32 = 1.0 / COUNTS_PER_MM;
798 assert_lt!(
799 MOTION_MM,
800 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
801 );
802
803 let scaled =
804 get_scaled_motion_mm(Position { x: MOTION_MM, y: MOTION_MM }, TICK_DURATION).await;
805 assert_near!(scaled.x, scaled.y, SCALE_EPSILON);
806 }
807
808 #[fuchsia::test(allow_stalls = false)]
809 async fn medium_speed_motion_scales_quadratically() {
810 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
811 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
812 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
813 assert_gt!(
814 MOTION_A_MM,
815 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
816 );
817 assert_lt!(
818 MOTION_B_MM,
819 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
820 );
821
822 let scaled_a =
823 get_scaled_motion_mm(Position { x: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
824 let scaled_b =
825 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
826 assert_near!(scaled_b.x / scaled_a.x, 4.0, SCALE_EPSILON);
827 }
828
829 #[fuchsia::test(allow_stalls = false)]
835 async fn high_speed_motion_scaling_is_increasing() {
836 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
837 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
838 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
839 assert_gt!(
840 MOTION_A_MM,
841 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
842 );
843
844 let scaled_a =
845 get_scaled_motion_mm(Position { x: MOTION_A_MM, y: 0.0 }, TICK_DURATION).await;
846 let scaled_b =
847 get_scaled_motion_mm(Position { x: MOTION_B_MM, y: 0.0 }, TICK_DURATION).await;
848 assert_gt!(scaled_b.x, scaled_a.x)
849 }
850
851 #[fuchsia::test(allow_stalls = false)]
852 async fn zero_motion_maps_to_zero_motion() {
853 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
854 let scaled = get_scaled_motion_mm(Position { x: 0.0, y: 0.0 }, TICK_DURATION).await;
855 assert_eq!(scaled, Position::zero())
856 }
857
858 #[fuchsia::test(allow_stalls = false)]
859 async fn zero_duration_does_not_crash() {
860 get_scaled_motion_mm(
861 Position { x: 1.0 / COUNTS_PER_MM, y: 0.0 },
862 zx::MonotonicDuration::from_millis(0),
863 )
864 .await;
865 }
866 }
867
868 mod scroll_scaling_tick {
869 use super::*;
870 use test_case::test_case;
871
872 #[test_case(mouse_binding::MouseEvent {
873 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
874 millimeters: Position::zero(),
875 }),
876 wheel_delta_v: Some(mouse_binding::WheelDelta {
877 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
878 physical_pixel: None,
879 }),
880 wheel_delta_h: None,
881 phase: mouse_binding::MousePhase::Wheel,
882 affected_buttons: hashset! {},
883 pressed_buttons: hashset! {},
884 is_precision_scroll: None,
885 wake_lease: None.into(),
886 } => (Some(PIXELS_PER_TICK), None); "v")]
887 #[test_case(mouse_binding::MouseEvent {
888 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
889 millimeters: Position::zero(),
890 }),
891 wheel_delta_v: None,
892 wheel_delta_h: Some(mouse_binding::WheelDelta {
893 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
894 physical_pixel: None,
895 }),
896 phase: mouse_binding::MousePhase::Wheel,
897 affected_buttons: hashset! {},
898 pressed_buttons: hashset! {},
899 is_precision_scroll: None,
900 wake_lease: None.into(),
901 } => (None, Some(PIXELS_PER_TICK)); "h")]
902 #[fuchsia::test(allow_stalls = false)]
903 async fn scaled(event: mouse_binding::MouseEvent) -> (Option<f32>, Option<f32>) {
904 let inspector = fuchsia_inspect::Inspector::default();
905 let test_node = inspector.root().create_child("test_node");
906 let handler =
907 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
908 let unhandled_event = make_unhandled_input_event(event);
909
910 let events = handler.clone().handle_unhandled_input_event(unhandled_event).await;
911 assert_matches!(
912 events.as_slice(),
913 [input_device::InputEvent {
914 device_event: input_device::InputDeviceEvent::Mouse(
915 mouse_binding::MouseEvent { .. }
916 ),
917 ..
918 }]
919 );
920 if let input_device::InputEvent {
921 device_event:
922 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
923 wheel_delta_v,
924 wheel_delta_h,
925 ..
926 }),
927 ..
928 } = events[0].clone()
929 {
930 match (wheel_delta_v, wheel_delta_h) {
931 (None, None) => return (None, None),
932 (None, Some(delta_h)) => return (None, delta_h.physical_pixel),
933 (Some(delta_v), None) => return (delta_v.physical_pixel, None),
934 (Some(delta_v), Some(delta_h)) => {
935 return (delta_v.physical_pixel, delta_h.physical_pixel);
936 }
937 }
938 } else {
939 unreachable!();
940 }
941 }
942 }
943
944 mod scroll_scaling_mm {
945 use super::*;
946 use pretty_assertions::assert_eq;
947
948 async fn get_scaled_scroll_mm(
949 wheel_delta_v_mm: Option<f32>,
950 wheel_delta_h_mm: Option<f32>,
951 duration: zx::MonotonicDuration,
952 ) -> (Option<mouse_binding::WheelDelta>, Option<mouse_binding::WheelDelta>) {
953 let inspector = fuchsia_inspect::Inspector::default();
954 let test_node = inspector.root().create_child("test_node");
955 let handler =
956 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
957
958 let input_event = input_device::UnhandledInputEvent {
960 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
961 location: mouse_binding::MouseLocation::Relative(Default::default()),
962 wheel_delta_v: Some(mouse_binding::WheelDelta {
963 raw_data: mouse_binding::RawWheelDelta::Millimeters(1.0),
964 physical_pixel: None,
965 }),
966 wheel_delta_h: None,
967 phase: mouse_binding::MousePhase::Wheel,
968 affected_buttons: hashset! {},
969 pressed_buttons: hashset! {},
970 is_precision_scroll: None,
971 wake_lease: None.into(),
972 }),
973 device_descriptor: DEVICE_DESCRIPTOR.clone(),
974 event_time: zx::MonotonicInstant::from_nanos(0),
975 trace_id: None,
976 };
977 handler.clone().handle_unhandled_input_event(input_event).await;
978
979 let input_event = input_device::UnhandledInputEvent {
981 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
982 location: mouse_binding::MouseLocation::Relative(Default::default()),
983 wheel_delta_v: match wheel_delta_v_mm {
984 None => None,
985 Some(delta) => Some(mouse_binding::WheelDelta {
986 raw_data: mouse_binding::RawWheelDelta::Millimeters(delta),
987 physical_pixel: None,
988 }),
989 },
990 wheel_delta_h: match wheel_delta_h_mm {
991 None => None,
992 Some(delta) => Some(mouse_binding::WheelDelta {
993 raw_data: mouse_binding::RawWheelDelta::Millimeters(delta),
994 physical_pixel: None,
995 }),
996 },
997 phase: mouse_binding::MousePhase::Wheel,
998 affected_buttons: hashset! {},
999 pressed_buttons: hashset! {},
1000 is_precision_scroll: None,
1001 wake_lease: None.into(),
1002 }),
1003 device_descriptor: DEVICE_DESCRIPTOR.clone(),
1004 event_time: zx::MonotonicInstant::from_nanos(duration.into_nanos()),
1005 trace_id: None,
1006 };
1007 let transformed_events =
1008 handler.clone().handle_unhandled_input_event(input_event).await;
1009
1010 assert_eq!(transformed_events.len(), 1);
1011
1012 if let input_device::InputEvent {
1013 device_event:
1014 input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1015 wheel_delta_v: delta_v,
1016 wheel_delta_h: delta_h,
1017 ..
1018 }),
1019 ..
1020 } = transformed_events[0].clone()
1021 {
1022 return (delta_v, delta_h);
1023 } else {
1024 unreachable!()
1025 }
1026 }
1027
1028 fn velocity_to_mm(velocity_mm_per_sec: f32, duration: zx::MonotonicDuration) -> f32 {
1029 velocity_mm_per_sec * (duration.into_nanos() as f32 / 1E9)
1030 }
1031
1032 #[fuchsia::test(allow_stalls = false)]
1033 async fn low_speed_horizontal_scroll_scales_linearly() {
1034 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1035 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
1036 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
1037 assert_lt!(
1038 MOTION_B_MM,
1039 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1040 );
1041
1042 let (_, scaled_a_h) =
1043 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1044
1045 let (_, scaled_b_h) =
1046 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1047
1048 match (scaled_a_h, scaled_b_h) {
1049 (Some(a_h), Some(b_h)) => {
1050 assert_ne!(a_h.physical_pixel, None);
1051 assert_ne!(b_h.physical_pixel, None);
1052 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1053 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1054 assert_near!(
1055 b_h.physical_pixel.unwrap() / a_h.physical_pixel.unwrap(),
1056 2.0,
1057 SCALE_EPSILON
1058 );
1059 }
1060 _ => {
1061 panic!("wheel delta is none");
1062 }
1063 }
1064 }
1065
1066 #[fuchsia::test(allow_stalls = false)]
1067 async fn low_speed_vertical_scroll_scales_linearly() {
1068 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1069 const MOTION_A_MM: f32 = 1.0 / COUNTS_PER_MM;
1070 const MOTION_B_MM: f32 = 2.0 / COUNTS_PER_MM;
1071 assert_lt!(
1072 MOTION_B_MM,
1073 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1074 );
1075
1076 let (scaled_a_v, _) =
1077 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1078
1079 let (scaled_b_v, _) =
1080 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1081
1082 match (scaled_a_v, scaled_b_v) {
1083 (Some(a_v), Some(b_v)) => {
1084 assert_ne!(a_v.physical_pixel, None);
1085 assert_ne!(b_v.physical_pixel, None);
1086 assert_near!(
1087 b_v.physical_pixel.unwrap() / a_v.physical_pixel.unwrap(),
1088 2.0,
1089 SCALE_EPSILON
1090 );
1091 }
1092 _ => {
1093 panic!("wheel delta is none");
1094 }
1095 }
1096 }
1097
1098 #[fuchsia::test(allow_stalls = false)]
1099 async fn medium_speed_horizontal_scroll_scales_quadratically() {
1100 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1101 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
1102 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
1103 assert_gt!(
1104 MOTION_A_MM,
1105 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1106 );
1107 assert_lt!(
1108 MOTION_B_MM,
1109 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1110 );
1111
1112 let (_, scaled_a_h) =
1113 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1114
1115 let (_, scaled_b_h) =
1116 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1117
1118 match (scaled_a_h, scaled_b_h) {
1119 (Some(a_h), Some(b_h)) => {
1120 assert_ne!(a_h.physical_pixel, None);
1121 assert_ne!(b_h.physical_pixel, None);
1122 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1123 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1124 assert_near!(
1125 b_h.physical_pixel.unwrap() / a_h.physical_pixel.unwrap(),
1126 4.0,
1127 SCALE_EPSILON
1128 );
1129 }
1130 _ => {
1131 panic!("wheel delta is none");
1132 }
1133 }
1134 }
1135
1136 #[fuchsia::test(allow_stalls = false)]
1137 async fn medium_speed_vertical_scroll_scales_quadratically() {
1138 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1139 const MOTION_A_MM: f32 = 7.0 / COUNTS_PER_MM;
1140 const MOTION_B_MM: f32 = 14.0 / COUNTS_PER_MM;
1141 assert_gt!(
1142 MOTION_A_MM,
1143 velocity_to_mm(MEDIUM_SPEED_RANGE_BEGIN_MM_PER_SEC, TICK_DURATION)
1144 );
1145 assert_lt!(
1146 MOTION_B_MM,
1147 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1148 );
1149
1150 let (scaled_a_v, _) =
1151 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1152
1153 let (scaled_b_v, _) =
1154 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1155
1156 match (scaled_a_v, scaled_b_v) {
1157 (Some(a_v), Some(b_v)) => {
1158 assert_ne!(a_v.physical_pixel, None);
1159 assert_ne!(b_v.physical_pixel, None);
1160 assert_near!(
1161 b_v.physical_pixel.unwrap() / a_v.physical_pixel.unwrap(),
1162 4.0,
1163 SCALE_EPSILON
1164 );
1165 }
1166 _ => {
1167 panic!("wheel delta is none");
1168 }
1169 }
1170 }
1171
1172 #[fuchsia::test(allow_stalls = false)]
1173 async fn high_speed_horizontal_scroll_scaling_is_inreasing() {
1174 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1175 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
1176 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
1177 assert_gt!(
1178 MOTION_A_MM,
1179 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1180 );
1181
1182 let (_, scaled_a_h) =
1183 get_scaled_scroll_mm(None, Some(MOTION_A_MM), TICK_DURATION).await;
1184
1185 let (_, scaled_b_h) =
1186 get_scaled_scroll_mm(None, Some(MOTION_B_MM), TICK_DURATION).await;
1187
1188 match (scaled_a_h, scaled_b_h) {
1189 (Some(a_h), Some(b_h)) => {
1190 assert_ne!(a_h.physical_pixel, None);
1191 assert_ne!(b_h.physical_pixel, None);
1192 assert_ne!(a_h.physical_pixel.unwrap(), 0.0);
1193 assert_ne!(b_h.physical_pixel.unwrap(), 0.0);
1194 assert_gt!(b_h.physical_pixel.unwrap(), a_h.physical_pixel.unwrap());
1195 }
1196 _ => {
1197 panic!("wheel delta is none");
1198 }
1199 }
1200 }
1201
1202 #[fuchsia::test(allow_stalls = false)]
1203 async fn high_speed_vertical_scroll_scaling_is_inreasing() {
1204 const TICK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(8);
1205 const MOTION_A_MM: f32 = 16.0 / COUNTS_PER_MM;
1206 const MOTION_B_MM: f32 = 20.0 / COUNTS_PER_MM;
1207 assert_gt!(
1208 MOTION_A_MM,
1209 velocity_to_mm(MEDIUM_SPEED_RANGE_END_MM_PER_SEC, TICK_DURATION)
1210 );
1211
1212 let (scaled_a_v, _) =
1213 get_scaled_scroll_mm(Some(MOTION_A_MM), None, TICK_DURATION).await;
1214
1215 let (scaled_b_v, _) =
1216 get_scaled_scroll_mm(Some(MOTION_B_MM), None, TICK_DURATION).await;
1217
1218 match (scaled_a_v, scaled_b_v) {
1219 (Some(a_v), Some(b_v)) => {
1220 assert_ne!(a_v.physical_pixel, None);
1221 assert_ne!(b_v.physical_pixel, None);
1222 assert_gt!(b_v.physical_pixel.unwrap(), a_v.physical_pixel.unwrap());
1223 }
1224 _ => {
1225 panic!("wheel delta is none");
1226 }
1227 }
1228 }
1229 }
1230
1231 mod metadata_preservation {
1232 use super::*;
1233 use test_case::test_case;
1234
1235 #[test_case(mouse_binding::MouseEvent {
1236 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1237 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
1238 }),
1239 wheel_delta_v: None,
1240 wheel_delta_h: None,
1241 phase: mouse_binding::MousePhase::Move,
1242 affected_buttons: hashset! {},
1243 pressed_buttons: hashset! {},
1244 is_precision_scroll: None,
1245 wake_lease: None.into(),
1246 }; "move event")]
1247 #[test_case(mouse_binding::MouseEvent {
1248 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1249 millimeters: Position::zero(),
1250 }),
1251 wheel_delta_v: Some(mouse_binding::WheelDelta {
1252 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1253 physical_pixel: None,
1254 }),
1255 wheel_delta_h: None,
1256 phase: mouse_binding::MousePhase::Wheel,
1257 affected_buttons: hashset! {},
1258 pressed_buttons: hashset! {},
1259 is_precision_scroll: None,
1260 wake_lease: None.into(),
1261 }; "wheel event")]
1262 #[fuchsia::test(allow_stalls = false)]
1263 async fn does_not_consume_event(event: mouse_binding::MouseEvent) {
1264 let inspector = fuchsia_inspect::Inspector::default();
1265 let test_node = inspector.root().create_child("test_node");
1266 let handler =
1267 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1268 let input_event = make_unhandled_input_event(event);
1269 assert_matches!(
1270 handler.clone().handle_unhandled_input_event(input_event).await.as_slice(),
1271 [input_device::InputEvent { handled: input_device::Handled::No, .. }]
1272 );
1273 }
1274
1275 #[test_case(mouse_binding::MouseEvent {
1278 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1279 millimeters: Position { x: 1.5 / COUNTS_PER_MM, y: 4.5 / COUNTS_PER_MM },
1280 }),
1281 wheel_delta_v: None,
1282 wheel_delta_h: None,
1283 phase: mouse_binding::MousePhase::Move,
1284 affected_buttons: hashset! {},
1285 pressed_buttons: hashset! {},
1286 is_precision_scroll: None,
1287 wake_lease: None.into(),
1288 }; "move event")]
1289 #[test_case(mouse_binding::MouseEvent {
1290 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1291 millimeters: Position::zero(),
1292 }),
1293 wheel_delta_v: Some(mouse_binding::WheelDelta {
1294 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1295 physical_pixel: None,
1296 }),
1297 wheel_delta_h: None,
1298 phase: mouse_binding::MousePhase::Wheel,
1299 affected_buttons: hashset! {},
1300 pressed_buttons: hashset! {},
1301 is_precision_scroll: None,
1302 wake_lease: None.into(),
1303 }; "wheel event")]
1304 #[fuchsia::test(allow_stalls = false)]
1305 async fn preserves_event_time(event: mouse_binding::MouseEvent) {
1306 let inspector = fuchsia_inspect::Inspector::default();
1307 let test_node = inspector.root().create_child("test_node");
1308 let handler =
1309 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1310 let mut input_event = make_unhandled_input_event(event);
1311 const EVENT_TIME: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(42);
1312 input_event.event_time = EVENT_TIME;
1313
1314 let events = handler.clone().handle_unhandled_input_event(input_event).await;
1315 assert_eq!(events.len(), 1, "{events:?} should be length 1");
1316 assert_eq!(events[0].event_time, EVENT_TIME);
1317 }
1318
1319 #[test_case(
1320 mouse_binding::MouseEvent {
1321 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1322 millimeters: Position::zero(),
1323 }),
1324 wheel_delta_v: Some(mouse_binding::WheelDelta {
1325 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1326 physical_pixel: Some(1.0),
1327 }),
1328 wheel_delta_h: None,
1329 phase: mouse_binding::MousePhase::Wheel,
1330 affected_buttons: hashset! {},
1331 pressed_buttons: hashset! {},
1332 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
1333 wake_lease: None.into(),
1334 } => matches input_device::InputEvent {
1335 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1336 is_precision_scroll: Some(mouse_binding::PrecisionScroll::No),
1337 ..
1338 }),
1339 ..
1340 }; "no")]
1341 #[test_case(
1342 mouse_binding::MouseEvent {
1343 location: mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation {
1344 millimeters: Position::zero(),
1345 }),
1346 wheel_delta_v: Some(mouse_binding::WheelDelta {
1347 raw_data: mouse_binding::RawWheelDelta::Ticks(1),
1348 physical_pixel: Some(1.0),
1349 }),
1350 wheel_delta_h: None,
1351 phase: mouse_binding::MousePhase::Wheel,
1352 affected_buttons: hashset! {},
1353 pressed_buttons: hashset! {},
1354 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
1355 wake_lease: None.into(),
1356 } => matches input_device::InputEvent {
1357 device_event: input_device::InputDeviceEvent::Mouse(mouse_binding::MouseEvent {
1358 is_precision_scroll: Some(mouse_binding::PrecisionScroll::Yes),
1359 ..
1360 }),
1361 ..
1362 }; "yes")]
1363 #[fuchsia::test(allow_stalls = false)]
1364 async fn preserves_is_precision_scroll(
1365 event: mouse_binding::MouseEvent,
1366 ) -> input_device::InputEvent {
1367 let inspector = fuchsia_inspect::Inspector::default();
1368 let test_node = inspector.root().create_child("test_node");
1369 let handler =
1370 PointerSensorScaleHandler::new(&test_node, metrics::MetricsLogger::default());
1371 let input_event = make_unhandled_input_event(event);
1372
1373 handler.clone().handle_unhandled_input_event(input_event).await[0].clone()
1374 }
1375 }
1376}