spinners_rs/spinner.rs
1use std::{
2 io::{stdout, Write},
3 sync::{
4 mpsc::{channel, SendError, Sender, TryRecvError},
5 Arc,
6 },
7 thread,
8 time::Duration,
9};
10
11use parking_lot::Mutex;
12use strum::Display;
13
14use crate::Spinners;
15
16#[derive(Debug, Clone, Display)]
17/// All the different events that can occur
18pub enum Event {
19 /// The spinner has finished.
20 Stop,
21 /// Update the spinner message
22 SetMessage(String),
23 /// Update the spinner interval
24 SetInterval(u64),
25 /// Update the spinner frames
26 SetFrames(Vec<&'static str>),
27}
28
29#[derive(Debug, Clone)]
30/// Main spinner struct
31///
32/// This holds all the information for the actual spinners
33pub struct Spinner {
34 /// The enum variant used in this spinner
35 pub spinner: Spinners,
36 sender: Option<Sender<Event>>,
37 frames: Arc<Mutex<Vec<&'static str>>>,
38 interval: Arc<Mutex<u64>>,
39 message: Arc<Mutex<String>>,
40}
41
42impl Drop for Spinner {
43 fn drop(&mut self) {
44 self.stop();
45 }
46}
47
48impl Spinner {
49 /// Create a new spinner along with a message
50 ///
51 /// # Examples
52 ///
53 /// ## Basic Usage:
54 ///
55 /// ```
56 /// use spinners_rs::{Spinners, Spinner};
57 ///
58 /// let mut sp = Spinner::new(Spinners::Dots, "Doing some cool things...");
59 /// sp.start();
60 /// ```
61 ///
62 /// ## No Message:
63 ///
64 /// ```
65 /// use spinners_rs::{Spinners, Spinner};
66 ///
67 /// let mut sp: Spinner = Spinners::Dots.into();
68 /// sp.start();
69 /// ```
70 pub fn new<T, S>(spinner: T, message: S) -> Self
71 where
72 T: Into<Spinners> + Copy,
73 S: std::fmt::Display,
74 {
75 let spinner_type: Spinners = spinner.into();
76 let frames = spinner_type.get_frames();
77 let length = frames.len();
78
79 Self {
80 spinner: spinner.into(),
81 frames: Arc::new(Mutex::new(frames)),
82 interval: Arc::new(Mutex::new(1000 / length as u64)),
83 message: Arc::new(Mutex::new(message.to_string())),
84 sender: None,
85 }
86 }
87
88 /// Start the spinner
89 ///
90 /// Explained more in depth in the [`Spinner::new`] function.
91 pub fn start(&mut self) {
92 let spinner = self.clone();
93
94 let (sender, recv) = channel::<Event>();
95
96 thread::spawn(move || 'outer: loop {
97 let mut stdout = stdout();
98 let mut frames = spinner.frames.lock();
99
100 for frame in frames.clone().iter() {
101 let mut message = spinner.message.lock();
102 let mut interval = spinner.interval.lock();
103
104 match recv.try_recv() {
105 Ok(Event::Stop) | Err(TryRecvError::Disconnected) => break 'outer,
106 Ok(Event::SetMessage(message_)) => *message = message_,
107 Ok(Event::SetInterval(interval_)) => *interval = interval_,
108 Ok(Event::SetFrames(frames_)) => {
109 *frames = frames_;
110 // Break the inner loop and start over with the new frames
111 break;
112 }
113 Err(TryRecvError::Empty) => {}
114 };
115
116 print!("\r{} {}", frame, *message);
117 stdout.flush().unwrap();
118 thread::sleep(Duration::from_millis(*interval));
119 }
120 });
121
122 self.sender = Some(sender);
123 }
124
125 /// Stops the spinner from running
126 ///
127 /// Alternatively you can use the [`Spinner::stop_with_message`] or [`Spinner::stop_with_symbol`] function.
128 ///
129 /// # Example:
130 ///
131 /// ```
132 /// use spinners_rs::{Spinners, Spinner};
133 /// use std::{thread, time::Duration};
134 ///
135 /// let mut sp: Spinner = Spinners::Dots.into();
136 /// sp.start();
137 ///
138 /// thread::sleep(Duration::from_millis(1000));
139 ///
140 /// sp.stop();
141 /// ```
142 pub fn stop(&mut self) -> Option<SendError<Event>> {
143 let mut e = None;
144 if let Some(sender) = &self.sender {
145 e = sender.send(Event::Stop).err();
146 }
147
148 self.sender = None;
149
150 e
151 }
152
153 /// Stops the spinner and replaces it with the given message
154 ///
155 /// # Example:
156 ///
157 /// ```
158 /// use spinners_rs::{Spinners, Spinner};
159 /// use std::{thread, time::Duration};
160 ///
161 /// let mut sp: Spinner = Spinners::Dots.into();
162 /// sp.start();
163 ///
164 /// thread::sleep(Duration::from_millis(1000));
165 ///
166 /// sp.stop_with_message("We've finished that thing!");
167 /// ```
168 pub fn stop_with_message<S: std::fmt::Display>(&mut self, message: S) {
169 self.stop();
170 print!("\r{}", message);
171 stdout().flush().unwrap();
172 }
173
174 /// Stops the spinner and replaces the current frame with the given symbol
175 ///
176 /// # Example:
177 ///
178 /// ```
179 /// use spinners_rs::{Spinners, Spinner};
180 /// use std::{thread, time::Duration};
181 ///
182 /// let mut sp: Spinner = Spinners::Dots.into();
183 /// sp.start();
184 ///
185 /// thread::sleep(Duration::from_millis(1000));
186 ///
187 /// sp.stop_with_symbol('✓');
188 /// ```
189 pub fn stop_with_symbol<S: std::fmt::Display>(&mut self, symbol: S) {
190 self.stop();
191 print!("\r{} {}", symbol, *self.message.lock());
192 stdout().flush().unwrap();
193 }
194
195 /// Updates the frame interval
196 ///
197 /// This changes how fast each frame comes up
198 ///
199 /// This can be changed before the spinner is started or after
200 ///
201 /// # Example:
202 ///
203 /// ```
204 /// use spinners_rs::{Spinners, Spinner};
205 /// use std::{thread, time::Duration};
206 ///
207 /// let mut sp: Spinner = Spinners::Dots.into();
208 /// sp.start();
209 ///
210 /// // Will run through one iteration of frames
211 /// thread::sleep(Duration::from_millis(1000));
212 ///
213 /// sp.set_interval(500);
214 ///
215 /// // Will now run through two iterations of the frames
216 /// thread::sleep(Duration::from_millis(1000));
217 ///
218 /// sp.stop();
219 /// ```
220 pub fn set_interval(&mut self, interval: u64) {
221 if let Some(sender) = &self.sender {
222 sender.send(Event::SetInterval(interval)).unwrap();
223 } else {
224 *self.interval.lock() = interval;
225 }
226 }
227
228 /// Sets the message to display
229 ///
230 /// Similar to [`Spinner::set_interval`], this can be set before or after a spinner is started
231 ///
232 /// # Example :
233 ///
234 /// ```
235 /// use spinners_rs::{Spinners, Spinner};
236 /// use std::{thread, time::Duration};
237 ///
238 /// let mut sp: Spinner = Spinners::Dots.into();
239 /// sp.start();
240 ///
241 /// thread::sleep(Duration::from_millis(1000));
242 ///
243 /// sp.set_message("Doing some cool things...");
244 ///
245 /// thread::sleep(Duration::from_millis(1000));
246 ///
247 /// sp.stop();
248 /// ```
249 pub fn set_message<S: std::fmt::Display>(&mut self, message: S) {
250 if let Some(sender) = &self.sender {
251 sender.send(Event::SetMessage(message.to_string())).unwrap();
252 } else {
253 *self.message.lock() = message.to_string();
254 }
255 }
256
257 /// Changes the spinner mid run
258 ///
259 /// This will change the spinner to the given one, allowing you to change the frames shown, on the current spinner without allocating a new variable and memory.
260 ///
261 /// # Example:
262 ///
263 /// ```
264 /// use std::{thread, time::Duration};
265 ///
266 /// use spinners_rs::{Spinner, Spinners};
267 ///
268 /// use strum::IntoEnumIterator;
269 ///
270 /// let sps = Spinners::iter().collect::<Vec<Spinners>>();
271 /// let len = sps.len();
272 /// let sp = sps.get(0).unwrap();
273 /// let mut spinner: Spinner = (*sp).into();
274 /// spinner.start();
275 ///
276 /// for (i, sp) in sps[1..].iter().enumerate() {
277 /// spinner.set_spinner(*sp);
278 ///
279 /// thread::sleep(Duration::from_millis(1000));
280 /// }
281 /// ```
282 pub fn set_spinner(&mut self, spinner: Spinners) {
283 self.spinner = spinner;
284 if let Some(sender) = &self.sender {
285 sender.send(Event::SetFrames(spinner.get_frames())).unwrap();
286 } else {
287 *self.frames.lock() = spinner.get_frames();
288 }
289 }
290
291 /// Gets the spinner name capitalizes the first letter.
292 ///
293 /// # Example:
294 ///
295 /// ```
296 /// use spinners_rs::{Spinners, Spinner};
297 ///
298 /// let sp: Spinner = Spinners::Dots.into();
299 /// assert_eq!(sp.get_name(), "Dots");
300 /// ```
301 pub fn get_name(&self) -> String {
302 let sp_string = self.spinner.to_string().chars().collect::<Vec<char>>();
303
304 sp_string[0].to_uppercase().to_string()
305 + sp_string[1..].iter().cloned().collect::<String>().as_str()
306 }
307}
308
309impl From<Spinners> for Spinner {
310 fn from(spinner: Spinners) -> Self {
311 Spinner::new(spinner, "")
312 }
313}