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