pub const PIXELS_PER_NOTCH: f64 = 40.0;
pub const NOTCHES_PER_PAGE: f64 = 8.0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WheelDeltaMode {
Pixel,
Line,
Page,
}
impl WheelDeltaMode {
pub fn from_dom(delta_mode: u32) -> WheelDeltaMode {
match delta_mode {
1 => WheelDeltaMode::Line,
2 => WheelDeltaMode::Page,
_ => WheelDeltaMode::Pixel,
}
}
}
pub fn to_notches(delta: f64, mode: WheelDeltaMode) -> f64 {
if !delta.is_finite() {
return 0.0;
}
match mode {
WheelDeltaMode::Pixel => delta / PIXELS_PER_NOTCH,
WheelDeltaMode::Line => delta,
WheelDeltaMode::Page => delta * NOTCHES_PER_PAGE,
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct WheelNormalizer {
acc_x: f64,
acc_y: f64,
}
impl WheelNormalizer {
pub fn new() -> WheelNormalizer {
WheelNormalizer::default()
}
pub fn normalize(&mut self, delta_x: f64, delta_y: f64, mode: WheelDeltaMode) -> (f64, f64) {
let x = accumulate(&mut self.acc_x, to_notches(delta_x, mode));
let y = accumulate(&mut self.acc_y, to_notches(delta_y, mode));
(x, y)
}
pub fn reset(&mut self) {
self.acc_x = 0.0;
self.acc_y = 0.0;
}
}
const NOTCH_EPSILON: f64 = 1e-9;
fn accumulate(acc: &mut f64, notches: f64) -> f64 {
if notches == 0.0 {
return 0.0;
}
if *acc != 0.0 && acc.signum() != notches.signum() {
*acc = 0.0;
}
*acc += notches;
let whole = (acc.abs() + NOTCH_EPSILON).trunc().copysign(*acc);
*acc -= whole;
whole
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_notch_in_each_mode() {
let mut n = WheelNormalizer::new();
assert_eq!(n.normalize(0.0, 100.0, WheelDeltaMode::Pixel).1, 2.0);
assert_eq!(n.normalize(0.0, 3.0, WheelDeltaMode::Line).1, 3.0);
assert_eq!(
n.normalize(0.0, 1.0, WheelDeltaMode::Page).1,
NOTCHES_PER_PAGE
);
}
#[test]
fn sub_notch_deltas_accumulate_instead_of_jumping_or_vanishing() {
let mut n = WheelNormalizer::new();
for _ in 0..9 {
assert_eq!(
n.normalize(0.0, 4.0, WheelDeltaMode::Pixel).1,
0.0,
"a fraction of a notch must not reach a sign-reading consumer"
);
}
assert_eq!(n.normalize(0.0, 4.0, WheelDeltaMode::Pixel).1, 1.0);
let mut fine = WheelNormalizer::new();
let total: f64 = (0..400)
.map(|_| fine.normalize(0.0, 1.0, WheelDeltaMode::Pixel).1)
.sum();
assert_eq!(total, 10.0);
let mut coarse = WheelNormalizer::new();
assert_eq!(coarse.normalize(0.0, 400.0, WheelDeltaMode::Pixel).1, 10.0);
}
#[test]
fn a_direction_change_drops_the_partial_notch() {
let mut n = WheelNormalizer::new();
assert_eq!(n.normalize(0.0, 30.0, WheelDeltaMode::Pixel).1, 0.0);
assert_eq!(n.normalize(0.0, -30.0, WheelDeltaMode::Pixel).1, 0.0);
assert_eq!(n.normalize(0.0, -10.0, WheelDeltaMode::Pixel).1, -1.0);
}
#[test]
fn the_axes_accumulate_independently() {
let mut n = WheelNormalizer::new();
assert_eq!(n.normalize(30.0, 20.0, WheelDeltaMode::Pixel), (0.0, 0.0));
assert_eq!(n.normalize(10.0, 0.0, WheelDeltaMode::Pixel), (1.0, 0.0));
assert_eq!(n.normalize(0.0, 20.0, WheelDeltaMode::Pixel), (0.0, 1.0));
}
#[test]
fn non_finite_deltas_are_ignored() {
let mut n = WheelNormalizer::new();
assert_eq!(
n.normalize(f64::NAN, f64::INFINITY, WheelDeltaMode::Pixel),
(0.0, 0.0)
);
assert_eq!(n.normalize(0.0, 40.0, WheelDeltaMode::Pixel).1, 1.0);
}
#[test]
fn reset_forgets_partial_travel() {
let mut n = WheelNormalizer::new();
n.normalize(0.0, 30.0, WheelDeltaMode::Pixel);
n.reset();
assert_eq!(n.normalize(0.0, 30.0, WheelDeltaMode::Pixel).1, 0.0);
}
#[test]
fn dom_delta_modes_map_across() {
assert_eq!(WheelDeltaMode::from_dom(0), WheelDeltaMode::Pixel);
assert_eq!(WheelDeltaMode::from_dom(1), WheelDeltaMode::Line);
assert_eq!(WheelDeltaMode::from_dom(2), WheelDeltaMode::Page);
assert_eq!(WheelDeltaMode::from_dom(7), WheelDeltaMode::Pixel);
}
}