pub fn calculate_sensitivity(cols: usize, rows: usize) -> f32 {
let reference_cells = 80.0 * 25.0; let current_cells = (cols * rows) as f32;
let base_sensitivity = 0.35;
let scale = (current_cells / reference_cells).sqrt();
(base_sensitivity * scale).clamp(0.2, 1.0)
}
pub struct CursorTracker {
pub x: usize,
pub y: usize,
max_x: usize,
max_y: usize,
sensitivity: f32,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
invert_x: bool,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
invert_y: bool,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
accum_x: f32,
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
accum_y: f32,
}
impl CursorTracker {
pub fn new(max_x: usize, max_y: usize, invert_x: bool, invert_y: bool) -> Self {
let sensitivity = calculate_sensitivity(max_x, max_y);
Self {
x: max_x / 2,
y: max_y / 2,
max_x,
max_y,
sensitivity,
invert_x,
invert_y,
accum_x: 0.0,
accum_y: 0.0,
}
}
#[cfg_attr(
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)),
allow(dead_code)
)]
pub fn update(&mut self, dx: i8, dy: i8) {
let dx = if self.invert_x { -dx } else { dx };
let dy = if self.invert_y { dy } else { -dy };
self.accum_x += dx as f32 * self.sensitivity;
self.accum_y += dy as f32 * self.sensitivity;
let move_x = self.accum_x.trunc() as i32;
let move_y = self.accum_y.trunc() as i32;
self.accum_x -= move_x as f32;
self.accum_y -= move_y as f32;
let new_x = (self.x as i32 + move_x).max(0).min(self.max_x as i32 - 1);
let new_y = (self.y as i32 + move_y).max(0).min(self.max_y as i32 - 1);
self.x = new_x as usize;
self.y = new_y as usize;
}
#[allow(dead_code)]
pub fn position(&self) -> (usize, usize) {
(self.x, self.y)
}
pub fn position_u16(&self) -> (u16, u16) {
(self.x as u16, self.y as u16)
}
#[allow(dead_code)]
pub fn set_bounds(&mut self, max_x: usize, max_y: usize) {
self.max_x = max_x;
self.max_y = max_y;
self.x = self.x.min(max_x.saturating_sub(1));
self.y = self.y.min(max_y.saturating_sub(1));
}
#[allow(dead_code)]
pub fn set_position(&mut self, x: usize, y: usize) {
self.x = x.min(self.max_x.saturating_sub(1));
self.y = y.min(self.max_y.saturating_sub(1));
}
pub fn set_sensitivity(&mut self, sensitivity: f32) {
self.sensitivity = sensitivity.clamp(0.1, 5.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sensitivity_calculation() {
let sens = calculate_sensitivity(80, 25);
assert!((sens - 0.35).abs() < 0.01);
let sens_large = calculate_sensitivity(160, 50);
assert!(sens_large > sens);
let sens_small = calculate_sensitivity(40, 12);
assert!(sens_small >= 0.2);
}
#[test]
fn test_cursor_tracker_movement() {
let mut cursor = CursorTracker::new(80, 25, false, false);
assert_eq!(cursor.x, 40);
assert_eq!(cursor.y, 12);
cursor.update(10, 0);
assert!(cursor.x >= 40);
}
#[test]
fn test_cursor_tracker_bounds() {
let mut cursor = CursorTracker::new(80, 25, false, false);
for _ in 0..1000 {
cursor.update(127, 0);
}
assert!(cursor.x < 80);
for _ in 0..1000 {
cursor.update(-128, 0);
}
assert_eq!(cursor.x, 0);
}
}