aboutsummaryrefslogtreecommitdiff
path: root/src/app/input/mouse.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-21 14:02:19 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-21 14:03:59 +0300
commit4421c0febbb03a1a4264729a70fb7c440a3b9ca8 (patch)
tree72d1ade2b453ac1948daa85c50c2b6e3599cc204 /src/app/input/mouse.rs
parent92a10fc044d42b216317696a4e8e6f70d7cc7a7d (diff)
fix: add configurable pane gaps
refs #271
Diffstat (limited to 'src/app/input/mouse.rs')
-rw-r--r--src/app/input/mouse.rs152
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");