use std::io::{self, Write};
use crate::detect;
use crate::theme;
pub struct ProgressBar {
width: usize,
message: String,
}
impl ProgressBar {
pub fn new(message: &str, width: usize) -> Self {
Self {
width,
message: message.to_string(),
}
}
pub fn render(&self, fraction: f64) {
let fraction = fraction.clamp(0.0, 1.0);
let filled = (self.width as f64 * fraction).round() as usize;
let empty = self.width - filled;
if detect::should_style() && detect::is_tty_stderr() {
let fill_color = theme::COLOR_PROGRESS_START.resolve();
let empty_color = theme::COLOR_PROGRESS_EMPTY.resolve();
let fill_ansi = color_to_fg(fill_color);
let empty_ansi = color_to_fg(empty_color);
eprint!(
"\r{} {}{}{}{}{} {:.0}% ",
self.message,
fill_ansi,
"█".repeat(filled),
empty_ansi,
"░".repeat(empty),
"\x1b[0m",
fraction * 100.0,
);
} else {
eprint!(
"\r{} [{}{}] {:.0}% ",
self.message,
"#".repeat(filled),
"-".repeat(empty),
fraction * 100.0,
);
}
let _ = io::stderr().flush();
}
pub fn finish(&self) {
eprintln!();
}
pub fn finish_with_message(&self, message: &str) {
eprint!("\r{}\r", " ".repeat(self.width + self.message.len() + 20));
eprintln!("{message}");
}
}
fn color_to_fg(color: ratatui::style::Color) -> String {
use ratatui::style::Color;
match color {
Color::Rgb(r, g, b) => format!("\x1b[38;2;{r};{g};{b}m"),
Color::Reset => String::new(),
_ => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn progress_bar_clamps_fraction() {
let pb = ProgressBar::new("test", 20);
pb.render(1.5);
pb.render(-0.5);
pb.finish();
}
}