summaryrefslogtreecommitdiffstats
path: root/zellij-server/src/ui
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2023-02-17 12:05:50 +0100
committerGitHub <noreply@github.com>2023-02-17 12:05:50 +0100
commitf1ff272b0b65f6d328fef24531ada67ea585ce85 (patch)
tree0e33aa4a1af21c0785fa30118ee42a21972497cf /zellij-server/src/ui
parent1517036c2489b2a7dc43230e2ba8a05841a69dbe (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.rs14
-rw-r--r--zellij-server/src/ui/pane_boundaries_frame.rs133
-rw-r--r--zellij-server/src/ui/pane_contents_and_ui.rs15
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,
}
};