diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-21 14:02:19 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-21 14:03:59 +0300 |
| commit | 4421c0febbb03a1a4264729a70fb7c440a3b9ca8 (patch) | |
| tree | 72d1ade2b453ac1948daa85c50c2b6e3599cc204 /src/app/input/mouse.rs | |
| parent | 92a10fc044d42b216317696a4e8e6f70d7cc7a7d (diff) | |
fix: add configurable pane gaps
refs #271
Diffstat (limited to 'src/app/input/mouse.rs')
| -rw-r--r-- | src/app/input/mouse.rs | 152 |
1 files changed, 142 insertions, 10 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs index 41bb5267..c8a03674 100644 --- a/src/app/input/mouse.rs +++ b/src/app/input/mouse.rs @@ -393,11 +393,16 @@ impl AppState { if !in_sidebar { if let Some(border) = self.find_border_at(mouse.column, mouse.row) { + let grab_offset = match border.direction { + Direction::Horizontal => border.pos.saturating_sub(mouse.column), + Direction::Vertical => border.pos.saturating_sub(mouse.row), + }; self.drag = Some(DragState { target: DragTarget::PaneSplit { path: border.path.clone(), direction: border.direction, area: border.area, + grab_offset, }, }); return None; @@ -694,14 +699,23 @@ impl AppState { path, direction, area, + grab_offset, } => { let ratio = match direction { Direction::Horizontal => { - (mouse.column.saturating_sub(area.x)) as f32 + (mouse + .column + .saturating_add(*grab_offset) + .saturating_sub(area.x)) + as f32 / area.width.max(1) as f32 } Direction::Vertical => { - (mouse.row.saturating_sub(area.y)) as f32 + (mouse + .row + .saturating_add(*grab_offset) + .saturating_sub(area.y)) + as f32 / area.height.max(1) as f32 } }; @@ -1257,18 +1271,39 @@ 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 >= b.pos.saturating_sub(1) + Direction::Horizontal if self.pane_borders && !self.pane_gaps => { + col == b.pos && row >= b.area.y && row < b.area.y + b.area.height + } + Direction::Horizontal if self.pane_borders && self.pane_gaps => { + row >= b.area.y + && row < b.area.y + b.area.height + && col >= b.pos.saturating_sub(1) && col <= b.pos - && row >= b.area.y + } + Direction::Horizontal if !self.pane_borders && self.pane_gaps => { + row >= b.area.y && row < b.area.y + b.area.height + && b.pos.checked_sub(1).is_some_and(|gap_col| { + col == gap_col && self.pane_frame_at(col, row).is_none() + }) + } + Direction::Vertical if self.pane_borders && !self.pane_gaps => { + row == b.pos && col >= b.area.x && col < b.area.x + b.area.width } - Direction::Vertical => { - row >= b.pos.saturating_sub(1) + Direction::Vertical if self.pane_borders && self.pane_gaps => { + col >= b.area.x + && col < b.area.x + b.area.width + && row >= b.pos.saturating_sub(1) && row <= b.pos - && col >= b.area.x + } + Direction::Vertical if !self.pane_borders && self.pane_gaps => { + col >= b.area.x && col < b.area.x + b.area.width + && b.pos.checked_sub(1).is_some_and(|gap_row| { + row == gap_row && self.pane_frame_at(col, row).is_none() + }) } + _ => false, }) } @@ -2612,6 +2647,7 @@ mod tests { app.state.workspaces = vec![Workspace::test_new("test")]; app.state.active = Some(0); app.state.selected = 0; + app.state.pane_gaps = false; 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(); @@ -2620,7 +2656,7 @@ mod tests { assert!(app .state .find_border_at(border.pos.saturating_sub(1), row) - .is_some()); + .is_none()); assert!(app.state.find_border_at(border.pos, row).is_some()); assert!(app .state @@ -2634,6 +2670,7 @@ mod tests { app.state.workspaces = vec![Workspace::test_new("test")]; app.state.active = Some(0); app.state.selected = 0; + app.state.pane_gaps = false; 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(); @@ -2642,7 +2679,7 @@ mod tests { assert!(app .state .find_border_at(col, border.pos.saturating_sub(1)) - .is_some()); + .is_none()); assert!(app.state.find_border_at(col, border.pos).is_some()); assert!(app .state @@ -2651,6 +2688,101 @@ mod tests { } #[test] + fn borderless_no_gap_split_has_no_mouse_hitbox_over_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.pane_borders = false; + 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, row).is_none()); + } + + #[test] + fn bordered_pane_gaps_keep_both_split_borders_draggable() { + 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.pane_gaps = true; + 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 borderless_pane_gap_is_not_a_pane_but_remains_split_draggable() { + 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.pane_borders = false; + app.state.pane_gaps = true; + 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); + let gap_col = border.pos.saturating_sub(1); + + assert!(app.state.pane_at(gap_col, row).is_none()); + assert!(app.state.find_border_at(gap_col, row).is_some()); + assert!(app.state.find_border_at(border.pos, row).is_none()); + } + + #[test] + fn borderless_gap_hitbox_is_empty_when_first_split_side_has_one_cell() { + 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.pane_borders = false; + app.state.pane_gaps = true; + app.state.workspaces[0].test_split(Direction::Horizontal); + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 2, 4)); + let border = app.state.view.split_borders[0].clone(); + let row = border.area.y.saturating_add(1); + let candidate_gap_col = border.pos.saturating_sub(1); + + assert!(app.state.pane_frame_at(candidate_gap_col, row).is_some()); + assert!(app.state.find_border_at(candidate_gap_col, row).is_none()); + } + + #[test] + fn borderless_gap_hitbox_is_empty_when_first_split_side_has_zero_width() { + 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.pane_borders = false; + app.state.pane_gaps = true; + app.state.workspaces[0].test_split(Direction::Horizontal); + app.state.workspaces[0].tabs[0] + .layout + .set_ratio_at(&[], 0.1); + crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 1, 4)); + let border = app.state.view.split_borders[0].clone(); + let row = border.area.y.saturating_add(1); + + assert_eq!(border.pos, 0); + assert!(app.state.find_border_at(0, row).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"); |
