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}