From f2c5ee44f7f2c61f50b043bd8e55f915a3667fce Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Sat, 29 May 2021 23:12:11 +0100 Subject: [PATCH] Getting back to where we started... (Buggy Resizing) --- Cargo.lock | 7 + default-plugins/status-bar/src/main.rs | 2 +- default-plugins/tab-bar/src/main.rs | 2 +- zellij-server/Cargo.toml | 1 + zellij-server/src/lib.rs | 2 + zellij-server/src/panes/plugin_pane.rs | 32 +- zellij-server/src/panes/terminal_pane.rs | 27 +- zellij-server/src/screen.rs | 16 +- zellij-server/src/tab.rs | 40 +- zellij-server/src/ui/layout.rs | 29 +- zellij-server/src/ui/pane_resizer.rs | 720 +++++++---------------- zellij-server/src/wasm_vm.rs | 22 +- zellij-tile/src/shim.rs | 11 +- zellij-utils/src/errors.rs | 3 +- zellij-utils/src/pane_size.rs | 7 +- 15 files changed, 327 insertions(+), 594 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc862d53..31e038a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cc" version = "1.0.67" @@ -2345,6 +2351,7 @@ version = "0.13.0" dependencies = [ "ansi_term 0.12.1", "async-trait", + "cassowary", "daemonize", "insta", "serde_json", diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index cf9445d6..83eb50c9 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -135,7 +135,7 @@ impl ZellijPlugin for State { fn load(&mut self) { set_selectable(false); set_invisible_borders(true); - set_max_height(2); + set_fixed_height(2); subscribe(&[EventType::ModeUpdate]); } diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index 46336be3..7ff639e8 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -26,7 +26,7 @@ impl ZellijPlugin for State { fn load(&mut self) { set_selectable(false); set_invisible_borders(true); - set_max_height(1); + set_fixed_height(1); subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]); } diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index 1168dac4..b04bd04d 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -16,6 +16,7 @@ serde_json = "1.0" unicode-width = "0.1.8" wasmer = "1.0.0" wasmer-wasi = "1.0.0" +cassowary = "0.3.0" zellij-utils = { path = "../zellij-utils/", version = "0.13.0" } [dev-dependencies] diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 76b4b7b1..6e6392c2 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -112,6 +112,8 @@ pub fn start_server(os_input: Box, socket_path: PathBuf) { daemonize::Daemonize::new() .working_directory(std::env::current_dir().unwrap()) .umask(0o077) + // FIXME: My cherished `dbg!` was broken, so this is a hack to bring it back + .stderr(std::fs::File::create("dbg.log").unwrap()) .start() .expect("could not daemonize the server process"); diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index e5c5e15d..b08d528f 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -16,8 +16,6 @@ pub(crate) struct PluginPane { pub position_and_size: PositionAndSize, pub position_and_size_override: Option, pub send_plugin_instructions: SenderWithContext, - pub max_height: Option, - pub max_width: Option, pub active_at: Instant, } @@ -35,8 +33,6 @@ impl PluginPane { position_and_size, position_and_size_override: None, send_plugin_instructions, - max_height: None, - max_width: None, active_at: Instant::now(), } } @@ -95,7 +91,9 @@ impl Pane for PluginPane { fn adjust_input_to_terminal(&self, _input_bytes: Vec) -> Vec { unimplemented!() // FIXME: Shouldn't need this implmented? } - + fn position_and_size(&self) -> PositionAndSize { + self.position_and_size + } fn position_and_size_override(&self) -> Option { self.position_and_size_override } @@ -114,11 +112,13 @@ impl Pane for PluginPane { fn set_invisible_borders(&mut self, invisible_borders: bool) { self.invisible_borders = invisible_borders; } - fn set_max_height(&mut self, max_height: usize) { - self.max_height = Some(max_height); + fn set_fixed_height(&mut self, fixed_height: usize) { + self.position_and_size.rows = fixed_height; + self.position_and_size.rows_fixed = true; } - fn set_max_width(&mut self, max_width: usize) { - self.max_width = Some(max_width); + fn set_fixed_width(&mut self, fixed_width: usize) { + self.position_and_size.columns = fixed_width; + self.position_and_size.cols_fixed = true; } fn render(&mut self) -> Option { // if self.should_render { @@ -204,11 +204,21 @@ impl Pane for PluginPane { fn clear_scroll(&mut self) { unimplemented!() } + // FIXME: This need to be reevaluated and deleted if possible. + // `max` doesn't make sense when things are fixed... fn max_height(&self) -> Option { - self.max_height + if self.position_and_size.rows_fixed { + Some(self.position_and_size.rows) + } else { + None + } } fn max_width(&self) -> Option { - self.max_width + if self.position_and_size.cols_fixed { + Some(self.position_and_size.columns) + } else { + None + } } fn invisible_borders(&self) -> bool { self.invisible_borders diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 32910618..2dbd11a0 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -27,8 +27,6 @@ pub struct TerminalPane { pub selectable: bool, pub position_and_size: PositionAndSize, pub position_and_size_override: Option, - pub max_height: Option, - pub max_width: Option, pub active_at: Instant, pub colors: Palette, vte_parser: vte::Parser, @@ -52,8 +50,7 @@ impl Pane for TerminalPane { self.reflow_lines(); } fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) { - self.position_and_size.columns = position_and_size.columns; - self.position_and_size.rows = position_and_size.rows; + self.position_and_size = *position_and_size; self.reflow_lines(); } fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) { @@ -119,7 +116,9 @@ impl Pane for TerminalPane { }; input_bytes } - + fn position_and_size(&self) -> PositionAndSize { + self.position_and_size + } fn position_and_size_override(&self) -> Option { self.position_and_size_override } @@ -135,21 +134,17 @@ impl Pane for TerminalPane { fn set_selectable(&mut self, selectable: bool) { self.selectable = selectable; } - fn set_max_height(&mut self, max_height: usize) { - self.max_height = Some(max_height); + fn set_fixed_height(&mut self, fixed_height: usize) { + self.position_and_size.rows = fixed_height; + self.position_and_size.rows_fixed = true; } - fn set_max_width(&mut self, max_width: usize) { - self.max_width = Some(max_width); + fn set_fixed_width(&mut self, fixed_width: usize) { + self.position_and_size.columns = fixed_width; + self.position_and_size.cols_fixed = true; } fn set_invisible_borders(&mut self, _invisible_borders: bool) { unimplemented!(); } - fn max_height(&self) -> Option { - self.max_height - } - fn max_width(&self) -> Option { - self.max_width - } fn render(&mut self) -> Option { if self.should_render() { let mut vte_output = String::new(); @@ -294,8 +289,6 @@ impl TerminalPane { selectable: true, position_and_size, position_and_size_override: None, - max_height: None, - max_width: None, vte_parser: vte::Parser::new(), active_at: Instant::now(), colors: palette, diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 48be44bf..ab34dfaf 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -55,7 +55,8 @@ pub(crate) enum ScreenInstruction { CloseFocusedPane, ToggleActiveTerminalFullscreen, SetSelectable(PaneId, bool), - SetMaxHeight(PaneId, usize), + SetFixedHeight(PaneId, usize), + SetFixedWidth(PaneId, usize), SetInvisibleBorders(PaneId, bool), ClosePane(PaneId), ApplyLayout(Layout, Vec), @@ -106,7 +107,8 @@ impl From<&ScreenInstruction> for ScreenContext { } ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, - ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, + ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight, + ScreenInstruction::SetFixedWidth(..) => ScreenContext::SetFixedWidth, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, ScreenInstruction::NewTab(_) => ScreenContext::NewTab, @@ -574,11 +576,17 @@ pub(crate) fn screen_thread_main( .unwrap() .set_pane_selectable(id, selectable); } - ScreenInstruction::SetMaxHeight(id, max_height) => { + ScreenInstruction::SetFixedHeight(id, fixed_height) => { screen .get_active_tab_mut() .unwrap() - .set_pane_max_height(id, max_height); + .set_pane_fixed_height(id, fixed_height); + } + ScreenInstruction::SetFixedWidth(id, fixed_width) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_fixed_width(id, fixed_width); } ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => { screen diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 7daf2092..45f386c7 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -104,15 +104,15 @@ pub trait Pane { fn handle_pty_bytes(&mut self, bytes: VteBytes); fn cursor_coordinates(&self) -> Option<(usize, usize)>; fn adjust_input_to_terminal(&self, input_bytes: Vec) -> Vec; - + fn position_and_size(&self) -> PositionAndSize; fn position_and_size_override(&self) -> Option; fn should_render(&self) -> bool; fn set_should_render(&mut self, should_render: bool); fn selectable(&self) -> bool; fn set_selectable(&mut self, selectable: bool); fn set_invisible_borders(&mut self, invisible_borders: bool); - fn set_max_height(&mut self, max_height: usize); - fn set_max_width(&mut self, max_width: usize); + fn set_fixed_height(&mut self, fixed_height: usize); + fn set_fixed_width(&mut self, fixed_width: usize); fn render(&mut self) -> Option; fn pid(&self) -> PaneId; fn reduce_height_down(&mut self, count: usize); @@ -178,15 +178,6 @@ pub trait Pane { std::cmp::min(self.x() + self.columns(), other.x() + other.columns()) - std::cmp::max(self.x(), other.x()) } - fn position_and_size(&self) -> PositionAndSize { - PositionAndSize { - x: self.x(), - y: self.y(), - columns: self.columns(), - rows: self.rows(), - ..Default::default() - } - } fn can_increase_height_by(&self, increase_by: usize) -> bool { self.max_height() .map(|max_height| self.rows() + increase_by <= max_height) @@ -294,12 +285,6 @@ impl Tab { match positions_and_size.next() { Some((_, position_and_size)) => { terminal_pane.reset_size_and_position_override(); - if let Some(max_rows) = position_and_size.max_rows { - terminal_pane.set_max_height(max_rows); - } - if let Some(max_columns) = position_and_size.max_columns { - terminal_pane.set_max_width(max_columns); - } terminal_pane.change_pos_and_size(&position_and_size); self.os_api.set_terminal_size_using_fd( *pid, @@ -317,24 +302,18 @@ impl Tab { } let mut new_pids = new_pids.iter(); for (layout, position_and_size) in positions_and_size { - // Just a regular terminal + // A plugin pane if let Some(plugin) = &layout.plugin { let (pid_tx, pid_rx) = channel(); self.senders .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone())) .unwrap(); let pid = pid_rx.recv().unwrap(); - let mut new_plugin = PluginPane::new( + let new_plugin = PluginPane::new( pid, *position_and_size, self.senders.to_plugin.as_ref().unwrap().clone(), ); - if let Some(max_rows) = position_and_size.max_rows { - new_plugin.set_max_height(max_rows); - } - if let Some(max_columns) = position_and_size.max_columns { - new_plugin.set_max_width(max_columns); - } self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); // Send an initial mode update to the newly loaded plugin only! self.senders @@ -2121,9 +2100,14 @@ impl Tab { pane.set_invisible_borders(invisible_borders); } } - pub fn set_pane_max_height(&mut self, id: PaneId, max_height: usize) { + pub fn set_pane_fixed_height(&mut self, id: PaneId, fixed_height: usize) { if let Some(pane) = self.panes.get_mut(&id) { - pane.set_max_height(max_height); + pane.set_fixed_height(fixed_height); + } + } + pub fn set_pane_fixed_width(&mut self, id: PaneId, fixed_width: usize) { + if let Some(pane) = self.panes.get_mut(&id) { + pane.set_fixed_width(fixed_width); } } pub fn close_pane(&mut self, id: PaneId) { diff --git a/zellij-server/src/ui/layout.rs b/zellij-server/src/ui/layout.rs index a9af665f..7a19d889 100644 --- a/zellij-server/src/ui/layout.rs +++ b/zellij-server/src/ui/layout.rs @@ -19,17 +19,14 @@ fn split_space_to_parts_vertically( // First fit in the parameterized sizes for size in sizes { - let (columns, max_columns) = match size { + let columns = match size { Some(SplitSize::Percent(percent)) => { - ((max_width as f32 * (percent as f32 / 100.0)) as usize, None) + (max_width as f32 * (percent as f32 / 100.0)) as usize } // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), + Some(SplitSize::Fixed(size)) => size as usize, None => { parts_to_grow.push(current_x_position); - ( - 1, // This is grown later on - None, - ) + 1 // This is grown later on } }; split_parts.push(PositionAndSize { @@ -37,7 +34,6 @@ fn split_space_to_parts_vertically( y: space_to_split.y, columns, rows: space_to_split.rows, - max_columns, ..Default::default() }); current_width += columns; @@ -87,18 +83,14 @@ fn split_space_to_parts_horizontally( let mut parts_to_grow = Vec::new(); for size in sizes { - let (rows, max_rows) = match size { - Some(SplitSize::Percent(percent)) => ( - (max_height as f32 * (percent as f32 / 100.0)) as usize, - None, - ), // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), + 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 - None, - ) + 1 // This is grown later on } }; split_parts.push(PositionAndSize { @@ -106,7 +98,6 @@ fn split_space_to_parts_horizontally( y: current_y_position, columns: space_to_split.columns, rows, - max_rows, ..Default::default() }); current_height += rows; diff --git a/zellij-server/src/ui/pane_resizer.rs b/zellij-server/src/ui/pane_resizer.rs index b2001945..73673a9c 100644 --- a/zellij-server/src/ui/pane_resizer.rs +++ b/zellij-server/src/ui/pane_resizer.rs @@ -1,531 +1,247 @@ use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane}; +use cassowary::{ + strength::{REQUIRED, STRONG}, + Constraint, Solver, Variable, + WeightedRelation::*, +}; use std::{ - cmp::Ordering, collections::{BTreeMap, HashSet}, + ops::Not, }; use zellij_utils::pane_size::PositionAndSize; -pub(crate) struct PaneResizer<'a> { +const GAP_SIZE: usize = 1; // Panes are separated by this number of rows / columns + +pub struct PaneResizer<'a> { panes: &'a mut BTreeMap>, + vars: BTreeMap, + solver: Solver, os_api: &'a mut Box, } +#[derive(Debug, Clone, Copy)] +enum Direction { + Horizontal, + Vertical, +} + +impl Not for Direction { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Direction::Horizontal => Direction::Vertical, + Direction::Vertical => Direction::Horizontal, + } + } +} + +#[derive(Debug, Clone, Copy)] +struct Span { + pid: PaneId, + direction: Direction, + fixed: bool, + pos: usize, + size: usize, + pos_var: Variable, + size_var: Variable, +} + // TODO: currently there are some functions here duplicated with Tab // all resizing functions should move here +// FIXME: +// 1. Rounding causes a loss of ratios, I need to store an internal f64 for +// each pane as well as the displayed usize and add custom rounding logic. +// 2. Vertical resizing doesn't seem to respect the space consumed by the tab +// and status bars? +// 3. A 2x2 layout and simultaneous vertical + horizontal resizing sometimes +// leads to unsolvable constraints? Maybe related to 2 (and possibly 1). +// I should sanity-check the `spans_in_boundary()` here! + impl<'a> PaneResizer<'a> { pub fn new( panes: &'a mut BTreeMap>, os_api: &'a mut Box, ) -> Self { - PaneResizer { panes, os_api } + let mut vars = BTreeMap::new(); + for &k in panes.keys() { + vars.insert(k, (Variable::new(), Variable::new())); + } + PaneResizer { + panes, + vars, + solver: Solver::new(), + os_api, + } } + pub fn resize( &mut self, - mut current_size: PositionAndSize, + 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.columns.cmp(¤t_size.columns) { - Ordering::Greater => { - let increase_by = new_size.columns - current_size.columns; - if let Some(panes_to_resize) = find_increasable_vertical_chain( - &self.panes, - increase_by, - current_size.columns, - current_size.rows, - ) { - self.increase_panes_right_and_push_adjacents_right( - panes_to_resize, - increase_by, - ); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.columns - new_size.columns; - if let Some(panes_to_resize) = find_reducible_vertical_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), + let col_delta = new_size.columns as isize - current_size.columns as isize; + let row_delta = new_size.rows as isize - current_size.rows as isize; + if col_delta != 0 { + let spans = self.solve_direction(Direction::Horizontal, new_size.columns)?; + self.collapse_spans(&spans); } - 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.columns, - current_size.rows, - ) { - self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.rows - new_size.rows; - if let Some(panes_to_resize) = find_reducible_horizontal_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), + self.solver.reset(); + if row_delta != 0 { + let spans = self.solve_direction(Direction::Vertical, new_size.rows)?; + self.collapse_spans(&spans); } - if successfully_resized { - Some((column_difference, row_difference)) + Some((col_delta, row_delta)) + } + + fn solve_direction(&mut self, direction: Direction, space: usize) -> Option> { + let mut grid = Vec::new(); + for boundary in self.grid_boundaries(direction) { + grid.push(self.spans_in_boundary(direction, boundary)); + } + + let constraints: Vec<_> = grid + .iter() + .flat_map(|s| constrain_spans(space, s)) + .collect(); + + // FIXME: This line needs to be restored before merging! + //self.solver.add_constraints(&constraints).ok()?; + self.solver.add_constraints(&constraints).unwrap(); + Some(grid.into_iter().flatten().collect()) + } + + fn grid_boundaries(&self, direction: Direction) -> Vec<(usize, usize)> { + // Select the spans running *perpendicular* to the direction of resize + let spans: Vec = self + .panes + .values() + .map(|p| self.get_span(!direction, p.as_ref())) + .collect(); + + let mut last_edge = 0; + let mut bounds = Vec::new(); + loop { + let mut spans_on_edge: Vec<&Span> = + spans.iter().filter(|p| p.pos == last_edge).collect(); + spans_on_edge.sort_unstable_by_key(|s| s.size); + if let Some(next) = spans_on_edge.first() { + let next_edge = last_edge + next.size; + bounds.push((last_edge, next_edge)); + last_edge = next_edge + GAP_SIZE; + } else { + break; + } + } + bounds + } + + fn spans_in_boundary(&self, direction: Direction, boundary: (usize, usize)) -> Vec { + let (start, end) = boundary; + let bwn = |v| start <= v && v < end; + let mut spans: Vec<_> = self + .panes + .values() + .filter(|p| { + let s = self.get_span(!direction, p.as_ref()); + bwn(s.pos) || bwn(s.pos + s.size) + }) + .map(|p| self.get_span(direction, p.as_ref())) + .collect(); + spans.sort_unstable_by_key(|s| s.pos); + spans + } + + fn get_span(&self, direction: Direction, pane: &dyn Pane) -> Span { + let pas = pane.position_and_size(); + let (pos_var, size_var) = self.vars[&pane.pid()]; + match direction { + Direction::Horizontal => Span { + pid: pane.pid(), + direction, + fixed: pas.cols_fixed, + pos: pas.x, + size: pas.columns, + pos_var, + size_var, + }, + Direction::Vertical => Span { + pid: pane.pid(), + direction, + fixed: pas.rows_fixed, + pos: pas.y, + size: pas.rows, + pos_var, + size_var, + }, + } + } + + fn collapse_spans(&mut self, spans: &[Span]) { + for span in spans { + let solver = &self.solver; // Hand-holding the borrow-checker + let pane = self.panes.get_mut(&span.pid).unwrap(); + let fetch_usize = |v| solver.get_value(v).round() as usize; + match span.direction { + Direction::Horizontal => pane.change_pos_and_size(&PositionAndSize { + x: fetch_usize(span.pos_var), + columns: fetch_usize(span.size_var), + ..pane.position_and_size() + }), + Direction::Vertical => pane.change_pos_and_size(&PositionAndSize { + y: fetch_usize(span.pos_var), + rows: fetch_usize(span.size_var), + ..pane.position_and_size() + }), + } + if let PaneId::Terminal(pid) = pane.pid() { + self.os_api.set_terminal_size_using_fd( + pid, + pane.columns() as u16, + pane.rows() as u16, + ); + } + } + } +} + +fn constrain_spans(space: usize, spans: &[Span]) -> HashSet { + let mut constraints = HashSet::new(); + + // The first span needs to start at 0 + constraints.insert(spans[0].pos_var | EQ(REQUIRED) | 0.0); + + // Calculating "flexible" space (space not consumed by fixed-size spans) + let gap_space = GAP_SIZE * (spans.len() - 1); + let old_flex_space = spans + .iter() + .fold(0, |a, s| if !s.fixed { a + s.size } else { a }); + let new_flex_space = spans.iter().fold( + space - gap_space, + |a, s| if s.fixed { a - s.size } else { a }, + ); + + // Keep spans stuck together + for pair in spans.windows(2) { + let (ls, rs) = (pair[0], pair[1]); + constraints + .insert((ls.pos_var + ls.size_var + GAP_SIZE as f64) | EQ(REQUIRED) | rs.pos_var); + } + + // Try to maintain ratios and lock non-flexible sizes + for span in spans { + if span.fixed { + constraints.insert(span.size_var | EQ(REQUIRED) | span.size as f64); } else { - None - } - } - fn reduce_panes_left_and_pull_adjacents_left( - &mut self, - panes_to_reduce: Vec, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_left(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_width_left(&pane_id, reduce_by); - } - } - fn reduce_panes_up_and_pull_adjacents_up( - &mut self, - panes_to_reduce: Vec, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_up(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_height_up(&pane_id, reduce_by); - } - } - fn increase_panes_down_and_push_down_adjacents( - &mut self, - panes_to_increase: Vec, - increase_by: usize, - ) { - let mut pushed_panes: HashSet = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_down(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_height_down(&pane_id, increase_by); - } - } - fn increase_panes_right_and_push_adjacents_right( - &mut self, - panes_to_increase: Vec, - increase_by: usize, - ) { - let mut pushed_panes: HashSet = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_right(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_width_right(&pane_id, increase_by); - } - } - fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_height_up(count); - if let PaneId::Terminal(pid) = id { - self.os_api - .set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_height_down(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_width_right(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_width_left(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } -} - -fn find_next_increasable_horizontal_pane( - panes: &BTreeMap>, - right_of: &dyn Pane, - increase_by: usize, -) -> Option { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_increasable_vertical_pane( - panes: &BTreeMap>, - below: &dyn Pane, - increase_by: usize, -) -> Option { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_reducible_vertical_pane( - panes: &BTreeMap>, - below: &dyn Pane, - reduce_by: usize, -) -> Option { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_reducible_horizontal_pane( - panes: &BTreeMap>, - right_of: &dyn Pane, - reduce_by: usize, -) -> Option { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_increasable_horizontal_chain( - panes: &BTreeMap>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } - - match panes - .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - { - Some(leftmost_pane) => { - if !leftmost_pane.can_increase_height_by(increase_by) { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = leftmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.x() + current_pane.columns() == screen_width { - return Some(panes_to_resize); - } - match find_next_increasable_horizontal_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_increasable_vertical_chain( - panes: &BTreeMap>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option> { - let mut vertical_coordinate = 0; - loop { - if vertical_coordinate == screen_width { - return None; - } - - match panes - .values() - .find(|p| p.y() == 0 && p.x() == vertical_coordinate) - { - Some(topmost_pane) => { - if !topmost_pane.can_increase_width_by(increase_by) { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = topmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.y() + current_pane.rows() == screen_height { - return Some(panes_to_resize); - } - match find_next_increasable_vertical_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_reducible_horizontal_chain( - panes: &BTreeMap>, - reduce_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } - - match panes - .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - { - Some(leftmost_pane) => { - if !leftmost_pane.can_reduce_height_by(reduce_by) { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = leftmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.x() + current_pane.columns() == screen_width { - return Some(panes_to_resize); - } - match find_next_reducible_horizontal_pane( - panes, - current_pane.as_ref(), - reduce_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_reducible_vertical_chain( - panes: &BTreeMap>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option> { - let mut vertical_coordinate = 0; - loop { - if vertical_coordinate == screen_width { - return None; - } - - match panes - .values() - .find(|p| p.y() == 0 && p.x() == vertical_coordinate) - { - Some(topmost_pane) => { - if !topmost_pane.can_reduce_width_by(increase_by) { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = topmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.y() + current_pane.rows() == screen_height { - return Some(panes_to_resize); - } - match find_next_reducible_vertical_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - break; - } - }; - } - } - None => { - return None; - } + let ratio = span.size as f64 / old_flex_space as f64; + constraints.insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | ratio); } } + + // The last pane needs to end at the end of the space + let last = spans.last().unwrap(); + constraints.insert((last.pos_var + last.size_var) | EQ(REQUIRED) | space as f64); + + constraints } diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 53dceaa7..62198d68 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -158,7 +158,8 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj host_subscribe, host_unsubscribe, host_set_invisible_borders, - host_set_max_height, + host_set_fixed_height, + host_set_fixed_width, host_set_selectable, host_get_plugin_ids, host_open_file, @@ -189,13 +190,24 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { .unwrap() } -fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { - let max_height = max_height as usize; +fn host_set_fixed_height(plugin_env: &PluginEnv, fixed_height: i32) { + let fixed_height = fixed_height as usize; plugin_env .senders - .send_to_screen(ScreenInstruction::SetMaxHeight( + .send_to_screen(ScreenInstruction::SetFixedHeight( PaneId::Plugin(plugin_env.plugin_id), - max_height, + fixed_height, + )) + .unwrap() +} + +fn host_set_fixed_width(plugin_env: &PluginEnv, fixed_width: i32) { + let fixed_width = fixed_width as usize; + plugin_env + .senders + .send_to_screen(ScreenInstruction::SetFixedWidth( + PaneId::Plugin(plugin_env.plugin_id), + fixed_width, )) .unwrap() } diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 856dcc54..ed033d4b 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -17,8 +17,12 @@ pub fn unsubscribe(event_types: &[EventType]) { // Plugin Settings -pub fn set_max_height(max_height: i32) { - unsafe { host_set_max_height(max_height) }; +pub fn set_fixed_height(fixed_height: i32) { + unsafe { host_set_fixed_height(fixed_height) }; +} + +pub fn set_fixed_width(fixed_width: i32) { + unsafe { host_set_fixed_width(fixed_width) }; } pub fn set_selectable(selectable: bool) { @@ -64,7 +68,8 @@ pub fn object_to_stdout(object: &impl Serialize) { extern "C" { fn host_subscribe(); fn host_unsubscribe(); - fn host_set_max_height(max_height: i32); + fn host_set_fixed_height(fixed_height: i32); + fn host_set_fixed_width(fixed_width: i32); fn host_set_selectable(selectable: i32); fn host_set_invisible_borders(invisible_borders: i32); fn host_get_plugin_ids(); diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index b47ddb9a..b37a3a07 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -207,7 +207,8 @@ pub enum ScreenContext { ToggleActiveTerminalFullscreen, SetSelectable, SetInvisibleBorders, - SetMaxHeight, + SetFixedHeight, + SetFixedWidth, ClosePane, ApplyLayout, NewTab, diff --git a/zellij-utils/src/pane_size.rs b/zellij-utils/src/pane_size.rs index 2744c43f..b242b8ba 100644 --- a/zellij-utils/src/pane_size.rs +++ b/zellij-utils/src/pane_size.rs @@ -9,8 +9,11 @@ pub struct PositionAndSize { pub y: usize, pub rows: usize, pub columns: usize, - pub max_rows: Option, - pub max_columns: Option, + // 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 for PositionAndSize {