diff options
author | Aram Drevekenin <aram@poor.dev> | 2024-07-05 15:13:51 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-05 15:13:51 +0200 |
commit | a6d6c0e4ff03c35cafb6f04363ecab856ee3682d (patch) | |
tree | eadf1c24c4c6a1e4b7499b3cd047351fd29e52de /default-plugins/tab-bar | |
parent | 8e33b20559ecddcaba17e5cc8d6f05b638718cac (diff) |
feat(ui): status bar redesign (#3475)
* work
* work
* working
* get default mode from server and some ui responsiveness
* work
* finish design and get tests to pass
* get e2e tests to pass
* add classic layout
* add classic layout assets
* fix e2e tests
* style(fmt): rustfmt
* fix plugin system test
* style(fmt): some cleanups
Diffstat (limited to 'default-plugins/tab-bar')
-rw-r--r-- | default-plugins/tab-bar/src/line.rs | 211 | ||||
-rw-r--r-- | default-plugins/tab-bar/src/main.rs | 17 | ||||
-rw-r--r-- | default-plugins/tab-bar/src/tab.rs | 2 |
3 files changed, 224 insertions, 6 deletions
diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 668507a01..349981f4d 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -1,9 +1,14 @@ use ansi_term::ANSIStrings; +use ansi_term::{ + Color::{Fixed, RGB}, + Style, +}; use unicode_width::UnicodeWidthStr; use crate::{LinePart, ARROW_SEPARATOR}; +use zellij_tile::prelude::actions::Action; use zellij_tile::prelude::*; -use zellij_tile_utils::style; +use zellij_tile_utils::{palette_match, style}; fn get_current_title_len(current_title: &[LinePart]) -> usize { current_title.iter().map(|p| p.len).sum() @@ -224,6 +229,9 @@ pub fn tab_line( palette: Palette, capabilities: PluginCapabilities, hide_session_name: bool, + tab_info: Option<&TabInfo>, + mode_info: &ModeInfo, + hide_swap_layout_indicator: bool, ) -> Vec<LinePart> { let mut tabs_after_active = all_tabs.split_off(active_tab_index); let mut tabs_before_active = all_tabs; @@ -236,10 +244,26 @@ pub fn tab_line( true => tab_line_prefix(None, palette, cols), false => tab_line_prefix(session_name, palette, cols), }; - let prefix_len = get_current_title_len(&prefix); + + let mut swap_layout_indicator = if hide_swap_layout_indicator { + None + } else { + tab_info.and_then(|tab_info| { + swap_layout_status( + cols, + &tab_info.active_swap_layout_name, + tab_info.is_swap_layout_dirty, + mode_info, + !capabilities.arrow_fonts, + ) + }) + }; + + let non_tab_len = + get_current_title_len(&prefix) + swap_layout_indicator.as_ref().map(|s| s.len).unwrap_or(0); // if active tab alone won't fit in cols, don't draw any tabs - if prefix_len + active_tab.len > cols { + if non_tab_len + active_tab.len > cols { return prefix; } @@ -249,10 +273,189 @@ pub fn tab_line( &mut tabs_before_active, &mut tabs_after_active, &mut tabs_to_render, - cols.saturating_sub(prefix_len), + cols.saturating_sub(non_tab_len), palette, capabilities, ); prefix.append(&mut tabs_to_render); + + if let Some(mut swap_layout_indicator) = swap_layout_indicator.take() { + let remaining_space = cols + .saturating_sub(prefix.iter().fold(0, |len, part| len + part.len)) + .saturating_sub(swap_layout_indicator.len); + let mut padding = String::new(); + let mut padding_len = 0; + for _ in 0..remaining_space { + padding.push_str(" "); + padding_len += 1; + } + swap_layout_indicator.part = format!("{}{}", padding, swap_layout_indicator.part); + swap_layout_indicator.len += padding_len; + prefix.push(swap_layout_indicator); + } + prefix } + +fn swap_layout_status( + cols: usize, + swap_layout_name: &Option<String>, + is_swap_layout_dirty: bool, + mode_info: &ModeInfo, + supports_arrow_fonts: bool, +) -> Option<LinePart> { + match swap_layout_name { + Some(swap_layout_name) => { + let mode_keybinds = mode_info.get_mode_keybinds(); + let prev_next_keys = action_key_group( + &mode_keybinds, + &[&[Action::PreviousSwapLayout], &[Action::NextSwapLayout]], + ); + let mut text = style_key_with_modifier(&prev_next_keys, Some(0)); + text.append(&ribbon_as_line_part( + &swap_layout_name.to_uppercase(), + !is_swap_layout_dirty, + supports_arrow_fonts, + )); + Some(text) + }, + None => None, + } +} + +pub fn ribbon_as_line_part(text: &str, is_selected: bool, supports_arrow_fonts: bool) -> LinePart { + let ribbon_text = if is_selected { + Text::new(text).selected() + } else { + Text::new(text) + }; + let part = serialize_ribbon(&ribbon_text); + let mut len = text.width() + 2; + if supports_arrow_fonts { + len += 2; + }; + LinePart { + part, + len, + tab_index: None, + } +} + +pub fn style_key_with_modifier(keyvec: &[KeyWithModifier], color_index: Option<usize>) -> LinePart { + if keyvec.is_empty() { + return LinePart::default(); + } + + let common_modifiers = get_common_modifiers(keyvec.iter().collect()); + + let no_common_modifier = common_modifiers.is_empty(); + let modifier_str = common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::<Vec<_>>() + .join("-"); + + // Prints the keys + let key = keyvec + .iter() + .map(|key| { + if no_common_modifier || keyvec.len() == 1 { + format!("{}", key) + } else { + format!("{}", key.strip_common_modifiers(&common_modifiers)) + } + }) + .collect::<Vec<String>>(); + + // Special handling of some pre-defined keygroups + let key_string = key.join(""); + let key_separator = match &key_string[..] { + "HJKL" => "", + "hjkl" => "", + "←↓↑→" => "", + "←→" => "", + "↓↑" => "", + "[]" => "", + _ => "|", + }; + + if no_common_modifier || key.len() == 1 { + let key_string_text = format!(" {} ", key.join(key_separator)); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text).color_range(color_index, ..) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + ..Default::default() + } + } else { + let key_string_without_modifier = format!("{}", key.join(key_separator)); + let key_string_text = format!(" {} <{}> ", modifier_str, key_string_without_modifier); + let text = if let Some(color_index) = color_index { + Text::new(&key_string_text) + .color_range(color_index, ..modifier_str.width() + 1) + .color_range( + color_index, + modifier_str.width() + 3 + ..modifier_str.width() + 3 + key_string_without_modifier.width(), + ) + } else { + Text::new(&key_string_text) + }; + LinePart { + part: serialize_text(&text), + len: key_string_text.width(), + ..Default::default() + } + } +} + +pub fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec<KeyModifier> { + if keyvec.is_empty() { + return vec![]; + } + let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone(); + for key in keyvec { + common_modifiers = common_modifiers + .intersection(&key.key_modifiers) + .cloned() + .collect(); + } + common_modifiers.into_iter().collect() +} + +pub fn action_key_group( + keymap: &[(KeyWithModifier, Vec<Action>)], + actions: &[&[Action]], +) -> Vec<KeyWithModifier> { + let mut ret = vec![]; + for action in actions { + ret.extend(action_key(keymap, action)); + } + ret +} + +pub fn action_key( + keymap: &[(KeyWithModifier, Vec<Action>)], + action: &[Action], +) -> Vec<KeyWithModifier> { + keymap + .iter() + .filter_map(|(key, acvec)| { + let matching = acvec + .iter() + .zip(action) + .filter(|(a, b)| a.shallow_eq(b)) + .count(); + + if matching == acvec.len() && matching == action.len() { + Some(key.clone()) + } else { + None + } + }) + .collect::<Vec<KeyWithModifier>>() +} diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index 208558251..8230467d1 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -18,12 +18,20 @@ pub struct LinePart { tab_index: Option<usize>, } +impl LinePart { + pub fn append(&mut self, to_append: &LinePart) { + self.part.push_str(&to_append.part); + self.len += to_append.len; + } +} + #[derive(Default)] struct State { tabs: Vec<TabInfo>, active_tab_idx: usize, mode_info: ModeInfo, tab_line: Vec<LinePart>, + hide_swap_layout_indication: bool, } static ARROW_SEPARATOR: &str = ""; @@ -31,7 +39,11 @@ static ARROW_SEPARATOR: &str = ""; register_plugin!(State); impl ZellijPlugin for State { - fn load(&mut self, _configuration: BTreeMap<String, String>) { + fn load(&mut self, configuration: BTreeMap<String, String>) { + self.hide_swap_layout_indication = configuration + .get("hide_swap_layout_indication") + .map(|s| s == "true") + .unwrap_or(false); set_selectable(false); subscribe(&[ EventType::TabUpdate, @@ -120,6 +132,9 @@ impl ZellijPlugin for State { self.mode_info.style.colors, self.mode_info.capabilities, self.mode_info.style.hide_session_name, + self.tabs.iter().find(|t| t.active), + &self.mode_info, + self.hide_swap_layout_indication, ); let output = self diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index 4b2ae7776..5044ac872 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -51,7 +51,7 @@ pub fn render_tab( let right_separator = style!(background_color, foreground_color).paint(separator); let tab_styled_text = if !focused_clients.is_empty() { let (cursor_section, extra_length) = cursors(focused_clients, palette); - tab_text_len += extra_length; + tab_text_len += extra_length + 2; // 2 for cursor_beginning and cursor_end let mut s = String::new(); let cursor_beginning = style!(foreground_color, background_color) .bold() |