diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-02-17 12:05:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-17 12:05:50 +0100 |
commit | f1ff272b0b65f6d328fef24531ada67ea585ce85 (patch) | |
tree | 0e33aa4a1af21c0785fa30118ee42a21972497cf /zellij-server/src/ui | |
parent | 1517036c2489b2a7dc43230e2ba8a05841a69dbe (diff) |
feat(ui): swap layouts and stacked panes (#2167)
* relayout working with hard coded layout
* work
* refactor(layout): PaneLayout => TiledPaneLayout
* tests passing
* tests passing
* tests passing
* stacked panes and passing tests
* tests for stacked panes
* refactor(panes): stacked panes
* fix: focusing into stacked panes from the left/right
* fix(layouts): handle stacked layouts in the middle of the screen
* fix(pane-stack): focus correctly when coming to stack from above/below
* fix(stacked-panes): resize stack
* fix(stacked-panes): focus with mouse
* fix(stacked-panes): focus next pane
* fix(layout-applier): sane focus order
* fix(stacked-panes): better titles for one-liners
* fix(stacked-panes): handle moving pane location in stack
* fix(relayout): properly calculate display area
* fix(relayout): properly calculate rounding errors
* fix(stacked-panes): properly handle closing a pane near a stack
* fix(swap-layouts): adjust swap layout sort order
* feat(swap-layouts): ui + ux
* fix(swap-layouts): include base layout
* refactor(layout): remove unused method
* fix(swap-layouts): respect pane contents and focus
* work
* fix(swap-layouts): load swap layouts from external file
* fix(swap-layouts): properly truncate layout children
* fix(stacked-panes): allow stacked panes to become fullscreen
* fix(swap-layouts): work with multiple tabs
* fix(swap-layouts): embed/eject panes properly with auto-layout
* fix(stacked-panes): close last pane in stack
* fix(stacked-panes): move focus for all clients in stack
* fix(floating-panes): set layout damaged when moving panes
* fix(relayout): move out of unfitting layout when resizing whole tab
* fix(ui): background color for swap layout indicator
* fix(keybinds): add switch next layout in tmux
* fix(ui): swap layout indication in compact layout
* fix(compact): correct swap constraint
* fix(tests): tmux swap config shortcut
* fix(resizes): cache resizes so as not to confuse panes (eg. vim) with multiple resizes that it debounces weirdly
* feat(cli): dump swap layouts
* fix(ui): stacked panes without pane frames
* fix(ux): move pane forward/backwards also with floating panes
* refactor(lint): remove unused stuff
* refactor(tab): move swap layouts to separate file
* style(fmt): rustfmt
* style(fmt): rustfmt
* refactor(panes): various cleanups
* chore(deps): upgrade termwiz to get alt left-bracket
* fix(assets): merge conflicts of binary files
* style(fmt): rustfmt
* style(clippy): no thank you!
* chore(repo): remove garbage file
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r-- | zellij-server/src/ui/boundaries.rs | 14 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_boundaries_frame.rs | 133 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_contents_and_ui.rs | 15 |
3 files changed, 117 insertions, 45 deletions
diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs index 2ba3e67e5..29b614c10 100644 --- a/zellij-server/src/ui/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -436,6 +436,7 @@ pub struct Boundaries { pub boundary_characters: HashMap<Coordinates, BoundarySymbol>, } +#[allow(clippy::if_same_then_else)] impl Boundaries { pub fn new(viewport: Viewport) -> Self { Boundaries { @@ -444,6 +445,7 @@ impl Boundaries { } } pub fn add_rect(&mut self, rect: &dyn Pane, color: Option<PaletteColor>) { + let pane_is_stacked = rect.current_geom().is_stacked; if !self.is_fully_inside_screen(rect) { return; } @@ -456,8 +458,11 @@ impl Boundaries { let coordinates = Coordinates::new(boundary_x_coords, row); let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y { BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) + } else if row == first_row_coordinates && pane_is_stacked { + BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) } else if row == last_row_coordinates - 1 && row != self.viewport.y + self.viewport.rows - 1 + && !pane_is_stacked { BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) } else { @@ -471,7 +476,7 @@ impl Boundaries { self.boundary_characters.insert(coordinates, next_symbol); } } - if rect.y() > self.viewport.y { + if rect.y() > self.viewport.y && !pane_is_stacked { // top boundary let boundary_y_coords = rect.y() - 1; let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); @@ -504,6 +509,7 @@ impl Boundaries { BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) } else if row == last_row_coordinates - 1 && row != self.viewport.y + self.viewport.rows - 1 + && !pane_is_stacked { BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) } else { @@ -517,7 +523,7 @@ impl Boundaries { self.boundary_characters.insert(coordinates, next_symbol); } } - if self.rect_bottom_boundary_is_before_screen_edge(rect) { + if self.rect_bottom_boundary_is_before_screen_edge(rect) && !pane_is_stacked { // bottom boundary let boundary_y_coords = rect.bottom_boundary_y_coords() - 1; let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); @@ -570,8 +576,10 @@ impl Boundaries { rect.y() + rect.rows() < self.viewport.y + self.viewport.rows } fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize { + let pane_is_stacked = rect.current_geom().is_stacked; + let horizontal_frame_offset = if pane_is_stacked { 0 } else { 1 }; if rect.y() > self.viewport.y { - rect.y() - 1 + rect.y() - horizontal_frame_offset } else { self.viewport.y } diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 537a80c47..61ec7a4b9 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -75,6 +75,9 @@ pub struct FrameParams { pub style: Style, pub color: Option<PaletteColor>, pub other_cursors_exist_in_session: bool, + pub pane_is_stacked_under: bool, + pub pane_is_stacked_over: bool, + pub should_draw_pane_frames: bool, } #[derive(Default, PartialEq)] @@ -90,6 +93,9 @@ pub struct PaneFrame { pub other_focused_clients: Vec<ClientId>, exit_status: Option<ExitStatus>, is_first_run: bool, + pane_is_stacked_over: bool, + pane_is_stacked_under: bool, + should_draw_pane_frames: bool, } impl PaneFrame { @@ -111,6 +117,9 @@ impl PaneFrame { other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session, exit_status: None, is_first_run: false, + pane_is_stacked_over: frame_params.pane_is_stacked_over, + pane_is_stacked_under: frame_params.pane_is_stacked_under, + should_draw_pane_frames: frame_params.should_draw_pane_frames, } } pub fn add_exit_status(&mut self, exit_status: Option<i32>) { @@ -130,6 +139,17 @@ impl PaneFrame { background_color(" ", color.map(|c| c.0)) } fn get_corner(&self, corner: &'static str) -> &'static str { + let corner = if !self.should_draw_pane_frames + && (corner == boundary_type::TOP_LEFT || corner == boundary_type::TOP_RIGHT) + { + boundary_type::HORIZONTAL + } else if self.pane_is_stacked_under && corner == boundary_type::TOP_RIGHT { + boundary_type::BOTTOM_RIGHT + } else if self.pane_is_stacked_under && corner == boundary_type::TOP_LEFT { + boundary_type::BOTTOM_LEFT + } else { + corner + }; if self.style.rounded_corners { match corner { boundary_type::TOP_RIGHT => boundary_type::TOP_RIGHT_ROUND, @@ -323,6 +343,13 @@ impl PaneFrame { self.render_my_and_others_focus(max_length) } else if !self.other_focused_clients.is_empty() { self.render_other_focused_users(max_length) + } else if self.pane_is_stacked_under || self.pane_is_stacked_over { + let (first_part, first_part_len) = self.first_exited_held_title_part_full(); + if first_part_len <= max_length { + Some((first_part, first_part_len)) + } else { + None + } } else { None } @@ -617,6 +644,14 @@ impl PaneFrame { .or_else(|| Some(self.title_line_without_middle())) .with_context(|| format!("failed to render title '{}'", self.title)) } + fn render_one_line_title(&self) -> Result<Vec<TerminalCharacter>> { + let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners + + self.render_title_middle(total_title_length) + .map(|(middle, middle_length)| self.title_line_with_middle(middle, &middle_length)) + .or_else(|| Some(self.title_line_without_middle())) + .with_context(|| format!("failed to render title '{}'", self.title)) + } fn render_held_undertitle(&self) -> Result<Vec<TerminalCharacter>> { let max_undertitle_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let (mut first_part, first_part_len) = self.first_exited_held_title_part_full(); @@ -678,55 +713,69 @@ impl PaneFrame { pub fn render(&self) -> Result<(Vec<CharacterChunk>, Option<String>)> { let err_context = || "failed to render pane frame"; let mut character_chunks = vec![]; - for row in 0..self.geom.rows { - if row == 0 { - // top row - let title = self.render_title().with_context(err_context)?; - let x = self.geom.x; - let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new(title, x, y)); - } else if row == self.geom.rows - 1 { - // bottom row - if self.exit_status.is_some() || self.is_first_run { + if self.geom.rows == 1 || !self.should_draw_pane_frames { + // we do this explicitly when not drawing pane frames because this should only happen + // if this is a stacked pane with pane frames off (and it doesn't necessarily have only + // 1 row because it could also be a flexible stacked pane) + // in this case we should always draw the pane title line, and only the title line + let one_line_title = self.render_one_line_title().with_context(err_context)?; + character_chunks.push(CharacterChunk::new( + one_line_title, + self.geom.x, + self.geom.y, + )); + } else { + for row in 0..self.geom.rows { + if row == 0 { + // top row + let title = self.render_title().with_context(err_context)?; let x = self.geom.x; let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new( - self.render_held_undertitle().with_context(err_context)?, - x, - y, - )); - } else { - let mut bottom_row = vec![]; - for col in 0..self.geom.cols { - let boundary = if col == 0 { - // bottom left corner - self.get_corner(boundary_type::BOTTOM_LEFT) - } else if col == self.geom.cols - 1 { - // bottom right corner - self.get_corner(boundary_type::BOTTOM_RIGHT) - } else { - boundary_type::HORIZONTAL - }; + character_chunks.push(CharacterChunk::new(title, x, y)); + } else if row == self.geom.rows - 1 { + // bottom row + if self.exit_status.is_some() || self.is_first_run { + let x = self.geom.x; + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new( + self.render_held_undertitle().with_context(err_context)?, + x, + y, + )); + } else { + let mut bottom_row = vec![]; + for col in 0..self.geom.cols { + let boundary = if col == 0 { + // bottom left corner + self.get_corner(boundary_type::BOTTOM_LEFT) + } else if col == self.geom.cols - 1 { + // bottom right corner + self.get_corner(boundary_type::BOTTOM_RIGHT) + } else { + boundary_type::HORIZONTAL + }; - let mut boundary_character = foreground_color(boundary, self.color); - bottom_row.append(&mut boundary_character); + let mut boundary_character = foreground_color(boundary, self.color); + bottom_row.append(&mut boundary_character); + } + let x = self.geom.x; + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new(bottom_row, x, y)); } + } else { + let boundary_character_left = + foreground_color(boundary_type::VERTICAL, self.color); + let boundary_character_right = + foreground_color(boundary_type::VERTICAL, self.color); + let x = self.geom.x; let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new(bottom_row, x, y)); - } - } else { - let boundary_character_left = foreground_color(boundary_type::VERTICAL, self.color); - let boundary_character_right = - foreground_color(boundary_type::VERTICAL, self.color); - - let x = self.geom.x; - let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new(boundary_character_left, x, y)); + character_chunks.push(CharacterChunk::new(boundary_character_left, x, y)); - let x = (self.geom.x + self.geom.cols).saturating_sub(1); - let y = self.geom.y + row; - character_chunks.push(CharacterChunk::new(boundary_character_right, x, y)); + let x = (self.geom.x + self.geom.cols).saturating_sub(1); + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new(boundary_character_right, x, y)); + } } } Ok((character_chunks, None)) diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs index 7f718d0b0..72f0b8049 100644 --- a/zellij-server/src/ui/pane_contents_and_ui.rs +++ b/zellij-server/src/ui/pane_contents_and_ui.rs @@ -16,6 +16,9 @@ pub struct PaneContentsAndUi<'a> { focused_clients: Vec<ClientId>, multiple_users_exist_in_session: bool, z_index: Option<usize>, + pane_is_stacked_under: bool, + pane_is_stacked_over: bool, + should_draw_pane_frames: bool, } impl<'a> PaneContentsAndUi<'a> { @@ -26,6 +29,9 @@ impl<'a> PaneContentsAndUi<'a> { active_panes: &HashMap<ClientId, PaneId>, multiple_users_exist_in_session: bool, z_index: Option<usize>, + pane_is_stacked_under: bool, + pane_is_stacked_over: bool, + should_draw_pane_frames: bool, ) -> Self { let mut focused_clients: Vec<ClientId> = active_panes .iter() @@ -40,6 +46,9 @@ impl<'a> PaneContentsAndUi<'a> { focused_clients, multiple_users_exist_in_session, z_index, + pane_is_stacked_under, + pane_is_stacked_over, + should_draw_pane_frames, } } pub fn render_pane_contents_to_multiple_clients( @@ -194,6 +203,9 @@ impl<'a> PaneContentsAndUi<'a> { style: self.style, color: frame_color, other_cursors_exist_in_session: false, + pane_is_stacked_over: self.pane_is_stacked_over, + pane_is_stacked_under: self.pane_is_stacked_under, + should_draw_pane_frames: self.should_draw_pane_frames, } } else { FrameParams { @@ -203,6 +215,9 @@ impl<'a> PaneContentsAndUi<'a> { style: self.style, color: frame_color, other_cursors_exist_in_session: self.multiple_users_exist_in_session, + pane_is_stacked_over: self.pane_is_stacked_over, + pane_is_stacked_under: self.pane_is_stacked_under, + should_draw_pane_frames: self.should_draw_pane_frames, } }; |