diff options
author | Brooks J Rady <b.j.rady@gmail.com> | 2021-05-29 23:12:11 +0100 |
---|---|---|
committer | Brooks J Rady <b.j.rady@gmail.com> | 2021-05-29 23:12:11 +0100 |
commit | f2c5ee44f7f2c61f50b043bd8e55f915a3667fce (patch) | |
tree | 3e0ab2bb70869352c47085cc857b6e86becb840a /zellij-server/src/ui | |
parent | fe299325eb54e6642d27a417a3922a757b4390e4 (diff) |
Getting back to where we started... (Buggy Resizing)
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r-- | zellij-server/src/ui/layout.rs | 29 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_resizer.rs | 680 |
2 files changed, 208 insertions, 501 deletions
diff --git a/zellij-server/src/ui/layout.rs b/zellij-server/src/ui/layout.rs index a9af665fa..7a19d889a 100644 --- a/zellij-server/src/ui/layout.rs +++ b/zellij-server/src/ui/layout.rs @@ -19,17 +19,14 @@ fn split_space_to_parts_vertically( // First fit in the parameterized sizes for size in sizes { - let (columns, max_columns) = match size { + let columns = match size { Some(SplitSize::Percent(percent)) => { - ((max_width as f32 * (percent as f32 / 100.0)) as usize, None) + (max_width as f32 * (percent as f32 / 100.0)) as usize } // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), + Some(SplitSize::Fixed(size)) => size as usize, None => { parts_to_grow.push(current_x_position); - ( - 1, // This is grown later on - None, - ) + 1 // This is grown later on } }; split_parts.push(PositionAndSize { @@ -37,7 +34,6 @@ fn split_space_to_parts_vertically( y: space_to_split.y, columns, rows: space_to_split.rows, - max_columns, ..Default::default() }); current_width += columns; @@ -87,18 +83,14 @@ fn split_space_to_parts_horizontally( let mut parts_to_grow = Vec::new(); for size in sizes { - let (rows, max_rows) = match size { - Some(SplitSize::Percent(percent)) => ( - (max_height as f32 * (percent as f32 / 100.0)) as usize, - None, - ), // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), + let rows = match size { + Some(SplitSize::Percent(percent)) => { + (max_height as f32 * (percent as f32 / 100.0)) as usize + } // TODO: round properly + Some(SplitSize::Fixed(size)) => size as usize, None => { parts_to_grow.push(current_y_position); - ( - 1, // This is grown later on - None, - ) + 1 // This is grown later on } }; split_parts.push(PositionAndSize { @@ -106,7 +98,6 @@ fn split_space_to_parts_horizontally( y: current_y_position, columns: space_to_split.columns, rows, - max_rows, ..Default::default() }); current_height += rows; diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index b2001945e..73673a9c5 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -1,531 +1,247 @@ use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; +use cassowary::{ + strength::{REQUIRED, STRONG}, + Constraint, Solver, Variable, + WeightedRelation::*, +}; use std::{ - cmp::Ordering, collections::{BTreeMap, HashSet}, + ops::Not, }; use zellij_utils::pane_size::PositionAndSize; -pub(crate) struct PaneResizer<'a> { +const GAP_SIZE: usize = 1; // Panes are separated by this number of rows / columns + +pub struct PaneResizer<'a> { panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, + vars: BTreeMap<PaneId, (Variable, Variable)>, + solver: Solver, os_api: &'a mut Box<dyn ServerOsApi>, } +#[derive(Debug, Clone, Copy)] +enum Direction { + Horizontal, + Vertical, +} + +impl Not for Direction { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Direction::Horizontal => Direction::Vertical, + Direction::Vertical => Direction::Horizontal, + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Span { + pid: PaneId, + direction: Direction, + fixed: bool, + pos: usize, + size: usize, + pos_var: Variable, + size_var: Variable, +} + // TODO: currently there are some functions here duplicated with Tab // all resizing functions should move here +// FIXME: +// 1. Rounding causes a loss of ratios, I need to store an internal f64 for +// each pane as well as the displayed usize and add custom rounding logic. +// 2. Vertical resizing doesn't seem to respect the space consumed by the tab +// and status bars? +// 3. A 2x2 layout and simultaneous vertical + horizontal resizing sometimes +// leads to unsolvable constraints? Maybe related to 2 (and possibly 1). +// I should sanity-check the `spans_in_boundary()` here! + impl<'a> PaneResizer<'a> { pub fn new( panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, os_api: &'a mut Box<dyn ServerOsApi>, ) -> Self { - PaneResizer { panes, os_api } + let mut vars = BTreeMap::new(); + for &k in panes.keys() { + vars.insert(k, (Variable::new(), Variable::new())); + } + PaneResizer { + panes, + vars, + solver: Solver::new(), + os_api, + } } + pub fn resize( &mut self, - mut current_size: PositionAndSize, + current_size: PositionAndSize, new_size: PositionAndSize, ) -> Option<(isize, isize)> { - // (column_difference, row_difference) - let mut successfully_resized = false; - let mut column_difference: isize = 0; - let mut row_difference: isize = 0; - match new_size.columns.cmp(¤t_size.columns) { - Ordering::Greater => { - let increase_by = new_size.columns - current_size.columns; - if let Some(panes_to_resize) = find_increasable_vertical_chain( - &self.panes, - increase_by, - current_size.columns, - current_size.rows, - ) { - self.increase_panes_right_and_push_adjacents_right( - panes_to_resize, - increase_by, - ); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.columns - new_size.columns; - if let Some(panes_to_resize) = find_reducible_vertical_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), - } - match new_size.rows.cmp(¤t_size.rows) { - Ordering::Greater => { - let increase_by = new_size.rows - current_size.rows; - if let Some(panes_to_resize) = find_increasable_horizontal_chain( - &self.panes, - increase_by, - current_size.columns, - current_size.rows, - ) { - self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.rows - new_size.rows; - if let Some(panes_to_resize) = find_reducible_horizontal_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), - } - if successfully_resized { - Some((column_difference, row_difference)) - } else { - None - } - } - fn reduce_panes_left_and_pull_adjacents_left( - &mut self, - panes_to_reduce: Vec<PaneId>, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_left(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_width_left(&pane_id, reduce_by); - } - } - fn reduce_panes_up_and_pull_adjacents_up( - &mut self, - panes_to_reduce: Vec<PaneId>, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_up(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_height_up(&pane_id, reduce_by); - } - } - fn increase_panes_down_and_push_down_adjacents( - &mut self, - panes_to_increase: Vec<PaneId>, - increase_by: usize, - ) { - let mut pushed_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_down(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_height_down(&pane_id, increase_by); - } - } - fn increase_panes_right_and_push_adjacents_right( - &mut self, - panes_to_increase: Vec<PaneId>, - increase_by: usize, - ) { - let mut pushed_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_right(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_width_right(&pane_id, increase_by); - } - } - fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_height_up(count); - if let PaneId::Terminal(pid) = id { - self.os_api - .set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_height_down(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_width_right(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_width_left(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } + let col_delta = new_size.columns as isize - current_size.columns as isize; + let row_delta = new_size.rows as isize - current_size.rows as isize; + if col_delta != 0 { + let spans = self.solve_direction(Direction::Horizontal, new_size.columns)?; + self.collapse_spans(&spans); + } + self.solver.reset(); + if row_delta != 0 { + let spans = self.solve_direction(Direction::Vertical, new_size.rows)?; + self.collapse_spans(&spans); + } + Some((col_delta, row_delta)) } -} -fn find_next_increasable_horizontal_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - right_of: &dyn Pane, - increase_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id - } else { - Some(p.pid()) - } + fn solve_direction(&mut self, direction: Direction, space: usize) -> Option<Vec<Span>> { + let mut grid = Vec::new(); + for boundary in self.grid_boundaries(direction) { + grid.push(self.spans_in_boundary(direction, boundary)); } - None => Some(p.pid()), - }) -} -fn find_next_increasable_vertical_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - below: &dyn Pane, - increase_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} + let constraints: Vec<_> = grid + .iter() + .flat_map(|s| constrain_spans(space, s)) + .collect(); -fn find_next_reducible_vertical_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - below: &dyn Pane, - reduce_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} + // FIXME: This line needs to be restored before merging! + //self.solver.add_constraints(&constraints).ok()?; + self.solver.add_constraints(&constraints).unwrap(); + Some(grid.into_iter().flatten().collect()) + } -fn find_next_reducible_horizontal_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - right_of: &dyn Pane, - reduce_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id + fn grid_boundaries(&self, direction: Direction) -> Vec<(usize, usize)> { + // Select the spans running *perpendicular* to the direction of resize + let spans: Vec<Span> = self + .panes + .values() + .map(|p| self.get_span(!direction, p.as_ref())) + .collect(); + + let mut last_edge = 0; + let mut bounds = Vec::new(); + loop { + let mut spans_on_edge: Vec<&Span> = + spans.iter().filter(|p| p.pos == last_edge).collect(); + spans_on_edge.sort_unstable_by_key(|s| s.size); + if let Some(next) = spans_on_edge.first() { + let next_edge = last_edge + next.size; + bounds.push((last_edge, next_edge)); + last_edge = next_edge + GAP_SIZE; } else { - Some(p.pid()) + break; } } - None => Some(p.pid()), - }) -} - -fn find_increasable_horizontal_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } + bounds + } - match panes + fn spans_in_boundary(&self, direction: Direction, boundary: (usize, usize)) -> Vec<Span> { + let (start, end) = boundary; + let bwn = |v| start <= v && v < end; + let mut spans: Vec<_> = self + .panes .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - { - Some(leftmost_pane) => { - if !leftmost_pane.can_increase_height_by(increase_by) { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = leftmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.x() + current_pane.columns() == screen_width { - return Some(panes_to_resize); - } - match find_next_increasable_horizontal_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - break; - } - }; - } - } - None => { - return None; - } - } + .filter(|p| { + let s = self.get_span(!direction, p.as_ref()); + bwn(s.pos) || bwn(s.pos + s.size) + }) + .map(|p| self.get_span(direction, p.as_ref())) + .collect(); + spans.sort_unstable_by_key(|s| s.pos); + spans } -} -fn find_increasable_vertical_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut vertical_coordinate = 0; - loop { - if vertical_coordinate == screen_width { - return None; + fn get_span(&self, direction: Direction, pane: &dyn Pane) -> Span { + let pas = pane.position_and_size(); + let (pos_var, size_var) = self.vars[&pane.pid()]; + match direction { + Direction::Horizontal => Span { + pid: pane.pid(), + direction, + fixed: pas.cols_fixed, + pos: pas.x, + size: pas.columns, + pos_var, + size_var, + }, + Direction::Vertical => Span { + pid: pane.pid(), + direction, + fixed: pas.rows_fixed, + pos: pas.y, + size: pas.rows, + pos_var, + size_var, + }, } + } - match panes - .values() - .find(|p| p.y() == 0 && p.x() == vertical_coordinate) - { - Some(topmost_pane) => { - if !topmost_pane.can_increase_width_by(increase_by) { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = topmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.y() + current_pane.rows() == screen_height { - return Some(panes_to_resize); - } - match find_next_increasable_vertical_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - break; - } - }; - } - } - None => { - return None; + fn collapse_spans(&mut self, spans: &[Span]) { + for span in spans { + let solver = &self.solver; // Hand-holding the borrow-checker + let pane = self.panes.get_mut(&span.pid).unwrap(); + let fetch_usize = |v| solver.get_value(v).round() as usize; + match span.direction { + Direction::Horizontal => pane.change_pos_and_size(&PositionAndSize { + x: fetch_usize(span.pos_var), + columns: fetch_usize(span.size_var), + ..pane.position_and_size() + }), + Direction::Vertical => pane.change_pos_and_size(&PositionAndSize { + y: fetch_usize(span.pos_var), + rows: fetch_usize(span.size_var), + ..pane.position_and_size() + }), + } + if let PaneId::Terminal(pid) = pane.pid() { + self.os_api.set_terminal_size_using_fd( + pid, + pane.columns() as u16, + pane.rows() as u16, + ); } } } } -fn find_reducible_horizontal_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - reduce_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } +fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<Constraint> { + let mut constraints = HashSet::new(); - match panes - .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - |