use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use futures_lite::Stream;
pub mod styles {
use super::Spinner;
pub const ARC: Spinner = Spinner::new("◜◝◞◟");
pub const DOTS: Spinner = Spinner::new("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏");
pub const DOTS_2: Spinner = Spinner::new("⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓");
pub const DOTS_3: Spinner = Spinner::new("⠖⠲⠴⠦");
pub const DOTS_4: Spinner = Spinner::new("⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆");
pub const DOTS_5: Spinner = Spinner::new("⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓");
pub const DOTS_6: Spinner = Spinner::new("⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉");
pub const DOTS_7: Spinner = Spinner::new("⣾⣽⣻⢿⡿⣟⣯⣷");
pub const DOTS_8: Spinner = Spinner::new("⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈");
pub const DOTS_CIRCLE: Spinner = Spinner::new("⠃⠉⠘⠰⢠⣀⡄⠆");
pub const DOT_LARGE_SQUARE: Spinner = Spinner::new("⠁⠂⠄⡀⢀⠠⠐⠈");
pub const STAR: Spinner = Spinner::new("✶✸✹✺✹✷");
pub const SAND: Spinner = Spinner::new("⠁⠂⠄⡀⡈⡐⡠⣀⣁⣂⣄⣌⣔⣤⣥⣦⣮⣶⣷⣿⡿⠿⢟⠟⡛⠛⠫⢋⠋⠍⡉⠉⠑⠡⢁");
}
pub struct Ticks<'a> {
all_chars: &'a str,
chars: std::str::Chars<'a>,
timer: async_io::Timer,
}
impl Stream for Ticks<'_> {
type Item = char;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<char>> {
let this = self.get_mut();
match Pin::new(&mut this.timer).poll_next(cx) {
Poll::Ready(Some(_)) => {}
Poll::Ready(None) => return Poll::Ready(None),
Poll::Pending => return Poll::Pending,
}
let ch = match this.chars.next() {
Some(ch) => ch,
None => {
this.chars = this.all_chars.chars();
this.chars.next().expect("non-empty spinner chars")
}
};
Poll::Ready(Some(ch))
}
}
#[derive(Clone)]
pub struct Spinner<'a> {
chars: &'a str,
interval: Duration,
}
impl<'a> Spinner<'a> {
pub const fn new(chars: &'a str) -> Self {
Self {
chars,
interval: Duration::from_millis(80),
}
}
pub const fn inactive() -> Self {
Self {
chars: "",
interval: Duration::MAX,
}
}
pub fn with_interval(mut self, interval: Duration) -> Self {
self.interval = interval;
self
}
pub fn ticks(&self) -> Ticks<'a> {
Ticks {
all_chars: self.chars,
chars: self.chars.chars(),
timer: async_io::Timer::interval(self.interval),
}
}
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use super::*;
use futures_lite::{StreamExt, future};
#[test]
fn spinner() {
let interval = Duration::from_millis(20);
let spinner = styles::DOTS_3.with_interval(interval);
let num = spinner.chars.chars().count();
let ticks = spinner.ticks();
future::block_on(async move {
let start = Instant::now();
let ticks = ticks.take(num + 1).collect::<Vec<_>>().await;
let elapsed = start.elapsed();
let at_least = interval.saturating_mul(num as u32 + 1);
assert!(elapsed >= at_least);
assert_eq!(ticks[..num], spinner.chars.chars().collect::<Vec<_>>());
assert_eq!(ticks[0], ticks[num]);
});
}
}