summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src
diff options
context:
space:
mode:
authorBrooks Rady <b.j.rady@gmail.com>2021-08-28 17:46:24 +0100
committerGitHub <noreply@github.com>2021-08-28 17:46:24 +0100
commit76a5bc8a05c33fb3f46cff1ce95aa1af694b9927 (patch)
treeaa0c9e4b317d9d01906d11817b3b57f0047d7be8 /zellij-utils/src
parent1544de266501bb7dbb0b044a04283b4fd5f59c6e (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-utils/src')
-rw-r--r--zellij-utils/src/input/actions.rs2
-rw-r--r--zellij-utils/src/input/layout.rs245
-rw-r--r--zellij-utils/src/input/unit/layout_test.rs42
-rw-r--r--zellij-utils/src/ipc.rs13
-rw-r--r--zellij-utils/src/pane_size.rs153
-rw-r--r--zellij-utils/src/position.rs8
-rw-r--r--zellij-utils/src/shared.rs3
7 files changed, 253 insertions, 213 deletions
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index cd4ca4641..915f2dad3 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -22,7 +22,7 @@ pub enum Direction {
// They might need to be adjusted in the default config
// as well `../../assets/config/default.yaml`
/// Actions that can be bound to keys.
-#[derive(Eq, Clone, Debug, PartialEq, Deserialize, Serialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action {
/// Quit Zellij.
Quit,
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index a3da6abab..be71130cd 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -10,28 +10,43 @@
// then [`zellij-utils`] could be a proper place.
use crate::{
input::{command::RunCommand, config::ConfigError},
- pane_size::PositionAndSize,
+ pane_size::{Dimension, PaneGeom},
setup,
};
use crate::{serde, serde_yaml};
use serde::{Deserialize, Serialize};
-use std::path::{Path, PathBuf};
use std::vec::Vec;
+use std::{
+ cmp::max,
+ ops::Not,
+ path::{Path, PathBuf},
+};
use std::{fs::File, io::prelude::*};
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
#[serde(crate = "self::serde")]
pub enum Direction {
Horizontal,
Vertical,
}
-#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
+impl Not for Direction {
+ type Output = Self;
+
+ fn not(self) -> Self::Output {
+ match self {
+ Direction::Horizontal => Direction::Vertical,
+ Direction::Vertical => Direction::Horizontal,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(crate = "self::serde")]
pub enum SplitSize {
- Percent(u8), // 1 to 100
- Fixed(u16), // An absolute number of columns or rows
+ Percent(f64), // 1 to 100
+ Fixed(usize), // An absolute number of columns or rows
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@@ -44,7 +59,7 @@ pub enum Run {
}
// The layout struct ultimately used to build the layouts.
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct Layout {
pub direction: Direction,
@@ -58,7 +73,7 @@ pub struct Layout {
// The struct that is used to deserialize the layout from
// a yaml configuration file
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct LayoutFromYaml {
@@ -148,7 +163,7 @@ impl LayoutFromYaml {
// The struct that carries the information template that is used to
// construct the layout
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct LayoutTemplate {
pub direction: Direction,
@@ -191,7 +206,7 @@ impl LayoutTemplate {
}
// The tab-layout struct used to specify each individual tab.
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct TabLayout {
pub direction: Direction,
@@ -238,10 +253,7 @@ impl Layout {
run_instructions
}
- pub fn position_panes_in_space(
- &self,
- space: &PositionAndSize,
- ) -> Vec<(Layout, PositionAndSize)> {
+ pub fn position_panes_in_space(&self, space: &PaneGeom) -> Vec<(Layout, PaneGeom)> {
split_space(space, self)
}
@@ -268,149 +280,90 @@ impl Layout {
}
}
-fn split_space_to_parts_vertically(
- space_to_split: &PositionAndSize,
- sizes: Vec<Option<SplitSize>>,
-) -> Vec<PositionAndSize> {
- let mut split_parts = Vec::new();
- let mut current_x_position = space_to_split.x;
- let mut current_width = 0;
- let max_width = space_to_split.cols;
-
- let mut parts_to_grow = Vec::new();
-
- // First fit in the parameterized sizes
- for size in sizes {
- let columns = match size {
- Some(SplitSize::Percent(percent)) => {
- (max_width 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_x_position);
- 1 // This is grown later on
- }
- };
- split_parts.push(PositionAndSize {
- x: current_x_position,
- y: space_to_split.y,
- cols: columns,
- rows: space_to_split.rows,
- ..Default::default()
- });
- current_width += columns;
- current_x_position += columns;
- }
-
- if current_width > max_width {
- panic!("Layout contained too many columns to fit onto the screen!");
- }
-
- let mut last_flexible_index = split_parts.len() - 1;
- if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) {
- current_width = 0;
- current_x_position = 0;
- for (idx, part) in split_parts.iter_mut().enumerate() {
- part.x = current_x_position;
- if parts_to_grow.contains(&part.x) {
- part.cols = new_columns;
- last_flexible_index = idx;
- }
- current_width += part.cols;
- current_x_position += part.cols;
- }
- }
-
- if current_width < max_width {
- // we have some extra space left, let's add it to the last flexible part
- let extra = max_width - current_width;
- let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
- last_part.cols += extra;
- for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
- part.x += extra;
+fn layout_size(direction: Direction, layout: &Layout) -> usize {
+ fn child_layout_size(
+ direction: Direction,
+ parent_direction: Direction,
+ layout: &Layout,
+ ) -> usize {
+ let size = if parent_direction == direction { 1 } else { 0 };
+ if layout.parts.is_empty() {
+ size
+ } else {
+ let children_size = layout
+ .parts
+ .iter()
+ .map(|p| child_layout_size(direction, layout.direction, p))
+ .sum::<usize>();
+ max(size, children_size)
}
}
- split_parts
+ child_layout_size(direction, direction, layout)
}
-fn split_space_to_parts_horizontally(
- space_to_split: &PositionAndSize,
- sizes: Vec<Option<SplitSize>>,
-) -> Vec<PositionAndSize> {
- let mut split_parts = Vec::new();
- let mut current_y_position = space_to_split.y;
- let mut current_height = 0;
- let max_height = space_to_split.rows;
-
- let mut parts_to_grow = Vec::new();
-
- for size in sizes {
- 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
- }
- };
- split_parts.push(PositionAndSize {
- x: space_to_split.x,
- y: current_y_position,
- cols: space_to_split.cols,
- rows,
- ..Default::default()
- });
- current_height += rows;
- current_y_position += rows;
- }
+fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneGeom)> {
+ let mut pane_positions = Vec::new();
+ let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect();
- if current_height > max_height {
- panic!("Layout contained too many rows to fit onto the screen!");
- }
+ let mut split_geom = Vec::new();
+ let (mut current_position, split_dimension_space, mut inherited_dimension) =
+ match layout.direction {
+ Direction::Vertical => (space_to_split.x, space_to_split.cols, space_to_split.rows),
+ Direction::Horizontal => (space_to_split.y, space_to_split.rows, space_to_split.cols),
+ };
- let mut last_flexible_index = split_parts.len() - 1;
- if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) {
- current_height = 0;
- current_y_position = 0;
+ let flex_parts = sizes.iter().filter(|s| s.is_none()).count();
- for (idx, part) in split_parts.iter_mut().enumerate() {
- part.y = current_y_position;
- if parts_to_grow.contains(&part.y) {
- part.rows = new_rows;
- last_flexible_index = idx;
+ for (&size, part) in sizes.iter().zip(&layout.parts) {
+ let split_dimension = match size {
+ Some(SplitSize::Percent(percent)) => Dimension::percent(percent),
+ Some(SplitSize::Fixed(size)) => Dimension::fixed(size),
+ None => {
+ let free_percent = if let Some(p) = split_dimension_space.as_percent() {
+ p - sizes
+ .iter()
+ .map(|&s| {
+ if let Some(SplitSize::Percent(ip)) = s {
+ ip
+ } else {
+ 0.0
+ }
+ })
+ .sum::<f64>()
+ } else {
+ panic!("Implicit sizing within fixed-size panes is not supported");
+ };
+ Dimension::percent(free_percent / flex_parts as f64)
}
- current_height += part.rows;
- current_y_position += part.rows;
- }
- }
-
- if current_height < max_height {
- // we have some extra space left, let's add it to the last flexible part
- let extra = max_height - current_height;
- let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
- last_part.rows += extra;
- for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
- part.y += extra;
- }
+ };
+ inherited_dimension.set_inner(
+ layout
+ .parts
+ .iter()
+ .map(|p| layout_size(!layout.direction, p))
+ .max()
+ .unwrap(),
+ );
+ let geom = match layout.direction {
+ Direction::Vertical => PaneGeom {
+ x: current_position,
+ y: space_to_split.y,
+ cols: split_dimension,
+ rows: inherited_dimension,
+ },
+ Direction::Horizontal => PaneGeom {
+ x: space_to_split.x,
+ y: current_position,
+ cols: inherited_dimension,
+ rows: split_dimension,
+ },
+ };
+ split_geom.push(geom);
+ current_position += layout_size(layout.direction, part);
}
- split_parts
-}
-
-fn split_space(
- space_to_split: &PositionAndSize,
- layout: &Layout,
-) -> Vec<(Layout, PositionAndSize)> {
- let mut pane_positions = Vec::new();
- let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect();
- let split_parts = match layout.direction {
- Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes),
- Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes),
- };
for (i, part) in layout.parts.iter().enumerate() {
- let part_position_and_size = split_parts.get(i).unwrap();
+ let part_position_and_size = split_geom.get(i).unwrap();
if !part.parts.is_empty() {
let mut part_positions = split_space(part_position_and_size, part);
pane_positions.append(&mut part_positions);
diff --git a/zellij-utils/src/input/unit/layout_test.rs b/zellij-utils/src/input/unit/layout_test.rs
index 23c57b3d6..abce56a4e 100644
--- a/zellij-utils/src/input/unit/layout_test.rs
+++ b/zellij-utils/src/input/unit/layout_test.rs
@@ -163,7 +163,7 @@ fn three_panes_with_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -174,14 +174,14 @@ fn three_panes_with_tab_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
],
@@ -263,7 +263,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -274,14 +274,14 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
],
@@ -380,7 +380,7 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(21)),
+ split_size: Some(SplitSize::Percent(21.0)),
run: None,
},
Layout {
@@ -391,7 +391,7 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(22)),
+ split_size: Some(SplitSize::Percent(22.0)),
run: None,
},
Layout {
@@ -402,47 +402,47 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(23)),
+ split_size: Some(SplitSize::Percent(23.0)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(24)),
+ split_size: Some(SplitSize::Percent(24.0)),
run: None,
},
],
- split_size: Some(SplitSize::Percent(78)),
+ split_size: Some(SplitSize::Percent(78.0)),
run: None,
},
],
- split_size: Some(SplitSize::Percent(79)),
+ split_size: Some(SplitSize::Percent(79.0)),
run: None,
},
],
- split_size: Some(SplitSize::Percent(90)),
+ split_size: Some(SplitSize::Percent(90.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(15)),
+ split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(15)),
+ split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(15)),
+ split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
],
@@ -484,7 +484,7 @@ fn three_tabs_tab_one_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -523,7 +523,7 @@ fn three_tabs_tab_two_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -534,7 +534,7 @@ fn three_tabs_tab_two_merged_correctly() {
run: None,
},
],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -573,7 +573,7 @@ fn three_tabs_tab_three_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@@ -584,7 +584,7 @@ fn three_tabs_tab_three_merged_correctly() {
run: None,
},
],
- split_size: Some(SplitSize::Percent(50)),
+ split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs
index 009d99a3d..a7cda9772 100644
--- a/zellij-utils/src/ipc.rs
+++ b/zellij-utils/src/ipc.rs
@@ -4,7 +4,7 @@ use crate::{
cli::CliArgs,
errors::{get_current_ctx, ErrorContext},
input::{actions::Action, layout::LayoutFromYaml, options::Options},
- pane_size::PositionAndSize,
+ pane_size::Size,
};
use interprocess::local_socket::LocalSocketStream;
use nix::unistd::dup;
@@ -39,7 +39,7 @@ pub enum ClientType {
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy)]
pub struct ClientAttributes {
- pub position_and_size: PositionAndSize,
+ pub size: Size,
pub palette: Palette,
}
@@ -57,13 +57,8 @@ pub enum ClientToServerMsg {
DetachSession(SessionId),
// Disconnect from the session we're connected to
DisconnectFromSession,*/
- TerminalResize(PositionAndSize),
- NewClient(
- ClientAttributes,
- Box<CliArgs>,
- Box<Options>,
- Option<LayoutFromYaml>,
- ),
+ TerminalResize(Size),
+ NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml),
AttachClient(ClientAttributes, bool, Options),
Action(Action),
ClientExited,
diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs
index db439b612..8ae766301 100644
--- a/zellij-utils/src/pane_size.rs
+++ b/zellij-utils/src/pane_size.rs
@@ -1,49 +1,144 @@
-use nix::pty::Winsize;
use serde::{Deserialize, Serialize};
use crate::position::Position;
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
/// in character rows and columns.
+#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
+pub struct PaneGeom {
+ pub x: usize,
+ pub y: usize,
+ pub rows: Dimension,
+ pub cols: Dimension,
+}
+
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
-pub struct PositionAndSize {
+pub struct Viewport {
pub x: usize,
pub y: usize,
pub rows: usize,
pub cols: usize,
- // FIXME: Honestly, these shouldn't exist and rows / columns should be enums like:
- // Dimension::Flex(usize) / Dimension::Fixed(usize), but 400+ compiler errors is more than
- // I'm in the mood for right now...
- pub rows_fixed: bool,
- pub cols_fixed: bool,
-}
-
-impl From<Winsize> for PositionAndSize {
- fn from(winsize: Winsize) -> PositionAndSize {
- PositionAndSize {
- cols: winsize.ws_col as usize,
- rows: winsize.ws_row as usize,
- ..Default::default()
+}
+
+#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
+pub struct Offset {
+ pub top: usize,
+ pub bottom: usize,
+ pub right: usize,
+ pub left: usize,
+}
+
+#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
+pub struct Size {
+ pub rows: usize,
+ pub cols: usize,
+}
+
+#[der