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 { fn parse_ncurses_char(ch: i32) -> Event {
match ch { match ch {
// Value sent by ncurses when nothing happens
-1 => Event::Refresh,
// Values under 256 are chars and control values // Values under 256 are chars and control values
// //
// Tab is '\t' // Tab is '\t'

View File

@ -14,12 +14,47 @@
//! table is checked. //! table is checked.
use std::rc::Rc; use std::rc::Rc;
use std::ops::Deref;
use Cursive; use Cursive;
/// Callback is a function that can be triggered by an event. /// Callback is a function that can be triggered by an event.
/// It has a mutable access to the cursive root. /// 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. /// Answer to an event notification.
/// The event can be consumed or ignored. /// The event can be consumed or ignored.
@ -27,13 +62,13 @@ pub enum EventResult {
/// The event was ignored. The parent can keep handling it. /// The event was ignored. The parent can keep handling it.
Ignored, Ignored,
/// The event was consumed. An optionnal callback to run is attached. /// The event was consumed. An optionnal callback to run is attached.
Consumed(Option<Callback>), Consumed(Option<Callback>), // TODO: make this a FnOnce?
} }
impl EventResult { impl EventResult {
/// Convenient method to create `Consumed(Some(f))` /// Convenient method to create `Consumed(Some(f))`
pub fn with_cb<F: 'static + Fn(&mut Cursive)>(f: F) -> Self { 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`. /// Returns `true` if `self` is `EventResult::Consumed`.
@ -138,29 +173,32 @@ impl Key {
/// Represents an event as seen by the application. /// Represents an event as seen by the application.
#[derive(PartialEq,Eq,Clone,Copy,Hash,Debug)] #[derive(PartialEq,Eq,Clone,Copy,Hash,Debug)]
pub enum Event { pub enum Event {
/// Event fired when the window is resized /// Event fired when the window is resized.
WindowResize, 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), Char(char),
/// A character was entered with the Ctrl key pressed /// A character was entered with the Ctrl key pressed.
CtrlChar(char), CtrlChar(char),
/// A character was entered with the Alt key pressed /// A character was entered with the Alt key pressed.
AltChar(char), AltChar(char),
/// A non-character key was pressed /// A non-character key was pressed.
Key(Key), 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), 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), 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), 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), 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), 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), CtrlAlt(Key),
/// An unknown event was received. /// An unknown event was received.

View File

@ -97,7 +97,6 @@ pub use printer::Printer;
use backend::{Backend, NcursesBackend}; use backend::{Backend, NcursesBackend};
use std::any::Any; use std::any::Any;
use std::rc::Rc;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; 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) pub fn add_global_callback<F, E: Into<Event>>(&mut self, event: E, cb: F)
where F: Fn(&mut Cursive) + 'static 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. /// 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, pub fn add_leaf<F: 'static + Fn(&mut Cursive)>(&mut self, title: &str,
cb: F) { cb: F) {
self.children 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. /// 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 {Cursive, Printer, With};
pub use event::{Event, Key}; pub use event::{Event, Key};
pub use view::{BoxView, Button, Checkbox, Dialog, EditView, FullView, IdView, pub use view::{BoxView, Button, Checkbox, Dialog, EditView, FullView, IdView,
KeyEventView, LinearLayout, ListView, SelectView, Selector, Identifiable, KeyEventView, LinearLayout, ListView,
TextView, View}; ProgressBar, SelectView, Selector, TextView, View};
pub use vec::Vec2; pub use vec::Vec2;
pub use menu::MenuTree; 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 direction::Direction;
use theme::ColorStyle; use theme::ColorStyle;
use Cursive;
use With;
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use event::*;
use Printer;
use unicode_width::UnicodeWidthStr;
/// Simple text label with a callback when <Enter> is pressed. /// Simple text label with a callback when <Enter> is pressed.
/// ///
@ -33,7 +31,7 @@ impl Button {
{ {
Button { Button {
label: label.to_string(), label: label.to_string(),
callback: Rc::new(cb), callback: Callback::from_fn(cb),
enabled: true, enabled: true,
} }
} }
@ -82,12 +80,12 @@ impl View for Button {
} else { } else {
ColorStyle::Highlight 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.with_color(style, |printer| {
printer.print((1, 0), &self.label); printer.print((offset, 0), &format!("<{}>", self.label));
printer.print((0, 0), "<");
printer.print((x, 0), ">");
}); });
} }

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. /// Convenient method to create an infobox.
/// ///
/// It will contain the given text and a `Ok` dismiss button. /// It will contain the given text and a `Ok` dismiss button.
@ -108,6 +115,30 @@ impl Dialog {
self 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 { impl View for Dialog {

View File

@ -5,7 +5,7 @@ use With;
use direction::Direction; use direction::Direction;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
use vec::Vec2; use vec::Vec2;
use view::{IdView, View}; use view::View;
use event::*; use event::*;
use Printer; use Printer;
@ -135,11 +135,6 @@ impl EditView {
self 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 { 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::collections::HashMap;
use std::rc::Rc;
use Cursive; use Cursive;
use event::{Callback, Event, EventResult}; 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 pub fn register<F, E: Into<Event>>(mut self, event: E, cb: F) -> Self
where F: Fn(&mut Cursive) + 'static where F: Fn(&mut Cursive) + 'static
{ {
self.callbacks.insert(event.into(), Rc::new(cb)); self.callbacks.insert(event.into(), Callback::from_fn(cb));
self self
} }

View File

@ -101,7 +101,7 @@ impl MenuPopup {
/// ///
/// (When the user hits <ESC>) /// (When the user hits <ESC>)
pub fn on_dismiss<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self { 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 self
} }
@ -111,7 +111,7 @@ impl MenuPopup {
/// ///
/// Usually used to hide the parent view. /// Usually used to hide the parent view.
pub fn on_action<F: 'static + Fn(&mut Cursive)>(mut self, f: F) -> Self { 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 self
} }

View File

@ -58,6 +58,7 @@ mod linear_layout;
mod list_view; mod list_view;
mod menubar; mod menubar;
mod menu_popup; mod menu_popup;
mod progress_bar;
mod shadow_view; mod shadow_view;
mod select_view; mod select_view;
mod sized_view; mod sized_view;
@ -78,7 +79,7 @@ pub use self::position::{Offset, Position};
pub use self::scroll::ScrollBase; 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::box_view::BoxView;
pub use self::button::Button; pub use self::button::Button;
pub use self::checkbox::Checkbox; pub use self::checkbox::Checkbox;
@ -91,12 +92,13 @@ pub use self::list_view::ListView;
pub use self::menubar::Menubar; pub use self::menubar::Menubar;
pub use self::menu_popup::MenuPopup; pub use self::menu_popup::MenuPopup;
pub use self::view_path::ViewPath; pub use self::view_path::ViewPath;
pub use self::progress_bar::ProgressBar;
pub use self::select_view::SelectView; pub use self::select_view::SelectView;
pub use self::shadow_view::ShadowView; pub use self::shadow_view::ShadowView;
pub use self::sized_view::SizedView;
pub use self::stack_view::StackView; pub use self::stack_view::StackView;
pub use self::text_view::TextView; pub use self::text_view::TextView;
pub use self::tracked_view::TrackedView; pub use self::tracked_view::TrackedView;
pub use self::sized_view::SizedView;
pub use self::view_wrapper::ViewWrapper; 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 view::{IdView, View};
use align::{Align, HAlign, VAlign}; use align::{Align, HAlign, VAlign};
use view::scroll::ScrollBase; use view::scroll::ScrollBase;
use event::{Event, EventResult, Key}; use event::{Callback, Event, EventResult, Key};
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use Printer; use Printer;
@ -387,7 +387,7 @@ impl<T: 'static> View for SelectView<T> {
let cb = self.select_cb.as_ref().unwrap().clone(); let cb = self.select_cb.as_ref().unwrap().clone();
let v = self.selection(); let v = self.selection();
// We return a Callback Rc<|s| cb(s, &*v)> // 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) cb(s, &*v)
}))); })));
} }