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
jmap-eventsource
Manos Pitsidianakis 2020-10-14 20:07:39 +03:00
parent 310d02042f
commit 4e72b6552a
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 122 additions and 29 deletions

View File

@ -654,6 +654,17 @@ impl fmt::Display for StatusBar {
impl StatusBar { impl StatusBar {
pub fn new(context: &Context, container: Box<dyn Component>) -> Self { pub fn new(context: &Context, container: Box<dyn Component>) -> 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 { StatusBar {
container, container,
status: String::with_capacity(256), status: String::with_capacity(256),
@ -667,12 +678,13 @@ impl StatusBar {
height: 1, height: 1,
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
auto_complete: AutoComplete::new(Vec::new()), auto_complete: AutoComplete::new(Vec::new()),
progress_spinner: ProgressSpinner::new(1), progress_spinner,
in_progress_jobs: HashSet::default(), in_progress_jobs: HashSet::default(),
done_jobs: HashSet::default(), done_jobs: HashSet::default(),
cmd_history: crate::command::history::old_cmd_history(), cmd_history: crate::command::history::old_cmd_history(),
} }
} }
fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let mut attribute = crate::conf::value(context, "status.bar"); let mut attribute = crate::conf::value(context, "status.bar");
if !context.settings.terminal.use_color() { 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() { 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) { if let Some(cell) = grid.get_mut(x.saturating_sub(idx).saturating_sub(1), y) {
cell.set_ch(c); cell.set_ch(c);
@ -714,6 +729,16 @@ impl StatusBar {
break; 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); context.dirty_areas.push_back(area);
} }
@ -763,26 +788,16 @@ impl Component for StatusBar {
context, 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.dirty = false;
self.draw_status_bar( self.draw_status_bar(
grid, grid,
(set_y(upper_left, get_y(bottom_right)), bottom_right), (set_y(upper_left, get_y(bottom_right)), bottom_right),
context, context,
); );
if self.mode != UIMode::Command && !self.is_dirty() {
return;
}
match self.mode { match self.mode {
UIMode::Normal => {} UIMode::Normal => {}
UIMode::Command => { UIMode::Command => {
@ -1020,7 +1035,11 @@ impl Component for StatusBar {
match event { match event {
UIEvent::ChangeMode(m) => { UIEvent::ChangeMode(m) => {
let offset = self.status.find('|').unwrap_or_else(|| self.status.len()); 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 { if self.mouse {
context context
.settings .settings
@ -1032,7 +1051,8 @@ impl Component for StatusBar {
} else { } else {
"" ""
}, },
)); ),
);
self.set_dirty(true); self.set_dirty(true);
self.container.set_dirty(true); self.container.set_dirty(true);
self.mode = *m; self.mode = *m;

View File

@ -1049,18 +1049,17 @@ impl ScrollBar {
#[derive(Debug)] #[derive(Debug)]
pub struct ProgressSpinner { pub struct ProgressSpinner {
//total_work: usize,
//finished: usize,
timer: crate::timer::PosixTimer, timer: crate::timer::PosixTimer,
stage: usize, stage: usize,
kind: usize, pub kind: std::result::Result<usize, Vec<String>>,
pub width: usize,
active: bool, active: bool,
dirty: bool, dirty: bool,
id: ComponentId, id: ComponentId,
} }
impl ProgressSpinner { 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 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 { pub fn new(kind: usize) -> Self {
let timer = crate::timer::PosixTimer::new_with_signal( let timer = crate::timer::PosixTimer::new_with_signal(
@ -1088,22 +1103,50 @@ impl ProgressSpinner {
nix::sys::signal::Signal::SIGALRM, nix::sys::signal::Signal::SIGALRM,
) )
.unwrap(); .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 { ProgressSpinner {
timer, timer,
stage: 0, stage: 0,
kind: kind % Self::KINDS.len(), kind: Ok(kind),
width,
dirty: true, dirty: true,
active: false, active: false,
id: ComponentId::new_v4(), 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<String>) {
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) { pub fn start(&mut self) {
self.active = true; self.active = true;
self.timer self.timer
.set_interval(Self::INTERVAL) .set_interval(Self::INTERVAL)
.set_value(Self::VALUE) .set_value(Self::INTERVAL)
.rearm() .rearm()
} }
@ -1117,6 +1160,12 @@ impl ProgressSpinner {
} }
} }
impl Drop for ProgressSpinner {
fn drop(&mut self) {
self.stop();
}
}
impl fmt::Display for ProgressSpinner { impl fmt::Display for ProgressSpinner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "progress bar") write!(f, "progress bar")
@ -1129,10 +1178,19 @@ impl Component for ProgressSpinner {
let theme_attr = crate::conf::value(context, "status.bar"); let theme_attr = crate::conf::value(context, "status.bar");
clear_area(grid, area, theme_attr); clear_area(grid, area, theme_attr);
if self.active { if self.active {
let stage = self.stage;
self.stage = (self.stage + 1).wrapping_rem(Self::KINDS[self.kind].len());
write_string_to_grid( 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, grid,
theme_attr.fg, theme_attr.fg,
theme_attr.bg, theme_attr.bg,

View File

@ -47,6 +47,11 @@ pub struct TerminalSettings {
pub window_title: Option<String>, pub window_title: Option<String>,
#[serde(deserialize_with = "non_empty_string")] #[serde(deserialize_with = "non_empty_string")]
pub file_picker_command: Option<String>, pub file_picker_command: Option<String>,
/// 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<ProgressSpinnerSequence>,
} }
impl Default for TerminalSettings { impl Default for TerminalSettings {
@ -60,6 +65,7 @@ impl Default for TerminalSettings {
mouse_flag: Some("🖱️ ".to_string()), mouse_flag: Some("🖱️ ".to_string()),
window_title: Some("meli".to_string()), window_title: Some("meli".to_string()),
file_picker_command: None, 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<String>),
}
impl DotAddressable for ProgressSpinnerSequence {}