mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
ProgressBar now shows a percentage status
The label can be customized though `ProgressBar::with_label`.
This commit is contained in:
parent
5dd20db676
commit
6e021867ab
@ -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()
|
||||||
|
@ -68,7 +68,7 @@ macro_rules! new_default(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
@ -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;
|
||||||
|
@ -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,17 +47,11 @@ 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);
|
||||||
B::print_at((p.x, p.y), &text.replace("%", "%%"));
|
|
||||||
} else {
|
|
||||||
B::print_at((p.x, p.y), text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints a vertical line using the given character.
|
/// Prints a vertical line using the given character.
|
||||||
|
43
src/utils.rs
Normal file
43
src/utils.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user