diff --git a/.travis.yml b/.travis.yml index 48dbc63..6fc53dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,6 @@ rust: - stable - nightly script: - - cargo build --verbose - - cargo test --verbose - - - - cargo build --verbose --features=pancurses-backend --no-default-features - - cargo test --verbose --features=pancurses-backend --no-default-features - - - - cargo build --verbose --features=termion-backend --no-default-features - - cargo test --verbose --features=termion-backend --no-default-features + - cargo check --all-features + - cargo build --verbose --features "pancurses-backend termion-backend" + - cargo test --verbose --features "pancurses-backend termion-backend" diff --git a/CHANGELOG.md b/CHANGELOG.md index b080a71..e59f3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ # Changelog -## Next version: 0.8.2 +## Next version: 0.9.0 + +### New features + +- Make backend a dynamic choice + - User must select a backend in `Cursive::new` + - 3rd party libraries do not need to play with backend features anymore +- Add `StackView::find_layer_from_id` +- Add `SelectView::insert_item` +- Add `TextArea::{enable, disable}` +- Reworked `AnyView` +- `SelectView`: Fix mouse events +- Return callbacks from manual control methods + - `SelectView::{set_selection, select_up, select_down, remove_item}` + - `EditView::{set_content, insert, remove}` +- Add `rect::Rect` + +### Changes + +- Renamed `Vec4` to `Margins` +- `Callbacks` cannot be created from functions that return a value + - The returned value used to be completely ignored +- `AnyView` does not extend `View` anymore (instead, `View` extends `AnyView`) + - If you were using `AnyView` before, you probably need to replace it with `View` ## 0.8.1 diff --git a/Cargo.toml b/Cargo.toml index 87dca91..e098758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" name = "cursive" readme = "Readme.md" repository = "https://github.com/gyscos/Cursive" -version = "0.8.2-alpha.0" +version = "0.9.0-alpha.0" [badges.travis-ci] repository = "gyscos/Cursive" @@ -23,6 +23,7 @@ toml = "0.4" unicode-segmentation = "1.0" unicode-width = "0.1" xi-unicode = "0.1.0" +libc = "0.2" [dependencies.maplit] optional = true @@ -61,6 +62,7 @@ version = "1.5.0" [dev-dependencies] rand = "0.4" +pretty-bytes = "0.2.2" [features] blt-backend = ["bear-lib-terminal"] diff --git a/doc/tutorial_1.md b/doc/tutorial_1.md index ff5b06e..762f9bd 100644 --- a/doc/tutorial_1.md +++ b/doc/tutorial_1.md @@ -12,7 +12,7 @@ use cursive::Cursive; use cursive::views::TextView; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_global_callback('q', |s| s.quit()); @@ -80,7 +80,7 @@ extern crate cursive; use cursive::Cursive; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.run(); } @@ -91,7 +91,7 @@ It's rather easy to identify the two steps involved. If you run this, you'll get an empty blue terminal, with no way of properly leaving the application (you'll have to press to kill it). -[`Cursive`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html +[`Cursive`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html ## Interactivity @@ -117,7 +117,7 @@ extern crate cursive; use cursive::Cursive; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_global_callback('q', |s| s.quit()); @@ -128,10 +128,10 @@ fn main() { As expected, running it show no visible change, but hitting the `q` key at least closes the application. -[`add_global_callback`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.add_global_callback -[`event::Event`]: http://gyscos.github.io/Cursive/cursive/event/enum.Event.html -[`event::Key`]: http://gyscos.github.io/Cursive/cursive/event/enum.Key.html -[`Cursive::quit`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.quit +[`add_global_callback`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.add_global_callback +[`event::Event`]: https://docs.rs/cursive/0/cursive/event/enum.Event.html +[`event::Key`]: https://docs.rs/cursive/0/cursive/event/enum.Key.html +[`Cursive::quit`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.quit ## Views @@ -159,7 +159,7 @@ use cursive::Cursive; use cursive::views::TextView; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_global_callback('q', |s| s.quit()); @@ -169,9 +169,9 @@ fn main() { } ``` -[`View`s]: http://gyscos.github.io/Cursive/cursive/view/trait.View.html -[`TextView`]: http://gyscos.github.io/Cursive/cursive/views/struct.TextView.html -[`StackView`]: http://gyscos.github.io/Cursive/cursive/views/struct.StackView.html -[`Cursive::add_layer`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.add_layer +[`View`s]: https://docs.rs/cursive/0/cursive/view/trait.View.html +[`TextView`]: https://docs.rs/cursive/0/cursive/views/struct.TextView.html +[`StackView`]: https://docs.rs/cursive/0/cursive/views/struct.StackView.html +[`Cursive::add_layer`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.add_layer Next: [Starting with Cursive (2/3)](./tutorial_2.md) diff --git a/doc/tutorial_2.md b/doc/tutorial_2.md index 5bc0ade..cd4b731 100644 --- a/doc/tutorial_2.md +++ b/doc/tutorial_2.md @@ -12,7 +12,7 @@ use cursive::Cursive; use cursive::views::Dialog; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer(Dialog::text("This is a survey!\nPress when you're ready.") .title("Important survey") @@ -51,7 +51,7 @@ extern crate cursive; use cursive::Cursive; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.run(); } @@ -74,7 +74,7 @@ use cursive::views::Dialog; use cursive::views::TextView; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer(Dialog::around(TextView::new("..."))); @@ -103,11 +103,11 @@ This way of chaining method to set-up the view is very common in cursive. Most views provide chainable variants of their methods, to allow creating the view and configuring it in one spot. -[`TextView`]: http://gyscos.github.io/Cursive/cursive/views/struct.TextView -[`Dialog`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html -[`Dialog::around`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html#method.around -[`Dialog::text`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html#method.text -[`Dialog::title`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html#method.title +[`TextView`]: https://docs.rs/cursive/0/cursive/views/struct.TextView +[`Dialog`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html +[`Dialog::around`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html#method.around +[`Dialog::text`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html#method.text +[`Dialog::title`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html#method.title ## Buttons @@ -135,7 +135,7 @@ use cursive::Cursive; use cursive::views::Dialog; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer(Dialog::text("This is a survey!\nPress when you're ready.") .title("Important survey") @@ -149,7 +149,7 @@ fn show_next(_: &mut Cursive) { } ``` -[`Dialog::button`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html#method.button +[`Dialog::button`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html#method.button ## Layers @@ -210,7 +210,7 @@ fn show_answer(s: &mut Cursive, msg: &str) { Here, `show_answer()` does the same thing: remove the previous layer, and add a new `Dialog` instead. -[`Cursive::pop_layer`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.pop_layer +[`Cursive::pop_layer`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.pop_layer ## Conclusion diff --git a/doc/tutorial_3.md b/doc/tutorial_3.md index a63460e..ab51a25 100644 --- a/doc/tutorial_3.md +++ b/doc/tutorial_3.md @@ -17,7 +17,7 @@ use cursive::views::{Button, Dialog, DummyView, EditView, use cursive::traits::*; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); let select = SelectView::::new() .on_submit(on_submit) @@ -138,11 +138,11 @@ not on the `BoxView` returned by `fixed_size`!) What we do there should be pretty familiar by now: replace the layer with a simple dialog. -[`SelectView`]: http://gyscos.github.io/Cursive/cursive/views/struct.SelectView.html -[`BoxView`]: http://gyscos.github.io/Cursive/cursive/views/struct.BoxView.html -[`Boxable`]: http://gyscos.github.io/Cursive/cursive/view/trait.Boxable.html -[`traits`]: http://gyscos.github.io/Cursive/cursive/traits/index.html -[`SelectView::on_submit`]: http://gyscos.github.io/Cursive/cursive/views/struct.SelectView.html#method.on_submit +[`SelectView`]: https://docs.rs/cursive/0/cursive/views/struct.SelectView.html +[`BoxView`]: https://docs.rs/cursive/0/cursive/views/struct.BoxView.html +[`Boxable`]: https://docs.rs/cursive/0/cursive/view/trait.Boxable.html +[`traits`]: https://docs.rs/cursive/0/cursive/traits/index.html +[`SelectView::on_submit`]: https://docs.rs/cursive/0/cursive/views/struct.SelectView.html#method.on_submit ## Linear layouts @@ -188,10 +188,10 @@ We've added a `DummyView` again to add some space between the list and the buttons. Though with an empty list, it doesn't look like much yet. Let's fill this list with names! -[`Button`]: http://gyscos.github.io/Cursive/cursive/views/struct.Button.html -[`Dialog::around`]: http://gyscos.github.io/Cursive/cursive/views/struct.Dialog.html#method.new -[`LinearLayout`]: http://gyscos.github.io/Cursive/cursive/views/struct.LinearLayout.html -[`DummyView`]: http://gyscos.github.io/Cursive/cursive/views/struct.DummyView.html +[`Button`]: https://docs.rs/cursive/0/cursive/views/struct.Button.html +[`Dialog::around`]: https://docs.rs/cursive/0/cursive/views/struct.Dialog.html#method.new +[`LinearLayout`]: https://docs.rs/cursive/0/cursive/views/struct.LinearLayout.html +[`DummyView`]: https://docs.rs/cursive/0/cursive/views/struct.DummyView.html ## IDs @@ -311,14 +311,14 @@ this method returns a handle, through which we can mutate the view. It uses `Rc` and `RefCell` under the hood to provide mutable access to the view without borrowing the `Cursive` root, leaving us free to pop layers. -[`EditView`]: http://gyscos.github.io/Cursive/cursive/views/struct.EditView.html -[`IdView`]: http://gyscos.github.io/Cursive/cursive/views/struct.IdView.html -[`IdView::new`]: http://gyscos.github.io/Cursive/cursive/prelude/struct.IdView.html#method.around -[`Identifiable`]: http://gyscos.github.io/Cursive/cursive/view/trait.Identifiable.html -[`Cursive::find_id`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.find_id -[`Cursive::call_on_id`]: http://gyscos.github.io/Cursive/cursive/struct.Cursive.html#method.call_on_id -[`SelectView::selected_id`]: http://gyscos.github.io/Cursive/cursive/views/struct.SelectView.html#method.selected_id -[`SelectView::remove_item`]: http://gyscos.github.io/Cursive/cursive/views/struct.SelectView.html#method.remove_item +[`EditView`]: https://docs.rs/cursive/0/cursive/views/struct.EditView.html +[`IdView`]: https://docs.rs/cursive/0/cursive/views/struct.IdView.html +[`IdView::new`]: https://docs.rs/cursive/0/cursive/prelude/struct.IdView.html#method.around +[`Identifiable`]: https://docs.rs/cursive/0/cursive/view/trait.Identifiable.html +[`Cursive::find_id`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.find_id +[`Cursive::call_on_id`]: https://docs.rs/cursive/0/cursive/struct.Cursive.html#method.call_on_id +[`SelectView::selected_id`]: https://docs.rs/cursive/0/cursive/views/struct.SelectView.html#method.selected_id +[`SelectView::remove_item`]: https://docs.rs/cursive/0/cursive/views/struct.SelectView.html#method.remove_item ## Conclusion diff --git a/examples/colors.rs b/examples/colors.rs index f9af2e3..bba7f15 100644 --- a/examples/colors.rs +++ b/examples/colors.rs @@ -17,7 +17,7 @@ use cursive::views::Canvas; // 256 colors. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer( Canvas::new(()) diff --git a/examples/dialog.rs b/examples/dialog.rs index 6668ac0..126ad32 100644 --- a/examples/dialog.rs +++ b/examples/dialog.rs @@ -5,7 +5,7 @@ use cursive::views::{Dialog, TextView}; fn main() { // Creates the cursive root - required for every application. - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // Creates a dialog with a single "Quit" button siv.add_layer( diff --git a/examples/edit.rs b/examples/edit.rs index f2a3470..b6d695f 100644 --- a/examples/edit.rs +++ b/examples/edit.rs @@ -5,7 +5,7 @@ use cursive::traits::*; use cursive::views::{Dialog, EditView, TextView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // Create a dialog with an edit text and a button. // The user can either hit the button, diff --git a/examples/hello_world.rs b/examples/hello_world.rs index d1c4cee..fb5fae7 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -4,7 +4,7 @@ use cursive::Cursive; use cursive::views::TextView; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We can quit by pressing `q` siv.add_global_callback('q', Cursive::quit); diff --git a/examples/key_codes.rs b/examples/key_codes.rs index b20a024..f97ba70 100644 --- a/examples/key_codes.rs +++ b/examples/key_codes.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::{Cursive, Printer}; use cursive::event::{Event, EventResult}; use cursive::traits::*; +use cursive::{Cursive, Printer}; // This example define a custom view that prints any event it receives. // This is a handy way to check the input received by cursive. diff --git a/examples/linear.rs b/examples/linear.rs index bce55d6..1d38f9d 100644 --- a/examples/linear.rs +++ b/examples/linear.rs @@ -8,7 +8,7 @@ use cursive::views::{Dialog, DummyView, LinearLayout, TextView}; // This example uses a LinearLayout to stick multiple views next to each other. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // Some description text let text = "This is a very simple example of linear layout. Two views \ diff --git a/examples/list_view.rs b/examples/list_view.rs index af69e68..a87ed11 100644 --- a/examples/list_view.rs +++ b/examples/list_view.rs @@ -10,7 +10,7 @@ use cursive::views::{Checkbox, Dialog, EditView, LinearLayout, ListView, // ListView can be used to build forms, with a list of inputs. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer( Dialog::new() diff --git a/examples/logs.rs b/examples/logs.rs index 283d820..74268f5 100644 --- a/examples/logs.rs +++ b/examples/logs.rs @@ -14,7 +14,7 @@ use std::time::Duration; fn main() { // As usual, create the Cursive root - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We want to refresh the page even when no input is given. siv.set_fps(10); diff --git a/examples/lorem.rs b/examples/lorem.rs index 014949a..a76a9e3 100644 --- a/examples/lorem.rs +++ b/examples/lorem.rs @@ -9,7 +9,7 @@ fn main() { // Read some long text from a file. let content = include_str!("../assets/lorem.txt"); - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We can quit by pressing q siv.add_global_callback('q', |s| s.quit()); diff --git a/examples/markup.rs b/examples/markup.rs index 02f7815..6eac432 100644 --- a/examples/markup.rs +++ b/examples/markup.rs @@ -9,7 +9,7 @@ use cursive::utils::markup::StyledString; use cursive::views::{Dialog, TextView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); let mut styled = StyledString::plain("Isn't "); styled.append(StyledString::styled( diff --git a/examples/menubar.rs b/examples/menubar.rs index c1432c7..fb063ff 100644 --- a/examples/menubar.rs +++ b/examples/menubar.rs @@ -11,7 +11,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; // application. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We'll use a counter to name new files. let counter = AtomicUsize::new(1); diff --git a/examples/mines/main.rs b/examples/mines/main.rs index ce98b49..c4ef63f 100644 --- a/examples/mines/main.rs +++ b/examples/mines/main.rs @@ -12,7 +12,7 @@ use cursive::vec::Vec2; use cursive::views::{Button, Dialog, LinearLayout, Panel, SelectView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_layer( Dialog::new() diff --git a/examples/mutation.rs b/examples/mutation.rs index 346ca3f..db65c26 100644 --- a/examples/mutation.rs +++ b/examples/mutation.rs @@ -8,7 +8,7 @@ use cursive::views::{Dialog, OnEventView, TextView}; // This example modifies a view after creation. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); let content = "Press Q to quit the application.\n\nPress P to open the \ popup."; diff --git a/examples/position.rs b/examples/position.rs index 094765e..cf4dff4 100644 --- a/examples/position.rs +++ b/examples/position.rs @@ -22,7 +22,7 @@ fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) { } fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.set_fps(60); // We can quit by pressing `q` diff --git a/examples/progress.rs b/examples/progress.rs index 5020da4..98dd55c 100644 --- a/examples/progress.rs +++ b/examples/progress.rs @@ -4,7 +4,7 @@ extern crate rand; use cursive::Cursive; use cursive::traits::*; use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView}; -use cursive::views::Counter; +use cursive::utils::Counter; use rand::Rng; use std::cmp::min; use std::thread; @@ -17,7 +17,7 @@ use std::time::Duration; // "ticked" to indicate progress. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We'll start slowly with a simple start button... siv.add_layer( diff --git a/examples/radio.rs b/examples/radio.rs index f544af7..f7ccfbc 100644 --- a/examples/radio.rs +++ b/examples/radio.rs @@ -6,7 +6,7 @@ use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup}; // This example uses radio buttons. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // We need to pre-create the groups for our RadioButtons. let mut color_group: RadioGroup = RadioGroup::new(); diff --git a/examples/refcell_view.rs b/examples/refcell_view.rs index 45e3ecf..a6ec910 100644 --- a/examples/refcell_view.rs +++ b/examples/refcell_view.rs @@ -7,7 +7,7 @@ use cursive::views::{Dialog, EditView, LinearLayout, TextView}; // This example shows a way to access multiple views at the same time. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // Create a dialog with 2 edit fields, and a text view. // The text view indicates when the 2 fields content match. diff --git a/examples/select.rs b/examples/select.rs index 045771d..d930be6 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -33,7 +33,7 @@ fn main() { Some(EventResult::Consumed(None)) }); - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // Let's add a BoxView to keep the list at a reasonable size // (it can scroll anyway). diff --git a/examples/slider.rs b/examples/slider.rs index 42fa634..b84a2e3 100644 --- a/examples/slider.rs +++ b/examples/slider.rs @@ -5,7 +5,7 @@ use cursive::traits::*; use cursive::views::{Dialog, SliderView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); siv.add_global_callback('q', |s| s.quit()); diff --git a/examples/terminal_default.rs b/examples/terminal_default.rs index 3531d24..57f4c36 100644 --- a/examples/terminal_default.rs +++ b/examples/terminal_default.rs @@ -9,7 +9,7 @@ use cursive::views::TextView; // This way, it looks more natural. fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); let theme = custom_theme_from_cursive(&siv); siv.set_theme(theme); diff --git a/examples/text_area.rs b/examples/text_area.rs index 5cbee72..b1e768e 100644 --- a/examples/text_area.rs +++ b/examples/text_area.rs @@ -6,7 +6,7 @@ use cursive::traits::*; use cursive::views::{Dialog, EditView, OnEventView, TextArea}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // The main dialog will just have a textarea. // Its size expand automatically with the content. diff --git a/examples/theme.rs b/examples/theme.rs index ae9a950..3494961 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -4,7 +4,7 @@ use cursive::Cursive; use cursive::views::{Dialog, TextView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); // You can load a theme from a file at runtime for fast development. siv.load_theme_file("assets/style.toml") .unwrap(); diff --git a/examples/theme_manual.rs b/examples/theme_manual.rs index 3bdabb7..1af3533 100644 --- a/examples/theme_manual.rs +++ b/examples/theme_manual.rs @@ -5,7 +5,7 @@ use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle}; use cursive::views::{Dialog, EditView, LinearLayout, TextView}; fn main() { - let mut siv = Cursive::new(); + let mut siv = Cursive::default(); let layout = LinearLayout::vertical() .child(TextView::new( diff --git a/examples/vpv.rs b/examples/vpv.rs new file mode 100644 index 0000000..c3ba833 --- /dev/null +++ b/examples/vpv.rs @@ -0,0 +1,139 @@ +extern crate cursive; +extern crate pretty_bytes; + +use std::io; + +use cursive::Cursive; +use cursive::traits::{Boxable, With}; +use cursive::utils; +use cursive::views::{Canvas, Dialog, LinearLayout, ProgressBar}; +use pretty_bytes::converter::convert; +use std::thread; +use std::time; + +// This example is a visual version of the `pv` tool. + +fn main() { + let mut siv = Cursive::default(); + + // We'll use this channel to signal the end of the transfer + let cb_sink = siv.cb_sink().clone(); + + // Use a counter to track progress + let counter = utils::Counter::new(0); + let counter_copy = counter.clone(); + let start = time::Instant::now(); + + // If an argument is given, it is the file we'll read from. + // Else, read from stdin. + let (source, len) = match std::env::args().nth(1) { + Some(source) => { + let meta = std::fs::metadata(&source).unwrap(); + // If possible, read the file size to have a progress bar. + let len = meta.len(); + ( + Some(source), + if len > 0 { Some(len) } else { None }, + ) + } + None => (None, None), + }; + + // Start the copy in a separate thread + thread::spawn(move || { + // Copy to stdout - lock it for better performance. + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + match source { + None => { + // Copy from stdin - lock it for better performance. + let stdin = io::stdin(); + let stdin = stdin.lock(); + let mut reader = + utils::ProgressReader::new(counter_copy, stdin); + + // And copy! + io::copy(&mut reader, &mut stdout).unwrap(); + } + Some(source) => { + // Copy from stdin - lock it for better performance. + let input = std::fs::File::open(source).unwrap(); + let mut reader = + utils::ProgressReader::new(counter_copy, input); + + // And copy! + io::copy(&mut reader, &mut stdout).unwrap(); + } + } + + // When we're done, shut down the application + cb_sink + .send(Box::new(|s: &mut Cursive| s.quit())) + .unwrap(); + }); + + // Add a single view: progress status + siv.add_layer( + Dialog::new().title("Copying...").content( + LinearLayout::vertical() + .child( + Canvas::new(counter.clone()) + .with_draw(move |c, printer| { + let ticks = c.get() as f64; + let now = time::Instant::now(); + let duration = now - start; + + let seconds = duration.as_secs() as f64 + + duration.subsec_nanos() as f64 * 1e-9; + + let speed = ticks / seconds; + + // Print ETA if we have a file size + // Otherwise prints elapsed time. + if let Some(len) = len { + let remaining = (len as f64 - ticks) / speed; + printer.print( + (0, 0), + &format!( + "ETA: {:.1} seconds", + remaining + ), + ); + } else { + printer.print( + (0, 0), + &format!( + "Elapsed: {:.1} seconds", + seconds + ), + ); + } + printer.print( + (0, 1), + &format!("Copied: {}", convert(ticks)), + ); + printer.print( + (0, 2), + &format!("Speed: {}/s", convert(speed)), + ); + }) + .fixed_size((25, 3)), + ) + .with(|l| { + // If we have a file length, add a progress bar + if let Some(len) = len { + l.add_child( + ProgressBar::new() + .max(len as usize) + .with_value(counter.clone()), + ); + } + }), + ), + ); + + siv.set_fps(10); + + siv.run(); +} diff --git a/src/backend/blt.rs b/src/backend/blt.rs index b1e7fd1..19784ff 100644 --- a/src/backend/blt.rs +++ b/src/backend/blt.rs @@ -1,3 +1,8 @@ +//! Backend using BearLibTerminal +//! +//! Requires the `blt-backend` feature. +#![cfg(feature = "bear-lib-terminal")] + extern crate bear_lib_terminal; use self::bear_lib_terminal::Color as BltColor; @@ -15,12 +20,34 @@ enum ColorRole { Background, } -pub struct Concrete { +pub struct Backend { mouse_position: Vec2, buttons_pressed: HashSet, } -impl Concrete { +impl Backend { + pub fn init() -> Box { + terminal::open("Cursive", 80, 24); + terminal::set(terminal::config::Window::empty().resizeable(true)); + terminal::set(vec![ + terminal::config::InputFilter::Group { + group: terminal::config::InputFilterGroup::Keyboard, + both: false, + }, + terminal::config::InputFilter::Group { + group: terminal::config::InputFilterGroup::Mouse, + both: true, + }, + ]); + + let c = Backend { + mouse_position: Vec2::zero(), + buttons_pressed: HashSet::new(), + }; + + Box::new(c) + } + fn blt_keycode_to_ev( &mut self, kc: KeyCode, shift: bool, ctrl: bool ) -> Event { @@ -144,52 +171,52 @@ impl Concrete { } } -impl backend::Backend for Concrete { - fn init() -> Self { - terminal::open("Cursive", 80, 24); - terminal::set(terminal::config::Window::empty().resizeable(true)); - terminal::set(vec![ - terminal::config::InputFilter::Group { - group: terminal::config::InputFilterGroup::Keyboard, - both: false, - }, - terminal::config::InputFilter::Group { - group: terminal::config::InputFilterGroup::Mouse, - both: true, - }, - ]); - - Concrete { - mouse_position: Vec2::zero(), - buttons_pressed: HashSet::new(), - } - } - +impl backend::Backend for Backend { fn finish(&mut self) { terminal::close(); } - fn with_color(&self, color: ColorPair, f: F) { + fn set_color(&self, color: ColorPair) -> ColorPair { + let current = ColorPair { + front: blt_colour_to_colour(state::foreground()), + back: blt_colour_to_colour(state::background()) + }; + let fg = colour_to_blt_colour(color.front, ColorRole::Foreground); let bg = colour_to_blt_colour(color.back, ColorRole::Background); - terminal::with_colors(fg, bg, f); + + terminal::set_colors(fg, bg); + + current } - fn with_effect(&self, effect: Effect, f: F) { + fn set_effect(&self, effect: Effect) { match effect { // TODO: does BLT support bold/italic/underline? Effect::Bold | Effect::Italic | Effect::Underline - | Effect::Simple => f(), + | Effect::Simple => {}, // TODO: how to do this correctly?` // BLT itself doesn't do this kind of thing, // we'd need the colours in our position, // but `f()` can do whatever - Effect::Reverse => terminal::with_colors( - BltColor::from_rgb(0, 0, 0), - BltColor::from_rgb(255, 255, 255), - f, + Effect::Reverse => terminal::set_colors( + state::background(), state::foreground() + ), + } + } + + fn unset_effect(&self, effect: Effect) { + match effect { + // TODO: does BLT support bold/italic/underline? + Effect::Bold + | Effect::Italic + | Effect::Underline + | Effect::Simple => {}, + // The process of reversing is the same as unreversing + Effect::Reverse => terminal::set_colors( + state::background(), state::foreground() ), } } @@ -198,9 +225,9 @@ impl backend::Backend for Concrete { true } - fn screen_size(&self) -> (usize, usize) { + fn screen_size(&self) -> Vec2 { let Size { width, height } = terminal::state::size(); - (width as usize, height as usize) + (width, height).into() } fn clear(&self, color: Color) { @@ -215,8 +242,8 @@ impl backend::Backend for Concrete { terminal::refresh(); } - fn print_at(&self, (x, y): (usize, usize), text: &str) { - terminal::print_xy(x as i32, y as i32, text); + fn print_at(&self, pos: Vec2, text: &str) { + terminal::print_xy(pos.x as i32, pos.y as i32, text); } fn set_refresh_rate(&mut self, _: u32) { @@ -284,6 +311,10 @@ impl backend::Backend for Concrete { } } +fn blt_colour_to_colour(c: BltColor) -> Color { + Color::Rgb(c.red, c.green, c.blue) +} + fn colour_to_blt_colour(clr: Color, role: ColorRole) -> BltColor { let (r, g, b) = match clr { Color::TerminalDefault => { diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs index 9d20f27..7458b31 100644 --- a/src/backend/curses/mod.rs +++ b/src/backend/curses/mod.rs @@ -1,16 +1,17 @@ +//! Common module for the ncurses and pancurses backends. +//! +//! Requires either of `ncurses-backend` or `pancurses-backend`. +#![cfg(any(feature = "ncurses", feature = "pancurses"))] + use event::{Event, Key}; use std::collections::HashMap; use theme::{BaseColor, Color, ColorPair}; #[cfg(feature = "ncurses")] -mod n; -#[cfg(feature = "ncurses")] -pub use self::n::*; +pub mod n; #[cfg(feature = "pancurses")] -mod pan; -#[cfg(feature = "pancurses")] -pub use self::pan::*; +pub mod pan; fn split_i32(code: i32) -> Vec { (0..4).map(|i| ((code >> (8 * i)) & 0xFF) as u8).collect() diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 8d5bfe5..da0d98a 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,17 +1,21 @@ extern crate ncurses; -use self::ncurses::mmask_t; use self::super::split_i32; +use self::ncurses::mmask_t; use backend; use event::{Event, Key, MouseButton, MouseEvent}; +use libc; use std::cell::{Cell, RefCell}; use std::collections::HashMap; -use std::io::{stdout, Write}; +use std::ffi::CString; +use std::fs::File; +use std::io; +use std::io::Write; use theme::{Color, ColorPair, Effect}; use utf8; use vec::Vec2; -pub struct Concrete { +pub struct Backend { current_style: Cell, // Maps (front, back) ncurses colors to ncurses pairs @@ -27,10 +31,73 @@ fn find_closest_pair(pair: &ColorPair) -> (i16, i16) { super::find_closest_pair(pair, ncurses::COLORS() as i16) } -impl Concrete { +/// Writes some bytes directly to `/dev/tty` +/// +/// Since this is not going to be used often, we can afford to re-open the +/// file every time. +fn write_to_tty(bytes: &[u8]) -> io::Result<()> { + let mut tty_output = + File::create("/dev/tty").expect("cursive can only run with a tty"); + tty_output.write_all(bytes)?; + // tty_output will be flushed automatically at the end of the function. + Ok(()) +} + +impl Backend { + pub fn init() -> Box { + // Change the locale. + // For some reasons it's mandatory to get some UTF-8 support. + ncurses::setlocale(ncurses::LcCategory::all, ""); + + // The delay is the time ncurses wait after pressing ESC + // to see if it's an escape sequence. + // Default delay is way too long. 25 is imperceptible yet works fine. + ::std::env::set_var("ESCDELAY", "25"); + + let tty_path = CString::new("/dev/tty").unwrap(); + let mode = CString::new("r+").unwrap(); + let tty = unsafe { libc::fopen(tty_path.as_ptr(), mode.as_ptr()) }; + ncurses::newterm(None, tty, tty); + ncurses::keypad(ncurses::stdscr(), true); + + // This disables mouse click detection, + // and provides 0-delay access to mouse presses. + ncurses::mouseinterval(0); + // Listen to all mouse events. + ncurses::mousemask( + (ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION) + as mmask_t, + None, + ); + ncurses::noecho(); + ncurses::cbreak(); + ncurses::start_color(); + // Pick up background and text color from the terminal theme. + ncurses::use_default_colors(); + // No cursor + ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); + + // This asks the terminal to provide us with mouse drag events + // (Mouse move when a button is pressed). + // Replacing 1002 with 1003 would give us ANY mouse move. + write_to_tty(b"\x1B[?1002h").unwrap(); + + let c = Backend { + current_style: Cell::new(ColorPair::from_256colors(0, 0)), + pairs: RefCell::new(HashMap::new()), + + last_mouse_button: None, + event_queue: Vec::new(), + + key_codes: initialize_keymap(), + }; + + Box::new(c) + } + /// Save a new color pair. fn insert_color( - &self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16) + &self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16), ) -> i16 { let n = 1 + pairs.len() as i16; @@ -96,7 +163,7 @@ impl Concrete { let make_event = |event| Event::Mouse { offset: Vec2::zero(), position: Vec2::new(mevent.x as usize, mevent.y as usize), - event: event, + event, }; if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t { @@ -153,59 +220,12 @@ impl Concrete { } } -impl backend::Backend for Concrete { - fn init() -> Self { - // Change the locale. - // For some reasons it's mandatory to get some UTF-8 support. - ncurses::setlocale(ncurses::LcCategory::all, ""); - - // The delay is the time ncurses wait after pressing ESC - // to see if it's an escape sequence. - // Default delay is way too long. 25 is imperceptible yet works fine. - ::std::env::set_var("ESCDELAY", "25"); - - ncurses::initscr(); - ncurses::keypad(ncurses::stdscr(), true); - - // This disables mouse click detection, - // and provides 0-delay access to mouse presses. - ncurses::mouseinterval(0); - // Listen to all mouse events. - ncurses::mousemask( - (ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION) - as mmask_t, - None, - ); - ncurses::noecho(); - ncurses::cbreak(); - ncurses::start_color(); - // Pick up background and text color from the terminal theme. - ncurses::use_default_colors(); - // No cursor - ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); - - // This asks the terminal to provide us with mouse drag events - // (Mouse move when a button is pressed). - // Replacing 1002 with 1003 would give us ANY mouse move. - print!("\x1B[?1002h"); - stdout().flush().expect("could not flush stdout"); - - Concrete { - current_style: Cell::new(ColorPair::from_256colors(0, 0)), - pairs: RefCell::new(HashMap::new()), - - last_mouse_button: None, - event_queue: Vec::new(), - - key_codes: initialize_keymap(), - } - } - - fn screen_size(&self) -> (usize, usize) { +impl backend::Backend for Backend { + fn screen_size(&self) -> Vec2 { let mut x: i32 = 0; let mut y: i32 = 0; ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x); - (x as usize, y as usize) + (x, y).into() } fn has_colors(&self) -> bool { @@ -213,26 +233,21 @@ impl backend::Backend for Concrete { } fn finish(&mut self) { - print!("\x1B[?1002l"); - stdout().flush().expect("could not flush stdout"); + write_to_tty(b"\x1B[?1002l").unwrap(); ncurses::endwin(); } - fn with_color(&self, colors: ColorPair, f: F) { + fn set_color(&self, colors: ColorPair) -> ColorPair { // eprintln!("Color used: {:?}", colors); let current = self.current_style.get(); if current != colors { self.set_colors(colors); } - f(); - - if current != colors { - self.set_colors(current); - } + current } - fn with_effect(&self, effect: Effect, f: F) { + fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Reverse => ncurses::A_REVERSE(), Effect::Simple => ncurses::A_NORMAL(), @@ -241,7 +256,16 @@ impl backend::Backend for Concrete { Effect::Underline => ncurses::A_UNDERLINE(), }; ncurses::attron(style); - f(); + } + + fn unset_effect(&self, effect: Effect) { + let style = match effect { + Effect::Reverse => ncurses::A_REVERSE(), + Effect::Simple => ncurses::A_NORMAL(), + Effect::Bold => ncurses::A_BOLD(), + Effect::Italic => ncurses::A_ITALIC(), + Effect::Underline => ncurses::A_UNDERLINE(), + }; ncurses::attroff(style); } @@ -259,8 +283,8 @@ impl backend::Backend for Concrete { ncurses::refresh(); } - fn print_at(&self, (x, y): (usize, usize), text: &str) { - ncurses::mvaddstr(y as i32, x as i32, text); + fn print_at(&self, pos: Vec2, text: &str) { + ncurses::mvaddstr(pos.y as i32, pos.x as i32, text); } fn poll_event(&mut self) -> Event { diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 1938847..c4848d3 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -1,7 +1,7 @@ extern crate pancurses; -use self::pancurses::mmask_t; use self::super::split_i32; +use self::pancurses::mmask_t; use backend; use event::{Event, Key, MouseButton, MouseEvent}; use std::cell::{Cell, RefCell}; @@ -10,7 +10,7 @@ use std::io::{stdout, Write}; use theme::{Color, ColorPair, Effect}; use vec::Vec2; -pub struct Concrete { +pub struct Backend { // Used current_style: Cell, pairs: RefCell>, @@ -28,10 +28,46 @@ fn find_closest_pair(pair: &ColorPair) -> (i16, i16) { super::find_closest_pair(pair, pancurses::COLORS() as i16) } -impl Concrete { +impl Backend { + pub fn init() -> Box { + ::std::env::set_var("ESCDELAY", "25"); + + let window = pancurses::initscr(); + window.keypad(true); + pancurses::noecho(); + pancurses::cbreak(); + pancurses::start_color(); + pancurses::use_default_colors(); + pancurses::curs_set(0); + pancurses::mouseinterval(0); + pancurses::mousemask( + pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION, + ::std::ptr::null_mut(), + ); + + // This asks the terminal to provide us with mouse drag events + // (Mouse move when a button is pressed). + // Replacing 1002 with 1003 would give us ANY mouse move. + print!("\x1B[?1002h"); + stdout() + .flush() + .expect("could not flush stdout"); + + let c = Backend { + current_style: Cell::new(ColorPair::from_256colors(0, 0)), + pairs: RefCell::new(HashMap::new()), + window: window, + last_mouse_button: None, + event_queue: Vec::new(), + key_codes: initialize_keymap(), + }; + + Box::new(c) + } + /// Save a new color pair. fn insert_color( - &self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16) + &self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16), ) -> i32 { let n = 1 + pairs.len() as i32; @@ -133,42 +169,11 @@ impl Concrete { } } -impl backend::Backend for Concrete { - fn init() -> Self { - ::std::env::set_var("ESCDELAY", "25"); - - let window = pancurses::initscr(); - window.keypad(true); - pancurses::noecho(); - pancurses::cbreak(); - pancurses::start_color(); - pancurses::use_default_colors(); - pancurses::curs_set(0); - pancurses::mouseinterval(0); - pancurses::mousemask( - pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION, - ::std::ptr::null_mut(), - ); - - // This asks the terminal to provide us with mouse drag events - // (Mouse move when a button is pressed). - // Replacing 1002 with 1003 would give us ANY mouse move. - print!("\x1B[?1002h"); - stdout().flush().expect("could not flush stdout"); - - Concrete { - current_style: Cell::new(ColorPair::from_256colors(0, 0)), - pairs: RefCell::new(HashMap::new()), - window: window, - last_mouse_button: None, - event_queue: Vec::new(), - key_codes: initialize_keymap(), - } - } - - fn screen_size(&self) -> (usize, usize) { +impl backend::Backend for Backend { + fn screen_size(&self) -> Vec2 { + // Coordinates are reversed here let (y, x) = self.window.get_max_yx(); - (x as usize, y as usize) + (x, y).into() } fn has_colors(&self) -> bool { @@ -177,25 +182,23 @@ impl backend::Backend for Concrete { fn finish(&mut self) { print!("\x1B[?1002l"); - stdout().flush().expect("could not flush stdout"); + stdout() + .flush() + .expect("could not flush stdout"); pancurses::endwin(); } - fn with_color(&self, colors: ColorPair, f: F) { + fn set_color(&self, colors: ColorPair) -> ColorPair { let current = self.current_style.get(); if current != colors { self.set_colors(colors); } - f(); - - if current != colors { - self.set_colors(current); - } + current } - fn with_effect(&self, effect: Effect, f: F) { + fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Simple => pancurses::Attribute::Normal, Effect::Reverse => pancurses::Attribute::Reverse, @@ -204,7 +207,16 @@ impl backend::Backend for Concrete { Effect::Underline => pancurses::Attribute::Underline, }; self.window.attron(style); - f(); + } + + fn unset_effect(&self, effect: Effect) { + let style = match effect { + Effect::Simple => pancurses::Attribute::Normal, + Effect::Reverse => pancurses::Attribute::Reverse, + Effect::Bold => pancurses::Attribute::Bold, + Effect::Italic => pancurses::Attribute::Italic, + Effect::Underline => pancurses::Attribute::Underline, + }; self.window.attroff(style); } @@ -221,8 +233,9 @@ impl backend::Backend for Concrete { self.window.refresh(); } - fn print_at(&self, (x, y): (usize, usize), text: &str) { - self.window.mvaddstr(y as i32, x as i32, text); + fn print_at(&self, pos: Vec2, text: &str) { + self.window + .mvaddstr(pos.y as i32, pos.x as i32, text); } fn poll_event(&mut self) -> Event { diff --git a/src/backend/dummy.rs b/src/backend/dummy.rs new file mode 100644 index 0000000..0d7e907 --- /dev/null +++ b/src/backend/dummy.rs @@ -0,0 +1,49 @@ +//! Dummy backend +use backend; +use theme; +use event; +use vec::Vec2; + +pub struct Backend; + +impl Backend { + pub fn init() -> Box + where + Self: Sized, + { + Box::new(Backend) + } +} + +impl backend::Backend for Backend { + fn finish(&mut self) {} + + fn refresh(&mut self) {} + + fn has_colors(&self) -> bool { + false + } + + fn screen_size(&self) -> Vec2 { + (1, 1).into() + } + + fn poll_event(&mut self) -> event::Event { + event::Event::Exit + } + + fn print_at(&self, _: Vec2, _: &str) {} + + fn clear(&self, _: theme::Color) {} + + fn set_refresh_rate(&mut self, _: u32) {} + + // This sets the Colours and returns the previous colours + // to allow you to set them back when you're done. + fn set_color(&self, colors: theme::ColorPair) -> theme::ColorPair { + colors + } + + fn set_effect(&self, _: theme::Effect) {} + fn unset_effect(&self, _: theme::Effect) {} +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index f5619f8..e2a82bd 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,40 +1,64 @@ +//! Define backends using common libraries. +//! +//! Cursive doesn't print anything by itself: it delegates this job to a +//! backend library, which handles all actual input and output. +//! +//! This module defines the `Backend` trait, as well as a few implementations +//! using some common libraries. Each of those included backends needs a +//! corresonding feature to be enabled. + use event; use theme; -#[cfg(feature = "termion")] -mod termion; -#[cfg(feature = "bear-lib-terminal")] -mod blt; -#[cfg(any(feature = "ncurses", feature = "pancurses"))] -mod curses; +use vec::Vec2; -#[cfg(feature = "bear-lib-terminal")] -pub use self::blt::*; -#[cfg(any(feature = "ncurses", feature = "pancurses"))] -pub use self::curses::*; -#[cfg(feature = "termion")] -pub use self::termion::*; +pub mod dummy; +pub mod termion; +pub mod blt; +pub mod curses; + +/// Trait defining the required methods to be a backend. pub trait Backend { - fn init() -> Self; // TODO: take `self` by value? // Or implement Drop? + /// Prepares to close the backend. + /// + /// This should clear any state in the terminal. fn finish(&mut self); + /// Refresh the screen. fn refresh(&mut self); + /// Should return `true` if this backend supports colors. fn has_colors(&self) -> bool; - fn screen_size(&self) -> (usize, usize); + + /// Returns the screen size. + fn screen_size(&self) -> Vec2; /// Main input method fn poll_event(&mut self) -> event::Event; /// Main method used for printing - fn print_at(&self, (usize, usize), &str); + fn print_at(&self, pos: Vec2, text: &str); + + /// Clears the screen with the given color. fn clear(&self, color: theme::Color); + /// Sets the refresh rate for the backend. + /// + /// If no event is detected in the interval, send an `Event::Refresh`. fn set_refresh_rate(&mut self, fps: u32); - // TODO: unify those into a single method? - fn with_color(&self, colors: theme::ColorPair, f: F); - fn with_effect(&self, effect: theme::Effect, f: F); + + /// Starts using a new color. + /// + /// This should return the previously active color. + fn set_color(&self, colors: theme::ColorPair) -> theme::ColorPair; + + /// Enables the given effect. + fn set_effect(&self, effect: theme::Effect); + + + /// Disables the given effect. + fn unset_effect(&self, effect: theme::Effect); } diff --git a/src/backend/termion.rs b/src/backend/termion.rs index 689b0a7..7503a39 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -1,3 +1,8 @@ +//! Backend using the pure-rust termion library. +//! +//! Requires the `termion-backend` feature. +#![cfg(feature = "termion")] + extern crate termion; extern crate chan_signal; @@ -20,7 +25,7 @@ use std::thread; use theme; use vec::Vec2; -pub struct Concrete { +pub struct Backend { terminal: AlternateScreen>>, current_style: Cell, input: chan::Receiver, @@ -56,7 +61,39 @@ impl Effectable for theme::Effect { } } -impl Concrete { +impl Backend { + pub fn init() -> Box { + print!("{}", termion::cursor::Hide); + + let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); + + // TODO: lock stdout + let terminal = AlternateScreen::from(MouseTerminal::from( + ::std::io::stdout().into_raw_mode().unwrap(), + )); + + let (sender, receiver) = chan::async(); + + thread::spawn(move || { + for key in ::std::io::stdin().events() { + if let Ok(key) = key { + sender.send(key) + } + } + }); + + let c = Backend { + terminal: terminal, + current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), + input: receiver, + resize: resize, + timeout: None, + last_button: None, + }; + + Box::new(c) + } + fn apply_colors(&self, colors: theme::ColorPair) { with_color(&colors.front, |c| print!("{}", tcolor::Fg(c))); with_color(&colors.back, |c| print!("{}", tcolor::Bg(c))); @@ -136,37 +173,7 @@ impl Concrete { } } -impl backend::Backend for Concrete { - fn init() -> Self { - print!("{}", termion::cursor::Hide); - - let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); - - // TODO: lock stdout - let terminal = AlternateScreen::from(MouseTerminal::from( - ::std::io::stdout().into_raw_mode().unwrap(), - )); - - let (sender, receiver) = chan::async(); - - thread::spawn(move || { - for key in ::std::io::stdin().events() { - if let Ok(key) = key { - sender.send(key) - } - } - }); - - Concrete { - terminal: terminal, - current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), - input: receiver, - resize: resize, - timeout: None, - last_button: None, - } - } - +impl backend::Backend for Backend { fn finish(&mut self) { print!( "{}{}", @@ -181,7 +188,7 @@ impl backend::Backend for Concrete { ); } - fn with_color(&self, color: theme::ColorPair, f: F) { + fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair { let current_style = self.current_style.get(); if current_style != color { @@ -189,17 +196,14 @@ impl backend::Backend for Concrete { self.current_style.set(color); } - f(); - - if current_style != color { - self.current_style.set(current_style); - self.apply_colors(current_style); - } + return current_style; } - fn with_effect(&self, effect: theme::Effect, f: F) { + fn set_effect(&self, effect: theme::Effect) { effect.on(); - f(); + } + + fn unset_effect(&self, effect: theme::Effect) { effect.off(); } @@ -208,9 +212,9 @@ impl backend::Backend for Concrete { true } - fn screen_size(&self) -> (usize, usize) { + fn screen_size(&self) -> Vec2 { let (x, y) = termion::terminal_size().unwrap_or((1, 1)); - (x as usize, y as usize) + (x, y).into() } fn clear(&self, color: theme::Color) { @@ -225,10 +229,10 @@ impl backend::Backend for Concrete { self.terminal.flush().unwrap(); } - fn print_at(&self, (x, y): (usize, usize), text: &str) { + fn print_at(&self, pos: Vec2, text: &str) { print!( "{}{}", - termion::cursor::Goto(1 + x as u16, 1 + y as u16), + termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16), text ); } diff --git a/src/cursive.rs b/src/cursive.rs index 18153e4..7d09407 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -1,5 +1,4 @@ use backend; -use backend::Backend; use direction; use event::{Callback, Event, EventResult}; use printer::Printer; @@ -33,6 +32,34 @@ impl () + Send> CbFunc for F { } } +#[cfg(feature = "termion")] +impl Default for Cursive { + fn default() -> Self { + Self::termion() + } +} + +#[cfg(all(not(feature = "termion"), feature = "pancurses"))] +impl Default for Cursive { + fn default() -> Self { + Self::pancurses() + } +} + +#[cfg(all(not(feature = "termion"), not(feature = "pancurses"), feature = "bear-lib-terminal"))] +impl Default for Cursive { + fn default() -> Self { + Self::blt() + } +} + +#[cfg(all(not(feature = "termion"), not(feature = "pancurses"), not(feature = "bear-lib-terminal"), feature = "ncurses"))] +impl Default for Cursive { + fn default() -> Self { + Self::ncurses() + } +} + /// Central part of the cursive library. /// /// It initializes ncurses on creation and cleans up on drop. @@ -54,19 +81,15 @@ pub struct Cursive { running: bool, - backend: backend::Concrete, + backend: Box, cb_source: mpsc::Receiver>, cb_sink: mpsc::Sender>, } -new_default!(Cursive); - impl Cursive { /// Creates a new Cursive root, and initialize the back-end. - pub fn new() -> Self { - let backend = backend::Concrete::init(); - + pub fn new(backend: Box) -> Self { let theme = theme::load_default(); // theme.activate(&mut backend); // let theme = theme::load_theme("assets/style.toml").unwrap(); @@ -87,6 +110,37 @@ impl Cursive { } } + /// Creates a new Cursive root using a ncurses backend. + #[cfg(feature = "ncurses")] + pub fn ncurses() -> Self { + Self::new(backend::curses::n::Backend::init()) + } + + /// Creates a new Cursive root using a pancurses backend. + #[cfg(feature = "pancurses")] + pub fn pancurses() -> Self { + Self::new(backend::curses::pan::Backend::init()) + } + + /// Creates a new Cursive root using a termion backend. + #[cfg(feature = "termion")] + pub fn termion() -> Self { + Self::new(backend::termion::Backend::init()) + } + + /// Creates a new Cursive root using a bear-lib-terminal backend. + #[cfg(feature = "bear-lib-terminal")] + pub fn blt() -> Self { + Self::new(backend::blt::Backend::init()) + } + + /// Creates a new Cursive root using a dummy backend. + /// + /// Nothing will be output. This is mostly here for tests. + pub fn dummy() -> Self { + Self::new(backend::dummy::Backend::init()) + } + /// Returns a sink for asynchronous callbacks. /// /// Returns the sender part of a channel, that allows to send @@ -100,11 +154,11 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::*; /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// siv.set_fps(10); /// /// // quit() will be called during the next event cycle @@ -137,7 +191,7 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # /// # use cursive::{Cursive, event}; @@ -146,7 +200,7 @@ impl Cursive { /// # use cursive::menu::*; /// # /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.menubar() /// .add_subtree("File", @@ -290,13 +344,13 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::{Cursive, views, view}; /// # use cursive::traits::*; /// # fn main() { /// fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.add_layer(views::TextView::new("Text #1").with_id("text")); /// @@ -328,12 +382,12 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::{Cursive, views}; /// # use cursive::traits::*; /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.add_layer(views::TextView::new("Text #1") /// .with_id("text")); @@ -361,10 +415,10 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # use cursive::Cursive; /// # use cursive::views::{TextView, ViewRef}; - /// # let mut siv = Cursive::new(); + /// # let mut siv = Cursive::dummy(); /// use cursive::traits::Identifiable; /// /// siv.add_layer(TextView::new("foo").with_id("id")); @@ -401,11 +455,11 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::*; /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.add_global_callback('q', |s| s.quit()); /// # } @@ -424,11 +478,11 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::*; /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.add_global_callback('q', |s| s.quit()); /// siv.clear_global_callbacks('q'); @@ -446,11 +500,11 @@ impl Cursive { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # extern crate cursive; /// # use cursive::*; /// # fn main() { - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// /// siv.add_layer(views::TextView::new("Hello world!")); /// # } @@ -499,12 +553,7 @@ impl Cursive { /// Returns the size of the screen, in characters. pub fn screen_size(&self) -> Vec2 { - let (x, y) = self.backend.screen_size(); - - Vec2 { - x: x as usize, - y: y as usize, - } + self.backend.screen_size() } fn layout(&mut self) { @@ -526,7 +575,7 @@ impl Cursive { } let printer = - Printer::new(self.screen_size(), &self.theme, &self.backend); + Printer::new(self.screen_size(), &self.theme, &*self.backend); let selected = self.menubar.receive_events(); diff --git a/src/lib.rs b/src/lib.rs index 76c9816..968b752 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,14 +32,14 @@ //! //! ## Examples //! -//! ```no_run +//! ```rust //! extern crate cursive; //! //! use cursive::Cursive; //! use cursive::views::TextView; //! //! fn main() { -//! let mut siv = Cursive::new(); +//! let mut siv = Cursive::dummy(); //! //! siv.add_layer(TextView::new("Hello World!\nPress q to quit.")); //! @@ -73,6 +73,7 @@ extern crate log; extern crate maplit; extern crate num; +extern crate libc; extern crate owning_ref; extern crate toml; extern crate unicode_segmentation; diff --git a/src/printer.rs b/src/printer.rs index ea12cfe..3a1353e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,6 +1,6 @@ //! Makes drawing on ncurses windows easier. -use backend::{self, Backend}; +use backend::Backend; use enumset::EnumSet; use std::cell::Cell; use std::cmp::min; @@ -34,7 +34,7 @@ pub struct Printer<'a> { new: Rc>, /// Backend used to actually draw things - backend: &'a backend::Concrete, + backend: &'a Backend, } impl<'a> Clone for Printer<'a> { @@ -57,16 +57,16 @@ impl<'a> Printer<'a> { /// But nobody needs to know that. #[doc(hidden)] pub fn new>( - size: T, theme: &'a Theme, backend: &'a backend::Concrete + size: T, theme: &'a Theme, backend: &'a Backend ) -> Self { Printer { offset: Vec2::zero(), content_offset: Vec2::zero(), size: size.into(), focused: true, - theme: theme, + theme, new: Rc::new(Cell::new(true)), - backend: backend, + backend, } } @@ -103,7 +103,7 @@ impl<'a> Printer<'a> { let text = &text[..prefix_len]; let p = p + self.offset; - self.backend.print_at((p.x, p.y), text); + self.backend.print_at(p, text); } /// Prints a vertical line using the given character. @@ -118,7 +118,7 @@ impl<'a> Printer<'a> { let p = p + self.offset; for y in 0..len { - self.backend.print_at((p.x, (p.y + y)), c); + self.backend.print_at(p + (0,y), c); } } @@ -134,7 +134,7 @@ impl<'a> Printer<'a> { let text: String = ::std::iter::repeat(c).take(len).collect(); let p = p + self.offset; - self.backend.print_at((p.x, p.y), &text); + self.backend.print_at(p, &text); } /// Call the given closure with a colored printer, @@ -142,13 +142,13 @@ impl<'a> Printer<'a> { /// /// # Examples /// - /// ```no_run + /// ```rust /// # use cursive::Printer; /// # use cursive::theme; - /// # use cursive::backend::{self, Backend}; - /// # let b = backend::Concrete::init(); + /// # use cursive::backend; + /// # let b = backend::dummy::Backend::init(); /// # let t = theme::load_default(); - /// # let printer = Printer::new((6,4), &t, &b); + /// # let printer = Printer::new((6,4), &t, &*b); /// printer.with_color(theme::ColorStyle::highlight(), |printer| { /// printer.print((0,0), "This text is highlighted!"); /// }); @@ -157,8 +157,9 @@ impl<'a> Printer<'a> { where F: FnOnce(&Printer), { - self.backend - .with_color(c.resolve(&self.theme.palette), || f(self)); + let old = self.backend.set_color(c.resolve(&self.theme.palette)); + f(self); + self.backend.set_color(old); } /// Call the given closure with a styled printer, @@ -190,7 +191,9 @@ impl<'a> Printer<'a> { where F: FnOnce(&Printer), { - self.backend.with_effect(effect, || f(self)); + self.backend.set_effect(effect); + f(self); + self.backend.unset_effect(effect); } /// Call the given closure with a modified printer @@ -217,13 +220,13 @@ impl<'a> Printer<'a> { /// /// # Examples /// - /// ```no_run + /// ```rust /// # use cursive::Printer; /// # use cursive::theme; - /// # use cursive::backend::{self, Backend}; - /// # let b = backend::Concrete::init(); + /// # use cursive::backend; + /// # let b = backend::dummy::Backend::init(); /// # let t = theme::load_default(); - /// # let printer = Printer::new((6,4), &t, &b); + /// # let printer = Printer::new((6,4), &t, &*b); /// printer.print_box((0,0), (6,4), false); /// ``` pub fn print_box, S: Into>( diff --git a/src/utils/counter.rs b/src/utils/counter.rs new file mode 100644 index 0000000..5a13875 --- /dev/null +++ b/src/utils/counter.rs @@ -0,0 +1,30 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +/// Atomic counter used by [`ProgressBar`]. +/// +/// [`ProgressBar`]: ../views/struct.ProgressBar.html +#[derive(Clone)] +pub struct Counter(pub Arc); + +impl Counter { + /// Creates a new `Counter` starting with the given value. + pub fn new(value: usize) -> Self { + Counter(Arc::new(AtomicUsize::new(value))) + } + + /// Retrieves the current progress value. + pub fn get(&self) -> usize { + self.0.load(Ordering::Relaxed) + } + + /// Sets the current progress value. + pub fn set(&self, value: usize) { + self.0.store(value, Ordering::Relaxed); + } + + /// Increase the current progress by `ticks`. + pub fn tick(&self, ticks: usize) { + self.0.fetch_add(ticks, Ordering::Relaxed); + } +} diff --git a/src/utils/lines/spans/mod.rs b/src/utils/lines/spans/mod.rs index 2fbda35..ab4c275 100644 --- a/src/utils/lines/spans/mod.rs +++ b/src/utils/lines/spans/mod.rs @@ -4,13 +4,13 @@ //! //! Computed rows will include a list of span segments. //! Each segment include the source span ID, and start/end byte offsets. -mod lines_iterator; -mod chunk_iterator; -mod segment_merge_iterator; -mod row; -mod prefix; mod chunk; +mod chunk_iterator; +mod lines_iterator; +mod prefix; +mod row; mod segment; +mod segment_merge_iterator; #[cfg(test)] mod tests; diff --git a/src/utils/markup/markdown.rs b/src/utils/markup/markdown.rs index bff8043..0bf983e 100644 --- a/src/utils/markup/markdown.rs +++ b/src/utils/markup/markdown.rs @@ -18,7 +18,7 @@ where let spans = parse_spans(&input); - StyledString::new(input, spans) + StyledString::with_spans(input, spans) } /// Iterator that parse a markdown text and outputs styled spans. diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 06d9b9b..88ee597 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,10 @@ //! Toolbox to make text layout easier. -mod reader; -pub mod span; +mod counter; pub mod lines; pub mod markup; +mod reader; +pub mod span; +pub use self::counter::Counter; pub use self::reader::ProgressReader; diff --git a/src/utils/reader.rs b/src/utils/reader.rs index 056ccea..1f5485f 100644 --- a/src/utils/reader.rs +++ b/src/utils/reader.rs @@ -1,5 +1,5 @@ use std::io::{self, Read}; -use views::Counter; +use utils::Counter; /// Wrapper around a `Read` that reports the progress made. /// @@ -32,7 +32,7 @@ impl ProgressReader { impl Read for ProgressReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - let result = try!(self.reader.read(buf)); + let result = self.reader.read(buf)?; self.counter.tick(result); Ok(result) } diff --git a/src/view/identifiable.rs b/src/view/identifiable.rs index 15a116c..27b55d1 100644 --- a/src/view/identifiable.rs +++ b/src/view/identifiable.rs @@ -13,13 +13,13 @@ pub trait Identifiable: View + Sized { /// /// # Examples /// - /// ```rust,no_run + /// ```rust /// # use cursive::Cursive; /// # use cursive::views::TextView; /// # use cursive::view::Boxable; /// use cursive::view::Identifiable; /// - /// let mut siv = Cursive::new(); + /// let mut siv = Cursive::dummy(); /// siv.add_layer( /// TextView::new("foo") /// .with_id("text") diff --git a/src/view/margins.rs b/src/view/margins.rs index 010bdc4..e358bda 100644 --- a/src/view/margins.rs +++ b/src/view/margins.rs @@ -18,10 +18,10 @@ impl Margins { /// Creates a new Margins. pub fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { Margins { - left: left, - right: right, - top: top, - bottom: bottom, + left, + right, + top, + bottom, } } @@ -53,7 +53,7 @@ impl Margins { impl From<(usize, usize, usize, usize)> for Margins { fn from( - (left, right, top, bottom): (usize, usize, usize, usize) + (left, right, top, bottom): (usize, usize, usize, usize), ) -> Margins { Margins::new(left, right, top, bottom) } @@ -72,14 +72,14 @@ impl From<(i32, i32, i32, i32)> for Margins { impl From<((i32, i32), (i32, i32))> for Margins { fn from( - ((left, right), (top, bottom)): ((i32, i32), (i32, i32)) + ((left, right), (top, bottom)): ((i32, i32), (i32, i32)), ) -> Margins { (left, right, top, bottom).into() } } impl From<((usize, usize), (usize, usize))> for Margins { fn from( - ((left, right), (top, bottom)): ((usize, usize), (usize, usize)) + ((left, right), (top, bottom)): ((usize, usize), (usize, usize)), ) -> Margins { (left, right, top, bottom).into() } diff --git a/src/view/scroll.rs b/src/view/scroll.rs index 797763b..20e5e4e 100644 --- a/src/view/scroll.rs +++ b/src/view/scroll.rs @@ -228,15 +228,15 @@ impl ScrollBase { /// /// # Examples /// - /// ```no_run + /// ```rust /// # use cursive::view::ScrollBase; /// # use cursive::Printer; /// # use cursive::theme; - /// # use cursive::backend::{self, Backend}; + /// # use cursive::backend; /// # let scrollbase = ScrollBase::new(); - /// # let b = backend::Concrete::init(); + /// # let b = backend::dummy::Backend::init(); /// # let t = theme::load_default(); - /// # let printer = Printer::new((5,1), &t, &b); + /// # let printer = Printer::new((5,1), &t, &*b); /// # let printer = &printer; /// let lines = ["Line 1", "Line number 2"]; /// scrollbase.draw(printer, |printer, i| { diff --git a/src/view/size_cache.rs b/src/view/size_cache.rs index 021a6b3..3786eab 100644 --- a/src/view/size_cache.rs +++ b/src/view/size_cache.rs @@ -1,5 +1,5 @@ -use XY; use vec::Vec2; +use XY; /// Cache around a one-dimensional layout result. /// @@ -19,8 +19,8 @@ impl SizeCache { /// Creates a new sized cache pub fn new(value: usize, constrained: bool) -> Self { SizeCache { - value: value, - constrained: constrained, + value, + constrained, } } diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index fe54fdb..aa940b8 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -152,7 +152,7 @@ impl View for T { /// /// # Examples /// -/// ```no_run +/// ```rust /// # #[macro_use] extern crate cursive; /// # use cursive::view::{View,ViewWrapper}; /// struct FooView { @@ -197,7 +197,7 @@ macro_rules! wrap_impl { /// /// # Examples /// -/// ```no_run +/// ```rust /// # #[macro_use] extern crate cursive; /// # use cursive::view::{View,ViewWrapper}; /// struct FooView { diff --git a/src/views/box_view.rs b/src/views/box_view.rs index 2086bd0..757d556 100644 --- a/src/views/box_view.rs +++ b/src/views/box_view.rs @@ -44,7 +44,7 @@ impl BoxView { BoxView { size: (width, height).into(), squishable: false, - view: view, + view, } } diff --git a/src/views/canvas.rs b/src/views/canvas.rs index 38b7348..8dc3aaa 100644 --- a/src/views/canvas.rs +++ b/src/views/canvas.rs @@ -55,7 +55,7 @@ impl Canvas { /// ``` pub fn new(state: T) -> Self { Canvas { - state: state, + state, draw: Box::new(|_, _| ()), on_event: Box::new(|_, _| EventResult::Ignored), required_size: Box::new(|_, _| Vec2::new(1, 1)), diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 7fd1d56..d824024 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -30,13 +30,13 @@ pub type OnSubmit = Fn(&mut Cursive, &str); /// /// [1]: https://github.com/gyscos/Cursive/blob/master/examples/edit.rs /// -/// ```no_run +/// ```rust /// # extern crate cursive; /// # use cursive::Cursive; /// # use cursive::traits::*; /// # use cursive::views::{Dialog, EditView, TextView}; /// # fn main() { -/// let mut siv = Cursive::new(); +/// let mut siv = Cursive::dummy(); /// /// // Create a dialog with an edit text and a button. /// // The user can either hit the button, diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index bda77f2..c95a2f6 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -113,7 +113,7 @@ impl LinearLayout { pub fn new(orientation: direction::Orientation) -> Self { LinearLayout { children: Vec::new(), - orientation: orientation, + orientation, focus: 0, cache: None, } diff --git a/src/views/menu_popup.rs b/src/views/menu_popup.rs index 1c006bd..42aa9d2 100644 --- a/src/views/menu_popup.rs +++ b/src/views/menu_popup.rs @@ -27,7 +27,7 @@ impl MenuPopup { /// Creates a new `MenuPopup` using the given menu tree. pub fn new(menu: Rc) -> Self { MenuPopup { - menu: menu, + menu, focus: 0, scrollbase: ScrollBase::new() .scrollbar_offset(1) diff --git a/src/views/mod.rs b/src/views/mod.rs index 4435d9d..e5b313b 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -78,7 +78,7 @@ pub use self::menu_popup::MenuPopup; pub use self::menubar::Menubar; pub use self::on_event_view::OnEventView; pub use self::panel::Panel; -pub use self::progress_bar::{Counter, ProgressBar}; +pub use self::progress_bar::ProgressBar; pub use self::radio::{RadioButton, RadioGroup}; pub use self::scroll_view::ScrollView; pub use self::select_view::SelectView; diff --git a/src/views/on_event_view.rs b/src/views/on_event_view.rs index e7a7e03..4db522a 100644 --- a/src/views/on_event_view.rs +++ b/src/views/on_event_view.rs @@ -72,7 +72,7 @@ impl OnEventView { /// Wraps the given view in a new OnEventView. pub fn new(view: T) -> Self { OnEventView { - view: view, + view, callbacks: HashMap::new(), } } diff --git a/src/views/panel.rs b/src/views/panel.rs index 2aa6ab3..9f00731 100644 --- a/src/views/panel.rs +++ b/src/views/panel.rs @@ -13,7 +13,7 @@ pub struct Panel { impl Panel { /// Creates a new panel around the given view. pub fn new(view: V) -> Self { - Panel { view: view } + Panel { view } } inner_getters!(self.view: V); diff --git a/src/views/progress_bar.rs b/src/views/progress_bar.rs index 781a652..e721923 100644 --- a/src/views/progress_bar.rs +++ b/src/views/progress_bar.rs @@ -1,40 +1,13 @@ use Printer; use align::HAlign; use std::cmp; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; use theme::{ColorStyle, Effect}; +use utils::Counter; use view::View; // pub type CbPromise = Option>; -/// Atomic counter used by `ProgressBar`. -#[derive(Clone)] -pub struct Counter(pub Arc); - -impl Counter { - /// Creates a new `Counter` starting with the given value. - pub fn new(value: usize) -> Self { - Counter(Arc::new(AtomicUsize::new(value))) - } - - /// Retrieves the current progress value. - pub fn get(&self) -> usize { - self.0.load(Ordering::Relaxed) - } - - /// Sets the current progress value. - pub fn set(&self, value: usize) { - self.0.store(value, Ordering::Relaxed); - } - - /// Increase the current progress by `ticks`. - pub fn tick(&self, ticks: usize) { - self.0.fetch_add(ticks, Ordering::Relaxed); - } -} - /// Animated bar showing a progress value. /// /// This bar has an internal counter, and adapts the length of the displayed diff --git a/src/views/radio.rs b/src/views/radio.rs index e88de26..68670ec 100644 --- a/src/views/radio.rs +++ b/src/views/radio.rs @@ -110,10 +110,10 @@ impl RadioButton { state: Rc>>, id: usize, label: String ) -> Self { RadioButton { - state: state, - id: id, + state, + id, enabled: true, - label: label, + label, } } diff --git a/src/views/select_view.rs b/src/views/select_view.rs index 92bb86d..8277149 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -22,7 +22,7 @@ use views::MenuPopup; /// /// # Examples /// -/// ```no_run +/// ```rust /// # extern crate cursive; /// # use cursive::Cursive; /// # use cursive::views::{SelectView, Dialog, TextView}; @@ -40,7 +40,7 @@ use views::MenuPopup; /// .button("Quit", |s| s.quit())); /// }); /// -/// let mut siv = Cursive::new(); +/// let mut siv = Cursive::dummy(); /// siv.add_layer(Dialog::around(time_select) /// .title("How long is your wait?")); /// # } @@ -262,6 +262,15 @@ impl SelectView { .unwrap_or_else(Callback::dummy) } + /// Inserts an item at position `index`, shifting all elements after it to + /// the right. + pub fn insert_item(&mut self, index: usize, label: S, value: T) + where + S: Into, + { + self.items.insert(index, Item::new(label.into(), value)); + } + /// Chainable variant of add_item pub fn item>(self, label: S, value: T) -> Self { self.with(|s| s.add_item(label, value)) @@ -334,7 +343,7 @@ impl SelectView { // TODO: Check if `i >= self.len()` ? // assert!(i < self.len(), "SelectView: trying to select out-of-bound"); // Or just cap the ID? - let i = if self.len() == 0 { + let i = if self.is_empty() { 0 } else { min(i, self.len() - 1) @@ -638,6 +647,12 @@ impl SelectView { self.with(|s| s.add_item_str(label)) } + /// Convenient method to use the label as value. + pub fn insert_item_str(&mut self, index: usize, label: S) where S: Into { + let label = label.into(); + self.insert_item(index, label.clone(), label); + } + /// Adds all strings from an iterator. /// /// # Examples diff --git a/src/views/text_area.rs b/src/views/text_area.rs index 9b96a55..9ea5be7 100644 --- a/src/views/text_area.rs +++ b/src/views/text_area.rs @@ -110,6 +110,37 @@ impl TextArea { self.with(|s| s.set_content(content)) } + /// Disables this view. + /// + /// A disabled view cannot be selected. + pub fn disable(&mut self) { + self.enabled = false; + } + + /// Disables this view. + /// + /// Chainable variant. + pub fn disabled(self) -> Self { + self.with(Self::disable) + } + + /// Re-enables this view. + pub fn enable(&mut self) { + self.enabled = true; + } + + /// Re-enables this view. + /// + /// Chainable variant. + pub fn enabled(self) -> Self { + self.with(Self::enable) + } + + /// Returns `true` if this view is enabled. + pub fn is_enabled(&self) -> bool { + self.enabled + } + /// Finds the row containing the grapheme at the given offset fn row_at(&self, offset: usize) -> usize { debug!("Offset: {}", offset); diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 7819442..128912d 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -155,10 +155,10 @@ impl TextContentInner { /// /// # Examples /// -/// ```rust,no_run +/// ```rust /// # use cursive::Cursive; /// # use cursive::views::TextView; -/// let mut siv = Cursive::new(); +/// let mut siv = Cursive::dummy(); /// /// siv.add_layer(TextView::new("Hello world!")); /// ``` diff --git a/src/with.rs b/src/with.rs index ba6df23..b183737 100644 --- a/src/with.rs +++ b/src/with.rs @@ -14,6 +14,17 @@ pub trait With: Sized { f(&mut self)?; Ok(self) } + + /// Calls the given closure if `condition == true`. + fn with_if(mut self, condition: bool, f: F) -> Self + where + F: FnOnce(&mut Self), + { + if condition { + f(&mut self); + } + self + } } impl With for T {} diff --git a/src/xy.rs b/src/xy.rs index 2b2bdca..ebfd6c8 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -13,7 +13,7 @@ pub struct XY { impl XY { /// Creates a new `XY` from the given values. pub fn new(x: T, y: T) -> Self { - XY { x: x, y: y } + XY { x, y } } /// Returns `f(self.x, self.y)`