term_kit/
spinner.rs

1pub use crate::spinner::LabelPosition::*;
2use crossterm::cursor::Hide;
3use crossterm::cursor::Show;
4use crossterm::terminal::Clear;
5use crossterm::{cursor, execute, style::Print, terminal::ClearType};
6use std::io::{stdout, Write};
7use std::sync::{
8    atomic::{AtomicBool, Ordering},
9    Arc,
10};
11use std::thread;
12use std::time::Duration;
13
14#[derive(Clone)]
15pub enum LabelPosition {
16    Before,
17    After,
18}
19
20pub struct Spinner {
21    frames: Vec<&'static str>,
22    is_spinning: Arc<AtomicBool>,
23    position: (u16, u16),
24    label: Option<String>,
25    label_position: LabelPosition,
26}
27
28impl Spinner {
29    pub fn new() -> Self {
30        let (x, y) = cursor::position().unwrap();
31        Self {
32            frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
33            is_spinning: Arc::new(AtomicBool::new(false)),
34            position: (x, y),
35            label: None,
36            label_position: After,
37        }
38    }
39
40    pub fn with_label(mut self, label: String) -> Self {
41        self.label = Some(label);
42        self
43    }
44
45    pub fn with_label_position(mut self, label_position: LabelPosition) -> Self {
46        self.label_position = label_position;
47        self
48    }
49
50    pub fn start(&self) {
51        let is_spinning = self.is_spinning.clone();
52        let position: (u16, u16) = self.position;
53        let frames = self.frames.clone();
54        let label = self.label.clone().unwrap_or("".to_string());
55        let label_position = self.label_position.clone();
56
57        is_spinning.store(true, Ordering::Relaxed);
58
59        thread::spawn(move || {
60            let mut current_frame = 0;
61            while is_spinning.load(Ordering::Relaxed) {
62                let frame = frames[current_frame];
63                let mut stdout = stdout();
64                execute!(
65                    stdout,
66                    Hide,
67                    cursor::MoveTo(position.0, position.1),
68                    Clear(ClearType::CurrentLine),
69                    match label_position {
70                        Before => {
71                            Print(format!(" {} {}", label, frame))
72                        }
73                        After => {
74                            Print(format!(" {} {}", frame, label))
75                        }
76                    }
77                )
78                .unwrap();
79                stdout.flush().unwrap();
80                current_frame = (current_frame + 1) % frames.len();
81                thread::sleep(Duration::from_millis(100));
82            }
83        });
84    }
85
86    pub fn stop(&self) {
87        execute!(
88            stdout(),
89            Show,
90            cursor::MoveTo(self.position.0, self.position.1),
91            Clear(ClearType::CurrentLine)
92        )
93        .unwrap();
94        self.is_spinning.store(false, Ordering::Relaxed);
95    }
96}