summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/input/layout.rs
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/input/layout.rs
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/input/layout.rs')
-rw-r--r--zellij-utils/src/input/layout.rs245
1 files changed, 99 insertions, 146 deletions
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);