1use crate::{Dp, ScrollDeltaUnit, ScrollEventContent, ScrollEventSource};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct PlatformScrollConfig {
12 pub wheel_line_step: Dp,
14}
15
16impl PlatformScrollConfig {
17 pub fn current() -> Self {
19 let step = if cfg!(target_family = "wasm") {
22 16.0
23 } else if cfg!(target_os = "windows") {
24 100.0
25 } else if cfg!(target_os = "linux") {
26 53.0
27 } else {
28 40.0
29 };
30 Self {
31 wheel_line_step: Dp(step),
32 }
33 }
34
35 pub fn normalize_scroll_delta(
37 self,
38 delta_x: f32,
39 delta_y: f32,
40 unit: ScrollDeltaUnit,
41 source: ScrollEventSource,
42 ) -> (f32, f32) {
43 let multiplier = match (source, unit) {
44 (ScrollEventSource::Wheel, ScrollDeltaUnit::Line) => {
45 self.wheel_line_step.to_pixels_f64() as f32
46 }
47 _ => 1.0,
48 };
49 (delta_x * multiplier, delta_y * multiplier)
50 }
51}
52
53impl Default for PlatformScrollConfig {
54 fn default() -> Self {
55 Self::current()
56 }
57}
58
59pub fn platform_scroll_config() -> PlatformScrollConfig {
61 PlatformScrollConfig::current()
62}
63
64pub fn normalize_platform_scroll_delta(
66 delta_x: f32,
67 delta_y: f32,
68 unit: ScrollDeltaUnit,
69 source: ScrollEventSource,
70) -> (f32, f32) {
71 platform_scroll_config().normalize_scroll_delta(delta_x, delta_y, unit, source)
72}
73
74pub(crate) fn is_discrete_wheel(event: &ScrollEventContent) -> bool {
75 event.source == ScrollEventSource::Wheel
76 && (event.unit == ScrollDeltaUnit::Line || is_wheel_step(event.delta_x, event.delta_y))
77}
78
79pub(crate) fn is_discrete_platform_wheel(event: &ScrollEventContent) -> bool {
80 match event.unit {
81 ScrollDeltaUnit::Line if cfg!(any(target_family = "wasm", target_os = "macos")) => true,
83 ScrollDeltaUnit::Line => {
86 let (x, y) = event.pixel_delta();
87 is_wheel_step(x, y)
88 }
89 ScrollDeltaUnit::Pixel => cfg!(target_family = "wasm") && is_discrete_wheel(event),
92 }
93}
94
95fn is_wheel_step(x: f32, y: f32) -> bool {
96 let threshold = Dp(40.0).to_pixels_f64() as f32;
97 x.abs().max(y.abs()) >= threshold
98}
99
100#[cfg(test)]
101mod tests {
102 use super::{PlatformScrollConfig, is_discrete_platform_wheel, is_discrete_wheel};
103 use crate::{Dp, ScrollDeltaUnit, ScrollEventContent, ScrollEventSource};
104
105 #[test]
106 fn wheel_line_delta_is_scaled_by_platform_step() {
107 let _runtime_session = crate::RuntimeSession::new();
108 let _runtime_scope = _runtime_session.enter();
109 let config = PlatformScrollConfig {
110 wheel_line_step: Dp(24.0),
111 };
112 let (x, y) = config.normalize_scroll_delta(
113 1.0,
114 -2.0,
115 ScrollDeltaUnit::Line,
116 ScrollEventSource::Wheel,
117 );
118 assert_eq!((x, y), (24.0, -48.0));
119 }
120
121 #[test]
122 fn wheel_classification_uses_logical_distance_and_native_precision() {
123 let session = crate::RuntimeSession::new();
124 session.run(|| {
125 crate::dp::set_scale_factor(2.0);
126 let mut event = ScrollEventContent {
127 delta_x: 0.0,
128 delta_y: -70.0,
129 unit: ScrollDeltaUnit::Pixel,
130 source: ScrollEventSource::Wheel,
131 };
132 assert!(!is_discrete_wheel(&event));
133 event.delta_y = -120.0;
134 assert!(is_discrete_wheel(&event));
135 assert_eq!(
136 is_discrete_platform_wheel(&event),
137 cfg!(target_family = "wasm")
138 );
139 });
140 }
141
142 #[test]
143 fn wheel_pixel_delta_is_left_untouched() {
144 let _runtime_session = crate::RuntimeSession::new();
145 let _runtime_scope = _runtime_session.enter();
146 let config = PlatformScrollConfig {
147 wheel_line_step: Dp(24.0),
148 };
149 let (x, y) = config.normalize_scroll_delta(
150 3.0,
151 -5.0,
152 ScrollDeltaUnit::Pixel,
153 ScrollEventSource::Wheel,
154 );
155 assert_eq!((x, y), (3.0, -5.0));
156 }
157}