use a7n_proto::v1::ScrollUnit;
pub(super) const SCROLL_PIXELS_PER_LINE: f64 = 15.0;
pub(super) const MAX_LINES_PER_EVENT: f64 = 100.0;
#[allow(clippy::cast_possible_truncation, reason = "clamped to the i32 range")]
fn take_whole(value: &mut f64) -> i32 {
let whole = value
.trunc()
.clamp(f64::from(i32::MIN), f64::from(i32::MAX));
*value -= whole;
whole as i32
}
#[derive(Debug, Default)]
pub(super) struct ScrollAccumulator {
x: f64,
y: f64,
}
impl ScrollAccumulator {
pub(super) fn push(&mut self, dx: f32, dy: f32, unit: ScrollUnit) -> (i32, i32) {
let pixels_per_unit = match unit {
ScrollUnit::Line => SCROLL_PIXELS_PER_LINE,
ScrollUnit::Pixel => 1.0,
ScrollUnit::Unspecified => return (0, 0),
};
self.x += pixels(dx, pixels_per_unit);
self.y += pixels(dy, pixels_per_unit);
(take_notches(&mut self.x), take_notches(&mut self.y))
}
}
fn pixels(delta: f32, pixels_per_unit: f64) -> f64 {
if !delta.is_finite() {
return 0.0;
}
let limit = MAX_LINES_PER_EVENT * SCROLL_PIXELS_PER_LINE;
(f64::from(delta) * pixels_per_unit).clamp(-limit, limit)
}
fn take_notches(pixels: &mut f64) -> i32 {
let mut notches = (*pixels / SCROLL_PIXELS_PER_LINE).trunc();
*pixels -= notches * SCROLL_PIXELS_PER_LINE;
take_whole(&mut notches)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_pixel_scroll_below_one_notch_produces_nothing() {
let mut scroll = ScrollAccumulator::default();
let notches = scroll.push(0.0, 14.0, ScrollUnit::Pixel);
assert_eq!(notches, (0, 0));
}
#[test]
fn push_pixel_scroll_crossing_threshold_emits_one_notch_and_keeps_remainder() {
let mut scroll = ScrollAccumulator::default();
scroll.push(0.0, 10.0, ScrollUnit::Pixel);
let crossed = scroll.push(0.0, 10.0, ScrollUnit::Pixel);
let remainder = scroll.push(0.0, 10.0, ScrollUnit::Pixel);
assert_eq!(crossed, (0, 1));
assert_eq!(remainder, (0, 1));
}
#[test]
fn push_line_scroll_hostile_values_are_bounded() {
let mut scroll = ScrollAccumulator::default();
let huge = scroll.push(f32::NAN, 1e30, ScrollUnit::Line);
assert_eq!(huge, (0, 100));
}
}