diff options
author | Aram Drevekenin <aram@poor.dev> | 2022-02-18 21:10:06 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-18 21:10:06 +0100 |
commit | 821e7cbc5a9711173061a272dc0378fff1fe5596 (patch) | |
tree | d7806a3d88dbf70d27996d9f85f66e93135670b2 /zellij-server/src/ui | |
parent | 10a22c479ffb4d76627eadbee2dc4b53ae4309c3 (diff) |
feat(ui): add floating panes (#1066)
* basic functionality
* close and reopen scratch terminal working
* embed/float and resize whole tab for floating and static floating panes
* move focus working
* fix focus change in floating panes
* move pane with mouse
* floating z indices
* tests and better resize algorithm
* starting to work on performance
* some performance experimentations
* new render engine
* reverse painters algorithm for floating panes
* fix frame buffering
* improve ux situation
* handle multiple new panes on screen without overlap
* adjust keybindings
* adjust key hints
* fix multiuser frame ui
* fix various floating/multiuser bugs
* remove stuff
* wide characters under floating panes
* fix wide character frame override
* fix non-frame boundaries interactions with floating panes
* fix selection character width
* fix title frame wide char overflow
* fix existing tests
* add tests
* refactor output out of tab
* refactor floating panes out of tab
* refactor tab
* moar refactoring
* refactorings and bring back terminal window title setting
* add frame vte output
* remove more unused stuff
* remove even more unused stuff
* you know the drill
* refactor floating panes and remove more stuffs
* refactor pane grids
* remove unused output caching
* refactor output
* remove unused stuff
* rustfmt
* some formatting
* rustfmt
* reduce clippy to normal
* remove comment
* remove unused
* fix closign pane
* fix tests
Diffstat (limited to 'zellij-server/src/ui')
-rw-r--r-- | zellij-server/src/ui/boundaries.rs | 37 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_boundaries_frame.rs | 506 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_contents_and_ui.rs | 79 |
3 files changed, 345 insertions, 277 deletions
diff --git a/zellij-server/src/ui/boundaries.rs b/zellij-server/src/ui/boundaries.rs index d26db14ac..649437415 100644 --- a/zellij-server/src/ui/boundaries.rs +++ b/zellij-server/src/ui/boundaries.rs @@ -1,9 +1,10 @@ use zellij_utils::{pane_size::Viewport, zellij_tile}; +use crate::output::CharacterChunk; +use crate::panes::terminal_character::{TerminalCharacter, EMPTY_TERMINAL_CHARACTER, RESET_STYLES}; use crate::tab::Pane; use ansi_term::Colour::{Fixed, RGB}; use std::collections::HashMap; -use std::fmt::Write; use zellij_tile::data::PaletteColor; use zellij_utils::shared::colors; @@ -43,6 +44,19 @@ impl BoundarySymbol { self.color = color; *self } + pub fn as_terminal_character(&self) -> TerminalCharacter { + if self.invisible { + EMPTY_TERMINAL_CHARACTER + } else { + let character = self.boundary_type.chars().next().unwrap(); + TerminalCharacter { + character, + width: 1, + styles: RESET_STYLES + .foreground(self.color.map(|palette_color| palette_color.into())), + } + } + } } impl Display for BoundarySymbol { @@ -511,19 +525,16 @@ impl Boundaries { } } } - pub fn vte_output(&self) -> String { - let mut vte_output = String::new(); + pub fn render(&self) -> Vec<CharacterChunk> { + let mut character_chunks = vec![]; for (coordinates, boundary_character) in &self.boundary_characters { - write!( - &mut vte_output, - "\u{1b}[{};{}H\u{1b}[m{}", - coordinates.y + 1, - coordinates.x + 1, - boundary_character - ) - .unwrap(); // goto row/col + boundary character - } - vte_output + character_chunks.push(CharacterChunk::new( + vec![boundary_character.as_terminal_character()], + coordinates.x, + coordinates.y, + )); + } + character_chunks } fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { rect.x() + rect.cols() < self.viewport.cols diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index f2052412b..c46e55342 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -1,32 +1,64 @@ +use crate::output::CharacterChunk; +use crate::panes::{AnsiCode, CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER}; use crate::ui::boundaries::boundary_type; use crate::ClientId; -use ansi_term::Colour::{Fixed, RGB}; -use ansi_term::Style; use zellij_utils::zellij_tile::prelude::{client_id_to_colors, Palette, PaletteColor}; use zellij_utils::{envs::get_session_name, pane_size::Viewport}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; -use std::fmt::Write; - -fn color_string(character: &str, color: Option<PaletteColor>) -> String { - match color { - Some(PaletteColor::Rgb((r, g, b))) => RGB(r, g, b).bold().paint(character).to_string(), - Some(PaletteColor::EightBit(color)) => Fixed(color).bold().paint(character).to_string(), - None => Style::new().bold().paint(character).to_string(), +fn foreground_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> { + let mut colored_string = Vec::with_capacity(characters.chars().count()); + for character in characters.chars() { + let styles = match color { + Some(palette_color) => { + let mut styles = CharacterStyles::new(); + styles.reset_all(); + styles + .foreground(Some(AnsiCode::from(palette_color))) + .bold(Some(AnsiCode::On)) + } + None => { + let mut styles = CharacterStyles::new(); + styles.reset_all(); + styles.bold(Some(AnsiCode::On)) + } + }; + let terminal_character = TerminalCharacter { + character, + styles, + width: character.width().unwrap_or(0), + }; + colored_string.push(terminal_character); } + colored_string } -fn background_color(character: &str, color: Option<PaletteColor>) -> String { - match color { - Some(PaletteColor::Rgb((r, g, b))) => { - Style::new().on(RGB(r, g, b)).paint(character).to_string() - } - Some(PaletteColor::EightBit(color)) => { - Style::new().on(Fixed(color)).paint(character).to_string() - } - None => character.to_string(), +fn background_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> { + let mut colored_string = Vec::with_capacity(characters.chars().count()); + for character in characters.chars() { + let styles = match color { + Some(palette_color) => { + let mut styles = CharacterStyles::new(); + styles.reset_all(); + styles + .background(Some(AnsiCode::from(palette_color))) + .bold(Some(AnsiCode::On)) + } + None => { + let mut styles = CharacterStyles::new(); + styles.reset_all(); + styles + } + }; + let terminal_character = TerminalCharacter { + character, + styles, + width: character.width().unwrap_or(0), + }; + colored_string.push(terminal_character); } + colored_string } pub struct FrameParams { @@ -70,11 +102,14 @@ impl PaneFrame { other_cursors_exist_in_session: frame_params.other_cursors_exist_in_session, } } - fn client_cursor(&self, client_id: ClientId) -> String { + fn client_cursor(&self, client_id: ClientId) -> Vec<TerminalCharacter> { let color = client_id_to_colors(client_id, self.colors); background_color(" ", color.map(|c| c.0)) } - fn render_title_right_side(&self, max_length: usize) -> Option<(String, usize)> { + fn render_title_right_side( + &self, + max_length: usize, + ) -> Option<(Vec<TerminalCharacter>, usize)> { // string and length because of color if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 { let prefix = " SCROLL: "; @@ -86,17 +121,17 @@ impl PaneFrame { let prefix_len = prefix.chars().count(); if prefix_len + full_indication_len <= max_length { Some(( - color_string(&format!("{}{}", prefix, full_indication), self.color), + foreground_color(&format!("{}{}", prefix, full_indication), self.color), prefix_len + full_indication_len, )) } else if full_indication_len <= max_length { Some(( - color_string(&full_indication, self.color), + foreground_color(&full_indication, self.color), full_indication_len, )) } else if short_indication_len <= max_length { Some(( - color_string(&short_indication, self.color), + foreground_color(&short_indication, self.color), short_indication_len, )) } else { @@ -106,24 +141,24 @@ impl PaneFrame { None } } - fn render_my_focus(&self, max_length: usize) -> Option<(String, usize)> { - let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color); - let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color); + fn render_my_focus(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> { + let left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); + let right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); let full_indication_text = "MY FOCUS"; - let full_indication = format!( - "{} {} {}", - left_separator, - color_string(full_indication_text, self.color), - right_separator - ); + let mut full_indication = vec![]; + full_indication.append(&mut left_separator.clone()); + full_indication.push(EMPTY_TERMINAL_CHARACTER); + full_indication.append(&mut foreground_color(full_indication_text, self.color)); + full_indication.push(EMPTY_TERMINAL_CHARACTER); + full_indication.append(&mut right_separator.clone()); let full_indication_len = full_indication_text.width() + 4; // 2 for separators 2 for padding let short_indication_text = "ME"; - let short_indication = format!( - "{} {} {}", - left_separator, - color_string(short_indication_text, self.color), - right_separator - ); + let mut short_indication = vec![]; + short_indication.append(&mut left_separator.clone()); + short_indication.push(EMPTY_TERMINAL_CHARACTER); + short_indication.append(&mut foreground_color(short_indication_text, self.color)); + short_indication.push(EMPTY_TERMINAL_CHARACTER); + short_indication.append(&mut right_separator.clone()); let short_indication_len = short_indication_text.width() + 4; // 2 for separators 2 for padding if full_indication_len <= max_length { Some((full_indication, full_indication_len)) @@ -133,91 +168,110 @@ impl PaneFrame { None } } - fn render_my_and_others_focus(&self, max_length: usize) -> Option<(String, usize)> { - let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color); - let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color); + fn render_my_and_others_focus( + &self, + max_length: usize, + ) -> Option<(Vec<TerminalCharacter>, usize)> { + let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); + let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); let full_indication_text = "MY FOCUS AND:"; let short_indication_text = "+"; - let mut full_indication = color_string(full_indication_text, self.color); + let mut full_indication = foreground_color(full_indication_text, self.color); let mut full_indication_len = full_indication_text.width(); - let mut short_indication = color_string(short_indication_text, self.color); + let mut short_indication = foreground_color(short_indication_text, self.color); let mut short_indication_len = short_indication_text.width(); for client_id in &self.other_focused_clients { - let text = format!(" {}", self.client_cursor(*client_id)); + let mut text = self.client_cursor(*client_id); full_indication_len += 2; - full_indication.push_str(&text); + full_indication.push(EMPTY_TERMINAL_CHARACTER); + full_indication.append(&mut text.clone()); short_indication_len += 2; - short_indication.push_str(&text); + short_indication.append(&mut text); } if full_indication_len + 4 <= max_length { // 2 for separators, 2 for padding - Some(( - format!("{} {} {}", left_separator, full_indication, right_separator), - full_indication_len + 4, - )) + let mut ret = vec![]; + ret.append(&mut left_separator); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut full_indication); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut right_separator); + Some((ret, full_indication_len + 4)) } else if short_indication_len + 4 <= max_length { // 2 for separators, 2 for padding - Some(( - format!( - "{} {} {}", - left_separator, short_indication, right_separator - ), - short_indication_len + 4, - )) + let mut ret = vec![]; + ret.append(&mut left_separator); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut short_indication); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut right_separator); + Some((ret, short_indication_len + 4)) } else { None } } - fn render_other_focused_users(&self, max_length: usize) -> Option<(String, usize)> { - let left_separator = color_string(boundary_type::VERTICAL_LEFT, self.color); - let right_separator = color_string(boundary_type::VERTICAL_RIGHT, self.color); + fn render_other_focused_users( + &self, + max_length: usize, + ) -> Option<(Vec<TerminalCharacter>, usize)> { + let mut left_separator = foreground_color(boundary_type::VERTICAL_LEFT, self.color); + let mut right_separator = foreground_color(boundary_type::VERTICAL_RIGHT, self.color); let full_indication_text = if self.other_focused_clients.len() == 1 { "FOCUSED USER:" } else { "FOCUSED USERS:" }; let middle_indication_text = "U:"; - let mut full_indication = color_string(full_indication_text, self.color); + let mut full_indication = foreground_color(full_indication_text, self.color); let mut full_indication_len = full_indication_text.width(); - let mut middle_indication = color_string(middle_indication_text, self.color); + let mut middle_indication = foreground_color(middle_indication_text, self.color); let mut middle_indication_len = middle_indication_text.width(); - let mut short_indication = String::from(""); + let mut short_indication = vec![]; let mut short_indication_len = 0; for client_id in &self.other_focused_clients { - let text = format!(" {}", self.client_cursor(*client_id)); + let mut text = self.client_cursor(*client_id); full_indication_len += 2; - full_indication.push_str(&text); + full_indication.push(EMPTY_TERMINAL_CHARACTER); + full_indication.append(&mut text.clone()); middle_indication_len += 2; - middle_indication.push_str(&text); + middle_indication.push(EMPTY_TERMINAL_CHARACTER); + middle_indication.append(&mut text.clone()); short_indication_len += 2; - short_indication.push_str(&text); + short_indication.push(EMPTY_TERMINAL_CHARACTER); + short_indication.append(&mut text); } if full_indication_len + 4 <= max_length { // 2 for separators, 2 for padding - Some(( - format!("{} {} {}", left_separator, full_indication, right_separator), - full_indication_len + 4, - )) + let mut ret = vec![]; + ret.append(&mut left_separator); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut full_indication); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut right_separator); + Some((ret, full_indication_len + 4)) } else if middle_indication_len + 4 <= max_length { // 2 for separators, 2 for padding - Some(( - format!( - "{} {} {}", - left_separator, middle_indication, right_separator - ), - middle_indication_len + 4, - )) + let mut ret = vec![]; + ret.append(&mut left_separator); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut middle_indication); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut right_separator); + Some((ret, middle_indication_len + 4)) } else if short_indication_len + 3 <= max_length { // 2 for separators, 1 for padding - Some(( - format!("{}{} {}", left_separator, short_indication, right_separator), - short_indication_len + 3, - )) + let mut ret = vec![]; + ret.append(&mut left_separator); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut short_indication); + ret.push(EMPTY_TERMINAL_CHARACTER); + ret.append(&mut right_separator); + Some((ret, short_indication_len + 3)) } else { None } } - fn render_title_middle(&self, max_length: usize) -> Option<(String, usize)> { + fn render_title_middle(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> { // string and length because of color if self.is_main_client && self.other_focused_clients.is_empty() @@ -237,7 +291,7 @@ impl PaneFrame { None } } - fn render_title_left_side(&self, max_length: usize) -> Option<(String, usize)> { + fn render_title_left_side(&self, max_length: usize) -> Option<(Vec<TerminalCharacter>, usize)> { let middle_truncated_sign = "[..]"; let middle_truncated_sign_long = "[...]"; let full_text = format!(" {} ", &self.title); @@ -245,7 +299,7 @@ impl PaneFrame { None } else if full_text.width() <= max_length { Some(( - color_string(&full_text, self.color), + foreground_color(&full_text, self.color), full_text.chars().count(), )) } else { @@ -269,35 +323,39 @@ impl PaneFrame { } } - let title_left_side = - if first_part.width() + middle_truncated_sign.width() + second_part.width() - < max_length - { - // this means we lost 1 character when dividing the total length into halves + let (title_left_side, title_length) = if first_part.width() + + middle_truncated_sign.width() + + second_part.width() + < max_length + { + // this means we lost 1 character when dividing the total length into halves + ( format!( "{}{}{}", first_part, middle_truncated_sign_long, second_part - ) - } else { - format!("{}{}{}", first_part, middle_truncated_sign, second_part) - }; - Some(( - color_string(&title_left_side, self.color), - title_left_side.chars().count(), - )) + ), + first_part.width() + middle_truncated_sign_long.width() + second_part.width(), + ) + } else { + ( + format!("{}{}{}", first_part, middle_truncated_sign, second_part), + first_part.width() + middle_truncated_sign.width() + second_part.width(), + ) + }; + Some((foreground_color(&title_left_side, self.color), title_length)) } } fn three_part_title_line( &self, - left_side: &str, + mut left_side: Vec<TerminalCharacter>, left_side_len: &usize, - middle: &str, + mut middle: Vec<TerminalCharacter>, middle_len: &usize, - right_side: &str, + mut right_side: Vec<TerminalCharacter>, right_side_len: &usize, - ) -> String { + ) -> Vec<TerminalCharacter> { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners - let mut title_line = String::new(); + let mut title_line = vec![]; let left_side_start_position = self.geom.x + 1; let middle_start_position = self.geom.x + (total_title_length / 2) - (middle_len / 2) + 1; let right_side_start_position = @@ -306,24 +364,23 @@ impl PaneFrame { let mut col = self.geom.x; loop { if col == self.geom.x { - title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color)); } else if col == self.geom.x + self.geom.cols - 1 { - title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color)); } else if col == left_side_start_position { - title_line.push_str(left_side); + title_line.append(&mut left_side); col += left_side_len; continue; } else if col == middle_start_position { - title_line.push_str(middle); + title_line.append(&mut middle); col += middle_len; continue; } else if col == right_side_start_position { - title_line.push_str(right_side); + title_line.append(&mut right_side); col += right_side_len; continue; } else { - title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color)); - // TODO: BETTER + title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -334,33 +391,32 @@ impl PaneFrame { } fn left_and_middle_title_line( &self, - left_side: &str, + mut left_side: Vec<TerminalCharacter>, left_side_len: &usize, - middle: &str, + mut middle: Vec<TerminalCharacter>, middle_len: &usize, - ) -> String { + ) -> Vec<TerminalCharacter> { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners - let mut title_line = String::new(); + let mut title_line = vec![]; let left_side_start_position = self.geom.x + 1; let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1; let mut col = self.geom.x; loop { if col == self.geom.x { - title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color)); } else if col == self.geom.x + self.geom.cols - 1 { - title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color)); } else if col == left_side_start_position { - title_line.push_str(left_side); + title_line.append(&mut left_side); col += *left_side_len; continue; } else if col == middle_start_position { - title_line.push_str(middle); + title_line.append(&mut middle); col += *middle_len; continue; } else { - title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color)); - // TODO: BETTER + title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -369,24 +425,27 @@ impl PaneFrame { } title_line } - fn middle_only_title_line(&self, middle: &str, middle_len: &usize) -> String { + fn middle_only_title_line( + &self, + mut middle: Vec<TerminalCharacter>, + middle_len: &usize, + ) -> Vec<TerminalCharacter> { let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners - let mut title_line = String::new(); + let mut title_line = vec![]; let middle_start_position = self.geom.x + (total_title_length / 2) - (*middle_len / 2) + 1; let mut col = self.geom.x; loop { if col == self.geom.x { - title_line.push_str(&color_string(boundary_type::TOP_LEFT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_LEFT, self.color)); } else if col == self.geom.x + self.geom.cols - 1 { - title_line.push_str(&color_string(boundary_type::TOP_RIGHT, self.color)); + title_line.append(&mut foreground_color(boundary_type::TOP_RIGHT, self.color)); } else if col == middle_start_position { - title_line.push_str(middle); + title_line.append(&mut middle); col += *middle_len; continue; } else { - title_line.push_str(&color_string(boundary_type::HORIZONTAL, self.color)); - // TODO: BETTER + title_line.append(&mut foreground_color(boundary_type::HORIZONTAL, self.color)); } if col == self.geom.x + self.geom.cols - 1 { break; @@ -397,81 +456,86 @@ impl PaneFrame { } fn two_part_title_line( &self, - left_side: &str, + mut left_side: Vec<TerminalCharacter>, left_side_len: &usize, - right_side: &str, + mut right_side: Vec<TerminalCharacter>, right_side_len: &usize, - ) -> String { - let left_boundary = color_string(boundary_type::TOP_LEFT, self.color); - let right_boundary = color_string(boundary_type::TOP_RIGHT, self.color); + ) -> Vec<TerminalCharacter> { + let mut left_boundary = foreground_color(boundary_type::TOP_LEFT, self.color); + let mut right_boundary = foreground_color(boundary_type::TOP_RIGHT, self.color); let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners let mut middle = String::new(); for _ in (left_side_len + right_side_len)..total_title_length { middle.push_str(boundary_type::HORIZONTAL); } - format!( - "{}{}{}{}{}", - left_boundary, - left_side, - color_string(&middle, self.color), - color_string(right_side, self.color), - &right_boundary - ) + let mut ret = vec![]; + ret |