From b2804b14b13c511cbf9c3bba88e1687a0d0908a8 Mon Sep 17 00:00:00 2001 From: wojciechkepka Date: Sun, 20 Jun 2021 21:31:18 +0200 Subject: [PATCH] Add a `Spinner` --- helix-term/src/ui/mod.rs | 2 + helix-term/src/ui/spinner.rs | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 helix-term/src/ui/spinner.rs diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index e0177b7c9..c062bffe3 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -5,6 +5,7 @@ mod menu; mod picker; mod popup; mod prompt; +mod spinner; mod text; pub use completion::Completion; @@ -14,6 +15,7 @@ pub use menu::Menu; pub use picker::Picker; pub use popup::Popup; pub use prompt::{Prompt, PromptEvent}; +pub use spinner::{ProgressSpinners, Spinner}; pub use text::Text; pub use tui::layout::Rect; diff --git a/helix-term/src/ui/spinner.rs b/helix-term/src/ui/spinner.rs new file mode 100644 index 000000000..e8a43b48d --- /dev/null +++ b/helix-term/src/ui/spinner.rs @@ -0,0 +1,75 @@ +use std::{collections::HashMap, time::SystemTime}; + +#[derive(Default, Debug)] +pub struct ProgressSpinners { + inner: HashMap, +} + +impl ProgressSpinners { + pub fn get(&self, id: usize) -> Option<&Spinner> { + self.inner.get(&id) + } + + pub fn get_or_create(&mut self, id: usize) -> &mut Spinner { + self.inner.entry(id).or_insert_with(Spinner::default) + } +} + +impl Default for Spinner { + fn default() -> Self { + Self::dots(80) + } +} + +#[derive(Debug)] +pub struct Spinner { + frames: Vec<&'static str>, + count: usize, + start: Option, + interval: u64, +} + +impl Spinner { + /// Creates a new spinner with `frames` and `interval`. + /// Expects the frames count and interval to be greater than 0. + pub fn new(frames: Vec<&'static str>, interval: u64) -> Self { + let count = frames.len(); + assert!(count > 0); + assert!(interval > 0); + + Self { + frames, + count, + interval, + start: None, + } + } + + pub fn dots(interval: u64) -> Self { + Self::new(vec!["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], interval) + } + + pub fn start(&mut self) { + self.start = Some(SystemTime::now()); + } + + pub fn frame(&self) -> Option<&str> { + let idx = (self + .start + .map(|time| SystemTime::now().duration_since(time))? + .ok()? + .as_millis() + / self.interval as u128) as usize + % self.count; + + self.frames.get(idx).copied() + } + + pub fn stop(&mut self) { + self.start = None; + } + + pub fn is_stopped(&self) -> bool { + self.start.is_none() + } +}