use crate::crosswords::pos::Column;
use crate::crosswords::pos::Line;
use crate::crosswords::pos::Side;
use crate::event::ClickState;
use rio_backend::crosswords::pos::Pos;
use rio_window::event::ElementState;
use rio_window::event::MouseButton;
use std::time::Instant;
#[derive(Default, Debug)]
pub struct AccumulatedScroll {
pub x: f64,
pub y: f64,
}
#[derive(Debug)]
pub struct Mouse {
pub multiplier: f64,
pub divider: f64,
pub left_button_state: ElementState,
pub middle_button_state: ElementState,
pub right_button_state: ElementState,
pub last_click_timestamp: Instant,
pub last_click_button: MouseButton,
pub click_state: ClickState,
pub accumulated_scroll: AccumulatedScroll,
pub square_side: Side,
pub inside_text_area: bool,
pub x: f64,
pub y: f64,
pub on_border: bool,
pub raw_y: f64,
pub last_cell: Option<Pos>,
}
impl Default for Mouse {
fn default() -> Mouse {
Mouse {
multiplier: 3.0,
divider: 1.0,
last_click_timestamp: Instant::now(),
last_click_button: MouseButton::Left,
left_button_state: ElementState::Released,
middle_button_state: ElementState::Released,
right_button_state: ElementState::Released,
click_state: ClickState::None,
square_side: Side::Left,
inside_text_area: Default::default(),
on_border: false,
accumulated_scroll: AccumulatedScroll::default(),
x: 0.0,
y: 0.0,
raw_y: 0.0,
last_cell: None,
}
}
}
impl Mouse {
pub fn new(multiplier: f64, divider: f64) -> Self {
Self {
multiplier,
divider,
..Default::default()
}
}
#[inline]
pub fn set_multiplier_and_divider(&mut self, multiplier: f64, divider: f64) {
self.multiplier = multiplier;
self.divider = divider;
}
}
#[inline]
pub fn calculate_mouse_position(
mouse: &Mouse,
display_offset: usize,
columns_rows: (usize, usize),
margin_x_left: f32,
margin_y_top: f32,
cell: (u32, u32),
) -> Pos {
let (cell_w, cell_h) = (cell.0 as f64, cell.1 as f64);
if cell_w == 0.0 || cell_h == 0.0 {
return Pos::default();
}
let margin_x = margin_x_left as f64;
let margin_y = margin_y_top as f64;
let x_in_grid = (mouse.x - margin_x).max(0.0);
let y_in_grid = (mouse.y - margin_y).max(0.0);
let col_idx = (x_in_grid / cell_w) as usize;
let row_idx = (y_in_grid / cell_h) as usize;
let col = std::cmp::min(Column(col_idx), Column(columns_rows.0 - 1));
let row = std::cmp::min(row_idx, columns_rows.1 - 1);
let row = Line(row as i32) - display_offset;
Pos::new(row, col)
}
#[inline]
pub fn calculate_side_by_pos(
x: f64,
margin_x: f32,
cell_width: u32,
grid_width: f32,
) -> Side {
let cell_w = cell_width as f64;
let margin = margin_x as f64;
let grid_w = grid_width as f64;
let x_in_grid = (x - margin).max(0.0);
let cell_x = x_in_grid % cell_w;
let threshold = cell_w * 0.6;
let usable = (grid_w - margin).max(0.0);
let additional_padding = usable % cell_w;
let end_of_grid = margin + usable - additional_padding;
if cell_x >= threshold || x >= end_of_grid {
Side::Right
} else {
Side::Left
}
}
#[cfg(test)]
pub mod test {
use super::*;
fn mk_mouse(x: f64, y: f64) -> Mouse {
Mouse {
x,
y,
..Default::default()
}
}
#[test]
fn pos_calc_basic_no_margin() {
let cols = 10;
let lines = 5;
let cell = (9u32, 18u32);
assert_eq!(
calculate_mouse_position(
&mk_mouse(8.0, 0.0),
0,
(cols, lines),
0.0,
0.0,
cell
)
.col,
Column(0),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(8.99, 0.0),
0,
(cols, lines),
0.0,
0.0,
cell
)
.col,
Column(0),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(9.0, 0.0),
0,
(cols, lines),
0.0,
0.0,
cell
)
.col,
Column(1),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(17.5, 0.0),
0,
(cols, lines),
0.0,
0.0,
cell
)
.col,
Column(1),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(18.0, 0.0),
0,
(cols, lines),
0.0,
0.0,
cell
)
.col,
Column(2),
);
}
#[test]
fn pos_calc_with_prescaled_margin() {
let cols = 10;
let lines = 5;
let cell = (9u32, 18u32);
let margin_x = 10.0_f32;
assert_eq!(
calculate_mouse_position(
&mk_mouse(5.0, 0.0),
0,
(cols, lines),
margin_x,
0.0,
cell
)
.col,
Column(0),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(10.0, 0.0),
0,
(cols, lines),
margin_x,
0.0,
cell
)
.col,
Column(0),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(18.99, 0.0),
0,
(cols, lines),
margin_x,
0.0,
cell
)
.col,
Column(0),
);
assert_eq!(
calculate_mouse_position(
&mk_mouse(19.0, 0.0),
0,
(cols, lines),
margin_x,
0.0,
cell
)
.col,
Column(1),
);
}
#[test]
fn pos_calc_no_drift_at_high_column_index() {
let cols = 200;
let lines = 50;
let cell = (16u32, 33u32);
let pos = calculate_mouse_position(
&mk_mouse(1608.0, 100.0),
0,
(cols, lines),
0.0,
0.0,
cell,
);
assert_eq!(pos.col, Column(100));
let pos = calculate_mouse_position(
&mk_mouse(2400.0, 100.0),
0,
(cols, lines),
0.0,
0.0,
cell,
);
assert_eq!(pos.col, Column(150));
}
#[test]
fn pos_calc_subpixel_precision_preserved() {
let cell = (16u32, 33u32);
let cols = 200;
let lines = 50;
let pos = calculate_mouse_position(
&mk_mouse(1599.9, 100.0),
0,
(cols, lines),
0.0,
0.0,
cell,
);
assert_eq!(pos.col, Column(99));
let pos = calculate_mouse_position(
&mk_mouse(1600.1, 100.0),
0,
(cols, lines),
0.0,
0.0,
cell,
);
assert_eq!(pos.col, Column(100));
}
#[test]
fn pos_calc_row_with_prescaled_margin() {
let cols = 96;
let lines = 27;
let margin_y = 72.0_f32;
let cell = (16u32, 33u32);
let pos = calculate_mouse_position(
&mk_mouse(100.0, 280.0),
0,
(cols, lines),
0.0,
margin_y,
cell,
);
assert_eq!(pos.row, Line(6));
let pos = calculate_mouse_position(
&mk_mouse(100.0, 820.0),
0,
(cols, lines),
0.0,
margin_y,
cell,
);
assert_eq!(pos.row, Line(22));
}
#[test]
fn pos_calc_display_offset_shifts_row() {
let cell = (16u32, 33u32);
let cols = 96;
let lines = 27;
let pos = calculate_mouse_position(
&mk_mouse(100.0, 5.0 * 33.0 + 1.0),
10,
(cols, lines),
0.0,
0.0,
cell,
);
assert_eq!(pos.row, Line(-5));
}
#[test]
fn side_by_pos_60_percent_threshold() {
let cell = 16u32;
let margin_x = 0.0_f32;
let grid_w = 16.0 * 200.0;
assert_eq!(
calculate_side_by_pos(9.59, margin_x, cell, grid_w),
Side::Left,
);
assert_eq!(
calculate_side_by_pos(9.61, margin_x, cell, grid_w),
Side::Right,
);
}
#[test]
fn side_by_pos_no_drift_at_high_columns() {
let cell = 16u32;
let margin_x = 0.0_f32;
let grid_w = 16.0 * 200.0;
assert_eq!(
calculate_side_by_pos(1600.0, margin_x, cell, grid_w),
Side::Left,
);
assert_eq!(
calculate_side_by_pos(1609.5, margin_x, cell, grid_w),
Side::Left,
);
assert_eq!(
calculate_side_by_pos(1609.61, margin_x, cell, grid_w),
Side::Right,
);
}
#[test]
fn side_by_pos_prescaled_margin() {
let cell = 16u32;
let margin_x = 40.0_f32;
let grid_w = 40.0 + 80.0 * 16.0;
assert_eq!(
calculate_side_by_pos(30.0, margin_x, cell, grid_w),
Side::Left,
);
assert_eq!(
calculate_side_by_pos(49.0, margin_x, cell, grid_w),
Side::Left,
);
assert_eq!(
calculate_side_by_pos(49.61, margin_x, cell, grid_w),
Side::Right,
);
}
}