1pub mod spinners;
2
3use std::{
4 sync::{Arc, Mutex},
5 thread::{self},
6 time::Duration,
7};
8
9use anyhow::Result;
10use crossterm::{
11 cursor::MoveToRow,
12 style::Print,
13 terminal::{Clear, ClearType},
14 ExecutableCommand,
15};
16
17use crate::spinners::Animation;
18
19#[must_use]
51#[derive(Clone)]
52pub struct Spinner {
53 spinner: Animation,
54 row: usize,
55 msg: String,
56 active: Arc<Mutex<bool>>,
57 stdout: Arc<Mutex<std::io::Stdout>>,
58 handle: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
59}
60
61impl Spinner {
62 pub fn builder() -> SpinnerBuilder {
63 SpinnerBuilder::default()
64 }
65
66 pub fn start(&mut self) {
71 *self.active.lock().expect("lock active's mutex") = true;
72
73 let active = self.active.clone();
74 let mut spinner = self.clone();
75
76 let handle = thread::spawn(move || {
77 while *active.lock().expect("lock active's mutex") {
78 spinner.spin().expect("spin");
79 thread::sleep(Duration::from_millis(65_u64));
80 }
81 });
82
83 *self.handle.lock().expect("lock handle's mutex") = Some(handle);
84 }
85
86 fn spin(&mut self) -> Result<()> {
92 let mut stdout = self.stdout.lock().expect("lock stdout's mutex");
93 let row = u16::try_from(self.row)?;
94 stdout.execute(MoveToRow(row))?;
95 stdout.execute(Clear(ClearType::CurrentLine))?;
96
97 let frame = self.spinner.next_frame();
98 let msg = &self.msg;
99
100 stdout.execute(MoveToRow(row))?;
101 stdout.execute(Print(format!("{frame} {msg}")))?;
102 drop(stdout);
103
104 Ok(())
105 }
106
107 pub fn stop(&mut self) -> Result<()> {
115 *self.active.lock().expect("lock active's mutex") = false;
116
117 let handle = self
118 .handle
119 .clone()
120 .lock()
121 .expect("lock handle's mutex")
122 .take();
123
124 if let Some(handle) = handle {
125 let () = handle.join().expect("join spinner thread");
126 }
127
128 let mut stdout = self.stdout.lock().expect("lock stdout's mutex");
129 let row = u16::try_from(self.row)?;
130 stdout.execute(MoveToRow(row))?;
131 stdout.execute(Clear(ClearType::CurrentLine))?;
132 drop(stdout);
133
134 Ok(())
135 }
136}
137
138#[must_use]
139#[derive(Clone)]
140pub struct SpinnerBuilder {
141 spinner: Animation,
142 row: usize,
143 msg: String,
144 active: Arc<Mutex<bool>>,
145 stdout: Arc<Mutex<std::io::Stdout>>,
146 handle: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
147}
148
149impl Default for SpinnerBuilder {
150 fn default() -> Self {
151 Self {
152 spinner: Animation::Dots2(0),
153 row: 0,
154 msg: "Loading".to_owned(),
155 active: Arc::new(Mutex::new(false)),
156 stdout: Arc::new(Mutex::new(std::io::stdout())),
157 handle: Arc::new(Mutex::new(None)),
158 }
159 }
160}
161
162impl SpinnerBuilder {
163 pub fn stdout(mut self, stdout: Arc<Mutex<std::io::Stdout>>) -> Self {
165 self.stdout = stdout;
166 self
167 }
168
169 pub fn msg(mut self, msg: String) -> Self {
171 self.msg = msg + "\n";
172 self
173 }
174
175 pub const fn row(mut self, row: usize) -> Self {
178 self.row = row;
179 self
180 }
181
182 pub const fn spinner(mut self, spinner: Animation) -> Self {
185 self.spinner = spinner;
186 self
187 }
188
189 pub fn start(self) -> Spinner {
191 let mut spinner = self.build();
192 spinner.start();
193 spinner
194 }
195
196 pub fn build(self) -> Spinner {
198 Spinner {
199 spinner: self.spinner,
200 row: self.row,
201 msg: self.msg,
202 active: self.active,
203 stdout: self.stdout,
204 handle: self.handle,
205 }
206 }
207}