aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-16 02:18:35 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-16 02:18:35 +0300
commit58c379f1f07adb4113e5f9ee673768b2e4b5be07 (patch)
treed090f20c0b78f370b769a250c9bcc47126a9b738 /src/app/input
parentc3d74be01e6c3485f50f74e1ff566499ebe092b0 (diff)
fix: keep split resize hitboxes off pane content
fixes #120
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mouse.rs118
1 files changed, 116 insertions, 2 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index 9b4055e5..a7a2159a 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -981,12 +981,14 @@ impl AppState {
pub(super) fn find_border_at(&self, col: u16, row: u16) -> Option<&SplitBorder> {
self.view.split_borders.iter().find(|b| match b.direction {
Direction::Horizontal => {
- (col as i32 - b.pos as i32).unsigned_abs() <= 1
+ col >= b.pos.saturating_sub(1)
+ && col <= b.pos
&& row >= b.area.y
&& row < b.area.y + b.area.height
}
Direction::Vertical => {
- (row as i32 - b.pos as i32).unsigned_abs() <= 1
+ row >= b.pos.saturating_sub(1)
+ && row <= b.pos
&& col >= b.area.x
&& col < b.area.x + b.area.width
}
@@ -1467,6 +1469,118 @@ mod tests {
assert_ne!(root_layout_ratio(&before), root_layout_ratio(&after));
}
+ #[test]
+ fn pane_split_hitbox_does_not_overlap_right_pane_content() {
+ let mut app = app_for_mouse_test();
+ app.state.workspaces = vec![Workspace::test_new("test")];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.workspaces[0].test_split(Direction::Horizontal);
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+ let border = app.state.view.split_borders[0].clone();
+ let row = border.area.y.saturating_add(1);
+
+ assert!(app
+ .state
+ .find_border_at(border.pos.saturating_sub(1), row)
+ .is_some());
+ assert!(app.state.find_border_at(border.pos, row).is_some());
+ assert!(app
+ .state
+ .find_border_at(border.pos.saturating_add(1), row)
+ .is_none());
+ }
+
+ #[test]
+ fn pane_split_hitbox_does_not_overlap_bottom_pane_content() {
+ let mut app = app_for_mouse_test();
+ app.state.workspaces = vec![Workspace::test_new("test")];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.workspaces[0].test_split(Direction::Vertical);
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+ let border = app.state.view.split_borders[0].clone();
+ let col = border.area.x.saturating_add(1);
+
+ assert!(app
+ .state
+ .find_border_at(col, border.pos.saturating_sub(1))
+ .is_some());
+ assert!(app.state.find_border_at(col, border.pos).is_some());
+ assert!(app
+ .state
+ .find_border_at(col, border.pos.saturating_add(1))
+ .is_none());
+ }
+
+ #[test]
+ fn selecting_from_right_pane_first_content_column_starts_selection() {
+ let mut app = app_for_mouse_test();
+ let mut ws = Workspace::test_new("test");
+ let second_pane = ws.test_split(Direction::Horizontal);
+ app.state.workspaces = vec![ws];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+
+ let second_info = app
+ .state
+ .view
+ .pane_infos
+ .iter()
+ .find(|info| info.id == second_pane)
+ .expect("second pane info")
+ .clone();
+ let col = second_info.inner_rect.x;
+ let row = second_info.inner_rect.y;
+
+ assert!(app.state.find_border_at(col, row).is_none());
+ app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), col, row));
+
+ assert!(app.state.drag.is_none());
+ assert_eq!(
+ app.state
+ .selection
+ .as_ref()
+ .map(|selection| selection.pane_id),
+ Some(second_pane)
+ );
+ }
+
+ #[test]
+ fn selecting_from_bottom_pane_first_content_row_starts_selection() {
+ let mut app = app_for_mouse_test();
+ let mut ws = Workspace::test_new("test");
+ let second_pane = ws.test_split(Direction::Vertical);
+ app.state.workspaces = vec![ws];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+
+ let second_info = app
+ .state
+ .view
+ .pane_infos
+ .iter()
+ .find(|info| info.id == second_pane)
+ .expect("second pane info")
+ .clone();
+ let col = second_info.inner_rect.x;
+ let row = second_info.inner_rect.y;
+
+ assert!(app.state.find_border_at(col, row).is_none());
+ app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), col, row));
+
+ assert!(app.state.drag.is_none());
+ assert_eq!(
+ app.state
+ .selection
+ .as_ref()
+ .map(|selection| selection.pane_id),
+ Some(second_pane)
+ );
+ }
+
#[tokio::test]
async fn dragging_vertical_pane_split_still_resizes_when_pane_mouse_reporting_is_enabled() {
let mut app = app_for_mouse_test();