use a7n_proto::v1::{ButtonEvent, InputEvent, KeyEvent, ScrollEvent, ScrollUnit, input_event};
use super::buttons::button_from_evdev;
use crate::input::accumulate::MotionAccumulator;
use crate::input::evdev_keymap::hid_from_evdev;
pub(super) const DISCRETE_PER_NOTCH: f32 = 120.0;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) enum Raw {
Motion { dx: f32, dy: f32 },
Key { code: u32, down: bool },
Button { code: u32, down: bool },
ScrollSmooth { dx: f32, dy: f32 },
ScrollDiscrete { dx: i32, dy: i32 },
Frame,
}
#[derive(Debug, PartialEq)]
pub(super) enum Translated {
Motion {
dx: i32,
dy: i32,
},
Input(InputEvent),
}
#[derive(Debug, Default)]
pub(super) struct Translator {
motion: MotionAccumulator,
smooth: Option<(f32, f32)>,
discrete: Option<(i32, i32)>,
}
impl Translator {
pub(super) fn push(&mut self, raw: Raw) -> Option<Translated> {
match raw {
Raw::Motion { dx, dy } => {
let (dx, dy) = self.motion.push(f64::from(dx), f64::from(dy));
(dx != 0 || dy != 0).then_some(Translated::Motion { dx, dy })
}
Raw::Key { code, down } => {
let usage = hid_from_evdev(u16::try_from(code).ok()?)?;
Some(input(input_event::Event::Key(KeyEvent {
usage,
down,
character: String::new(),
})))
}
Raw::Button { code, down } => {
let button = button_from_evdev(code)?;
Some(input(input_event::Event::Button(ButtonEvent {
button: button.into(),
down,
})))
}
Raw::ScrollSmooth { dx, dy } => {
let (x, y) = self.smooth.unwrap_or_default();
self.smooth = Some((x + dx, y + dy));
None
}
Raw::ScrollDiscrete { dx, dy } => {
let (x, y) = self.discrete.unwrap_or_default();
self.discrete = Some((x.saturating_add(dx), y.saturating_add(dy)));
None
}
Raw::Frame => self.finish_scroll_frame(),
}
}
#[allow(clippy::cast_precision_loss, reason = "wheel counts are small")]
fn finish_scroll_frame(&mut self) -> Option<Translated> {
let discrete = self.discrete.take();
let smooth = self.smooth.take();
let scroll = match (discrete, smooth) {
(Some((dx, dy)), _) => ScrollEvent {
dx: dx as f32 / DISCRETE_PER_NOTCH,
dy: dy as f32 / DISCRETE_PER_NOTCH,
unit: ScrollUnit::Line.into(),
},
(None, Some((dx, dy))) => ScrollEvent {
dx,
dy,
unit: ScrollUnit::Pixel.into(),
},
(None, None) => return None,
};
Some(input(input_event::Event::Scroll(scroll)))
}
}
fn input(event: input_event::Event) -> Translated {
Translated::Input(InputEvent { event: Some(event) })
}
#[cfg(test)]
mod tests {
use super::*;
fn scroll_of(translated: Option<&Translated>) -> Option<ScrollEvent> {
match translated {
Some(Translated::Input(InputEvent {
event: Some(input_event::Event::Scroll(scroll)),
})) => Some(*scroll),
_ => None,
}
}
#[test]
fn push_frame_with_smooth_and_discrete_wheel_reports_lines_once() {
let mut translator = Translator::default();
translator.push(Raw::ScrollSmooth { dx: 0.0, dy: 15.0 });
translator.push(Raw::ScrollDiscrete { dx: 0, dy: 120 });
let scroll = scroll_of(translator.push(Raw::Frame).as_ref());
let scroll = scroll.expect("one scroll event");
assert_eq!(scroll.unit(), ScrollUnit::Line);
assert!((scroll.dy - 1.0).abs() < f32::EPSILON);
}
#[test]
fn push_frame_with_smooth_only_reports_pixels() {
let mut translator = Translator::default();
translator.push(Raw::ScrollSmooth { dx: 2.5, dy: -4.0 });
let scroll = scroll_of(translator.push(Raw::Frame).as_ref());
let scroll = scroll.expect("one scroll event");
assert_eq!(scroll.unit(), ScrollUnit::Pixel);
assert!((scroll.dx - 2.5).abs() < f32::EPSILON);
assert!((scroll.dy + 4.0).abs() < f32::EPSILON);
}
}