use std::{
io::{stdout, Write},
sync::{
mpsc::{channel, SendError, Sender, TryRecvError},
Arc,
},
thread,
time::Duration,
};
use parking_lot::Mutex;
use strum::Display;
use crate::Spinners;
#[derive(Debug, Clone, Display)]
pub enum Event {
Stop,
SetMessage(String),
SetInterval(u64),
SetFrames(Vec<&'static str>),
}
#[derive(Debug, Clone)]
pub struct Spinner {
pub spinner: Spinners,
sender: Option<Sender<Event>>,
frames: Arc<Mutex<Vec<&'static str>>>,
interval: Arc<Mutex<u64>>,
message: Arc<Mutex<String>>,
}
impl Drop for Spinner {
fn drop(&mut self) {
self.stop();
}
}
impl Spinner {
pub fn new<T, S>(spinner: T, message: S) -> Self
where
T: Into<Spinners> + Copy,
S: std::fmt::Display,
{
let spinner_type: Spinners = spinner.into();
let frames = spinner_type.get_frames();
let length = frames.len();
Self {
spinner: spinner.into(),
frames: Arc::new(Mutex::new(frames)),
interval: Arc::new(Mutex::new(1000 / length as u64)),
message: Arc::new(Mutex::new(message.to_string())),
sender: None,
}
}
pub fn start(&mut self) {
let spinner = self.clone();
let (sender, recv) = channel::<Event>();
thread::spawn(move || 'outer: loop {
let mut stdout = stdout();
let mut frames = spinner.frames.lock();
for frame in frames.clone().iter() {
let mut message = spinner.message.lock();
let mut interval = spinner.interval.lock();
match recv.try_recv() {
Ok(Event::Stop) | Err(TryRecvError::Disconnected) => break 'outer,
Ok(Event::SetMessage(message_)) => *message = message_,
Ok(Event::SetInterval(interval_)) => *interval = interval_,
Ok(Event::SetFrames(frames_)) => {
*frames = frames_;
break;
}
Err(TryRecvError::Empty) => {}
};
print!("\r{} {}", frame, *message);
stdout.flush().unwrap();
thread::sleep(Duration::from_millis(*interval));
}
});
self.sender = Some(sender);
}
pub fn stop(&mut self) -> Option<SendError<Event>> {
let mut e = None;
if let Some(sender) = &self.sender {
e = sender.send(Event::Stop).err();
}
self.sender = None;
e
}
pub fn stop_with_message<S: std::fmt::Display>(&mut self, message: S) {
self.stop();
print!("\r{}", message);
stdout().flush().unwrap();
}
pub fn stop_with_symbol<S: std::fmt::Display>(&mut self, symbol: S) {
self.stop();
print!("\r{} {}", symbol, *self.message.lock());
stdout().flush().unwrap();
}
pub fn set_interval(&mut self, interval: u64) {
if let Some(sender) = &self.sender {
sender.send(Event::SetInterval(interval)).unwrap();
} else {
*self.interval.lock() = interval;
}
}
pub fn set_message<S: std::fmt::Display>(&mut self, message: S) {
if let Some(sender) = &self.sender {
sender.send(Event::SetMessage(message.to_string())).unwrap();
} else {
*self.message.lock() = message.to_string();
}
}
pub fn set_spinner(&mut self, spinner: Spinners) {
self.spinner = spinner;
if let Some(sender) = &self.sender {
sender.send(Event::SetFrames(spinner.get_frames())).unwrap();
} else {
*self.frames.lock() = spinner.get_frames();
}
}
pub fn get_name(&self) -> String {
let sp_string = self.spinner.to_string().chars().collect::<Vec<char>>();
sp_string[0].to_uppercase().to_string()
+ sp_string[1..].iter().cloned().collect::<String>().as_str()
}
}
impl From<Spinners> for Spinner {
fn from(spinner: Spinners) -> Self {
Spinner::new(spinner, "")
}
}