From 6e021867abaae78a642bb07d47a9b9fdc4a9dd1a Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 25 Jul 2016 23:54:33 -0700 Subject: [PATCH] ProgressBar now shows a percentage status The label can be customized though `ProgressBar::with_label`. --- examples/progress.rs | 2 +- src/lib.rs | 3 ++- src/printer.rs | 14 +++++-------- src/utils.rs | 43 ++++++++++++++++++++++++++++++++++++++++ src/view/progress_bar.rs | 39 +++++++++++++++++++++++++++++++++++- src/view/text_view.rs | 30 ++-------------------------- 6 files changed, 91 insertions(+), 40 deletions(-) create mode 100644 src/utils.rs diff --git a/examples/progress.rs b/examples/progress.rs index 9b95f5b..06fa7e9 100644 --- a/examples/progress.rs +++ b/examples/progress.rs @@ -18,7 +18,7 @@ fn main() { let value = Arc::new(AtomicUsize::new(0)); let cb = Arc::new(Mutex::new(None)); - let n_max = 100; + let n_max = 1000; s.pop_layer(); s.add_layer(Panel::new(FullView::full_width(ProgressBar::new() diff --git a/src/lib.rs b/src/lib.rs index 2fb29be..7f05c90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ macro_rules! new_default( } } } - ); +); pub mod prelude; @@ -81,6 +81,7 @@ pub mod menu; pub mod direction; // This probably doesn't need to be public? +mod utils; mod printer; mod xy; mod with; diff --git a/src/printer.rs b/src/printer.rs index 36c6ae8..ac13304 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,7 +1,9 @@ //! Makes drawing on ncurses windows easier. use std::cmp::min; +use unicode_segmentation::UnicodeSegmentation; +use utils::head_bytes; use backend::Backend; use B; @@ -45,17 +47,11 @@ impl Printer { let room = self.size.x - p.x; // We want the number of CHARACTERS, not bytes. // (Actually we want the "width" of the string, see unicode-width) - let text = match text.char_indices().nth(room) { - Some((i, _)) => &text[..i], - _ => text, - }; + let prefix_len = head_bytes(text.graphemes(true), room, ""); + let text = &text[..prefix_len]; 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. diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..f9545d5 --- /dev/null +++ b/src/utils.rs @@ -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>(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 + } +} diff --git a/src/view/progress_bar.rs b/src/view/progress_bar.rs index 48dbba7..34ce9a0 100644 --- a/src/view/progress_bar.rs +++ b/src/view/progress_bar.rs @@ -3,8 +3,9 @@ use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicUsize, Ordering}; use {Cursive, Printer}; +use align::HAlign; use event::*; -use theme::ColorStyle; +use theme::{ColorStyle, Effect}; use view::View; pub type CbPromise = Option>; @@ -16,6 +17,12 @@ pub struct ProgressBar { value: Arc, // TODO: use a Promise instead? callback: Option>>, + label_maker: Box String>, +} + +fn make_percentage(value: usize, (min, max): (usize, usize)) -> String { + let percent = 101 * (value - min) / (1 + max - min); + format!("{} %", percent) } new_default!(ProgressBar); @@ -34,6 +41,7 @@ impl ProgressBar { max: 100, value: Arc::new(AtomicUsize::new(0)), callback: None, + label_maker: Box::new(make_percentage), } } @@ -51,6 +59,26 @@ impl ProgressBar { 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 String + 'static> + (mut self, label_maker: F) + -> Self { + self.label_maker = Box::new(label_maker); + self + } + /// Sets the minimum value. /// /// 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 length = ((1 + available) * (value - 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_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((offset, 0), &label); }); } diff --git a/src/view/text_view.rs b/src/view/text_view.rs index ec58b2a..9b9be6a 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -9,6 +9,8 @@ use align::*; use event::*; use super::scroll::ScrollBase; +use utils::head_bytes; + use unicode_width::UnicodeWidthStr; use unicode_segmentation::UnicodeSegmentation; @@ -312,31 +314,3 @@ impl View for TextView { self.scrollbase.set_heights(size.y, self.rows.len()); } } - -fn head_bytes<'a, I: Iterator>(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 - } -}