use std::{
io::Write,
sync::mpsc::{self, Receiver, RecvTimeoutError, Sender},
time::Duration,
};
use crossterm::{
cursor, queue,
style::Print,
terminal::{Clear, ClearType},
};
use xacli_core::Context;
use crate::Result;
pub struct Spinner {
message: String,
rx: Receiver<SpinnerEvent>,
frames: Vec<&'static str>,
}
pub struct SpinnerHandle {
tx: Sender<SpinnerEvent>,
}
pub enum SpinnerEvent {
SetMessage(String),
Finish,
FinishWithMessage(String),
}
impl Spinner {
pub fn new(message: impl Into<String>) -> (Self, SpinnerHandle) {
let (tx, rx) = mpsc::channel();
let spinner = Self {
message: message.into(),
rx,
frames: vec!["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
};
let handle = SpinnerHandle { tx };
(spinner, handle)
}
pub fn new_with_frames(
message: impl Into<String>,
frames: Vec<&'static str>,
) -> (Self, SpinnerHandle) {
let (tx, rx) = mpsc::channel();
let spinner = Self {
message: message.into(),
rx,
frames,
};
let handle = SpinnerHandle { tx };
(spinner, handle)
}
pub fn run(mut self, ctx: &mut dyn Context) -> Result<()> {
let mut frame_index = 0;
let stdout = &mut ctx.stdout();
self.render(stdout, frame_index)?;
loop {
match self.rx.recv_timeout(Duration::from_millis(80)) {
Ok(SpinnerEvent::SetMessage(msg)) => {
self.message = msg;
self.render(stdout, frame_index)?;
}
Ok(SpinnerEvent::Finish) => {
self.clear_render(stdout)?;
break;
}
Ok(SpinnerEvent::FinishWithMessage(msg)) => {
self.clear_render(stdout)?;
println!("{}", msg);
break;
}
Err(RecvTimeoutError::Timeout) => {
frame_index = (frame_index + 1) % self.frames.len();
self.render(stdout, frame_index)?;
}
Err(RecvTimeoutError::Disconnected) => {
self.clear_render(stdout)?;
break;
}
}
}
Ok(())
}
fn render(&self, stdout: &mut impl Write, frame_index: usize) -> Result<()> {
queue!(
stdout,
cursor::MoveToColumn(0),
Clear(ClearType::CurrentLine),
Print(self.frames[frame_index]),
Print(" "),
Print(&self.message),
)?;
stdout.flush()?;
Ok(())
}
fn clear_render(&self, stdout: &mut impl Write) -> Result<()> {
queue!(
stdout,
cursor::MoveToColumn(0),
Clear(ClearType::CurrentLine),
)?;
stdout.flush()?;
Ok(())
}
}
impl SpinnerHandle {
pub fn set_message(&self, msg: impl Into<String>) {
let _ = self.tx.send(SpinnerEvent::SetMessage(msg.into()));
}
pub fn finish(&self) {
let _ = self.tx.send(SpinnerEvent::Finish);
}
pub fn finish_with_message(&self, msg: impl Into<String>) {
let _ = self.tx.send(SpinnerEvent::FinishWithMessage(msg.into()));
}
}
pub mod frames {
pub const DOTS: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub const LINE: &[&str] = &["-", "\\", "|", "/"];
pub const ARROW: &[&str] = &["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"];
pub const CIRCLE: &[&str] = &["◐", "◓", "◑", "◒"];
pub const SQUARE: &[&str] = &["◰", "◳", "◲", "◱"];
pub const BOUNCE: &[&str] = &["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"];
}