From 4e72b6552a0ed174e6ce000f5bb8b4cd66ccb43e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 14 Oct 2020 20:07:39 +0300 Subject: [PATCH] conf: add setting for progress spinner Choose between 30-something built in sequences (integers between 0-30) or define your own list of strings for the progress spinner animation. Default: 0 --- src/components/utilities.rs | 56 +++++++++++++------- src/components/utilities/widgets.rs | 80 +++++++++++++++++++++++++---- src/conf/terminal.rs | 15 ++++++ 3 files changed, 122 insertions(+), 29 deletions(-) diff --git a/src/components/utilities.rs b/src/components/utilities.rs index 146010df..313f63be 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -654,6 +654,17 @@ impl fmt::Display for StatusBar { impl StatusBar { pub fn new(context: &Context, container: Box) -> Self { + let mut progress_spinner = ProgressSpinner::new(0); + match context.settings.terminal.progress_spinner_sequence.as_ref() { + Some(conf::terminal::ProgressSpinnerSequence::Integer(k)) => { + progress_spinner.set_kind(*k); + } + Some(conf::terminal::ProgressSpinnerSequence::Custom(ref s)) => { + progress_spinner.set_custom_kind(s.clone()); + } + None => {} + } + StatusBar { container, status: String::with_capacity(256), @@ -667,12 +678,13 @@ impl StatusBar { height: 1, id: ComponentId::new_v4(), auto_complete: AutoComplete::new(Vec::new()), - progress_spinner: ProgressSpinner::new(1), + progress_spinner, in_progress_jobs: HashSet::default(), done_jobs: HashSet::default(), cmd_history: crate::command::history::old_cmd_history(), } } + fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { let mut attribute = crate::conf::value(context, "status.bar"); if !context.settings.terminal.use_color() { @@ -706,7 +718,10 @@ impl StatusBar { } } - let (x, y) = bottom_right!(area); + let (mut x, y) = bottom_right!(area); + if self.progress_spinner.is_active() { + x = x.saturating_sub(1 + self.progress_spinner.width); + } for (idx, c) in self.display_buffer.chars().rev().enumerate() { if let Some(cell) = grid.get_mut(x.saturating_sub(idx).saturating_sub(1), y) { cell.set_ch(c); @@ -714,6 +729,16 @@ impl StatusBar { break; } } + if self.progress_spinner.is_dirty() { + self.progress_spinner.draw( + grid, + ( + pos_dec(bottom_right!(area), (self.progress_spinner.width, 0)), + bottom_right!(area), + ), + context, + ); + } context.dirty_areas.push_back(area); } @@ -763,26 +788,16 @@ impl Component for StatusBar { context, ); - if self.progress_spinner.is_dirty() { - self.progress_spinner.draw( - grid, - ( - (get_x(bottom_right).saturating_sub(1), get_y(bottom_right)), - bottom_right, - ), - context, - ); - } - - if self.mode != UIMode::Command && !self.is_dirty() { - return; - } self.dirty = false; self.draw_status_bar( grid, (set_y(upper_left, get_y(bottom_right)), bottom_right), context, ); + + if self.mode != UIMode::Command && !self.is_dirty() { + return; + } match self.mode { UIMode::Normal => {} UIMode::Command => { @@ -1020,7 +1035,11 @@ impl Component for StatusBar { match event { UIEvent::ChangeMode(m) => { let offset = self.status.find('|').unwrap_or_else(|| self.status.len()); - self.status.replace_range(..offset, &format!("{} {}", m, + self.status.replace_range( + ..offset, + &format!( + "{} {}", + m, if self.mouse { context .settings @@ -1032,7 +1051,8 @@ impl Component for StatusBar { } else { "" }, - )); + ), + ); self.set_dirty(true); self.container.set_dirty(true); self.mode = *m; diff --git a/src/components/utilities/widgets.rs b/src/components/utilities/widgets.rs index 0b17d24e..823a379c 100644 --- a/src/components/utilities/widgets.rs +++ b/src/components/utilities/widgets.rs @@ -1049,18 +1049,17 @@ impl ScrollBar { #[derive(Debug)] pub struct ProgressSpinner { - //total_work: usize, - //finished: usize, timer: crate::timer::PosixTimer, stage: usize, - kind: usize, + pub kind: std::result::Result>, + pub width: usize, active: bool, dirty: bool, id: ComponentId, } impl ProgressSpinner { - const KINDS: [&'static [&'static str]; 15] = [ + pub const KINDS: [&'static [&'static str]; 30] = [ &["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"], &["⣀", "⣄", "⣤", "⣦", "⣶", "⣷", "⣿"], &["⣀", "⣄", "⣆", "⣇", "⣧", "⣷", "⣿"], @@ -1076,10 +1075,26 @@ impl ProgressSpinner { &["▯", "▮"], &["◯", "⬤"], &["⚪", "⚫"], + &["▖", "▗", "▘", "▝", "▞", "▚", "▙", "▟", "▜", "▛"], + &["|", "/", "-", "\\"], + &[".", "o", "O", "@", "*"], + &["◡◡", "⊙⊙", "◠◠", "⊙⊙"], + &["◜ ", " ◝", " ◞", "◟ "], + &["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"], + &["▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃"], + &[ + "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉", + ], + &["▖", "▘", "▝", "▗"], + &["▌", "▀", "▐", "▄"], + &["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"], + &["◢", "◣", "◤", "◥"], + &["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"], + &["⢎⡰", "⢎⡡", "⢎⡑", "⢎⠱", "⠎⡱", "⢊⡱", "⢌⡱", "⢆⡱"], + &[".", "o", "O", "°", "O", "o", "."], ]; const INTERVAL: std::time::Duration = std::time::Duration::from_millis(50); - const VALUE: std::time::Duration = std::time::Duration::from_millis(500); pub fn new(kind: usize) -> Self { let timer = crate::timer::PosixTimer::new_with_signal( @@ -1088,22 +1103,50 @@ impl ProgressSpinner { nix::sys::signal::Signal::SIGALRM, ) .unwrap(); - debug!("Requested timer {:?} for ProgressSpinner", timer.si_value); + let kind = kind % Self::KINDS.len(); + let width = Self::KINDS[kind] + .iter() + .map(|f| f.grapheme_len()) + .max() + .unwrap_or(0); ProgressSpinner { timer, stage: 0, - kind: kind % Self::KINDS.len(), + kind: Ok(kind), + width, dirty: true, active: false, id: ComponentId::new_v4(), } } + pub fn is_active(&self) -> bool { + self.active + } + + pub fn set_kind(&mut self, kind: usize) { + self.stage = 0; + self.width = Self::KINDS[kind % Self::KINDS.len()] + .iter() + .map(|f| f.grapheme_len()) + .max() + .unwrap_or(0); + self.kind = Ok(kind % Self::KINDS.len()); + self.dirty = true; + } + + pub fn set_custom_kind(&mut self, custom: Vec) { + self.stage = 0; + self.width = custom.iter().map(|f| f.grapheme_len()).max().unwrap_or(0); + self.kind = Err(custom); + self.dirty = true; + } + pub fn start(&mut self) { self.active = true; self.timer .set_interval(Self::INTERVAL) - .set_value(Self::VALUE) + .set_value(Self::INTERVAL) .rearm() } @@ -1117,6 +1160,12 @@ impl ProgressSpinner { } } +impl Drop for ProgressSpinner { + fn drop(&mut self) { + self.stop(); + } +} + impl fmt::Display for ProgressSpinner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "progress bar") @@ -1129,10 +1178,19 @@ impl Component for ProgressSpinner { let theme_attr = crate::conf::value(context, "status.bar"); clear_area(grid, area, theme_attr); if self.active { - let stage = self.stage; - self.stage = (self.stage + 1).wrapping_rem(Self::KINDS[self.kind].len()); write_string_to_grid( - Self::KINDS[self.kind][stage], + match self.kind.as_ref() { + Ok(kind) => { + let stage = self.stage; + self.stage = (self.stage + 1).wrapping_rem(Self::KINDS[*kind].len()); + Self::KINDS[*kind][stage].as_ref() + } + Err(custom) => { + let stage = self.stage; + self.stage = (self.stage + 1).wrapping_rem(custom.len()); + custom[stage].as_ref() + } + }, grid, theme_attr.fg, theme_attr.bg, diff --git a/src/conf/terminal.rs b/src/conf/terminal.rs index 2138f171..781d72aa 100644 --- a/src/conf/terminal.rs +++ b/src/conf/terminal.rs @@ -47,6 +47,11 @@ pub struct TerminalSettings { pub window_title: Option, #[serde(deserialize_with = "non_empty_string")] pub file_picker_command: Option, + /// Choose between 30-something built in sequences (integers between 0-30) or define your own + /// list of strings for the progress spinner animation. + /// Default: 0 + #[serde(default)] + pub progress_spinner_sequence: Option, } impl Default for TerminalSettings { @@ -60,6 +65,7 @@ impl Default for TerminalSettings { mouse_flag: Some("🖱️ ".to_string()), window_title: Some("meli".to_string()), file_picker_command: None, + progress_spinner_sequence: None, } } } @@ -100,3 +106,12 @@ impl DotAddressable for TerminalSettings { } } } + +#[derive(Debug, Deserialize, Clone, Serialize)] +#[serde(untagged)] +pub enum ProgressSpinnerSequence { + Integer(usize), + Custom(Vec), +} + +impl DotAddressable for ProgressSpinnerSequence {}