#![doc = include_str!("../examples/calculation.rs")]
#![doc = include_str!("../examples/download.rs")]
use std::io::Write;
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::thread::{self, JoinHandle};
use std::time::Duration;
pub const DEFAULT_F: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub const CIRCLE_F: [&str; 4] = ["◐", "◓", "◑", "◒"];
pub const ROTATE_F: [&str; 4] = ["|", "/", "-", "\\"];
pub const MOVE_EQ_F: [&str; 4] = ["[= ]", "[ = ]", "[ =]", "[ = ]"];
pub const MOVE_MIN_F: [&str; 4] = ["[- ]", "[ - ]", "[ -]", "[ - ]"];
pub const MOVE_EQ_LONG_F: [&str; 10] = [
"[= ]", "[== ]", "[ == ]", "[ == ]", "[ ==]", "[ =]", "[ ==]", "[ == ]",
"[ == ]", "[== ]",
];
pub const MOVE_MIN_LONG_F: [&str; 10] = [
"[- ]", "[-- ]", "[ -- ]", "[ -- ]", "[ --]", "[ -]", "[ --]", "[ -- ]",
"[ -- ]", "[-- ]",
];
pub struct Throbber {
anim: Option<ThrobberAnim>,
message: String,
interval: Duration,
frames: &'static [&'static str],
}
struct ThrobberAnim {
thread: JoinHandle<()>,
sender: Sender<ThrobberSignal>,
}
enum ThrobberSignal {
Start,
Finish,
Succ(String),
Fail(String),
ChMsg(String),
ChInt(Duration),
ChFrames(&'static [&'static str]),
End,
}
impl Default for Throbber {
fn default() -> Self {
Self {
anim: None,
message: "".to_owned(),
interval: Duration::from_millis(200),
frames: &DEFAULT_F,
}
}
}
impl Drop for Throbber {
fn drop(&mut self) {
if let Some(anim) = self.anim.take() {
anim.sender.send(ThrobberSignal::End).unwrap();
anim.thread.thread().unpark();
anim.thread.join().unwrap();
}
}
}
impl Throbber {
pub fn new<S: Into<String>>(
message: S,
interval: Duration,
frames: &'static [&'static str],
) -> Self {
Self {
anim: None,
message: message.into(),
interval,
frames,
}
}
pub fn message<S: Into<String>>(mut self, msg: S) -> Self {
self.set_message(msg);
self
}
pub fn set_message<S: Into<String>>(&mut self, msg: S) {
self.message = msg.into();
if let Some(ref anim) = self.anim {
anim.sender
.send(ThrobberSignal::ChMsg(self.message.clone()))
.unwrap();
anim.thread.thread().unpark();
}
}
pub fn interval<D: Into<Duration>>(mut self, interval: D) -> Self {
self.set_interval(interval);
self
}
pub fn set_interval<D: Into<Duration>>(&mut self, interval: D) {
self.interval = interval.into();
if let Some(ref anim) = self.anim {
anim.sender
.send(ThrobberSignal::ChInt(self.interval))
.unwrap();
anim.thread.thread().unpark();
}
}
pub fn frames(mut self, frames: &'static [&'static str]) -> Self {
self.set_frames(frames);
self
}
pub fn set_frames(&mut self, frames: &'static [&'static str]) {
self.frames = frames.into();
if let Some(ref anim) = self.anim {
anim.sender
.send(ThrobberSignal::ChFrames(self.frames))
.unwrap();
anim.thread.thread().unpark();
}
}
pub fn start(&mut self) {
if let Some(ref anim) = self.anim {
anim.sender.send(ThrobberSignal::Start).unwrap();
anim.thread.thread().unpark();
return;
}
let (sender, receiver): (Sender<ThrobberSignal>, Receiver<ThrobberSignal>) =
mpsc::channel();
let msg = self.message.clone();
let interval = self.interval;
let frames = self.frames;
let thread = thread::spawn(move || animation_thread(receiver, msg, interval, frames));
self.anim = Some(ThrobberAnim { thread, sender });
}
pub fn start_with_msg<S: Into<String>>(&mut self, msg: S) {
self.set_message(msg);
self.start();
}
pub fn finish(&mut self) {
if let Some(ref anim) = self.anim {
anim.sender.send(ThrobberSignal::Finish).unwrap();
anim.thread.thread().unpark();
}
}
pub fn success<'a, S: Into<String> + std::fmt::Display>(&mut self, msg: S) {
if let Some(ref anim) = self.anim {
anim.sender.send(ThrobberSignal::Succ(msg.into())).unwrap();
anim.thread.thread().unpark();
} else {
println!("\x1B[2K\r✔ {}", msg);
}
}
pub fn fail<'a, S: Into<String>>(&mut self, msg: S) {
let msg = msg.into();
if let Some(ref anim) = self.anim {
anim.sender.send(ThrobberSignal::Fail(msg)).unwrap();
anim.thread.thread().unpark();
} else {
println!("\x1B[2K\r✖ {}", msg);
}
}
}
fn animation_thread<'a>(
receiver: Receiver<ThrobberSignal>,
mut msg: String,
mut interval: Duration,
mut frames: &'static [&'static str],
) {
let mut play_anim = true;
let mut frame = 0;
loop {
match receiver.try_recv() {
Ok(ThrobberSignal::Start) => {
play_anim = true;
continue;
}
Ok(ThrobberSignal::Finish) => {
print!("\x1B[2K\r");
std::io::stdout().flush().unwrap();
play_anim = false;
continue;
}
Ok(ThrobberSignal::Succ(succ_msg)) => {
println!("\x1B[2K\r✔ {}", succ_msg);
play_anim = false;
continue;
}
Ok(ThrobberSignal::Fail(fail_msg)) => {
println!("\x1B[2K\r✖ {}", fail_msg);
play_anim = false;
continue;
}
Ok(ThrobberSignal::ChMsg(new_msg)) => {
msg = new_msg;
continue;
}
Ok(ThrobberSignal::ChInt(new_dur)) => {
interval = new_dur;
continue;
}
Ok(ThrobberSignal::ChFrames(new_frames)) => {
frames = new_frames;
frame = 0;
continue;
}
Ok(ThrobberSignal::End) => {
print!("\x1B[2K\r");
std::io::stdout().flush().unwrap();
break;
}
Err(TryRecvError::Disconnected) => {
print!("\x1B[2K\r");
std::io::stdout().flush().unwrap();
break;
}
Err(TryRecvError::Empty) => {
if play_anim == false {
thread::park();
continue;
}
}
}
print!("\x1B[2K\r");
print!("{} {}", frames[frame], msg);
std::io::stdout().flush().unwrap();
thread::sleep(interval);
frame = (frame + 1) % frames.len();
}
}