1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
use crossterm::terminal::Clear;
use crossterm::{cursor, execute, style::Print, terminal::ClearType};
use std::io::{stdout, Write};
use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
use std::thread;
use std::time::Duration;

pub struct Spinner {
    frames: Vec<&'static str>,
    is_spinning: Arc<AtomicBool>, 
    position: (u16, u16),
}

impl Spinner {
    pub fn new() -> Self {
        let (x, y) = cursor::position().unwrap();
        Self {
            frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
            is_spinning: Arc::new(AtomicBool::new(false)),
            position: (x, y),
        }
    }

    pub fn start(&self) {
        let is_spinning = self.is_spinning.clone();
        let position = self.position;
        let frames = self.frames.clone();
        
        is_spinning.store(true, Ordering::Relaxed);

        thread::spawn(move || { 
            let mut current_frame = 0;
            while is_spinning.load(Ordering::Relaxed) {
                let frame = frames[current_frame];
                let mut stdout = stdout();
                execute!(
                    stdout,
                    cursor::MoveTo(position.0, position.1),
                    Clear(ClearType::CurrentLine),
                    Print(frame)
                )
                .unwrap();
                stdout.flush().unwrap();
                current_frame = (current_frame + 1) % frames.len();
                thread::sleep(Duration::from_millis(100));
            }
        });
    }

    pub fn stop(&self) {
        print!("{esc}c", esc = 27 as char);
        self.is_spinning.store(false, Ordering::Relaxed);
    }
}