1use std::{io::{self, Write}, sync::{atomic::{AtomicBool, AtomicUsize, Ordering}, Arc}, thread::{self, JoinHandle}, time::Duration};
2
3use crossterm::{cursor, execute, terminal};
4
5pub struct Throbber {
6 frame: Arc<AtomicUsize>,
7 animation_length: Arc<usize>,
8 animation: Arc<Vec<char>>,
9 frame_time: Arc<f32>,
10 run: Arc<AtomicBool>,
11 killed: Arc<AtomicBool>,
12 join_handle: Option<JoinHandle<()>>
13}
14
15impl Throbber {
16 fn spawn(animation: Arc<Vec<char>>, animation_length: Arc<usize>, frame: Arc<AtomicUsize>, frame_time: Arc<f32>, killed: Arc<AtomicBool>, run: Arc<AtomicBool>) -> JoinHandle<()> {
17 thread::spawn(move || {
18 loop {
19 if killed.load(Ordering::SeqCst) {
20 break;
21 }
22
23 if run.load(Ordering::SeqCst) {
24 let mut stdout = io::stdout();
25
26 execute!(stdout, cursor::SavePosition {}).expect("Failed to save the cursor's position");
27 print!("{}", animation.get(frame.load(Ordering::SeqCst)).unwrap());
28 stdout.flush().expect("Failed to flush stdout");
29 execute!(stdout, cursor::RestorePosition {}).expect("Failed to restore the cursor's position");
30 frame.store((frame.load(Ordering::SeqCst) + 1) % animation_length.as_ref().clone(), Ordering::SeqCst);
31
32 thread::sleep(Duration::from_secs_f32(frame_time.as_ref().clone()));
33 }
34 }
35 })
36 }
37
38 pub fn custom(animation: Vec<char>, frame_time: f32) -> Self {
39 let mut new = Throbber {
40 frame: Arc::new(AtomicUsize::new(0)),
41 animation_length: Arc::new(animation.len()),
42 animation: Arc::new(animation),
43 run: Arc::new(AtomicBool::new(false)),
44 frame_time: Arc::new(frame_time),
45 killed: Arc::new(AtomicBool::new(false)),
46 join_handle: None
47 };
48
49 new.join_handle = Some(Self::spawn(new.animation.clone(), new.animation_length.clone(), new.frame.clone(), new.frame_time.clone(), new.killed.clone(), new.run.clone()));
50 new
51 }
52
53 pub fn classic(frame_time: f32) -> Self {
54 let mut new = Throbber {
55 frame: Arc::new(AtomicUsize::new(0)),
56 animation_length: Arc::new(4),
57 animation: Arc::new(['|', '/', '-', '\\'].to_vec()),
58 run: Arc::new(AtomicBool::new(false)),
59 frame_time: Arc::new(frame_time),
60 killed: Arc::new(AtomicBool::new(false)),
61 join_handle: None
62 };
63
64 new.join_handle = Some(Self::spawn(new.animation.clone(), new.animation_length.clone(), new.frame.clone(), new.frame_time.clone(), new.killed.clone(), new.run.clone()));
65 new
66 }
67
68 pub fn braille(frame_time: f32) -> Self {
69 let mut new = Throbber {
70 frame: Arc::new(AtomicUsize::new(0)),
71 animation_length: Arc::new(8),
72 animation: Arc::new(['⠇', '⠋', '⠙', '⠸', '⢰', '⣠', '⣄', '⡆'].to_vec()),
73 run: Arc::new(AtomicBool::new(false)),
74 frame_time: Arc::new(frame_time),
75 killed: Arc::new(AtomicBool::new(false)),
76 join_handle: None
77 };
78
79 new.join_handle = Some(Self::spawn(new.animation.clone(), new.animation_length.clone(), new.frame.clone(), new.frame_time.clone(), new.killed.clone(), new.run.clone()));
80 new
81 }
82
83 pub fn start(&self, raw_mode: bool) {
85 if raw_mode {
86 terminal::enable_raw_mode().expect("Failed to enable raw mode");
87 }
88
89 self.frame.store(0, Ordering::SeqCst);
90 self.run.store(true, Ordering::SeqCst);
91 }
92
93 pub fn stop(&self, raw_mode: bool) {
96 if raw_mode {
97 terminal::disable_raw_mode().expect("Failed to disable raw mode");
98 }
99
100 self.run.store(false, Ordering::SeqCst);
101 }
102
103 pub fn kill_thread(&self) {
106 self.killed.store(true, Ordering::SeqCst);
107 }
108}
109
110impl Drop for Throbber {
111 fn drop(&mut self) {
112 self.kill_thread();
113 }
114}
115