Skip to main content

promkit_widgets/
spinner.rs

1use std::time::Duration;
2
3use crate::core::{Pane, grapheme::StyledGraphemes, render::SharedRenderer};
4
5pub mod frame;
6use frame::Frame;
7
8/// Trait to define the state of the spinner.
9pub trait State {
10    fn is_idle(&self) -> impl Future<Output = bool> + Send;
11}
12
13/// A spinner that can be used to indicate loading or processing states.
14#[derive(Clone, Debug)]
15pub struct Spinner {
16    /// The frames of the spinner, which are the characters that will be displayed in a rotating manner.
17    pub frames: Frame,
18    /// A suffix that will be displayed alongside the spinner.
19    pub suffix: String,
20    /// The duration between frame updates.
21    pub duration: Duration,
22}
23
24impl Default for Spinner {
25    fn default() -> Self {
26        Self {
27            frames: frame::DOTS,
28            suffix: String::new(),
29            duration: Duration::from_millis(100),
30        }
31    }
32}
33
34impl Spinner {
35    /// Set frames for the spinner.
36    pub fn frames(mut self, frames: Frame) -> Self {
37        self.frames = frames;
38        self
39    }
40
41    /// Set a suffix for the spinner.
42    pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
43        self.suffix = suffix.into();
44        self
45    }
46
47    /// Set the duration between frame updates.
48    pub fn duration(mut self, duration: Duration) -> Self {
49        self.duration = duration;
50        self
51    }
52}
53
54/// Spawn a background task that shows a spinner while the state is active.
55pub async fn run<S, I>(
56    spinner: &Spinner,
57    state: S,
58    index: I,
59    renderer: SharedRenderer<I>,
60) -> anyhow::Result<()>
61where
62    S: State,
63    I: Clone + Ord + Send,
64{
65    let mut frame_index = 0;
66    let mut interval = tokio::time::interval(spinner.duration);
67
68    loop {
69        interval.tick().await;
70
71        if !state.is_idle().await {
72            frame_index = (frame_index + 1) % spinner.frames.len();
73
74            renderer
75                .update([(
76                    index.clone(),
77                    Pane::new(
78                        vec![StyledGraphemes::from(format!(
79                            "{} {}",
80                            spinner.frames[frame_index], spinner.suffix
81                        ))],
82                        0,
83                    ),
84                )])
85                .render()
86                .await?;
87        }
88    }
89}