Add ProgressBar

Also make Callback its own NewType to add comversion methods.
This commit is contained in:
Alexandre Bury 2016-07-24 23:00:13 -07:00
parent 10e072c140
commit e29511e757
15 changed files with 278 additions and 44 deletions

51
examples/progress.rs Normal file
View File

@ -0,0 +1,51 @@
extern crate cursive;
use cursive::prelude::*;
use std::thread;
use std::time::Duration;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
let mut siv = Cursive::new();
siv.add_layer(Dialog::new(Button::new("Start", |s| {
// These two values will allow us to communicate.
let value = Arc::new(AtomicUsize::new(0));
let cb = Arc::new(Mutex::new(None));
let n_max = 1000;
s.find_id::<Dialog>("dialog")
.unwrap()
.set_content(ProgressBar::new()
.range(0, n_max)
.with_value(value.clone())
.with_callback(cb.clone()));
// Spawn a thread to process things in the background.
thread::spawn(move || {
for _ in 0..n_max {
thread::sleep(Duration::from_millis(3));
value.fetch_add(1, Ordering::Relaxed);
}
*cb.lock().unwrap() = Some(Box::new(move |s| {
s.pop_layer();
s.add_layer(Dialog::new(TextView::new("Phew, that was \
a lot of work!"))
.title("Work done!")
.button("Sure!", |s| s.quit()));
}));
});
}))
.title("Progress bar example")
.padding_top(1)
.padding_bottom(1)
.with_id("dialog"));
siv.set_fps(10);
siv.run();
}

View File

@ -105,6 +105,9 @@ impl backend::Backend for NcursesBackend {
fn parse_ncurses_char(ch: i32) -> Event {
match ch {
// Value sent by ncurses when nothing happens
-1 => Event::Refresh,
// Values under 256 are chars and control values
//
// Tab is '\t'

View File

@ -14,12 +14,47 @@
//! table is checked.
use std::rc::Rc;
use std::ops::Deref;
use Cursive;
/// Callback is a function that can be triggered by an event.
/// It has a mutable access to the cursive root.
pub type Callback = Rc<Fn(&mut Cursive)>;
#[derive(Clone)]
pub struct Callback(Rc<Box<Fn(&mut Cursive)>>);
// TODO: remove the Box when Box<T: Sized> -> Rc<T> is possible
impl Callback {
/// Wraps the given function into a `Callback` object.
pub fn from_fn<F: Fn(&mut Cursive) + 'static>(f: F) -> Self {
Callback(Rc::new(Box::new(f)))
}
}
impl Deref for Callback {
type Target = Box<Fn(&mut Cursive)>;
fn deref<'a>(&'a self) -> &'a Box<Fn(&mut Cursive)> {
&self.0
}
}
impl From<Rc<Box<Fn(&mut Cursive)>>> for Callback {
fn from(f: Rc<Box<Fn(&mut Cursive)>>) -> Self {
Callback(f)
}
}
impl From<Box<Fn(&mut Cursive) + Send>> for Callback {
fn from(f: Box<Fn(&mut Cursive) + Send>) -> Self {
Callback(Rc::new(f))
}
}
impl From<Box<Fn(&mut Cursive)>> for Callback {
fn from(f: Box<Fn(&mut Cursive)>) -> Self {
Callback(Rc::new(f))
}
}
/// Answer to an event notification.
/// The event can be consumed or ignored.
@ -27,13 +62,13 @@ pub enum EventResult {
/// The event was ignored. The parent can keep handling it.
Ignored,
/// The event was consumed. An optionnal callback to run is attached.
Consumed(Option<Callback>),
Consumed(Option<Callback>), // TODO: make this a FnOnce?
}
impl EventResult {
/// Convenient method to create `Consumed(Some(f))`
pub fn with_cb<F: 'static + Fn(&mut Cursive)>(f: F) -> Self {
EventResult::Consumed(Some(Rc::new(f)))
EventResult::Consumed(Some(Callback::from_fn(f)))
}
/// Returns `true` if `self` is `EventResult::Consumed`.
@ -138,29 +173,32 @@ impl Key {
/// Represents an event as seen by the application.
#[derive(PartialEq,Eq,Clone,Copy,Hash,Debug)]
pub enum Event {
/// Event fired when the window is resized
/// Event fired when the window is resized.
WindowResize,
/// A character was entered (includes numbers, punctuation, ...)
/// Event fired regularly when a auto-refresh is set.
Refresh,
/// A character was entered (includes numbers, punctuation, ...).
Char(char),
/// A character was entered with the Ctrl key pressed
/// A character was entered with the Ctrl key pressed.
CtrlChar(char),
/// A character was entered with the Alt key pressed
/// A character was entered with the Alt key pressed.
AltChar(char),
/// A non-character key was pressed
/// A non-character key was pressed.
Key(Key),
/// A non-character key was pressed with the Shift key pressed
/// A non-character key was pressed with the Shift key pressed.
Shift(Key),
/// A non-character key was pressed with the Alt key pressed
/// A non-character key was pressed with the Alt key pressed.
Alt(Key),
/// A non-character key was pressed with the Shift and Alt keys pressed
/// A non-character key was pressed with the Shift and Alt keys pressed.
AltShift(Key),
/// A non-character key was pressed with the Ctrl key pressed
/// A non-character key was pressed with the Ctrl key pressed.
Ctrl(Key),
/// A non-character key was pressed with the Ctrl and Shift keys pressed
/// A non-character key was pressed with the Ctrl and Shift keys pressed.
CtrlShift(Key),
/// A non-character key was pressed with the Ctrl and Alt keys pressed
/// A non-character key was pressed with the Ctrl and Alt keys pressed.
CtrlAlt(Key),
/// An unknown event was received.

View File

@ -97,7 +97,6 @@ pub use printer::Printer;
use backend::{Backend, NcursesBackend};
use std::any::Any;
use std::rc::Rc;
use std::collections::HashMap;
use std::path::Path;
@ -364,7 +363,7 @@ impl Cursive {
pub fn add_global_callback<F, E: Into<Event>>(&mut self, event: E, cb: F)
where F: Fn(&mut Cursive) + 'static
{
self.global_callbacks.insert(event.into(), Rc::new(cb));
self.global_callbacks.insert(event.into(), Callback::from_fn(cb));
}
/// Convenient method to add a layer to the current screen.

View File

@ -98,7 +98,7 @@ impl MenuTree {
pub fn add_leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str,
cb: F) {
self.children
.push(MenuItem::Leaf(title.to_string(), Rc::new(cb)));
.push(MenuItem::Leaf(title.to_string(), Callback::from_fn(cb)));
}
/// Adds a actionnable leaf to the end of this tree - chainable variant.

View File

@ -9,7 +9,7 @@
pub use {Cursive, Printer, With};
pub use event::{Event, Key};
pub use view::{BoxView, Button, Checkbox, Dialog, EditView, FullView, IdView,
KeyEventView, LinearLayout, ListView, SelectView, Selector,
TextView, View};
Identifiable, KeyEventView, LinearLayout, ListView,
ProgressBar, SelectView, Selector, TextView, View};
pub use vec::Vec2;
pub use menu::MenuTree;

View File

@ -1,14 +1,12 @@
use std::rc::Rc;
use unicode_width::UnicodeWidthStr;
use {Cursive, Printer, With};
use align::HAlign;
use event::*;
use direction::Direction;
use theme::ColorStyle;
use Cursive;
use With;
use vec::Vec2;
use view::View;
use event::*;
use Printer;
use unicode_width::UnicodeWidthStr;
/// Simple text label with a callback when <Enter> is pressed.
///
@ -33,7 +31,7 @@ impl Button {
{
Button {
label: label.to_string(),
callback: Rc::new(cb),
callback: Callback::from_fn(cb),
enabled: true,
}
}
@ -82,12 +80,12 @@ impl View for Button {
} else {
ColorStyle::Highlight
};
let x = printer.size.x - 1;
let offset =
HAlign::Center.get_offset(self.label.len() + 2, printer.size.x);
printer.with_color(style, |printer| {
printer.print((1, 0), &self.label);
printer.print((0, 0), "<");
printer.print((x, 0), ">");
printer.print((offset, 0), &format!("<{}>", self.label));
});
}

View File

@ -56,6 +56,13 @@ impl Dialog {
}
}
/// Sets the content for this dialog.
///
/// Previous content will be dropped.
pub fn set_content<V: View + 'static>(&mut self, view: V) {
self.content = Box::new(view);
}
/// Convenient method to create an infobox.
///
/// It will contain the given text and a `Ok` dismiss button.
@ -108,6 +115,30 @@ impl Dialog {
self
}
/// Sets the top padding in the dialog (under the title).
pub fn padding_top(mut self, padding: usize) -> Self {
self.padding.top = padding;
self
}
/// Sets the bottom padding in the dialog (under buttons).
pub fn padding_bottom(mut self, padding: usize) -> Self {
self.padding.bottom = padding;
self
}
/// Sets the left padding in the dialog.
pub fn padding_left(mut self, padding: usize) -> Self {
self.padding.left = padding;
self
}
/// Sets the right padding in the dialog.
pub fn padding_right(mut self, padding: usize) -> Self {
self.padding.right = padding;
self
}
}
impl View for Dialog {

View File

@ -5,7 +5,7 @@ use With;
use direction::Direction;
use theme::{ColorStyle, Effect};
use vec::Vec2;
use view::{IdView, View};
use view::View;
use event::*;
use Printer;
@ -135,11 +135,6 @@ impl EditView {
self
}
/// Wraps this view into an IdView with the given id.
pub fn with_id(self, label: &str) -> IdView<Self> {
IdView::new(label, self)
}
}
impl View for EditView {

View File

@ -28,3 +28,14 @@ impl<T: View + Any> ViewWrapper for IdView<T> {
}
}
}
/// Makes a view wrappable in an `IdView`.
pub trait Identifiable: View + Sized {
/// Wraps this view into an IdView with the given id.
fn with_id(self, id: &str) -> IdView<Self> {
IdView::new(id, self)
}
}
impl <T: View> Identifiable for T {}

View File

@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::rc::Rc;
use Cursive;
use event::{Callback, Event, EventResult};
@ -35,7 +34,7 @@ impl KeyEventView {
pub fn register<F, E: Into<Event>>(mut self, event: E, cb: F) -> Self
where F: Fn(&mut Cursive) + 'static
{
self.callbacks.insert(event.into(), Rc::new(cb));
self.callbacks.insert(event.into(), Callback::from_fn(cb));
self
}

View File

@ -101,7 +101,7 @@ impl MenuPopup {
///
/// (When the user hits <ESC>)
pub fn on_dismiss<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self {
self.on_dismiss = Some(Rc::new(f));
self.on_dismiss = Some(Callback::from_fn(f));
self
}
@ -111,7 +111,7 @@ impl MenuPopup {
///
/// Usually used to hide the parent view.
pub fn on_action<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self {
self.on_action = Some(Rc::new(f));
self.on_action = Some(Callback::from_fn(f));
self
}

View File

@ -58,6 +58,7 @@ mod linear_layout;
mod list_view;
mod menubar;
mod menu_popup;
mod progress_bar;
mod shadow_view;
mod select_view;
mod sized_view;
@ -78,7 +79,7 @@ pub use self::position::{Offset, Position};
pub use self::scroll::ScrollBase;
pub use self::id_view::IdView;
pub use self::id_view::{IdView, Identifiable};
pub use self::box_view::BoxView;
pub use self::button::Button;
pub use self::checkbox::Checkbox;
@ -91,12 +92,13 @@ pub use self::list_view::ListView;
pub use self::menubar::Menubar;
pub use self::menu_popup::MenuPopup;
pub use self::view_path::ViewPath;
pub use self::progress_bar::ProgressBar;
pub use self::select_view::SelectView;
pub use self::shadow_view::ShadowView;
pub use self::sized_view::SizedView;
pub use self::stack_view::StackView;
pub use self::text_view::TextView;
pub use self::tracked_view::TrackedView;
pub use self::sized_view::SizedView;
pub use self::view_wrapper::ViewWrapper;

107
src/view/progress_bar.rs Normal file
View File

@ -0,0 +1,107 @@
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use {Cursive, Printer};
use event::*;
use theme::ColorStyle;
use view::View;
/// Display progress.
pub struct ProgressBar {
min: usize,
max: usize,
value: Arc<AtomicUsize>,
// TODO: use a Promise instead?
callback: Option<Arc<Mutex<Option<Box<Fn(&mut Cursive) + Send>>>>>,
}
impl ProgressBar {
/// Creates a new progress bar.
///
/// Default values:
///
/// * `min`: 0
/// * `max`: 100
/// * `value`: 0
pub fn new() -> Self {
ProgressBar {
min: 0,
max: 100,
value: Arc::new(AtomicUsize::new(0)),
callback: None,
}
}
/// Sets the value to follow.
pub fn with_value(mut self, value: Arc<AtomicUsize>) -> Self {
self.value = value;
self
}
/// Sets the callback to follow.
///
/// Whenever `callback` is set, it will be called on the next event loop.
pub fn with_callback(mut self,
callback: Arc<Mutex<Option<Box<Fn(&mut Cursive) + Send>>>>)
-> Self {
self.callback = Some(callback);
self
}
/// Sets the minimum value.
///
/// When `value` equals `min`, the bar is at the minimum level.
pub fn min(mut self, min: usize) -> Self {
self.min = min;
self
}
/// Sets the maximum value.
///
/// When `value` equals `max`, the bar is at the maximum level.
pub fn max(mut self, max: usize) -> Self {
self.max = max;
self
}
/// Sets the `min` and `max` range for the value.
pub fn range(self, min: usize, max: usize) -> Self {
self.min(min).max(max)
}
/// Sets the current value.
///
/// Value is clamped between `min` and `max`.
pub fn set_value(&mut self, value: usize) {
self.value.store(value, Ordering::Relaxed);
}
}
impl View for ProgressBar {
fn draw(&self, printer: &Printer) {
// TODO: make the brackets an option
// (Or a theme property? Or both?)
printer.print((0, 0), "[");
printer.print((printer.size.x - 1, 0), "]");
// Now, the bar itself...
let available = printer.size.x - 2;
let value = self.value.load(Ordering::Relaxed);
let length = (available * (value - self.min)) / (self.max - self.min);
printer.with_color(ColorStyle::Highlight, |printer| {
printer.print_hline((1, 0), length, " ");
});
}
fn on_event(&mut self, _: Event) -> EventResult {
if let Some(ref cb) = self.callback {
if let Some(cb) = cb.lock().unwrap().take() {
return EventResult::Consumed(Some(cb.into()));
}
}
EventResult::Ignored
}
}

View File

@ -11,7 +11,7 @@ use view::position::Position;
use view::{IdView, View};
use align::{Align, HAlign, VAlign};
use view::scroll::ScrollBase;
use event::{Event, EventResult, Key};
use event::{Callback, Event, EventResult, Key};
use theme::ColorStyle;
use vec::Vec2;
use Printer;
@ -387,7 +387,7 @@ impl<T: 'static> View for SelectView<T> {
let cb = self.select_cb.as_ref().unwrap().clone();
let v = self.selection();
// We return a Callback Rc<|s| cb(s, &*v)>
return EventResult::Consumed(Some(Rc::new(move |s| {
return EventResult::Consumed(Some(Callback::from_fn(move |s| {
cb(s, &*v)
})));
}