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