2016-07-26 06:54:33 +00:00
|
|
|
use align::HAlign;
|
2016-10-02 22:22:29 +00:00
|
|
|
use std::cmp;
|
|
|
|
use std::thread;
|
2018-08-08 17:23:15 +00:00
|
|
|
use theme::{ColorStyle, ColorType, Effect};
|
2018-04-10 18:45:02 +00:00
|
|
|
use utils::Counter;
|
2016-07-25 06:00:13 +00:00
|
|
|
use view::View;
|
2018-05-18 00:37:39 +00:00
|
|
|
use Printer;
|
2016-07-25 06:00:13 +00:00
|
|
|
|
2017-01-11 01:48:47 +00:00
|
|
|
// pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;
|
2016-07-25 20:38:13 +00:00
|
|
|
|
2016-07-27 04:48:26 +00:00
|
|
|
/// Animated bar showing a progress value.
|
|
|
|
///
|
|
|
|
/// This bar has an internal counter, and adapts the length of the displayed
|
|
|
|
/// bar to the relative position of the counter between a minimum and maximum
|
|
|
|
/// values.
|
|
|
|
///
|
|
|
|
/// It also prints a customizable text in the center of the bar, which
|
|
|
|
/// defaults to the progression percentage.
|
|
|
|
///
|
2018-08-08 17:23:15 +00:00
|
|
|
/// The bar defaults to the current theme's highlight color,
|
|
|
|
/// but that can be customized.
|
|
|
|
///
|
2016-07-27 04:48:26 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```
|
2016-09-29 05:45:27 +00:00
|
|
|
/// # use cursive::views::ProgressBar;
|
2016-07-27 04:48:26 +00:00
|
|
|
/// let bar = ProgressBar::new()
|
2016-07-28 07:29:11 +00:00
|
|
|
/// .with_task(|counter| {
|
2016-07-27 04:48:26 +00:00
|
|
|
/// // This closure is called in parallel.
|
|
|
|
/// for _ in 0..100 {
|
|
|
|
/// // Here we can communicate some
|
|
|
|
/// // advancement back to the bar.
|
2016-07-28 07:29:11 +00:00
|
|
|
/// counter.tick(1);
|
2016-07-27 04:48:26 +00:00
|
|
|
/// }
|
|
|
|
/// });
|
|
|
|
/// ```
|
2016-07-25 06:00:13 +00:00
|
|
|
pub struct ProgressBar {
|
|
|
|
min: usize,
|
|
|
|
max: usize,
|
2016-07-27 06:29:13 +00:00
|
|
|
value: Counter,
|
2018-08-08 17:23:15 +00:00
|
|
|
color: ColorType,
|
2016-07-25 06:00:13 +00:00
|
|
|
// TODO: use a Promise instead?
|
2016-07-26 06:54:33 +00:00
|
|
|
label_maker: Box<Fn(usize, (usize, usize)) -> String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
|
2016-07-27 06:05:18 +00:00
|
|
|
if value < min {
|
2018-06-26 20:13:39 +00:00
|
|
|
return format!("0 %");
|
2016-07-27 06:05:18 +00:00
|
|
|
}
|
2018-06-26 20:13:39 +00:00
|
|
|
|
|
|
|
let (percentage, extra) = ratio(value - min, max - min, 100);
|
2018-06-26 22:19:00 +00:00
|
|
|
let percentage = if extra > 4 {
|
|
|
|
percentage + 1
|
|
|
|
} else {
|
|
|
|
percentage
|
|
|
|
};
|
2018-06-26 20:13:39 +00:00
|
|
|
format!("{} %", percentage)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns length * value/max
|
|
|
|
///
|
|
|
|
/// Constraint: `value` from 0 to `max` should, as much as possible, produce equal-sized segments
|
|
|
|
/// from 0 to length.
|
|
|
|
///
|
|
|
|
/// Returns a tuple with:
|
|
|
|
/// * The integer part of the division
|
|
|
|
/// * A value between 0 and 8 (exclusive) corresponding to the remainder.
|
|
|
|
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)
|
2016-07-25 06:00:13 +00:00
|
|
|
}
|
|
|
|
|
2016-07-25 20:38:13 +00:00
|
|
|
new_default!(ProgressBar);
|
|
|
|
|
2016-07-25 06:00:13 +00:00
|
|
|
impl ProgressBar {
|
|
|
|
/// Creates a new progress bar.
|
|
|
|
///
|
|
|
|
/// Default values:
|
|
|
|
///
|
|
|
|
/// * `min`: 0
|
|
|
|
/// * `max`: 100
|
|
|
|
/// * `value`: 0
|
|
|
|
pub fn new() -> Self {
|
|
|
|
ProgressBar {
|
|
|
|
min: 0,
|
|
|
|
max: 100,
|
2016-07-27 06:29:13 +00:00
|
|
|
value: Counter::new(0),
|
2018-08-08 17:23:15 +00:00
|
|
|
color: ColorStyle::highlight().back,
|
2016-07-26 06:54:33 +00:00
|
|
|
label_maker: Box::new(make_percentage),
|
2016-07-25 06:00:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the value to follow.
|
2016-07-26 19:10:13 +00:00
|
|
|
///
|
|
|
|
/// Use this to manually control the progress to display
|
|
|
|
/// by directly modifying the value pointed to by `value`.
|
2016-07-27 06:29:13 +00:00
|
|
|
pub fn with_value(mut self, value: Counter) -> Self {
|
2016-07-25 06:00:13 +00:00
|
|
|
self.value = value;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-07-26 19:10:13 +00:00
|
|
|
/// Starts a function in a separate thread, and monitor the progress.
|
|
|
|
///
|
2016-07-28 06:54:49 +00:00
|
|
|
/// `f` will be given a `Counter` to increment the bar's progress.
|
2016-07-27 04:48:26 +00:00
|
|
|
///
|
|
|
|
/// This does not reset the value, so it can be called several times
|
|
|
|
/// to advance the progress in multiple sessions.
|
2016-07-28 06:54:49 +00:00
|
|
|
pub fn start<F: FnOnce(Counter) + Send + 'static>(&mut self, f: F) {
|
2016-07-28 07:29:11 +00:00
|
|
|
let counter: Counter = self.value.clone();
|
2016-07-26 19:10:13 +00:00
|
|
|
|
2017-10-12 23:38:55 +00:00
|
|
|
thread::spawn(move || {
|
|
|
|
f(counter);
|
|
|
|
});
|
2016-07-26 19:10:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Starts a function in a separate thread, and monitor the progress.
|
|
|
|
///
|
|
|
|
/// Chainable variant.
|
2017-10-12 23:38:55 +00:00
|
|
|
pub fn with_task<F: FnOnce(Counter) + Send + 'static>(
|
2018-05-18 00:37:39 +00:00
|
|
|
mut self, task: F,
|
2017-10-12 23:38:55 +00:00
|
|
|
) -> Self {
|
2016-07-26 19:10:13 +00:00
|
|
|
self.start(task);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-07-26 06:54:33 +00:00
|
|
|
/// Sets the label generator.
|
|
|
|
///
|
|
|
|
/// The given function will be called with `(value, (min, max))`.
|
|
|
|
/// Its output will be used as the label to print inside the progress bar.
|
|
|
|
///
|
|
|
|
/// The default one shows a percentage progress:
|
|
|
|
///
|
|
|
|
/// ```
|
2017-10-15 04:18:50 +00:00
|
|
|
/// fn make_progress(value: usize, (min, max): (usize, usize)) -> String {
|
2016-07-26 06:54:33 +00:00
|
|
|
/// let percent = 101 * (value - min) / (1 + max - min);
|
|
|
|
/// format!("{} %", percent)
|
|
|
|
/// }
|
|
|
|
/// ```
|
2017-10-12 23:38:55 +00:00
|
|
|
pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>(
|
2018-05-18 00:37:39 +00:00
|
|
|
mut self, label_maker: F,
|
2017-10-12 23:38:55 +00:00
|
|
|
) -> Self {
|
2016-07-26 06:54:33 +00:00
|
|
|
self.label_maker = Box::new(label_maker);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2016-07-25 06:00:13 +00:00
|
|
|
/// Sets the minimum value.
|
|
|
|
///
|
|
|
|
/// When `value` equals `min`, the bar is at the minimum level.
|
2016-07-27 06:05:18 +00:00
|
|
|
///
|
|
|
|
/// If `self.min > max`, `self.min` is set to `max`.
|
2016-07-25 06:00:13 +00:00
|
|
|
pub fn min(mut self, min: usize) -> Self {
|
|
|
|
self.min = min;
|
2016-07-27 06:05:18 +00:00
|
|
|
self.max = cmp::max(self.max, self.min);
|
|
|
|
|
2016-07-25 06:00:13 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the maximum value.
|
|
|
|
///
|
|
|
|
/// When `value` equals `max`, the bar is at the maximum level.
|
2016-07-27 06:05:18 +00:00
|
|
|
///
|
|
|
|
/// If `min > self.max`, `self.max` is set to `min`.
|
2016-07-25 06:00:13 +00:00
|
|
|
pub fn max(mut self, max: usize) -> Self {
|
|
|
|
self.max = max;
|
2016-07-27 06:05:18 +00:00
|
|
|
self.min = cmp::min(self.min, self.max);
|
|
|
|
|
2016-07-25 06:00:13 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the `min` and `max` range for the value.
|
2016-07-27 06:05:18 +00:00
|
|
|
///
|
|
|
|
/// If `min > max`, swap the two values.
|
2016-07-25 06:00:13 +00:00
|
|
|
pub fn range(self, min: usize, max: usize) -> Self {
|
2016-07-27 06:05:18 +00:00
|
|
|
if min > max {
|
|
|
|
self.min(max).max(min)
|
|
|
|
} else {
|
|
|
|
self.min(min).max(max)
|
|
|
|
}
|
2016-07-25 06:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the current value.
|
|
|
|
///
|
|
|
|
/// Value is clamped between `min` and `max`.
|
|
|
|
pub fn set_value(&mut self, value: usize) {
|
2016-07-27 06:29:13 +00:00
|
|
|
self.value.set(value);
|
2016-07-25 06:00:13 +00:00
|
|
|
}
|
2018-08-08 17:23:15 +00:00
|
|
|
|
|
|
|
/// Sets the color style.
|
|
|
|
pub fn set_color<C>(&mut self, color: C)
|
|
|
|
where
|
|
|
|
C: Into<ColorType>,
|
|
|
|
{
|
|
|
|
self.color = color.into();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the color style.
|
|
|
|
///
|
|
|
|
/// Chainable variant of `set_color`.
|
|
|
|
pub fn with_color<C>(mut self, color: C) -> Self
|
|
|
|
where
|
|
|
|
C: Into<ColorType>,
|
|
|
|
{
|
|
|
|
self.color = color.into();
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
2016-07-25 06:00:13 +00:00
|
|
|
}
|
|
|
|
|
2018-06-26 20:13:39 +00:00
|
|
|
fn sub_block(extra: usize) -> &'static str {
|
|
|
|
match extra {
|
|
|
|
0 => " ",
|
|
|
|
1 => "▏",
|
|
|
|
2 => "▎",
|
|
|
|
3 => "▍",
|
|
|
|
4 => "▌",
|
|
|
|
5 => "▋",
|
|
|
|
6 => "▊",
|
|
|
|
7 => "▉",
|
|
|
|
_ => "█",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 06:00:13 +00:00
|
|
|
impl View for ProgressBar {
|
|
|
|
fn draw(&self, printer: &Printer) {
|
|
|
|
// Now, the bar itself...
|
2016-07-25 06:03:59 +00:00
|
|
|
let available = printer.size.x;
|
2016-07-25 06:00:13 +00:00
|
|
|
|
2016-07-27 06:29:13 +00:00
|
|
|
let value = self.value.get();
|
2016-07-27 06:05:18 +00:00
|
|
|
|
|
|
|
// If we're under the minimum, don't draw anything.
|
|
|
|
// If we're over the maximum, we'll try to draw more, but the printer
|
|
|
|
// will crop us anyway, so it's not a big deal.
|
2018-06-26 20:13:39 +00:00
|
|
|
let (length, extra) = if value < self.min {
|
|
|
|
(0, 0)
|
2016-07-27 06:05:18 +00:00
|
|
|
} else {
|
2018-06-26 20:13:39 +00:00
|
|
|
ratio(value - self.min, self.max - self.min, available)
|
2016-07-27 06:05:18 +00:00
|
|
|
};
|
2016-07-26 06:54:33 +00:00
|
|
|
|
|
|
|
let label = (self.label_maker)(value, (self.min, self.max));
|
|
|
|
let offset = HAlign::Center.get_offset(label.len(), printer.size.x);
|
|
|
|
|
2018-08-08 17:23:15 +00:00
|
|
|
let color_style =
|
|
|
|
ColorStyle::new(ColorStyle::highlight().front, self.color);
|
|
|
|
|
|
|
|
printer.with_color(color_style, |printer| {
|
2018-06-26 20:13:39 +00:00
|
|
|
// Draw the right half of the label in reverse
|
2016-07-26 06:54:33 +00:00
|
|
|
printer.with_effect(Effect::Reverse, |printer| {
|
2018-06-26 20:13:39 +00:00
|
|
|
printer.print((length, 0), sub_block(extra));
|
2016-07-26 06:54:33 +00:00
|
|
|
printer.print((offset, 0), &label);
|
|
|
|
});
|
2018-04-03 23:38:54 +00:00
|
|
|
let printer = &printer.cropped((length, 1));
|
2016-07-25 06:03:59 +00:00
|
|
|
printer.print_hline((0, 0), length, " ");
|
2018-06-26 20:13:39 +00:00
|
|
|
|
2018-08-08 17:23:15 +00:00
|
|
|
// Draw the left part in color_style (it may be cropped)
|
2016-07-26 06:54:33 +00:00
|
|
|
printer.print((offset, 0), &label);
|
2016-07-25 06:00:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|