use crate::align::HAlign;
use crate::style::{ColorStyle, ColorType, Effect, PaletteColor};
use crate::utils::Counter;
use crate::view::View;
use crate::{Printer, With};
use std::cmp;
use std::thread;
pub struct ProgressBar {
min: usize,
max: usize,
value: Counter,
color: ColorType,
label_maker: Box<dyn Fn(usize, (usize, usize)) -> String + Send + Sync>,
}
fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
if value < min {
return String::from("0 %");
}
let (percentage, extra) = ratio(value - min, max - min, 100);
let percentage = if extra > 4 {
percentage + 1
} else {
percentage
};
format!("{percentage} %")
}
fn ratio(value: usize, max: usize, length: usize) -> (usize, usize) {
let integer = length * value / max;
let fraction = length * value - max * integer;
let fraction = fraction * 8 / max;
(integer, fraction)
}
new_default!(ProgressBar);
impl ProgressBar {
pub fn new() -> Self {
ProgressBar {
min: 0,
max: 100,
value: Counter::new(0),
color: PaletteColor::Highlight.into(),
label_maker: Box::new(make_percentage),
}
}
#[must_use]
pub fn with_value(self, value: Counter) -> Self {
self.with(|s| s.set_counter(value))
}
pub fn start<F: FnOnce(Counter) + Send + 'static>(&mut self, f: F) {
let counter: Counter = self.value.clone();
thread::spawn(move || {
f(counter);
});
}
#[must_use]
pub fn with_task<F: FnOnce(Counter) + Send + 'static>(self, task: F) -> Self {
self.with(|s| s.start(task))
}
#[must_use]
pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static + Send + Sync>(
self,
label_maker: F,
) -> Self {
self.with(|s| s.set_label(label_maker))
}
#[crate::callback_helpers]
pub fn set_label<F: Fn(usize, (usize, usize)) -> String + 'static + Send + Sync>(
&mut self,
label_maker: F,
) {
self.label_maker = Box::new(label_maker);
}
#[must_use]
pub fn min(self, min: usize) -> Self {
self.with(|s| s.set_min(min))
}
pub fn set_min(&mut self, min: usize) {
self.min = min;
self.max = cmp::max(self.max, self.min);
}
#[must_use]
pub fn max(self, max: usize) -> Self {
self.with(|s| s.set_max(max))
}
pub fn set_max(&mut self, max: usize) {
self.max = max;
self.min = cmp::min(self.min, self.max);
}
#[must_use]
pub fn range(self, min: usize, max: usize) -> Self {
self.with(|s| s.set_range(min, max))
}
pub fn set_range(&mut self, min: usize, max: usize) {
if min > max {
self.set_min(max);
self.set_max(min);
} else {
self.set_min(min);
self.set_max(max);
}
}
pub fn set_value(&mut self, value: usize) {
self.value.set(value);
}
pub fn set_counter(&mut self, value: Counter) {
self.value = value;
}
pub fn set_color<C>(&mut self, color: C)
where
C: Into<ColorType>,
{
self.color = color.into();
}
#[must_use]
pub fn with_color<C>(self, color: C) -> Self
where
C: Into<ColorType>,
{
self.with(|s| s.set_color(color))
}
}
fn sub_block(extra: usize) -> &'static str {
match extra {
0 => " ",
1 => "▏",
2 => "▎",
3 => "▍",
4 => "▌",
5 => "▋",
6 => "▊",
7 => "▉",
_ => "█",
}
}
impl View for ProgressBar {
fn draw(&self, printer: &Printer) {
let available = printer.size.x;
let value = self.value.get();
let (length, extra) = if value < self.min {
(0, 0)
} else {
ratio(value - self.min, self.max - self.min, available)
};
let label = (self.label_maker)(value, (self.min, self.max));
let offset = HAlign::Center.get_offset(label.len(), printer.size.x);
let color_style = ColorStyle::new(PaletteColor::HighlightText, self.color);
printer.with_color(color_style, |printer| {
printer.with_effect(Effect::Reverse, |printer| {
printer.print((length, 0), sub_block(extra));
printer.print((offset, 0), &label);
});
let printer = &printer.cropped((length, 1));
printer.print_hline((0, 0), length, " ");
printer.print((offset, 0), &label);
});
}
}
#[crate::blueprint(ProgressBar::new())]
struct Blueprint {
min: Option<usize>,
max: Option<usize>,
value: Option<usize>,
color: Option<ColorType>,
label: Option<_>,
}