use std::io::{Result, Write};
use std::thread;
use std::time::Duration;
use rand;
use rand::rngs::ThreadRng;
use rand_distr::{Distribution, Normal, Uniform};
use prettytty::cmd::{
DynMoveTo, EnterAlternateScreen, EraseScreen, ExitAlternateScreen, HideCursor,
RequestScreenSize, SetDefaultForeground, SetForeground8, ShowCursor,
};
use prettytty::{fuse, Connection, Output, Query};
pub type Progress = f32;
pub struct ProgressReporter {
normal: Normal<Progress>,
rng: ThreadRng,
status: Progress,
done: bool,
}
impl ProgressReporter {
pub fn new() -> Self {
Self {
normal: Normal::new(1.0, 2.0 / 3.0).unwrap(),
rng: rand::rng(),
status: 0.0,
done: false,
}
}
}
impl core::iter::Iterator for ProgressReporter {
type Item = Progress;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let result = self.status.min(100.0);
if 100.0 <= self.status {
self.done = true;
} else {
let incr = self.normal.sample(&mut self.rng).max(0.1);
self.status += incr;
}
Some(result)
}
}
impl core::iter::FusedIterator for ProgressReporter {}
pub struct Renderer(pub Progress);
const WIDTH: usize = CELLS + 9;
const CELLS: usize = 25;
const STEPS: usize = 4;
impl core::fmt::Display for Renderer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let uprog = self.0 as usize;
let full = uprog / STEPS;
let partial = uprog % STEPS;
let empty = CELLS - full - (if 0 < partial { 1 } else { 0 });
write!(f, "┫{}", SetForeground8::<10>)?;
for _ in 0..full {
f.write_str("█")?;
}
if 0 < partial {
f.write_str(["▎", "▌", "▊"][partial - 1])?;
}
for _ in 0..empty {
f.write_str(" ")?;
}
write!(f, "{}┣ {:5.1}%", SetDefaultForeground, self.0)
}
}
pub fn animate(output: &mut Output, row: u16, column: u16) -> Result<()> {
let uniform = Uniform::new_inclusive(16, 100).map_err(|e| std::io::Error::other(e))?;
let mut rng = rand::rng();
for progress in ProgressReporter::new() {
write!(output, "{}{}", DynMoveTo(row, column), Renderer(progress))?;
output.flush()?;
let nap = Duration::from_millis(uniform.sample(&mut rng));
thread::sleep(nap);
}
thread::sleep(Duration::from_millis(500));
Ok(())
}
fn main() -> Result<()> {
let tty = Connection::open()?;
let (mut input, mut output) = tty.io();
output.exec_defer(
fuse!(EnterAlternateScreen, EraseScreen, HideCursor),
fuse!(ShowCursor, ExitAlternateScreen),
)?;
let (row, column) = RequestScreenSize.run(&mut input, &mut output)?;
let (row, column) = (
row.saturating_sub(1) / 2,
column.saturating_sub(WIDTH as u16) / 2,
);
animate(&mut output, row, column)
}