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-utils/src/input/layout.rs | |
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-utils/src/input/layout.rs')
-rw-r--r-- | zellij-utils/src/input/layout.rs | 245 |
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); |