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}