diff --git a/Cargo.toml b/Cargo.toml index cf7eaed..e68d17c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ [package] authors = ["Alexandre Bury "] build = "build.rs" +categories = ["command-line-interface", "gui"] description = "A TUI (Text User Interface) library focused on ease-of-use." documentation = "https://gyscos.github.io/Cursive/cursive/index.html" exclude = ["doc", "assets"] keywords = ["ncurses", "TUI", "UI"] -categories = ["command-line-interface", "gui"] license = "MIT" name = "cursive" readme = "Readme.md" repository = "https://github.com/gyscos/Cursive" version = "0.4.1" -[badges] -travis-ci = { repository = "gyscos/Cursive" } +[badges.travis-ci] +repository = "gyscos/Cursive" [build-dependencies.skeptic] optional = true @@ -22,6 +22,7 @@ version = "0.7" [dependencies] num = "0.1" odds = "0.2" +owning_ref = "0.2.4" toml = "0.3" unicode-segmentation = "1.0" unicode-width = "0.1" diff --git a/doc/tutorial_2.md b/doc/tutorial_2.md index 6c3d846..5bc0ade 100644 --- a/doc/tutorial_2.md +++ b/doc/tutorial_2.md @@ -144,7 +144,7 @@ fn main() { siv.run(); } -fn show_next(s: &mut Cursive) { +fn show_next(_: &mut Cursive) { // Empty for now } ``` diff --git a/doc/tutorial_3.md b/doc/tutorial_3.md index 816f5b3..0065df2 100644 --- a/doc/tutorial_3.md +++ b/doc/tutorial_3.md @@ -40,7 +40,9 @@ fn main() { fn add_name(s: &mut Cursive) { fn ok(s: &mut Cursive, name: &str) { - s.find_id::>("select").unwrap().add_item_str(name); + s.find_id("select", |view: &mut SelectView| { + view.add_item_str(name) + }); s.pop_layer(); } @@ -51,19 +53,25 @@ fn add_name(s: &mut Cursive) { .title("Enter a new name") .button("Ok", |s| { let name = - s.find_id::("name").unwrap().get_content().clone(); + s.find_id("name", |view: &mut EditView| { + view.get_content().clone() + }).unwrap(); ok(s, &name); }) .button("Cancel", |s| s.pop_layer())); } fn delete_name(s: &mut Cursive) { - match s.find_id::>("select").unwrap().selected_id() { + let selection = s.find_id("select", |view: &mut SelectView| { + view.selected_id() + }).unwrap(); + + match selection { None => s.add_layer(Dialog::info("No name to remove")), Some(focus) => { - s.find_id::>("select") - .unwrap() - .remove_item(focus) + s.find_id("select", |view: &mut SelectView| { + view.remove_item(focus) + }); } } } @@ -224,8 +232,8 @@ Later, you can ask the Cursive root for this ID and get access to the view. Just what we need! Like `BoxView`, `IdView` can be used directly with [`IdView::new`], or through -the [`Identifiable`] trait. [`Cursive::find_id`] can then give you a mutable -reference to the view. +the [`Identifiable`] trait. [`Cursive::find_id`] allows you to run a closure +on the view. Here's what it looks like in action: @@ -236,8 +244,9 @@ fn add_name(s: &mut Cursive) { .fixed_width(10)) .title("Enter a new name") .button("Ok", |s| { - let name = - s.find_id::("name").unwrap().get_content().clone(); + let name = s.find_id("name", |view: &mut EditView| { + view.get_content().clone() + }).unwrap(); }) .button("Cancel", |s| s.pop_layer())); } @@ -263,7 +272,9 @@ That way, we can update it with a new item: ```rust,ignore fn add_name(s: &mut Cursive) { fn ok(s: &mut Cursive, name: &str) { - s.find_id::>("select").unwrap().add_item_str(name); + s.find_id("select", |view: &mut SelectView| { + view.add_item_str(name); + }); s.pop_layer(); } @@ -273,8 +284,9 @@ fn add_name(s: &mut Cursive) { .fixed_width(10)) .title("Enter a new name") .button("Ok", |s| { - let name = - s.find_id::("name").unwrap().get_content().clone(); + let name = s.find_id("name", |v: &mut EditView| { + v.get_content().clone() + }).unwrap(); ok(s, &name); }) .button("Cancel", |s| s.pop_layer())); @@ -286,13 +298,13 @@ complicated: ```rust,ignore fn delete_name(s: &mut Cursive) { - match s.find_id::>("select").unwrap().selected_id() { + match s.find_id("select", |v: &mut SelectView| { + v.selected_id() + }).unwrap() { None => s.add_layer(Dialog::info("No name to remove")), - Some(focus) => { - s.find_id::>("select") - .unwrap() - .remove_item(focus) - } + Some(focus) => s.find_id("select", |v: &mut SelectView| { + v.remove_item(focus) + }), } } ``` diff --git a/examples/edit.rs b/examples/edit.rs index d74d92b..ed36057 100644 --- a/examples/edit.rs +++ b/examples/edit.rs @@ -1,8 +1,8 @@ extern crate cursive; use cursive::Cursive; -use cursive::views::{Dialog, EditView, TextView}; use cursive::traits::*; +use cursive::views::{Dialog, EditView, TextView}; fn main() { let mut siv = Cursive::new(); @@ -18,9 +18,9 @@ fn main() { .with_id("name") .fixed_width(20)) .button("Ok", |s| { - let name = s.find_id::("name") - .unwrap() - .get_content(); + let name = + s.find_id("name", |view: &mut EditView| view.get_content()) + .unwrap(); show_popup(s, &name); })); diff --git a/examples/list_view.rs b/examples/list_view.rs index c18d985..e2a4ddf 100644 --- a/examples/list_view.rs +++ b/examples/list_view.rs @@ -1,9 +1,9 @@ extern crate cursive; use cursive::Cursive; +use cursive::traits::*; use cursive::views::{Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView, TextView}; -use cursive::traits::*; fn main() { let mut siv = Cursive::new(); @@ -25,12 +25,13 @@ fn main() { .with_id("email2") .fixed_width(10))) .child("Receive spam?", - Checkbox::new().on_change(|s, checked| { - for name in &["email1", "email2"] { - let view: &mut EditView = s.find_id(name).unwrap(); - view.set_enabled(checked); - } - })) + Checkbox::new() + .on_change(|s, checked| for name in &["email1", + "email2"] { + s.find_id(name, |view: &mut EditView| { + view.set_enabled(checked) + }); + })) .delimiter() .child("Age", SelectView::new() @@ -39,10 +40,8 @@ fn main() { .item_str("19-30") .item_str("31-40") .item_str("41+")) - .with(|list| { - for i in 0..50 { - list.add_child(&format!("Item {}", i), EditView::new()); - } + .with(|list| for i in 0..50 { + list.add_child(&format!("Item {}", i), EditView::new()); }))); siv.run(); diff --git a/examples/mutation.rs b/examples/mutation.rs index 31f8348..52ae466 100644 --- a/examples/mutation.rs +++ b/examples/mutation.rs @@ -13,9 +13,10 @@ fn show_popup(siv: &mut Cursive) { Dialog::around(TextView::new("Tak!")) .button("Change", |s| { // Look for a view tagged "text". We _know_ it's there, so unwrap it. - let view = s.find_id::("text").unwrap(); - let content: String = view.get_content().chars().rev().collect(); - view.set_content(content); + s.find_id("text", |view: &mut TextView| { + let content: String = view.get_content().chars().rev().collect(); + view.set_content(content); + }); }) .dismiss_button("Ok")); diff --git a/examples/refcell_view.rs b/examples/refcell_view.rs new file mode 100644 index 0000000..593d928 --- /dev/null +++ b/examples/refcell_view.rs @@ -0,0 +1,39 @@ +extern crate cursive; + +use cursive::Cursive; +use cursive::view::{Boxable, Identifiable}; +use cursive::views::{LinearLayout, EditView, TextView, Dialog}; + +fn main() { + let mut siv = Cursive::new(); + + // Create a dialog with 2 edit fields, and a text view. + // The text view indicates when the 2 fields content match. + siv.add_layer(Dialog::around(LinearLayout::vertical() + .child(EditView::new().on_edit(on_edit).with_id_mut("1")) + .child(EditView::new().on_edit(on_edit).with_id_mut("2")) + .child(TextView::new("match").with_id_mut("match")) + .fixed_width(10)) + .button("Quit", Cursive::quit)); + + siv.run(); +} + +// Compare the content of the two edit views, +// and update the TextView accordingly. +// +// We'll ignore the `content` and `cursor` arguments, +// and directly retrieve the content from the `Cursive` root. +fn on_edit(siv: &mut Cursive, _content: &str, _cursor: usize) { + // Get handles for each view. + let edit_1 = siv.find_id_mut::("1").unwrap(); + let edit_2 = siv.find_id_mut::("2").unwrap(); + + // Directly compare references to edit_1 and edit_2. + let matches = edit_1.get_content() == edit_2.get_content(); + siv.find_id("match", |v: &mut TextView| v.set_content(if matches { + "match" + } else { + "no match" + })); +} diff --git a/examples/slider.rs b/examples/slider.rs index 700bac2..dada9db 100644 --- a/examples/slider.rs +++ b/examples/slider.rs @@ -1,8 +1,8 @@ extern crate cursive; use cursive::Cursive; -use cursive::views::{Dialog, SliderView}; use cursive::traits::*; +use cursive::views::{Dialog, SliderView}; fn main() { let mut siv = Cursive::new(); @@ -14,7 +14,7 @@ fn main() { .value(7) .on_change(|s, v| { let title = format!("[ {} ]", v); - s.find_id::("dialog").unwrap().set_title(title); + s.find_id("dialog", |view: &mut Dialog| view.set_title(title)); }) .on_enter(|s, v| { s.pop_layer(); diff --git a/src/backend/blt.rs b/src/backend/blt.rs index 0eead9f..d8c021c 100644 --- a/src/backend/blt.rs +++ b/src/backend/blt.rs @@ -1,12 +1,12 @@ extern crate bear_lib_terminal; -use ::backend; -use ::event::{Event, Key}; use self::bear_lib_terminal::Color as BltColor; use self::bear_lib_terminal::geometry::Size; use self::bear_lib_terminal::terminal::{self, Event as BltEvent, KeyCode}; +use backend; +use event::{Event, Key}; use std::collections::BTreeMap; -use ::theme::{BaseColor, Color, ColorStyle, Effect}; +use theme::{BaseColor, Color, ColorStyle, Effect}; pub struct Concrete { colours: BTreeMap, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index d427591..f8289e1 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -8,12 +8,12 @@ mod blt; #[cfg(any(feature = "ncurses", feature = "pancurses"))] mod curses; +#[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::*; -#[cfg(feature = "bear-lib-terminal")] -pub use self::blt::*; pub trait Backend { fn init() -> Self; diff --git a/src/direction.rs b/src/direction.rs index 258674a..d882d54 100644 --- a/src/direction.rs +++ b/src/direction.rs @@ -15,8 +15,8 @@ //! * Relative direction: front or back. //! Its actual direction depends on the orientation. -use vec::Vec2; use XY; +use vec::Vec2; /// Describes a vertical or horizontal orientation for a view. #[derive(Clone,Copy,Debug,PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index a6a721c..f9a9028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,11 +64,13 @@ extern crate unicode_segmentation; extern crate unicode_width; extern crate odds; extern crate num; +extern crate owning_ref; #[cfg(feature = "termion")] #[macro_use] extern crate chan; + macro_rules! println_stderr( ($($arg:tt)*) => { { use ::std::io::Write; @@ -365,8 +367,11 @@ impl Cursive { /// Tries to find the view pointed to by the given selector. /// + /// Runs a closure on the view once it's found, and return the + /// result. + /// /// If the view is not found, or if it is not of the asked type, - /// it returns None. + /// returns None. /// /// # Examples /// @@ -381,15 +386,18 @@ impl Cursive { /// .with_id("text")); /// /// siv.add_global_callback('p', |s| { - /// s.find::(&view::Selector::Id("text")) - /// .unwrap() - /// .set_content("Text #2"); + /// s.find(&view::Selector::Id("text"), |view: &mut views::TextView| { + /// view.set_content("Text #2"); + /// }); /// }); /// # } /// ``` - pub fn find(&mut self, sel: &view::Selector) - -> Option<&mut V> { - self.screen_mut().find(sel) + pub fn find(&mut self, sel: &view::Selector, callback: F) + -> Option + where V: View + Any, + F: FnOnce(&mut V) -> R + { + self.screen_mut().find(sel, callback) } /// Tries to find the view identified by the given id. @@ -409,14 +417,29 @@ impl Cursive { /// .with_id("text")); /// /// siv.add_global_callback('p', |s| { - /// s.find_id::("text") - /// .unwrap() - /// .set_content("Text #2"); + /// s.find_id("text", |view: &mut views::TextView| { + /// view.set_content("Text #2"); + /// }); /// }); /// # } /// ``` - pub fn find_id(&mut self, id: &str) -> Option<&mut V> { - self.find(&view::Selector::Id(id)) + pub fn find_id(&mut self, id: &str, callback: F) -> Option + where V: View + Any, + F: FnOnce(&mut V) -> R + { + self.find(&view::Selector::Id(id), callback) + } + + /// Convenient method to find a view wrapped in [`RefCellView`]. + /// + /// This looks for a `RefCellView` with the given ID, and return + /// a mutable reference to the wrapped view. + /// + /// [`RefCellView`]: views/struct.RefCellView.html + pub fn find_id_mut(&mut self, id: &str) -> Option> + where V: View + Any + { + self.find_id(id, views::RefCellView::::get_mut) } /// Moves the focus to the view identified by `id`. diff --git a/src/menu.rs b/src/menu.rs index 37425af..a9fc00d 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -165,13 +165,13 @@ impl MenuTree { /// Returns `None` if the given title was not found, /// or if it wasn't a subtree. pub fn find_subtree(&mut self, title: &str) -> Option<&mut MenuTree> { - self.find_item(title).and_then(|item| { - if let MenuItem::Subtree(_, ref mut tree) = *item { + self.find_item(title) + .and_then(|item| if let MenuItem::Subtree(_, ref mut tree) = + *item { Some(Rc::make_mut(tree)) } else { None - } - }) + }) } /// Removes the item at the given position. @@ -191,5 +191,4 @@ impl MenuTree { pub fn is_empty(&self) -> bool { self.children.is_empty() } - } diff --git a/src/printer.rs b/src/printer.rs index d9d63e4..d8e5f55 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -34,7 +34,9 @@ impl<'a> Printer<'a> { /// /// But nobody needs to know that. #[doc(hidden)] - pub fn new>(size: T, theme: Theme, backend: &'a backend::Concrete) -> Self { + pub fn new>(size: T, theme: Theme, + backend: &'a backend::Concrete) + -> Self { Printer { offset: Vec2::zero(), size: size.into(), diff --git a/src/theme.rs b/src/theme.rs index 6e63f9e..5b4f0e4 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -129,7 +129,7 @@ pub enum Effect { /// No effect Simple, /// Reverses foreground and background colors - Reverse, + Reverse, // TODO: bold, italic, underline } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 71dc16c..6b33050 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -14,7 +14,7 @@ pub struct Prefix { /// The length (in bytes) of the string. pub length: usize, /// The unicode-width of the string. - pub width: usize + pub width: usize, } /// Computes the length (number of bytes) and width of a prefix that fits in the given `width`. @@ -42,7 +42,8 @@ pub struct Prefix { /// prefix(my_text.graphemes(true), 5, ""); /// # } /// ``` -pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix +pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) + -> Prefix where I: Iterator { let delimiter_width = delimiter.width(); @@ -74,7 +75,7 @@ pub fn prefix<'a, I>(iter: I, available_width: usize, delimiter: &str) -> Prefix Prefix { length: length, - width: current_width + width: current_width, } } diff --git a/src/vec.rs b/src/vec.rs index be4c894..4a8c316 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -95,7 +95,7 @@ impl XY { } } -impl >> From for XY { +impl>> From for XY { fn from(t: T) -> Self { let other = t.into(); Self::new(other.x as isize, other.y as isize) @@ -115,7 +115,7 @@ impl From<(u32, u32)> for XY { } -impl, O: Into>> Add for XY { +impl, O: Into>> Add for XY { type Output = Self; fn add(self, other: O) -> Self { @@ -123,7 +123,7 @@ impl, O: Into>> Add for XY { } } -impl, O: Into>> Sub for XY { +impl, O: Into>> Sub for XY { type Output = Self; fn sub(self, other: O) -> Self { @@ -131,7 +131,7 @@ impl, O: Into>> Sub for XY { } } -impl > Div for XY { +impl> Div for XY { type Output = Self; fn div(self, other: T) -> Self { diff --git a/src/view/identifiable.rs b/src/view/identifiable.rs index 162ab15..90fdbb8 100644 --- a/src/view/identifiable.rs +++ b/src/view/identifiable.rs @@ -1,16 +1,26 @@ use view::View; -use views::IdView; +use views::{IdView, RefCellView}; /// Makes a view wrappable in an [`IdView`]. /// /// [`IdView`]: ../views/struct.IdView.html pub trait Identifiable: View + Sized { - /// Wraps this view into an IdView with the given id. + /// Wraps this view into an `IdView` with the given id. /// /// This is just a shortcut for `IdView::new(id, self)` fn with_id(self, id: &str) -> IdView { IdView::new(id, self) } + + /// Wraps this view into both a [`RefCellView`] and an `IdView`. + /// + /// This allows to call [`Cursive::find_id_mut`]. + /// + /// [`RefCellView`]: ../views/struct.RefCellView.html + /// [`Cursive::find_id_mut`]: ../struct.Cursive.html#method.find_id_mut + fn with_id_mut(self, id: &str) -> IdView> { + RefCellView::new(self).with_id(id) + } } /// Any `View` implements this trait. diff --git a/src/view/mod.rs b/src/view/mod.rs index 29f499a..36e6b59 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -50,10 +50,6 @@ mod identifiable; mod boxable; -use Printer; - -use direction::Direction; -use event::{Event, EventResult}; pub use self::boxable::Boxable; pub use self::identifiable::Identifiable; @@ -65,9 +61,14 @@ pub use self::size_cache::SizeCache; pub use self::size_constraint::SizeConstraint; pub use self::view_path::ViewPath; pub use self::view_wrapper::ViewWrapper; -use std::any::Any; -use vec::Vec2; +use Printer; +use direction::Direction; +use event::{Event, EventResult}; +use vec::Vec2; +use views::RefCellView; + +use std::any::Any; /// Main trait defining a view behaviour. pub trait View { @@ -122,8 +123,8 @@ pub trait View { /// Returns None if the path doesn't lead to a view. /// /// Default implementation always return `None`. - fn find_any(&mut self, &Selector) -> Option<&mut Any> { - None + fn find_any<'a>(&mut self, _: &Selector, _: Box) { + // TODO: FnMut -> FnOnce once it works } /// Moves the focus to the view identified by the given selector. @@ -157,17 +158,41 @@ pub trait Finder { /// /// If the view is not found, or if it is not of the asked type, /// it returns None. - fn find(&mut self, sel: &Selector) -> Option<&mut V>; + fn find(&mut self, sel: &Selector, callback: F) -> Option + where V: View + Any, + F: FnOnce(&mut V) -> R; /// Convenient method to use `find` with a `view::Selector::Id`. - fn find_id(&mut self, id: &str) -> Option<&mut V> { - self.find(&Selector::Id(id)) + fn find_id(&mut self, id: &str, callback: F) -> Option + where V: View + Any, + F: FnOnce(&mut V) -> R + { + self.find(&Selector::Id(id), callback) } } impl Finder for T { - fn find(&mut self, sel: &Selector) -> Option<&mut V> { - self.find_any(sel).and_then(|b| b.downcast_mut::()) + fn find(&mut self, sel: &Selector, callback: F) -> Option + where V: View + Any, + F: FnOnce(&mut V) -> R + { + let mut result = None; + { + let result_ref = &mut result; + + let mut callback = Some(callback); + let callback = |v: &mut Any| if let Some(callback) = + callback.take() { + if v.is::() { + *result_ref = v.downcast_mut::().map(|v| callback(v)); + } else if v.is::>() { + *result_ref = v.downcast_mut::>() + .and_then(|v| v.with_view_mut(callback)); + } + }; + self.find_any(sel, Box::new(callback)); + } + result } } diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index 879f887..07d19d6 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -11,8 +11,8 @@ use view::{Selector, View}; /// Default implementation forwards all calls to the child view. /// Overrides some methods as desired. /// -/// You can use the [`wrap_impl!`] macro to define `get_view` and -/// `get_view_mut` for you. +/// You can use the [`wrap_impl!`] macro to define `with_view` and +/// `with_view_mut` for you. /// /// [`wrap_impl!`]: ../macro.wrap_impl.html pub trait ViewWrapper { @@ -20,49 +20,51 @@ pub trait ViewWrapper { type V: View; /// Get an immutable reference to the wrapped view. - fn get_view(&self) -> &Self::V; + fn with_view(&self, f: F) -> Option where F: FnOnce(&Self::V) -> R; /// Get a mutable reference to the wrapped view. - fn get_view_mut(&mut self) -> &mut Self::V; + fn with_view_mut(&mut self, f: F) -> Option + where F: FnOnce(&mut Self::V) -> R; /// Wraps the `draw` method. fn wrap_draw(&self, printer: &Printer) { - self.get_view().draw(printer); + self.with_view(|v| v.draw(printer)); } /// Wraps the `required_size` method. fn wrap_required_size(&mut self, req: Vec2) -> Vec2 { - self.get_view_mut().required_size(req) + self.with_view_mut(|v| v.required_size(req)).unwrap_or_else(Vec2::zero) } /// Wraps the `on_event` method. fn wrap_on_event(&mut self, ch: Event) -> EventResult { - self.get_view_mut().on_event(ch) + self.with_view_mut(|v| v.on_event(ch)).unwrap_or(EventResult::Ignored) } /// Wraps the `layout` method. fn wrap_layout(&mut self, size: Vec2) { - self.get_view_mut().layout(size); + self.with_view_mut(|v| v.layout(size)); } /// Wraps the `take_focus` method. fn wrap_take_focus(&mut self, source: Direction) -> bool { - self.get_view_mut().take_focus(source) + self.with_view_mut(|v| v.take_focus(source)).unwrap_or(false) } /// Wraps the `find` method. - fn wrap_find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.get_view_mut().find_any(selector) + fn wrap_find_any<'a>(&mut self, selector: &Selector, + callback: Box) { + self.with_view_mut(|v| v.find_any(selector, callback)); } /// Wraps the `focus_view` method. fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> { - self.get_view_mut().focus_view(selector) + self.with_view_mut(|v| v.focus_view(selector)).unwrap_or(Err(())) } /// Wraps the `needs_relayout` method. fn wrap_needs_relayout(&self) -> bool { - self.get_view().needs_relayout() + self.with_view(|v| v.needs_relayout()).unwrap_or(true) } } @@ -87,8 +89,9 @@ impl View for T { self.wrap_take_focus(source) } - fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.wrap_find_any(selector) + fn find_any<'a>(&mut self, selector: &Selector, + callback: Box) { + self.wrap_find_any(selector, callback) } fn needs_relayout(&self) -> bool { @@ -102,7 +105,7 @@ impl View for T { /// Convenient macro to implement the [`ViewWrapper`] trait. /// -/// It defines the `get_view` and `get_view_mut` implementations, +/// It defines the `with_view` and `with_view_mut` implementations, /// as well as the `type V` declaration. /// /// [`ViewWrapper`]: view/trait.ViewWrapper.html @@ -126,12 +129,16 @@ macro_rules! wrap_impl { (self.$v:ident: $t:ty) => { type V = $t; - fn get_view(&self) -> &Self::V { - &self.$v + fn with_view(&self, f: F) -> Option + where F: FnOnce(&Self::V) -> R + { + Some(f(&self.$v)) } - fn get_view_mut(&mut self) -> &mut Self::V { - &mut self.$v + fn with_view_mut(&mut self, f: F) -> Option + where F: FnOnce(&mut Self::V) -> R + { + Some(f(&mut self.$v)) } }; } diff --git a/src/views/checkbox.rs b/src/views/checkbox.rs index 2100946..7924e24 100644 --- a/src/views/checkbox.rs +++ b/src/views/checkbox.rs @@ -61,9 +61,7 @@ impl Checkbox { /// /// Chainable variant. pub fn checked(self) -> Self { - self.with(|s| { - s.check(); - }) + self.with(|s| { s.check(); }) } /// Returns `true` if the checkbox is checked. @@ -80,9 +78,7 @@ impl Checkbox { /// /// Chainable variant. pub fn unchecked(self) -> Self { - self.with(|s| { - s.uncheck(); - }) + self.with(|s| { s.uncheck(); }) } /// Sets the checkbox state. diff --git a/src/views/dialog.rs b/src/views/dialog.rs index 9f79b00..3d97afd 100644 --- a/src/views/dialog.rs +++ b/src/views/dialog.rs @@ -399,8 +399,9 @@ impl View for Dialog { } } - fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.content.find_any(selector) + fn find_any<'a>(&mut self, selector: &Selector, + callback: Box) { + self.content.find_any(selector, callback); } fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 9afa540..7683a5f 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -41,9 +41,8 @@ use view::View; /// .with_id("name") /// .fixed_width(20)) /// .button("Ok", |s| { -/// let name = s.find_id::("name") -/// .unwrap() -/// .get_content(); +/// let name = s.find_id("name", |view: &mut EditView| view.get_content()) +/// .unwrap(); /// show_popup(s, &name); /// })); /// diff --git a/src/views/id_view.rs b/src/views/id_view.rs index e26baf3..21302eb 100644 --- a/src/views/id_view.rs +++ b/src/views/id_view.rs @@ -21,10 +21,11 @@ impl IdView { impl ViewWrapper for IdView { wrap_impl!(self.view: T); - fn wrap_find_any(&mut self, selector: &Selector) -> Option<&mut Any> { + fn wrap_find_any<'a>(&mut self, selector: &Selector, + mut callback: Box) { match selector { - &Selector::Id(id) if id == self.id => Some(&mut self.view), - s => self.view.find_any(s), + &Selector::Id(id) if id == self.id => callback(&mut self.view), + s => self.view.find_any(s, callback), } } diff --git a/src/views/layer.rs b/src/views/layer.rs index 8370cb6..542d7ef 100644 --- a/src/views/layer.rs +++ b/src/views/layer.rs @@ -10,12 +10,10 @@ pub struct Layer { view: T, } -impl Layer { +impl Layer { /// Wraps the given view. pub fn new(view: T) -> Self { - Layer { - view: view, - } + Layer { view: view } } } diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 8519c0c..0e44298 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -395,11 +395,11 @@ impl View for LinearLayout { } } - fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.children - .iter_mut() - .filter_map(|c| c.view.find_any(selector)) - .next() + fn find_any<'a>(&mut self, selector: &Selector, + mut callback: Box) { + for child in &mut self.children { + child.view.find_any(selector, Box::new(|any| callback(any))); + } } fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { diff --git a/src/views/list_view.rs b/src/views/list_view.rs index ad94fb8..48a3fa3 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -191,7 +191,6 @@ impl View for ListView { .unwrap_or(0) + 1; // println_stderr!("Offset: {}", offset); - self.scrollbase.draw(printer, |printer, i| match self.children[i] { Child::Row(ref label, ref view) => { printer.print((0, 0), label); @@ -314,12 +313,13 @@ impl View for ListView { true } - fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.children + fn find_any<'a>(&mut self, selector: &Selector, + mut callback: Box) { + for view in self.children .iter_mut() - .filter_map(Child::view) - .filter_map(|v| v.find_any(selector)) - .next() + .filter_map(Child::view) { + view.find_any(selector, Box::new(|any| callback(any))); + } } fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { diff --git a/src/views/menu_popup.rs b/src/views/menu_popup.rs index 6beb6ca..3412cf9 100644 --- a/src/views/menu_popup.rs +++ b/src/views/menu_popup.rs @@ -228,12 +228,9 @@ impl View for MenuPopup { Event::Key(Key::PageDown) => self.scroll_down(5, false), Event::Key(Key::Home) => self.focus = 0, - Event::Key(Key::End) => { - self.focus = self.menu.children.len() - 1 - } + Event::Key(Key::End) => self.focus = self.menu.children.len() - 1, - Event::Key(Key::Right) if self.menu.children - [self.focus] + Event::Key(Key::Right) if self.menu.children[self.focus] .is_subtree() => { return match self.menu.children[self.focus] { MenuItem::Subtree(_, ref tree) => { @@ -243,8 +240,7 @@ impl View for MenuPopup { }; } - Event::Key(Key::Enter) if !self.menu.children - [self.focus] + Event::Key(Key::Enter) if !self.menu.children[self.focus] .is_delimiter() => { return match self.menu.children[self.focus] { MenuItem::Leaf(_, ref cb) => { diff --git a/src/views/menubar.rs b/src/views/menubar.rs index afe1469..c3fdb40 100644 --- a/src/views/menubar.rs +++ b/src/views/menubar.rs @@ -83,7 +83,7 @@ impl Menubar { /// Insert a new item at the given position. pub fn insert_subtree(&mut self, i: usize, title: &str, menu: MenuTree) - -> &mut Self { + -> &mut Self { self.menus.insert(i, (title.to_string(), Rc::new(menu))); self } diff --git a/src/views/mod.rs b/src/views/mod.rs index 074de54..0a01478 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -52,6 +52,7 @@ mod menu_popup; mod panel; mod progress_bar; mod radio; +mod refcell_view; mod select_view; mod slider_view; mod shadow_view; @@ -78,6 +79,7 @@ pub use self::menubar::Menubar; pub use self::panel::Panel; pub use self::progress_bar::{Counter, ProgressBar}; pub use self::radio::{RadioGroup, RadioButton}; +pub use self::refcell_view::{RefCellView, ViewRef}; pub use self::select_view::SelectView; pub use self::shadow_view::ShadowView; pub use self::sized_view::SizedView; diff --git a/src/views/progress_bar.rs b/src/views/progress_bar.rs index fb53cf4..82e9fd3 100644 --- a/src/views/progress_bar.rs +++ b/src/views/progress_bar.rs @@ -118,9 +118,7 @@ impl ProgressBar { pub fn start(&mut self, f: F) { let counter: Counter = self.value.clone(); - thread::spawn(move || { - f(counter); - }); + thread::spawn(move || { f(counter); }); } /// Starts a function in a separate thread, and monitor the progress. diff --git a/src/views/refcell_view.rs b/src/views/refcell_view.rs new file mode 100644 index 0000000..64f31d5 --- /dev/null +++ b/src/views/refcell_view.rs @@ -0,0 +1,49 @@ + + +use owning_ref::{RcRef, OwningHandle}; + +use std::cell::{RefCell, RefMut}; +use std::rc::Rc; +use view::{View, ViewWrapper}; + +/// Wrapper around a view to provide interior mutability. +pub struct RefCellView { + view: Rc>, +} + +/// Mutable reference to a view. +pub type ViewRef = OwningHandle>, RefMut<'static, V>>; + +impl RefCellView { + /// Wraps `view` in a new `RefCellView`. + pub fn new(view: V) -> Self { + RefCellView { view: Rc::new(RefCell::new(view)) } + } + + /// Gets mutable access to the inner view. + pub fn get_mut(&mut self) -> ViewRef { + // TODO: return a standalone item (not tied to our lifetime) + // that bundles `self.view.clone()` and allow mutable reference to + // the inner view. + let cell_ref = RcRef::new(self.view.clone()); + + OwningHandle::new(cell_ref, + |x| unsafe { x.as_ref() }.unwrap().borrow_mut()) + } +} + +impl ViewWrapper for RefCellView { + type V = T; + + fn with_view(&self, f: F) -> Option + where F: FnOnce(&Self::V) -> R + { + self.view.try_borrow().ok().map(|v| f(&*v)) + } + + fn with_view_mut(&mut self, f: F) -> Option + where F: FnOnce(&mut Self::V) -> R + { + self.view.try_borrow_mut().ok().map(|mut v| f(&mut *v)) + } +} diff --git a/src/views/slider_view.rs b/src/views/slider_view.rs index 235f7b6..3225e45 100644 --- a/src/views/slider_view.rs +++ b/src/views/slider_view.rs @@ -55,9 +55,7 @@ impl SliderView { /// /// Chainable variant. pub fn value(self, value: usize) -> Self { - self.with(|s| { - s.set_value(value); - }) + self.with(|s| { s.set_value(value); }) } /// Sets a callback to be called when the slider is moved. @@ -79,9 +77,7 @@ impl SliderView { fn get_change_result(&self) -> EventResult { EventResult::Consumed(self.on_change.clone().map(|cb| { let value = self.value; - Callback::from_fn(move |s| { - cb(s, value); - }) + Callback::from_fn(move |s| { cb(s, value); }) })) } @@ -148,9 +144,7 @@ impl View for SliderView { Event::Key(Key::Enter) if self.on_enter.is_some() => { let value = self.value; let cb = self.on_enter.clone().unwrap(); - EventResult::with_cb(move |s| { - cb(s, value); - }) + EventResult::with_cb(move |s| { cb(s, value); }) } _ => EventResult::Ignored, } diff --git a/src/views/stack_view.rs b/src/views/stack_view.rs index b79f121..2806e15 100644 --- a/src/views/stack_view.rs +++ b/src/views/stack_view.rs @@ -180,11 +180,11 @@ impl View for StackView { } } - fn find_any(&mut self, selector: &Selector) -> Option<&mut Any> { - self.layers - .iter_mut() - .filter_map(|l| l.view.find_any(selector)) - .next() + fn find_any<'a>(&mut self, selector: &Selector, + mut callback: Box) { + for layer in &mut self.layers { + layer.view.find_any(selector, Box::new(|any| callback(any))); + } } fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { diff --git a/src/views/text_area.rs b/src/views/text_area.rs index 5536e86..633a9cb 100644 --- a/src/views/text_area.rs +++ b/src/views/text_area.rs @@ -375,11 +375,10 @@ impl View for TextArea { } else { printer.size.x }; - printer.with_effect(effect, |printer| { - for y in 0..printer.size.y { - printer.print_hline((0, y), w, " "); - } - }); + printer.with_effect(effect, + |printer| for y in 0..printer.size.y { + printer.print_hline((0, y), w, " "); + }); // println_stderr!("Content: `{}`", &self.content); self.scrollbase.draw(printer, |printer, i| { diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 9a88578..0d49de2 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -53,6 +53,11 @@ impl TextView { } } + /// Creates a new empty `TextView`. + pub fn empty() -> Self { + TextView::new("") + } + /// Enable or disable the view's scrolling capabilities. /// /// When disabled, the view will never attempt to scroll @@ -98,6 +103,13 @@ impl TextView { self } + /// Replace the text in this view. + /// + /// Chainable variant. + pub fn content>(self, content: S) -> Self { + self.with(|s| s.set_content(content)) + } + /// Replace the text in this view. pub fn set_content>(&mut self, content: S) { let content = content.into();