use crate::app::window_state::WindowState;
use std::sync::Arc;
#[derive(Debug, Clone, Copy)]
pub(crate) struct PaneBoundsRaw {
pub bx: f64,
pub by: f64,
pub bw: f64,
pub bh: f64,
pub cell_width: f64,
pub cell_height: f64,
pub pane_padding: f64,
pub title_offset: f64,
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn pixel_to_cell_raw(
x: f64,
y: f64,
cell_width: f64,
cell_height: f64,
padding: f64,
content_offset_x: f64,
content_offset_y: f64,
) -> (usize, usize) {
let adjusted_x = (x - padding - content_offset_x).max(0.0);
let adjusted_y = (y - padding - content_offset_y).max(0.0);
let col = (adjusted_x / cell_width) as usize;
let row = (adjusted_y / cell_height) as usize;
(col, row)
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn pixel_to_pane_cell_raw(
x: f64,
y: f64,
pane: PaneBoundsRaw,
) -> Option<(usize, usize)> {
let PaneBoundsRaw {
bx,
by,
bw,
bh,
cell_width,
cell_height,
pane_padding,
title_offset,
} = pane;
if x < bx || x >= bx + bw || y < by || y >= by + bh {
return None;
}
let local_x = (x - bx - pane_padding).max(0.0);
let local_y = (y - by - pane_padding - title_offset).max(0.0);
let col = (local_x / cell_width) as usize;
let row = (local_y / cell_height) as usize;
Some((col, row))
}
impl WindowState {
pub(crate) fn pixel_to_cell(&self, x: f64, y: f64) -> Option<(usize, usize)> {
if let Some(renderer) = &self.renderer {
let cell_width = renderer.cell_width() as f64;
let cell_height = renderer.cell_height() as f64;
let padding = renderer.window_padding() as f64;
let content_offset_y = renderer.content_offset_y() as f64;
let content_offset_x = renderer.content_offset_x() as f64;
let adjusted_x = (x - padding - content_offset_x).max(0.0);
let adjusted_y = (y - padding - content_offset_y).max(0.0);
let col = (adjusted_x / cell_width) as usize;
let row = (adjusted_y / cell_height) as usize;
Some((col, row))
} else {
None
}
}
pub(crate) fn pixel_to_pane_cell(
&self,
x: f64,
y: f64,
pane_bounds: &crate::pane::PaneBounds,
) -> Option<(usize, usize)> {
let renderer = self.renderer.as_ref()?;
let bx = pane_bounds.x as f64;
let by = pane_bounds.y as f64;
let bw = pane_bounds.width as f64;
let bh = pane_bounds.height as f64;
if x < bx || x >= bx + bw || y < by || y >= by + bh {
return None;
}
let cell_width = renderer.cell_width() as f64;
let cell_height = renderer.cell_height() as f64;
let scale = renderer.scale_factor() as f64;
let pane_count = self
.tab_manager
.active_tab()
.and_then(|t| t.pane_manager.as_ref())
.map(|pm| pm.pane_count())
.unwrap_or(0);
let pane_padding = if self.is_gateway_active() || pane_count <= 1 {
0.0
} else {
(self.config.pane_divider_width.unwrap_or(2.0) / 2.0 + self.config.pane_padding) as f64
* scale
};
let title_offset = if self.config.show_pane_titles {
self.config.pane_title_height as f64 * scale
} else {
0.0
};
let local_x = (x - bx - pane_padding).max(0.0);
let local_y = (y - by - pane_padding - title_offset).max(0.0);
let col = (local_x / cell_width) as usize;
let row = (local_y / cell_height) as usize;
Some((col, row))
}
pub(crate) fn pixel_to_selection_cell(&self, x: f64, y: f64) -> Option<(usize, usize)> {
if let Some(tab) = self.tab_manager.active_tab()
&& let Some(ref pm) = tab.pane_manager
&& let Some(focused_pane) = pm.focused_pane()
{
self.pixel_to_pane_cell(x, y, &focused_pane.bounds)
} else {
self.pixel_to_cell(x, y)
}
}
pub(crate) fn handle_dropped_file(&mut self, path: std::path::PathBuf) {
use crate::shell_quote::quote_path;
let quoted_path = quote_path(&path, self.config.dropped_file_quote_style);
log::debug!(
"File dropped: {:?} -> {} (style: {:?})",
path,
quoted_path,
self.config.dropped_file_quote_style
);
let drop_pos = self
.tab_manager
.active_tab()
.map(|tab| tab.active_mouse().position);
if let Some((mx, my)) = drop_pos
&& let Some(tab) = self.tab_manager.active_tab_mut()
&& tab.has_multiple_panes()
&& let Some(pane_id) = tab.focus_pane_at(mx as f32, my as f32)
{
log::debug!("File drop focused pane {} at ({}, {})", pane_id, mx, my);
self.set_tmux_focused_pane_from_native(pane_id);
self.focus_state.needs_redraw = true;
}
if self.is_tmux_connected() && self.paste_via_tmux("ed_path) {
self.request_redraw();
return;
}
if let Some(tab) = self.tab_manager.active_tab() {
let terminal_clone = if tab.has_multiple_panes() {
tab.pane_manager()
.and_then(|pm| pm.focused_pane_id())
.and_then(|id| tab.pane_manager().and_then(|pm| pm.get_pane(id)))
.map(|pane| Arc::clone(&pane.terminal))
} else {
None
}
.unwrap_or_else(|| Arc::clone(&tab.terminal));
let runtime = Arc::clone(&self.runtime);
runtime.spawn(async move {
let term = terminal_clone.write().await;
let bytes = quoted_path.as_bytes().to_vec();
if let Err(e) = term.write(&bytes) {
log::error!("Failed to write dropped file path to terminal: {}", e);
}
});
self.request_redraw();
}
}
}
#[cfg(test)]
mod tests {
use super::{PaneBoundsRaw, pixel_to_cell_raw, pixel_to_pane_cell_raw};
#[test]
fn test_pixel_to_cell_origin_no_offset() {
let (col, row) = pixel_to_cell_raw(0.0, 0.0, 8.0, 16.0, 0.0, 0.0, 0.0);
assert_eq!(col, 0);
assert_eq!(row, 0);
}
#[test]
fn test_pixel_to_cell_exact_cell_boundary() {
let (col, row) = pixel_to_cell_raw(8.0, 16.0, 8.0, 16.0, 0.0, 0.0, 0.0);
assert_eq!(col, 1);
assert_eq!(row, 1);
}
#[test]
fn test_pixel_to_cell_with_padding() {
let (col, row) = pixel_to_cell_raw(12.0, 20.0, 8.0, 16.0, 4.0, 0.0, 0.0);
assert_eq!(col, 1);
assert_eq!(row, 1);
}
#[test]
fn test_pixel_to_cell_with_content_offsets() {
let (col, row) = pixel_to_cell_raw(28.0, 46.0, 8.0, 16.0, 0.0, 20.0, 30.0);
assert_eq!(col, 1);
assert_eq!(row, 1);
}
#[test]
fn test_pixel_to_cell_clamped_to_zero() {
let (col, row) = pixel_to_cell_raw(1.0, 1.0, 8.0, 16.0, 4.0, 0.0, 0.0);
assert_eq!(col, 0);
assert_eq!(row, 0);
}
#[test]
fn test_pixel_to_cell_large_grid() {
let (col, _) = pixel_to_cell_raw(1920.0, 0.0, 8.0, 16.0, 0.0, 0.0, 0.0);
assert_eq!(col, 240);
}
#[test]
fn test_pane_cell_inside_bounds_no_padding() {
let result = pixel_to_pane_cell_raw(
140.0,
248.0,
PaneBoundsRaw {
bx: 100.0,
by: 200.0,
bw: 400.0,
bh: 300.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 0.0,
title_offset: 0.0,
},
);
assert_eq!(result, Some((5, 3)));
}
#[test]
fn test_pane_cell_outside_left_edge() {
let result = pixel_to_pane_cell_raw(
99.9,
250.0,
PaneBoundsRaw {
bx: 100.0,
by: 200.0,
bw: 400.0,
bh: 300.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 0.0,
title_offset: 0.0,
},
);
assert_eq!(result, None);
}
#[test]
fn test_pane_cell_outside_right_edge() {
let result = pixel_to_pane_cell_raw(
500.0,
250.0,
PaneBoundsRaw {
bx: 100.0,
by: 200.0,
bw: 400.0,
bh: 300.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 0.0,
title_offset: 0.0,
},
);
assert_eq!(result, None);
}
#[test]
fn test_pane_cell_outside_top_edge() {
let result = pixel_to_pane_cell_raw(
150.0,
199.9,
PaneBoundsRaw {
bx: 100.0,
by: 200.0,
bw: 400.0,
bh: 300.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 0.0,
title_offset: 0.0,
},
);
assert_eq!(result, None);
}
#[test]
fn test_pane_cell_with_pane_padding() {
let result = pixel_to_pane_cell_raw(
16.0,
32.0,
PaneBoundsRaw {
bx: 0.0,
by: 0.0,
bw: 200.0,
bh: 200.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 8.0,
title_offset: 0.0,
},
);
assert_eq!(result, Some((1, 1)));
}
#[test]
fn test_pane_cell_with_title_offset() {
let result = pixel_to_pane_cell_raw(
8.0,
52.0,
PaneBoundsRaw {
bx: 0.0,
by: 0.0,
bw: 200.0,
bh: 200.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 0.0,
title_offset: 20.0,
},
);
assert_eq!(result, Some((1, 2)));
}
#[test]
fn test_pane_cell_padding_clamps_to_zero() {
let result = pixel_to_pane_cell_raw(
5.0,
5.0,
PaneBoundsRaw {
bx: 0.0,
by: 0.0,
bw: 200.0,
bh: 200.0,
cell_width: 8.0,
cell_height: 16.0,
pane_padding: 8.0,
title_offset: 0.0,
},
);
assert_eq!(result, Some((0, 0)));
}
}