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 /default-plugins | |
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 'default-plugins')
11 files changed, 350 insertions, 45 deletions
diff --git a/default-plugins/compact-bar/src/line.rs b/default-plugins/compact-bar/src/line.rs index 16d64d071..6bcbab18a 100644 --- a/default-plugins/compact-bar/src/line.rs +++ b/default-plugins/compact-bar/src/line.rs @@ -256,6 +256,8 @@ pub fn tab_line( palette: Palette, capabilities: PluginCapabilities, mode: InputMode, + active_swap_layout_name: &Option<String>, + is_swap_layout_dirty: bool, ) -> Vec<LinePart> { let mut tabs_after_active = all_tabs.split_off(active_tab_index); let mut tabs_before_active = all_tabs; @@ -283,5 +285,97 @@ pub fn tab_line( capabilities, ); prefix.append(&mut tabs_to_render); + + let current_title_len = get_current_title_len(&prefix); + if current_title_len < cols { + let mut remaining_space = cols - current_title_len; + if let Some(swap_layout_status) = swap_layout_status( + remaining_space, + active_swap_layout_name, + is_swap_layout_dirty, + mode, + &palette, + tab_separator(capabilities), + ) { + remaining_space -= swap_layout_status.len; + let mut buffer = String::new(); + for _ in 0..remaining_space { + buffer.push_str(&style!(palette.black, palette.black).paint(" ").to_string()); + } + prefix.push(LinePart { + part: buffer, + len: remaining_space, + tab_index: None, + }); + prefix.push(swap_layout_status); + } + } + prefix } + +fn swap_layout_status( + max_len: usize, + swap_layout_name: &Option<String>, + is_swap_layout_damaged: bool, + input_mode: InputMode, + palette: &Palette, + separator: &str, +) -> Option<LinePart> { + match swap_layout_name { + Some(swap_layout_name) => { + let mut swap_layout_name = format!(" {} ", swap_layout_name); + swap_layout_name.make_ascii_uppercase(); + let swap_layout_name_len = swap_layout_name.len() + 3; + + let (prefix_separator, swap_layout_name, suffix_separator) = + if input_mode == InputMode::Locked { + ( + style!(palette.black, palette.fg).paint(separator), + style!(palette.black, palette.fg) + .italic() + .paint(&swap_layout_name), + style!(palette.fg, palette.black).paint(separator), + ) + } else if is_swap_layout_damaged { + ( + style!(palette.black, palette.fg).paint(separator), + style!(palette.black, palette.fg) + .bold() + .paint(&swap_layout_name), + style!(palette.fg, palette.black).paint(separator), + ) + } else { + ( + style!(palette.black, palette.green).paint(separator), + style!(palette.black, palette.green) + .bold() + .paint(&swap_layout_name), + style!(palette.green, palette.black).paint(separator), + ) + }; + let swap_layout_indicator = format!( + "{}{}{}", + prefix_separator, swap_layout_name, suffix_separator + ); + let (part, full_len) = (format!("{}", swap_layout_indicator), swap_layout_name_len); + let short_len = swap_layout_name_len + 1; // 1 is the space between + if full_len <= max_len { + Some(LinePart { + part, + len: full_len, + tab_index: None, + }) + } else if short_len <= max_len && input_mode != InputMode::Locked { + Some(LinePart { + part: swap_layout_indicator, + len: short_len, + tab_index: None, + }) + } else { + None + } + }, + None => None, + } +} diff --git a/default-plugins/compact-bar/src/main.rs b/default-plugins/compact-bar/src/main.rs index c6ce9bbcd..71652f0f1 100644 --- a/default-plugins/compact-bar/src/main.rs +++ b/default-plugins/compact-bar/src/main.rs @@ -92,6 +92,8 @@ impl ZellijPlugin for State { } let mut all_tabs: Vec<LinePart> = vec![]; let mut active_tab_index = 0; + let mut active_swap_layout_name = None; + let mut is_swap_layout_dirty = false; let mut is_alternate_tab = false; for t in &mut self.tabs { let mut tabname = t.name.clone(); @@ -102,6 +104,8 @@ impl ZellijPlugin for State { active_tab_index = t.position; } else if t.active { active_tab_index = t.position; + is_swap_layout_dirty = t.is_swap_layout_dirty; + active_swap_layout_name = t.active_swap_layout_name.clone(); } let tab = tab_style( tabname, @@ -121,6 +125,8 @@ impl ZellijPlugin for State { self.mode_info.style.colors, self.mode_info.capabilities, self.mode_info.mode, + &active_swap_layout_name, + is_swap_layout_dirty, ); let mut s = String::new(); let mut len_cnt = 0; diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index ae8fc70e7..23a6d5869 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -1,9 +1,11 @@ -use ansi_term::ANSIStrings; +use ansi_term::{unstyled_len, ANSIStrings}; use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; use crate::color_elements; -use crate::{action_key, get_common_modifier, TO_NORMAL}; +use crate::{ + action_key, action_key_group, get_common_modifier, style_key_with_modifier, TO_NORMAL, +}; use crate::{ColoredElements, LinePart}; struct KeyShortcut { @@ -232,6 +234,102 @@ fn key_indicators( line_part } +fn swap_layout_keycode(mode_info: &ModeInfo, palette: &Palette) -> LinePart { + let mode_keybinds = mode_info.get_mode_keybinds(); + let prev_next_keys = action_key_group( + &mode_keybinds, + &[&[Action::PreviousSwapLayout], &[Action::NextSwapLayout]], + ); + let prev_next_keys_indicator = + style_key_with_modifier(&prev_next_keys, palette, Some(palette.black)); + let keycode = ANSIStrings(&prev_next_keys_indicator); + let len = unstyled_len(&keycode); + let part = keycode.to_string(); + LinePart { part, len } +} + +fn swap_layout_status( + max_len: usize, + swap_layout_name: &Option<String>, + is_swap_layout_damaged: bool, + mode_info: &ModeInfo, + colored_elements: ColoredElements, + palette: &Palette, + separator: &str, +) -> Option<LinePart> { + match swap_layout_name { + Some(swap_layout_name) => { + let mut swap_layout_name = format!(" {} ", swap_layout_name); + swap_layout_name.make_ascii_uppercase(); + let keycode = swap_layout_keycode(mode_info, palette); + let swap_layout_name_len = swap_layout_name.len() + 3; // 2 for the arrow separators, one for the screen end buffer + // + macro_rules! style_swap_layout_indicator { + ($style_name:ident) => {{ + ( + colored_elements + .$style_name + .prefix_separator + .paint(separator), + colored_elements + .$style_name + .styled_text + .paint(&swap_layout_name), + colored_elements + .$style_name + .suffix_separator + .paint(separator), + ) + }}; + } + let (prefix_separator, swap_layout_name, suffix_separator) = + if mode_info.mode == InputMode::Locked { + style_swap_layout_indicator!(disabled) + } else if is_swap_layout_damaged { + style_swap_layout_indicator!(unselected) + } else { + style_swap_layout_indicator!(selected) + }; + let swap_layout_indicator = format!( + "{}{}{}", + prefix_separator, swap_layout_name, suffix_separator + ); + let (part, full_len) = if mode_info.mode == InputMode::Locked { + ( + format!("{}", swap_layout_indicator), + swap_layout_name_len, // 1 is the space between + ) + } else { + ( + format!( + "{}{}{}{}", + keycode, + colored_elements.superkey_prefix.paint(" "), + swap_layout_indicator, + colored_elements.superkey_prefix.paint(" ") + ), + keycode.len + swap_layout_name_len + 1, // 1 is the space between + ) + }; + let short_len = swap_layout_name_len + 1; // 1 is the space between + if full_len <= max_len { + Some(LinePart { + part, + len: full_len, + }) + } else if short_len <= max_len && mode_info.mode != InputMode::Locked { + Some(LinePart { + part: swap_layout_indicator, + len: short_len, + }) + } else { + None + } + }, + None => None, + } +} + /// Get the keybindings for switching `InputMode`s and `Quit` visible in status bar. /// /// Return a Vector of `Key`s where each `Key` is a shortcut to switch to some `InputMode` or Quit @@ -351,7 +449,12 @@ fn get_key_shortcut_for_mode<'a>( None } -pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { +pub fn first_line( + help: &ModeInfo, + tab_info: Option<&TabInfo>, + max_len: usize, + separator: &str, +) -> LinePart { let supports_arrow_fonts = !help.capabilities.arrow_fonts; let colored_elements = color_elements(help.style.colors, !supports_arrow_fonts); let binds = &help.get_mode_keybinds(); @@ -432,7 +535,32 @@ pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart )); } - key_indicators(max_len, &default_keys, colored_elements, separator, help) + let mut key_indicators = + key_indicators(max_len, &default_keys, colored_elements, separator, help); + if key_indicators.len < max_len { + if let Some(tab_info) = tab_info { + let mut remaining_space = max_len - key_indicators.len; + if let Some(swap_layout_status) = swap_layout_status( + remaining_space, + &tab_info.active_swap_layout_name, + tab_info.is_swap_layout_dirty, + help, + colored_elements, + &help.style.colors, + separator, + ) { + remaining_space -= swap_layout_status.len; + for _ in 0..remaining_space { + key_indicators.part.push_str( + &ANSIStrings(&[colored_elements.superkey_prefix.paint(" ")]).to_string(), + ); + key_indicators.len += 1; + } + key_indicators.append(&swap_layout_status); + } + } + } + key_indicators } #[cfg(test)] @@ -735,7 +863,7 @@ mod tests { ..ModeInfo::default() }; - let ret = first_line(&mode_info, 500, ">"); + let ret = first_line(&mode_info, None, 500, ">"); let ret = unstyle(ret); assert_eq!( @@ -759,7 +887,7 @@ mod tests { ..ModeInfo::default() }; - let ret = first_line(&mode_info, 500, ">"); + let ret = first_line(&mode_info, None, 500, ">"); let ret = unstyle(ret); assert_eq!( @@ -785,7 +913,7 @@ mod tests { ..ModeInfo::default() }; - let ret = first_line(&mode_info, 500, ">"); + let ret = first_line(&mode_info, None, 500, ">"); let ret = unstyle(ret); assert_eq!( @@ -812,7 +940,7 @@ mod tests { ..ModeInfo::default() }; - let ret = first_line(&mode_info, 50, ">"); + let ret = first_line(&mode_info, None, 50, ">"); let ret = unstyle(ret); assert_eq!(ret, " Ctrl + >> a >> b >> c >> d >> e >".to_string()); @@ -833,7 +961,7 @@ mod tests { ..ModeInfo::default() }; - let ret = first_line(&mode_info, 30, ""); + let ret = first_line(&mode_info, None, 30, ""); let ret = unstyle(ret); assert_eq!(ret, " Ctrl + a b c ".to_string()); diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 603cdf351..3e6685596 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -44,6 +44,13 @@ pub struct LinePart { len: usize, } +impl LinePart { + pub fn append(&mut self, to_append: &LinePart) { + self.part.push_str(&to_append.part); + self.len += to_append.len; + } +} + impl Display for LinePart { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { write!(f, "{}", self.part) @@ -236,7 +243,8 @@ impl ZellijPlugin for State { "" }; - let first_line = first_line(&self.mode_info, cols, separator); + let active_tab = self.tabs.iter().find(|t| t.active); + let first_line = first_line(&self.mode_info, active_tab, cols, separator); let second_line = self.second_line(cols); let background = match self.mode_info.style.colors.theme_hue { @@ -396,7 +404,11 @@ pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) -> /// /// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`] /// type. -pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIString<'static>> { +pub fn style_key_with_modifier( + keyvec: &[Key], + palette: &Palette, + background: Option<PaletteColor>, +) -> Vec<ANSIString<'static>> { // Nothing to do, quit... if keyvec.is_empty() { return vec![]; @@ -419,13 +431,32 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIStr let painted_modifier = if modifier_str.is_empty() { Style::new().paint("") } else { - Style::new().fg(orange_color).bold().paint(modifier_str) + if let Some(background) = background { + let background = palette_match!(background); + Style::new() + .fg(orange_color) + .on(background) + .bold() + .paint(modifier_str) + } else { + Style::new().fg(orange_color).bold().paint(modifier_str) + } }; ret.push(painted_modifier); // Prints key group start let group_start_str = if no_modifier { "<" } else { " + <" }; - ret.push(Style::new().fg(text_color).paint(group_start_str)); + if let Some(background) = background { + let background = palette_match!(background); + ret.push( + Style::new() + .fg(text_color) + .on(background) + .paint(group_start_str), + ); + } else { + ret.push(Style::new().fg(text_color).paint(group_start_str)); + } // Prints the keys let key = keyvec @@ -451,18 +482,50 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec<ANSIStr "←↓↑→" => "", "←→" => "", "↓↑" => "", + "[]" => "", _ => "|", }; for (idx, key) in key.iter().enumerate() { if idx > 0 && !key_separator.is_empty() { - ret.push(Style::new().fg(text_color).paint(key_separator)); + if let Some(background) = background { + let background = palette_match!(background); + ret.push( + Style::new() + .fg(text_color) + .on(background) + .paint(key_separator), + ); + } else { + ret.push(Style::new().fg(text_color).paint(key_separator)); + } + } + if let Some(background) = background { + let background = palette_match!(background); + ret.push( + Style::new() + .fg(green_color) + .on(background) + .bold() + .paint(key.clone()), + ); + } else { + ret.push(Style::new().fg(green_color).bold().paint(key.clone())); } - ret.push(Style::new().fg(green_color).bold().paint(key.clone())); } let group_end_str = ">"; - ret.push(Style::new().fg(text_color).paint(group_end_str)); + if let Some(background) = background { + let background = palette_match!(background); + ret.push( + Style::new() + .fg(text_color) + .on(background) + .paint(group_end_str), + ); + } else { + ret.push(Style::new().fg(text_color).paint(group_end_str)); + } ret } @@ -623,7 +686,7 @@ pub mod tests { let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<a|b|c>".to_string()) @@ -639,7 +702,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<hjkl>".to_string()) @@ -656,7 +719,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<h|k|j|l>".to_string()) @@ -672,7 +735,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<←↓↑→>".to_string()) @@ -683,7 +746,7 @@ pub mod tests { let keyvec = vec![Key::Char('←'), Key::Char('→')]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<←→>".to_string()) @@ -694,7 +757,7 @@ pub mod tests { let keyvec = vec![Key::Char('↓'), Key::Char('↑')]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<↓↑>".to_string()) @@ -710,7 +773,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "Ctrl + <a|b|c|d>".to_string()) @@ -726,7 +789,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "Alt + <a|b|c|d>".to_string()) @@ -742,7 +805,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "Alt + <←↓↑→>".to_string()) @@ -757,7 +820,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string()) @@ -780,7 +843,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!( @@ -794,7 +857,7 @@ pub mod tests { let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); assert_eq!(ret, "Ctrl + <ENTER|SPACE|TAB>".to_string()) @@ -809,7 +872,7 @@ pub mod tests { ]; let palette = get_palette(); - let ret = style_key_with_modifier(&keyvec, &palette); + let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); |