Skip to main content

kozan_core/input/
wheel.rs

1//! Wheel (scroll) event types.
2//!
3//! Like Chrome's `WebMouseWheelEvent` — a dedicated struct for scroll input.
4//! Distinguishes between pixel-precise scrolling (trackpad) and line-based
5//! scrolling (mouse wheel) via [`WheelDelta`].
6//!
7//! Chrome equivalent: `third_party/blink/public/common/input/web_mouse_wheel_event.h`
8
9use std::time::Instant;
10
11use super::modifiers::Modifiers;
12
13/// Mouse wheel or trackpad scroll event.
14///
15/// Chrome equivalent: `WebMouseWheelEvent`.
16///
17/// The cursor position is included because scroll events should be
18/// dispatched to the element under the cursor (like Chrome's scroll targeting).
19#[derive(Debug, Clone, Copy)]
20pub struct WheelEvent {
21    /// Cursor X position in physical pixels, relative to view origin.
22    pub x: f64,
23    /// Cursor Y position in physical pixels, relative to view origin.
24    pub y: f64,
25    /// Scroll amount and type.
26    pub delta: WheelDelta,
27    /// Modifier keys and mouse button state at the time of this event.
28    pub modifiers: Modifiers,
29    /// When this event was received from the OS.
30    pub timestamp: Instant,
31}
32
33/// Scroll delta — distinguishes scroll source.
34///
35/// Chrome equivalent: `WebMouseWheelEvent::delta_x/y` +
36/// `has_precise_scrolling_deltas` flag.
37///
38/// Mouse wheels produce line deltas (discrete ticks).
39/// Trackpads produce pixel deltas (smooth, precise).
40/// The distinction matters for scroll behavior — line deltas get multiplied
41/// by a line height, pixel deltas are used directly.
42#[derive(Debug, Clone, Copy, PartialEq)]
43pub enum WheelDelta {
44    /// Mouse wheel: discrete line ticks. Positive = scroll up/left.
45    /// Chrome: `delta_x/y` when `has_precise_scrolling_deltas` is false.
46    Lines(f32, f32),
47
48    /// Trackpad: precise pixel offset. Positive = scroll up/left.
49    /// Chrome: `delta_x/y` when `has_precise_scrolling_deltas` is true.
50    Pixels(f64, f64),
51}
52
53/// Chrome: `kPixelsPerLineStep = 40`. Blink default for mouse wheel.
54const PIXELS_PER_LINE: f64 = 40.0;
55
56impl WheelDelta {
57    /// Raw horizontal component (lines or pixels, unscaled).
58    #[must_use]
59    pub fn dx(&self) -> f64 {
60        match self {
61            WheelDelta::Lines(x, _) => *x as f64,
62            WheelDelta::Pixels(x, _) => *x,
63        }
64    }
65
66    /// Raw vertical component (lines or pixels, unscaled).
67    #[must_use]
68    pub fn dy(&self) -> f64 {
69        match self {
70            WheelDelta::Lines(_, y) => *y as f64,
71            WheelDelta::Pixels(_, y) => *y,
72        }
73    }
74
75    /// Horizontal delta in CSS pixels. Lines are scaled by `PIXELS_PER_LINE`.
76    #[must_use]
77    pub fn px_dx(&self) -> f32 {
78        match self {
79            WheelDelta::Lines(x, _) => (*x as f64 * PIXELS_PER_LINE) as f32,
80            WheelDelta::Pixels(x, _) => *x as f32,
81        }
82    }
83
84    /// Vertical delta in CSS pixels. Lines are scaled by `PIXELS_PER_LINE`.
85    #[must_use]
86    pub fn px_dy(&self) -> f32 {
87        match self {
88            WheelDelta::Lines(_, y) => (*y as f64 * PIXELS_PER_LINE) as f32,
89            WheelDelta::Pixels(_, y) => *y as f32,
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn wheel_delta_lines() {
100        let delta = WheelDelta::Lines(0.0, -3.0);
101        assert_eq!(delta.dx(), 0.0);
102        assert_eq!(delta.dy(), -3.0);
103    }
104
105    #[test]
106    fn wheel_delta_pixels() {
107        let delta = WheelDelta::Pixels(10.5, -20.0);
108        assert_eq!(delta.dx(), 10.5);
109        assert_eq!(delta.dy(), -20.0);
110    }
111
112    #[test]
113    fn wheel_event_carries_position_and_modifiers() {
114        let evt = WheelEvent {
115            x: 300.0,
116            y: 400.0,
117            delta: WheelDelta::Lines(0.0, -1.0),
118            modifiers: Modifiers::EMPTY.with_shift(),
119            timestamp: Instant::now(),
120        };
121        assert!(evt.modifiers.shift());
122        assert_eq!(evt.x, 300.0);
123    }
124}