cosmic/
scroll.rs

1use iced::Task;
2use iced::mouse::ScrollDelta;
3use std::time::{Duration, Instant};
4
5// Number of scroll pixels before changing workspace
6const SCROLL_PIXELS: f32 = 24.0;
7
8// Timeout for scroll accumulation; older partial scroll is dropped
9const SCROLL_TIMEOUT: Duration = Duration::from_millis(100);
10
11/// A scroll delta with discrete integer deltas
12#[derive(Debug, Default, Clone, Copy)]
13pub struct DiscreteScrollDelta {
14    pub x: isize,
15    pub y: isize,
16}
17
18/// Helper for accumulating and converting pixel/line scrolls into and integer
19/// delta between discrete options.
20#[derive(Debug, Default)]
21pub struct DiscreteScrollState {
22    x: Scroll,
23    y: Scroll,
24    rate_limit: Option<Duration>,
25}
26
27impl DiscreteScrollState {
28    /// Set a rate limit. If set, a call to `update()` will only not produce
29    /// values other than 1, -1, or 0 and a non-zero return value will not
30    /// occur more frequently than this duration.
31    pub fn rate_limit(mut self, rate_limit: Option<Duration>) -> Self {
32        self.rate_limit = rate_limit;
33        self
34    }
35
36    /// Reset, clearing any acculuated scroll events that haven't been
37    /// converted to discrete events yet.
38    pub fn reset(&mut self) {
39        self.x.reset();
40        self.y.reset();
41    }
42
43    /// Accumulate delta with a timer
44    pub fn update(&mut self, delta: ScrollDelta) -> DiscreteScrollDelta {
45        let (x, y) = match delta {
46            ScrollDelta::Pixels { x, y } => (x / SCROLL_PIXELS, y / SCROLL_PIXELS),
47            ScrollDelta::Lines { x, y } => (x, y),
48        };
49
50        DiscreteScrollDelta {
51            x: self.x.update(x, self.rate_limit),
52            y: self.y.update(y, self.rate_limit),
53        }
54    }
55}
56
57/// Scroll over a single axis
58#[derive(Debug, Default)]
59struct Scroll {
60    scroll: Option<(f32, Instant)>,
61    last_discrete: Option<Instant>,
62}
63
64impl Scroll {
65    fn reset(&mut self) {
66        *self = Default::default();
67    }
68
69    fn update(&mut self, delta: f32, rate_limit: Option<Duration>) -> isize {
70        if delta == 0. {
71            // If delta is 0, scroll is on other axis; clear accumulated scroll
72            self.reset();
73            0
74        } else {
75            let previous_scroll = if let Some((scroll, last_scroll_time)) = self.scroll {
76                if last_scroll_time.elapsed() > SCROLL_TIMEOUT {
77                    0.
78                } else {
79                    scroll
80                }
81            } else {
82                0.
83            };
84
85            let scroll = previous_scroll + delta;
86
87            if self
88                .last_discrete
89                .is_some_and(|time| time.elapsed() < rate_limit.unwrap_or(Duration::ZERO))
90            {
91                // If rate limit is hit, continute accumulating, but don't return
92                // a discrete event yet.
93                self.scroll = Some((scroll, Instant::now()));
94                0
95            } else {
96                // Return integer part of scroll, and keep remainder
97                self.scroll = Some((scroll.fract(), Instant::now()));
98                let mut discrete = scroll.trunc() as isize;
99                if discrete != 0 {
100                    self.last_discrete = Some(Instant::now());
101                }
102                if rate_limit.is_some() {
103                    // If we are rate limiting, don't return multiple discrete events
104                    // at once; drop extras.
105                    discrete.signum()
106                } else {
107                    discrete
108                }
109            }
110        }
111    }
112}