diff options
Diffstat (limited to 'src/input/mouse.rs')
| -rw-r--r-- | src/input/mouse.rs | 89 |
1 files changed, 74 insertions, 15 deletions
diff --git a/src/input/mouse.rs b/src/input/mouse.rs index 62b5f6c2..984537d1 100644 --- a/src/input/mouse.rs +++ b/src/input/mouse.rs @@ -47,10 +47,12 @@ impl HostGeometry { )) } + #[cfg(test)] fn column_boundary(self, column: u16) -> Option<u32> { boundary(column, self.cols, self.width_px) } + #[cfg(test)] fn row_boundary(self, row: u16) -> Option<u32> { boundary(row, self.rows, self.height_px) } @@ -63,28 +65,68 @@ impl HostPixels { child_width_px: u32, child_height_px: u32, ) -> Option<Position> { - let start_x = self.geometry.column_boundary(inner.x)?; - let start_y = self.geometry.row_boundary(inner.y)?; - let end_x = self - .geometry - .column_boundary(inner.x.checked_add(inner.width)?)?; - let end_y = self - .geometry - .row_boundary(inner.y.checked_add(inner.height)?)?; - let x = self.x.checked_sub(1)?.checked_sub(start_x)?; - let y = self.y.checked_sub(1)?.checked_sub(start_y)?; - let source_width = end_x.checked_sub(start_x)?; - let source_height = end_y.checked_sub(start_y)?; - if x >= source_width || y >= source_height || child_width_px == 0 || child_height_px == 0 { + let (host_column, host_row) = self.geometry.cell(self.x, self.y)?; + let end_column = inner.x.checked_add(inner.width)?; + let end_row = inner.y.checked_add(inner.height)?; + if host_column < inner.x + || host_column >= end_column + || host_row < inner.y + || host_row >= end_row + { return None; } Some(Position::Pixels { - x: scale(x, source_width, child_width_px).checked_add(1)?, - y: scale(y, source_height, child_height_px).checked_add(1)?, + x: map_axis_within_cell( + self.x, + host_column, + inner.x, + inner.width, + self.geometry.cols, + self.geometry.width_px, + child_width_px, + )?, + y: map_axis_within_cell( + self.y, + host_row, + inner.y, + inner.height, + self.geometry.rows, + self.geometry.height_px, + child_height_px, + )?, }) } } +fn map_axis_within_cell( + pixel: u32, + host_cell: u16, + pane_start: u16, + pane_cells: u16, + host_cells: u16, + host_extent: u32, + child_extent: u32, +) -> Option<u32> { + let local_cell = host_cell.checked_sub(pane_start)?; + if local_cell >= pane_cells { + return None; + } + let source_start = boundary(host_cell, host_cells, host_extent)?; + let source_end = boundary(host_cell.checked_add(1)?, host_cells, host_extent)?; + let target_start = boundary(local_cell, pane_cells, child_extent)?; + let target_end = boundary(local_cell.checked_add(1)?, pane_cells, child_extent)?; + let source_width = source_end.checked_sub(source_start)?; + let target_width = target_end.checked_sub(target_start)?; + let offset = pixel.checked_sub(1)?.checked_sub(source_start)?; + if source_width == 0 || target_width == 0 || offset >= source_width { + return None; + } + target_start + .checked_add(scale(offset, source_width, target_width))? + .checked_add(1) +} + +#[cfg(any(unix, test))] pub(crate) fn parse_report(data: &[u8]) -> Option<(u32, u32)> { let body = data.strip_prefix(b"\x1b[<")?; let body = body @@ -97,6 +139,7 @@ pub(crate) fn parse_report(data: &[u8]) -> Option<(u32, u32)> { fields.next().is_none().then_some((x, y)) } +#[cfg(any(unix, test))] pub(crate) fn report_at_cell(data: &[u8], column: u16, row: u16) -> Option<Vec<u8>> { let body = data.strip_prefix(b"\x1b[<")?; let suffix = if body.ends_with(b"M") { 'M' } else { 'm' }; @@ -114,6 +157,7 @@ pub(crate) fn report_at_cell(data: &[u8], column: u16, row: u16) -> Option<Vec<u ) } +#[cfg(any(unix, test))] fn parse_number(value: &[u8]) -> Option<u32> { (!value.is_empty() && value.iter().all(u8::is_ascii_digit)) .then(|| std::str::from_utf8(value).ok()?.parse().ok()) @@ -184,6 +228,21 @@ mod tests { } #[test] + fn fractional_scaling_preserves_the_canonical_child_cell() { + let geometry = HostGeometry::new(80, 1, 805, 20).unwrap(); + assert_eq!(geometry.cell(11, 1), Some((1, 0))); + assert_eq!( + HostPixels { + x: 11, + y: 1, + geometry, + } + .pane_position(ratatui::layout::Rect::new(0, 0, 80, 1), 800, 20), + Some(Position::Pixels { x: 11, y: 1 }) + ); + } + + #[test] fn geometry_rejects_outside_pixels_and_maps_cells() { let geometry = HostGeometry::new(80, 24, 800, 480).unwrap(); assert_eq!(geometry.cell(1, 1), Some((0, 0))); |
