term_kit/
spinner.rs

1use crossterm::terminal::Clear;
2use crossterm::{cursor, execute, style::Print, terminal::ClearType};
3use std::io::{stdout, Write};
4use std::sync::{
5    atomic::{AtomicBool, Ordering},
6    Arc,
7};
8use std::thread;
9use std::time::Duration;
10
11pub struct Spinner {
12    frames: Vec<&'static str>,
13    is_spinning: Arc<AtomicBool>,
14    position: (u16, u16),
15}
16
17impl Spinner {
18    pub fn new() -> Self {
19        let (x, y) = cursor::position().unwrap();
20        Self {
21            frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
22            is_spinning: Arc::new(AtomicBool::new(false)),
23            position: (x, y),
24        }
25    }
26
27    pub fn render(&self) {
28        let is_spinning = self.is_spinning.clone();
29        let position: (u16, u16) = self.position;
30        let frames = self.frames.clone();
31
32        is_spinning.store(true, Ordering::Relaxed);
33
34        thread::spawn(move || {
35            let mut current_frame = 0;
36            while is_spinning.load(Ordering::Relaxed) {
37                let frame = frames[current_frame];
38                let mut stdout = stdout();
39                execute!(
40                    stdout,
41                    cursor::MoveTo(position.0, position.1),
42                    Clear(ClearType::CurrentLine),
43                    Print(frame)
44                )
45                .unwrap();
46                stdout.flush().unwrap();
47                current_frame = (current_frame + 1) % frames.len();
48                thread::sleep(Duration::from_millis(100));
49            }
50        });
51    }
52
53    pub fn stop(&self) {
54        execute!(
55            stdout(),
56            cursor::MoveTo(self.position.0, self.position.1),
57            Clear(ClearType::CurrentLine)
58        )
59        .unwrap();
60        self.is_spinning.store(false, Ordering::Relaxed);
61    }
62}