mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add ProgressBar
Also make Callback its own NewType to add comversion methods.
This commit is contained in:
parent
10e072c140
commit
e29511e757
51
examples/progress.rs
Normal file
51
examples/progress.rs
Normal 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();
|
||||
}
|
@ -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'
|
||||
|
66
src/event.rs
66
src/event.rs
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
107
src/view/progress_bar.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
})));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user