promkit_widgets/
spinner.rs1use std::time::Duration;
2
3use crate::core::{Pane, grapheme::StyledGraphemes, render::SharedRenderer};
4
5pub mod frame;
6use frame::Frame;
7
8pub trait State {
10 fn is_idle(&self) -> impl Future<Output = bool> + Send;
11}
12
13#[derive(Clone, Debug)]
15pub struct Spinner {
16 pub frames: Frame,
18 pub suffix: String,
20 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 pub fn frames(mut self, frames: Frame) -> Self {
37 self.frames = frames;
38 self
39 }
40
41 pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
43 self.suffix = suffix.into();
44 self
45 }
46
47 pub fn duration(mut self, duration: Duration) -> Self {
49 self.duration = duration;
50 self
51 }
52}
53
54pub 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}