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