From 76a5bc8a05c33fb3f46cff1ce95aa1af694b9927 Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Sat, 28 Aug 2021 17:46:24 +0100 Subject: feat(ui): overhauled resize and layout systems MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- zellij-utils/src/input/layout.rs | 245 ++++++++++++++++----------------------- 1 file changed, 99 insertions(+), 146 deletions(-) (limited to 'zellij-utils/src/input/layout.rs') 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>, -) -> Vec { - 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::(); + max(size, children_size) } } - split_parts + child_layout_size(direction, direction, layout) } -fn split_space_to_parts_horizontally( - space_to_split: &PositionAndSize, - sizes: Vec>, -) -> Vec { - 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> = 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::() + } 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> = 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); -- cgit v1.2.3