pub mod spinners;
use std::{
sync::{Arc, Mutex},
thread::{self},
time::Duration,
};
use anyhow::Result;
use crossterm::{
cursor::MoveToRow,
style::Print,
terminal::{Clear, ClearType},
ExecutableCommand,
};
use crate::spinners::Animation;
#[must_use]
#[derive(Clone)]
pub struct Spinner {
spinner: Animation,
row: usize,
msg: String,
active: Arc<Mutex<bool>>,
stdout: Arc<Mutex<std::io::Stdout>>,
handle: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
}
impl Spinner {
pub fn builder() -> SpinnerBuilder {
SpinnerBuilder::default()
}
pub fn start(&mut self) {
*self.active.lock().expect("lock active's mutex") = true;
let active = self.active.clone();
let mut spinner = self.clone();
let handle = thread::spawn(move || {
while *active.lock().expect("lock active's mutex") {
spinner.spin().expect("spin");
thread::sleep(Duration::from_millis(65_u64));
}
});
*self.handle.lock().expect("lock handle's mutex") = Some(handle);
}
fn spin(&mut self) -> Result<()> {
let mut stdout = self.stdout.lock().expect("lock stdout's mutex");
let row = u16::try_from(self.row)?;
stdout.execute(MoveToRow(row))?;
stdout.execute(Clear(ClearType::CurrentLine))?;
let frame = self.spinner.next_frame();
let msg = &self.msg;
stdout.execute(MoveToRow(row))?;
stdout.execute(Print(format!("{frame} {msg}")))?;
drop(stdout);
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
*self.active.lock().expect("lock active's mutex") = false;
let handle = self
.handle
.clone()
.lock()
.expect("lock handle's mutex")
.take();
if let Some(handle) = handle {
let () = handle.join().expect("join spinner thread");
}
let mut stdout = self.stdout.lock().expect("lock stdout's mutex");
let row = u16::try_from(self.row)?;
stdout.execute(MoveToRow(row))?;
stdout.execute(Clear(ClearType::CurrentLine))?;
drop(stdout);
Ok(())
}
}
#[must_use]
#[derive(Clone)]
pub struct SpinnerBuilder {
spinner: Animation,
row: usize,
msg: String,
active: Arc<Mutex<bool>>,
stdout: Arc<Mutex<std::io::Stdout>>,
handle: Arc<Mutex<Option<thread::JoinHandle<()>>>>,
}
impl Default for SpinnerBuilder {
fn default() -> Self {
Self {
spinner: Animation::Dots2(0),
row: 0,
msg: "Loading".to_owned(),
active: Arc::new(Mutex::new(false)),
stdout: Arc::new(Mutex::new(std::io::stdout())),
handle: Arc::new(Mutex::new(None)),
}
}
}
impl SpinnerBuilder {
pub fn stdout(mut self, stdout: Arc<Mutex<std::io::Stdout>>) -> Self {
self.stdout = stdout;
self
}
pub fn msg(mut self, msg: String) -> Self {
self.msg = msg + "\n";
self
}
pub const fn row(mut self, row: usize) -> Self {
self.row = row;
self
}
pub const fn spinner(mut self, spinner: Animation) -> Self {
self.spinner = spinner;
self
}
pub fn start(self) -> Spinner {
let mut spinner = self.build();
spinner.start();
spinner
}
pub fn build(self) -> Spinner {
Spinner {
spinner: self.spinner,
row: self.row,
msg: self.msg,
active: self.active,
stdout: self.stdout,
handle: self.handle,
}
}
}