ProgressBar now shows a percentage status

The label can be customized though `ProgressBar::with_label`.
This commit is contained in:
Alexandre Bury 2016-07-25 23:54:33 -07:00
parent 5dd20db676
commit 6e021867ab
6 changed files with 91 additions and 40 deletions

View File

@ -18,7 +18,7 @@ fn main() {
let value = Arc::new(AtomicUsize::new(0)); let value = Arc::new(AtomicUsize::new(0));
let cb = Arc::new(Mutex::new(None)); let cb = Arc::new(Mutex::new(None));
let n_max = 100; let n_max = 1000;
s.pop_layer(); s.pop_layer();
s.add_layer(Panel::new(FullView::full_width(ProgressBar::new() s.add_layer(Panel::new(FullView::full_width(ProgressBar::new()

View File

@ -81,6 +81,7 @@ pub mod menu;
pub mod direction; pub mod direction;
// This probably doesn't need to be public? // This probably doesn't need to be public?
mod utils;
mod printer; mod printer;
mod xy; mod xy;
mod with; mod with;

View File

@ -1,7 +1,9 @@
//! Makes drawing on ncurses windows easier. //! Makes drawing on ncurses windows easier.
use std::cmp::min; use std::cmp::min;
use unicode_segmentation::UnicodeSegmentation;
use utils::head_bytes;
use backend::Backend; use backend::Backend;
use B; use B;
@ -45,18 +47,12 @@ impl Printer {
let room = self.size.x - p.x; let room = self.size.x - p.x;
// We want the number of CHARACTERS, not bytes. // We want the number of CHARACTERS, not bytes.
// (Actually we want the "width" of the string, see unicode-width) // (Actually we want the "width" of the string, see unicode-width)
let text = match text.char_indices().nth(room) { let prefix_len = head_bytes(text.graphemes(true), room, "");
Some((i, _)) => &text[..i], let text = &text[..prefix_len];
_ => text,
};
let p = p + self.offset; let p = p + self.offset;
if text.contains('%') {
B::print_at((p.x, p.y), &text.replace("%", "%%"));
} else {
B::print_at((p.x, p.y), text); B::print_at((p.x, p.y), text);
} }
}
/// Prints a vertical line using the given character. /// Prints a vertical line using the given character.
pub fn print_vline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) { pub fn print_vline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) {

43
src/utils.rs Normal file
View File

@ -0,0 +1,43 @@
use unicode_width::UnicodeWidthStr;
/// Computes a sub-string length that fits in the given `width`.
///
/// Takes non-breakable elements from `iter`, while keeping the
/// string width under `width` (and adding the length of `delimiter`
/// between each element).
///
/// Example:
///
/// ```
/// let my_text = "blah...";
/// // This returns the number of bytes for a prefix of `my_text` that
/// // fits within 5 cells.
/// head_bytes(my_text.graphemes(true), 5, "");
/// ```
pub fn head_bytes<'a, I: Iterator<Item = &'a str>>(iter: I, width: usize,
delimiter: &str)
-> usize {
let delimiter_width = delimiter.width();
let delimiter_len = delimiter.len();
let sum = iter.scan(0, |w, token| {
*w += token.width();
if *w > width {
None
} else {
// Add a space
*w += delimiter_width;
Some(token)
}
})
.map(|token| token.len() + delimiter_len)
.fold(0, |a, b| a + b);
// We counted delimiter once too many times,
// but only if the iterator was non empty.
if sum == 0 {
sum
} else {
sum - delimiter_len
}
}

View File

@ -3,8 +3,9 @@ use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use {Cursive, Printer}; use {Cursive, Printer};
use align::HAlign;
use event::*; use event::*;
use theme::ColorStyle; use theme::{ColorStyle, Effect};
use view::View; use view::View;
pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>; pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;
@ -16,6 +17,12 @@ pub struct ProgressBar {
value: Arc<AtomicUsize>, value: Arc<AtomicUsize>,
// TODO: use a Promise instead? // TODO: use a Promise instead?
callback: Option<Arc<Mutex<CbPromise>>>, callback: Option<Arc<Mutex<CbPromise>>>,
label_maker: Box<Fn(usize, (usize, usize)) -> String>,
}
fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
let percent = 101 * (value - min) / (1 + max - min);
format!("{} %", percent)
} }
new_default!(ProgressBar); new_default!(ProgressBar);
@ -34,6 +41,7 @@ impl ProgressBar {
max: 100, max: 100,
value: Arc::new(AtomicUsize::new(0)), value: Arc::new(AtomicUsize::new(0)),
callback: None, callback: None,
label_maker: Box::new(make_percentage),
} }
} }
@ -51,6 +59,26 @@ impl ProgressBar {
self self
} }
/// 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:
///
/// ```
/// fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
/// let percent = 101 * (value - min) / (1 + max - min);
/// format!("{} %", percent)
/// }
/// ```
pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>
(mut self, label_maker: F)
-> Self {
self.label_maker = Box::new(label_maker);
self
}
/// Sets the minimum value. /// Sets the minimum value.
/// ///
/// When `value` equals `min`, the bar is at the minimum level. /// When `value` equals `min`, the bar is at the minimum level.
@ -88,8 +116,17 @@ impl View for ProgressBar {
let value = self.value.load(Ordering::Relaxed); let value = self.value.load(Ordering::Relaxed);
let length = ((1 + available) * (value - self.min)) / let length = ((1 + available) * (value - self.min)) /
(1 + self.max - self.min); (1 + self.max - self.min);
let label = (self.label_maker)(value, (self.min, self.max));
let offset = HAlign::Center.get_offset(label.len(), printer.size.x);
printer.with_color(ColorStyle::Highlight, |printer| { printer.with_color(ColorStyle::Highlight, |printer| {
printer.with_effect(Effect::Reverse, |printer| {
printer.print((offset, 0), &label);
});
let printer = &printer.sub_printer((0, 0), (length, 1), true);
printer.print_hline((0, 0), length, " "); printer.print_hline((0, 0), length, " ");
printer.print((offset, 0), &label);
}); });
} }

View File

@ -9,6 +9,8 @@ use align::*;
use event::*; use event::*;
use super::scroll::ScrollBase; use super::scroll::ScrollBase;
use utils::head_bytes;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -312,31 +314,3 @@ impl View for TextView {
self.scrollbase.set_heights(size.y, self.rows.len()); self.scrollbase.set_heights(size.y, self.rows.len());
} }
} }
fn head_bytes<'a, I: Iterator<Item = &'a str>>(iter: I, width: usize,
overhead: &str)
-> usize {
let overhead_width = overhead.width();
let overhead_len = overhead.len();
let sum = iter.scan(0, |w, token| {
*w += token.width();
if *w > width {
None
} else {
// Add a space
*w += overhead_width;
Some(token)
}
})
.map(|token| token.len() + overhead_len)
.fold(0, |a, b| a + b);
// We counted overhead_len once too many times,
// but only if the iterator was non empty.
if sum == 0 {
sum
} else {
sum - overhead_len
}
}