diff options
author | Brooks Rady <b.j.rady@gmail.com> | 2021-08-28 17:46:24 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-28 17:46:24 +0100 |
commit | 76a5bc8a05c33fb3f46cff1ce95aa1af694b9927 (patch) | |
tree | aa0c9e4b317d9d01906d11817b3b57f0047d7be8 /zellij-server/src/ui | |
parent | 1544de266501bb7dbb0b044a04283b4fd5f59c6e (diff) |
feat(ui): overhauled resize and layout systems
* refactor(panes): move to parametric pane sizes
* Fixed the simpler errors by casting to usize
* The least I can do is pass the formatting check...
* Move to stable toolchain
* Well, it compiles?
* And now it doesn't! ;)
* Baseline functionality with the new Dimension type
* Working POC for percent-based resizing
* REVERT THIS COMMIT – DELETES TESTS
* Perfected the discrete resize algorithm
* Fixed fixed-size panes
* Basic bidirectional resize
* feat(resize): finalised parametric resize algorithm
* Reduce the logging level a bit
* Fixed nested layouts using percents
* Bug squishing for implicit sizing
* Here is a funky (read: rubbish) rounding approach
* And now it's gone again!
* Improve discretisation algorithm to fix rounding errors
* Fix the last layout bug (maybe?)
* Mixed explicit and implied percents work now
* Let's pretend that didn't happen...
* Make things a bit less crashy
* Crash slightly more for now (to find bugs)
* Manaually splitting of panes works now
* Start moving to percent-based resizes
* Everything but fullscreen seems to be working
* Fix compilatation errors
* Culled a massive amount of border code
* Why not pause to please rustfmt?
* Turns out I was still missing a few tests...
* Bringing back even more tests!
* Fix tests and pane boarders
* Fix the resize system without gaps
* Fix content offset
* Fixed a bug with pane closing
* Add a hack to fix setting of the viewport
* Fix toggling between shared borders and frames
* fix(tests): make e2e properly use PaneGeom
* style(fmt): make rustfmt happy
* Revert unintentional rounding of borders
* Purge some old borderless stuff
* Fix busted tab-bar shrinking
* Update E2E tests
* Finish implementing fullscreen!
* Don't crash anymore?
* Fix (almost) all tests
* Fix a lack of tab-stops
* All tests passing
* I really can't be bothered to debug a CI issue
* Tie up loose ends
* Knock out some lingering FIXMEs
* Continue to clean things up
* Change some naming and address FIXMEs
* Cull more code + FIXMEs
* Refactor of the resize system + polish
* Only draw frames when absolutely necessary
* Fix the tab-bar crash
* Fix rendering of boarders on reattach
* Fix resizing at small pane sizes
* Deduplicate code in the layout system
* Update tab-bar WASM
* Fixed the pinching of panes during resize
* Unexpose needlessly public type
* Add back a lost test
* Re-add tab tests and get them to compile
* All tabs need layouts
* Start fixing tests + bug in main
* Stabilize the resize algorithm rounding
* All tests from main are now passing
* Cull more dead code
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r-- | zellij-server/src/ui/boundaries.rs | 99 | ||||
-rw-r--r-- | zellij-server/src/ui/mod.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_boundaries_frame.rs | 213 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_resizer.rs | 706 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_resizer_beta.rs | 248 |
5 files changed, 305 insertions, 962 deletions
diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs index b25f1cc08..a912664ca 100644 --- a/zellij-server/src/ui/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -1,5 +1,4 @@ -use zellij_utils::pane_size::PositionAndSize; -use zellij_utils::zellij_tile; +use zellij_utils::{pane_size::Viewport, zellij_tile}; use crate::tab::Pane; use ansi_term::Colour::{Fixed, RGB}; @@ -39,10 +38,6 @@ impl BoundarySymbol { color: Some(PaletteColor::EightBit(colors::GRAY)), } } - pub fn invisible(mut self) -> Self { - self.invisible = true; - self - } pub fn color(&mut self, color: Option<PaletteColor>) -> Self { self.color = color; *self @@ -407,14 +402,14 @@ impl Coordinates { } pub struct Boundaries { - position_and_size: PositionAndSize, + viewport: Viewport, boundary_characters: HashMap<Coordinates, BoundarySymbol>, } impl Boundaries { - pub fn new(position_and_size: &PositionAndSize) -> Self { + pub fn new(viewport: Viewport) -> Self { Boundaries { - position_and_size: *position_and_size, + viewport, boundary_characters: HashMap::new(), } } @@ -429,26 +424,22 @@ impl Boundaries { }, false => None, }; - if rect.x() > self.position_and_size.x { + if rect.x() > self.viewport.x { // left boundary let boundary_x_coords = rect.x() - 1; let first_row_coordinates = self.rect_right_boundary_row_start(rect); let last_row_coordinates = self.rect_right_boundary_row_end(rect); for row in first_row_coordinates..last_row_coordinates { let coordinates = Coordinates::new(boundary_x_coords, row); - let mut symbol_to_add = - if row == first_row_coordinates && row != self.position_and_size.y { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) - } else if row == last_row_coordinates - 1 - && row != self.position_and_size.y + self.position_and_size.rows - 1 - { - BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) - } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } + let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y { + BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) + } else if row == last_row_coordinates - 1 + && row != self.viewport.y + self.viewport.rows - 1 + { + BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) + } else { + BoundarySymbol::new(boundary_type::VERTICAL).color(color) + }; let next_symbol = self .boundary_characters .remove(&coordinates) @@ -457,26 +448,20 @@ impl Boundaries { self.boundary_characters.insert(coordinates, next_symbol); } } - if rect.y() > self.position_and_size.y { + if rect.y() > self.viewport.y { // top boundary let boundary_y_coords = rect.y() - 1; let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); for col in first_col_coordinates..last_col_coordinates { let coordinates = Coordinates::new(col, boundary_y_coords); - let mut symbol_to_add = if col == first_col_coordinates - && col != self.position_and_size.x - { + let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x { BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) - } else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1 - { + } else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 { BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) } else { BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } let next_symbol = self .boundary_characters .remove(&coordinates) @@ -492,19 +477,15 @@ impl Boundaries { let last_row_coordinates = self.rect_right_boundary_row_end(rect); for row in first_row_coordinates..last_row_coordinates { let coordinates = Coordinates::new(boundary_x_coords, row); - let mut symbol_to_add = - if row == first_row_coordinates && row != self.position_and_size.y { - BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) - } else if row == last_row_coordinates - 1 - && row != self.position_and_size.y + self.position_and_size.rows - 1 - { - BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) - } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } + let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y { + BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) + } else if row == last_row_coordinates - 1 + && row != self.viewport.y + self.viewport.rows - 1 + { + BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) + } else { + BoundarySymbol::new(boundary_type::VERTICAL).color(color) + }; let next_symbol = self .boundary_characters .remove(&coordinates) @@ -520,19 +501,13 @@ impl Boundaries { let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); for col in first_col_coordinates..last_col_coordinates { let coordinates = Coordinates::new(col, boundary_y_coords); - let mut symbol_to_add = if col == first_col_coordinates - && col != self.position_and_size.x - { + let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x { BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) - } else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1 - { + } else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 { BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) } else { BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } let next_symbol = self .boundary_characters .remove(&coordinates) @@ -555,16 +530,16 @@ impl Boundaries { vte_output } fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { - rect.x() + rect.columns() < self.position_and_size.cols + rect.x() + rect.cols() < self.viewport.cols } fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { - rect.y() + rect.rows() < self.position_and_size.y + self.position_and_size.rows + rect.y() + rect.rows() < self.viewport.y + self.viewport.rows } fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize { - if rect.y() > self.position_and_size.y { + if rect.y() > self.viewport.y { rect.y() - 1 } else { - self.position_and_size.y + self.viewport.y } } fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize { @@ -578,12 +553,12 @@ impl Boundaries { } } fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize { - rect.x() + rect.columns() + rect.x() + rect.cols() } fn is_fully_inside_screen(&self, rect: &dyn Pane) -> bool { - rect.x() >= self.position_and_size.x - && rect.x() + rect.columns() <= self.position_and_size.x + self.position_and_size.cols - && rect.y() >= self.position_and_size.y - && rect.y() + rect.rows() <= self.position_and_size.y + self.position_and_size.rows + rect.x() >= self.viewport.x + && rect.x() + rect.cols() <= self.viewport.x + self.viewport.cols + && rect.y() >= self.viewport.y + && rect.y() + rect.rows() <= self.viewport.y + self.viewport.rows } } diff --git a/zellij-server/src/ui/mod.rs b/zellij-server/src/ui/mod.rs index beb70bf04..6ebc46831 100644 --- a/zellij-server/src/ui/mod.rs +++ b/zellij-server/src/ui/mod.rs @@ -1,4 +1,3 @@ pub mod boundaries; pub mod pane_boundaries_frame; pub mod pane_resizer; -pub mod pane_resizer_beta; diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 0ae454d96..d3cd6d5af 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -1,7 +1,7 @@ use crate::ui::boundaries::boundary_type; use ansi_term::Colour::{Fixed, RGB}; use ansi_term::Style; -use zellij_utils::pane_size::PositionAndSize; +use zellij_utils::pane_size::Viewport; use zellij_utils::zellij_tile::prelude::PaletteColor; fn color_string(character: &str, color: Option<PaletteColor>) -> String { @@ -18,93 +18,15 @@ fn color_string(character: &str, color: Option<PaletteColor>) -> String { } } -pub struct PaneBoundariesFrame { - pub position_and_size: PositionAndSize, - base_title: String, - title: String, - scroll_position: (usize, usize), // (position, length) +#[derive(Default, PartialEq)] +pub struct PaneFrame { + pub geom: Viewport, + pub title: String, + pub scroll_position: (usize, usize), // (position, length) pub color: Option<PaletteColor>, - draw_title_only: bool, - should_render: bool, } -impl PaneBoundariesFrame { - pub fn new(position_and_size: PositionAndSize, title: String) -> Self { - PaneBoundariesFrame { - position_and_size, - color: None, - base_title: title.clone(), - title, - scroll_position: (0, 0), - draw_title_only: false, - should_render: true, - } - } - pub fn frame_title_only(mut self) -> Self { - // TODO: remove this? - self.draw_title_only = true; - self.should_render = true; - self - } - pub fn render_only_title(&mut self, should_render_only_title: bool) { - if should_render_only_title != self.draw_title_only { - self.should_render = true; - self.draw_title_only = should_render_only_title; - } - } - pub fn change_pos_and_size(&mut self, position_and_size: PositionAndSize) { - if position_and_size != self.position_and_size { - self.position_and_size = position_and_size; - self.should_render = true; - } - } - pub fn set_color(&mut self, color: Option<PaletteColor>) { - if color != self.color { - self.color = color; - self.should_render = true; - } - } - pub fn update_scroll(&mut self, scroll_position: (usize, usize)) { - if scroll_position != self.scroll_position { - self.scroll_position = scroll_position; - self.should_render = true; - } - } - pub fn update_title(&mut self, title: Option<&String>) { - match title { - Some(title) => { - if title != &self.title { - self.title = title.clone(); - self.should_render = true; - } - } - None => { - self.title = self.base_title.clone(); - self.should_render = true; - } - } - } - pub fn content_position_and_size(&self) -> PositionAndSize { - if self.draw_title_only { - self.position_and_size.reduce_top_line() - } else { - self.position_and_size.reduce_outer_frame(1) - } - } - pub fn content_offset(&self) -> (usize, usize) { - // (column_difference, row_difference) - let content_position_and_size = self.content_position_and_size(); - let column_difference = content_position_and_size - .x - .saturating_sub(self.position_and_size.x); - let row_difference = content_position_and_size - .y - .saturating_sub(self.position_and_size.y); - (column_difference, row_difference) - } - pub fn set_should_render(&mut self, should_render: bool) { - self.should_render = should_render; - } +impl PaneFrame { fn render_title_right_side(&self, max_length: usize) -> Option<String> { if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 { let prefix = " SCROLL: "; @@ -156,17 +78,9 @@ impl PaneBoundariesFrame { } } fn render_title(&self, vte_output: &mut String) { - let total_title_length = self.position_and_size.cols - 2; // 2 for the left and right corners - let left_boundary = if self.draw_title_only { - boundary_type::HORIZONTAL - } else { - boundary_type::TOP_LEFT - }; - let right_boundary = if self.draw_title_only { - boundary_type::HORIZONTAL - } else { - boundary_type::TOP_RIGHT - }; + let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners + let left_boundary = boundary_type::TOP_LEFT; + let right_boundary = boundary_type::TOP_RIGHT; let left_side = self.render_title_left_side(total_title_length); let right_side = left_side.as_ref().and_then(|left_side| { let space_left = total_title_length.saturating_sub(left_side.chars().count() + 1); // 1 for a middle separator @@ -205,73 +119,60 @@ impl PaneBoundariesFrame { }; vte_output.push_str(&format!( "\u{1b}[{};{}H\u{1b}[m{}", - self.position_and_size.y + 1, // +1 because goto is 1 indexed - self.position_and_size.x + 1, // +1 because goto is 1 indexed + self.geom.y + 1, // +1 because goto is 1 indexed + self.geom.x + 1, // +1 because goto is 1 indexed color_string(&title_text, self.color), )); // goto row/col + boundary character } - pub fn render(&mut self) -> Option<String> { - if !self.should_render { - return None; - } + pub fn render(&self) -> String { let mut vte_output = String::new(); - if self.draw_title_only { - self.render_title(&mut vte_output); - } else { - for row in - self.position_and_size.y..(self.position_and_size.y + self.position_and_size.rows) - { - if row == self.position_and_size.y { - // top row - self.render_title(&mut vte_output); - } else if row == self.position_and_size.y + self.position_and_size.rows - 1 { - // bottom row - for col in self.position_and_size.x - ..(self.position_and_size.x + self.position_and_size.cols) - { - if col == self.position_and_size.x { - // bottom left corner - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - row + 1, // +1 because goto is 1 indexed - col + 1, - color_string(boundary_type::BOTTOM_LEFT, self.color), - )); // goto row/col + boundary character - } else if col == self.position_and_size.x + self.position_and_size.cols - 1 - { - // bottom right corner - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - row + 1, // +1 because goto is 1 indexed - col + 1, - color_string(boundary_type::BOTTOM_RIGHT, self.color), - )); // goto row/col + boundary character - } else { - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - row + 1, // +1 because goto is 1 indexed - col + 1, - color_string(boundary_type::HORIZONTAL, self.color), - )); // goto row/col + boundary character - } + for row in self.geom.y..(self.geom.y + self.geom.rows) { + if row == self.geom.y { + // top row + self.render_title(&mut vte_output); + } else if row == self.geom.y + self.geom.rows - 1 { + // bottom row + for col in self.geom.x..(self.geom.x + self.geom.cols) { + if col == self.geom.x { + // bottom left corner + vte_output.push_str(&format!( + "\u{1b}[{};{}H\u{1b}[m{}", + row + 1, // +1 because goto is 1 indexed + col + 1, + color_string(boundary_type::BOTTOM_LEFT, self.color), + )); // goto row/col + boundary character + } else if col == self.geom.x + self.geom.cols - 1 { + // bottom right corner + vte_output.push_str(&format!( + "\u{1b}[{};{}H\u{1b}[m{}", + row + 1, // +1 because goto is 1 indexed + col + 1, + color_string(boundary_type::BOTTOM_RIGHT, self.color), + )); // goto row/col + boundary character + } else { + vte_output.push_str(&format!( + "\u{1b}[{};{}H\u{1b}[m{}", + row + 1, // +1 because goto is 1 indexed + col + 1, + color_string(boundary_type::HORIZONTAL, self.color), + )); // goto row/col + boundary character } - } else { - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - row + 1, // +1 because goto is 1 indexed - self.position_and_size.x + 1, - color_string(boundary_type::VERTICAL, self.color), - )); // goto row/col + boundary character - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - row + 1, // +1 because goto is 1 indexed - self.position_and_size.x + self.position_and_size.cols, - color_string(boundary_type::VERTICAL, self.color), - )); // goto row/col + boundary character } + } else { + vte_output.push_str(&format!( + "\u{1b}[{};{}H\u{1b}[m{}", + row + 1, // +1 because goto is 1 indexed + self.geom.x + 1, + color_string(boundary_type::VERTICAL, self.color), + )); // goto row/col + boundary character + vte_output.push_str(&format!( + "\u{1b}[{};{}H\u{1b}[m{}", + row + 1, // +1 because goto is 1 indexed + self.geom.x + self.geom.cols, + color_string(boundary_type::VERTICAL, self.color), + )); // goto row/col + boundary character } } - self.should_render = false; - Some(vte_output) + vte_output } } diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index 1b5ddabf3..0213f6d52 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -1,537 +1,253 @@ use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; -use std::{ - cmp::Ordering, - collections::{BTreeMap, HashSet}, +use cassowary::{ + strength::{REQUIRED, STRONG}, + Expression, Solver, Variable, + WeightedRelation::EQ, +}; +use std::collections::{HashMap, HashSet}; +use zellij_utils::{ + input::layout::Direction, + pane_size::{Constraint, Dimension, PaneGeom}, }; -use zellij_utils::pane_size::PositionAndSize; -pub(crate) struct PaneResizer<'a> { - panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, +pub struct PaneResizer<'a> { + panes: HashMap<&'a PaneId, &'a mut Box<dyn Pane>>, os_api: &'a mut Box<dyn ServerOsApi>, + vars: HashMap<PaneId, Variable>, + solver: Solver, +} + +// FIXME: Just hold a mutable Pane reference instead of the PaneId, fixed, pos, and size? +// Do this after panes are no longer trait-objects! +#[derive(Debug, Clone, Copy)] +struct Span { + pid: PaneId, + direction: Direction, + pos: usize, + size: Dimension, + size_var: Variable, } -// TODO: currently there are some functions here duplicated with Tab -// all resizing functions should move here +type Grid = Vec<Vec<Span>>; impl<'a> PaneResizer<'a> { pub fn new( - panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, + panes: impl Iterator<Item = (&'a PaneId, &'a mut Box<dyn Pane>)>, os_api: &'a mut Box<dyn ServerOsApi>, ) -> Self { - PaneResizer { panes, os_api } - } - pub fn resize( - &mut self, - mut 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.cols.cmp(¤t_size.cols) { - Ordering::Greater => { - 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.cols, - current_size.rows, - ) { - self.increase_panes_right_and_push_adjacents_right( - panes_to_resize, - increase_by, - ); - 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.cols - new_size.cols; - if let Some(panes_to_resize) = find_reducible_vertical_chain( - self.panes, - reduce_by, - current_size.cols, - current_size.rows, - ) { - self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by); - 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::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.cols, - current_size.rows, - ) { - self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by); - |