diff options
author | a-kenji <aks.kenji@protonmail.com> | 2021-06-14 22:05:25 +0200 |
---|---|---|
committer | a-kenji <aks.kenji@protonmail.com> | 2021-06-14 22:05:25 +0200 |
commit | 4743b7e7ab803b16cc703d2238068647857295c5 (patch) | |
tree | 297041fe76ca6fdeff65f4812f81e562e2cc2a96 /zellij-server/src/ui | |
parent | cafcca0c57696968b609c4f4a8c7a6fe7f2909a8 (diff) | |
parent | 7f67e6d97f158a00d1f2c21326fe320388ec8446 (diff) |
Merge branch 'main' of https://github.com/zellij-org/zellij into layout-error
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r-- | zellij-server/src/ui/mod.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_resizer.rs | 24 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_resizer_beta.rs | 248 |
3 files changed, 260 insertions, 13 deletions
diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs index b2d94d164..0087e54e8 100644 --- a/zellij-server/src/ui/mod.rs +++ b/zellij-server/src/ui/mod.rs @@ -1,2 +1,3 @@ pub mod boundaries; pub mod pane_resizer; +pub mod pane_resizer_beta; diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index b2001945e..5ab065481 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -29,37 +29,35 @@ impl<'a> PaneResizer<'a> { 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) { + match new_size.cols.cmp(¤t_size.cols) { Ordering::Greater => { - let increase_by = new_size.columns - current_size.columns; + let increase_by = new_size.cols - current_size.cols; if let Some(panes_to_resize) = find_increasable_vertical_chain( &self.panes, increase_by, - current_size.columns, + current_size.cols, 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; + column_difference = new_size.cols as isize - current_size.cols as isize; + current_size.cols = (current_size.cols as isize + column_difference) as usize; successfully_resized = true; }; } Ordering::Less => { - let reduce_by = current_size.columns - new_size.columns; + let reduce_by = current_size.cols - new_size.cols; if let Some(panes_to_resize) = find_reducible_vertical_chain( &self.panes, reduce_by, - current_size.columns, + current_size.cols, 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; + column_difference = new_size.cols as isize - current_size.cols as isize; + current_size.cols = (current_size.cols as isize + column_difference) as usize; successfully_resized = true; }; } @@ -71,7 +69,7 @@ impl<'a> PaneResizer<'a> { if let Some(panes_to_resize) = find_increasable_horizontal_chain( &self.panes, increase_by, - current_size.columns, + current_size.cols, current_size.rows, ) { self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by); @@ -85,7 +83,7 @@ impl<'a> PaneResizer<'a> { if let Some(panes_to_resize) = find_reducible_horizontal_chain( &self.panes, reduce_by, - current_size.columns, + current_size.cols, current_size.rows, ) { self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by); diff --git a/zellij-server/src/ui/pane_resizer_beta.rs b/zellij-server/src/ui/pane_resizer_beta.rs new file mode 100644 index 000000000..b6ffd7848 --- /dev/null +++ b/zellij-server/src/ui/pane_resizer_beta.rs @@ -0,0 +1,248 @@ +#![allow(dead_code)] +use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; +use cassowary::{ + strength::{REQUIRED, STRONG}, + Constraint, Solver, Variable, + WeightedRelation::*, +}; +use std::{ + collections::{BTreeMap, HashSet}, + ops::Not, +}; +use zellij_utils::pane_size::PositionAndSize; + +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 { + 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, + current_size: PositionAndSize, + new_size: PositionAndSize, + ) -> Option<(isize, isize)> { + let col_delta = new_size.cols as isize - current_size.cols 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.cols)?; + 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 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)); + } + + let constraints: Vec<_> = grid + .iter() + .flat_map(|s| constrain_spans(space, s)) + .collect(); + + // 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 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 { + break; + } + } + bounds + } + + 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() + .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 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.cols, + 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, + }, + } + } + + 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), + cols: 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 constrain_spans(space: usize, spans: &[Span]) -> HashSet<Constraint> { + let mut constraints = HashSet::new(); + + // The first span needs to start at 0 + constraints.insert(spans[0].pos_var | EQ(REQUIRED) | 0.0); + + // Calculating "flexible" space (space not consumed by fixed-size spans) + let gap_space = GAP_SIZE * (spans.len() - 1); + let old_flex_space = spans + .iter() + .fold(0, |a, s| if !s.fixed { a + s.size } else { a }); + let new_flex_space = spans.iter().fold( + space - gap_space, + |a, s| if s.fixed { a - s.size } else { a }, + ); + + // Keep spans stuck together + for pair in spans.windows(2) { + let (ls, rs) = (pair[0], pair[1]); + constraints + .insert((ls.pos_var + ls.size_var + GAP_SIZE as f64) | EQ(REQUIRED) | rs.pos_var); + } + + // Try to maintain ratios and lock non-flexible sizes + for span in spans { + if span.fixed { + constraints.insert(span.size_var | EQ(REQUIRED) | span.size as f64); + } else { + let ratio = span.size as f64 / old_flex_space as f64; + constraints.insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | ratio); + } + } + + // The last pane needs to end at the end of the space + let last = spans.last().unwrap(); + constraints.insert((last.pos_var + last.size_var) | EQ(REQUIRED) | space as f64); + + constraints +} |