From b63762d44166ad87d449bc2feee957c3702e8964 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Mon, 6 Feb 2017 18:18:17 -0800 Subject: [PATCH 1/8] View::find_any now takes a closure instead of returning reference Also updated Finder::find and find_id. --- doc/tutorial_2.md | 2 +- doc/tutorial_3.md | 50 +++++++++++++++++++++++--------------- examples/edit.rs | 8 +++--- examples/list_view.rs | 21 ++++++++-------- examples/mutation.rs | 7 +++--- examples/slider.rs | 4 +-- src/backend/blt.rs | 6 ++--- src/backend/curses/n.rs | 4 +-- src/backend/curses/pan.rs | 14 +++++++---- src/backend/mod.rs | 4 +-- src/backend/termion.rs | 8 +++--- src/direction.rs | 2 +- src/lib.rs | 34 +++++++++++++++++--------- src/menu.rs | 9 +++---- src/printer.rs | 4 ++- src/theme.rs | 2 +- src/utils/mod.rs | 7 +++--- src/vec.rs | 8 +++--- src/view/mod.rs | 44 ++++++++++++++++++++++++--------- src/view/view_wrapper.rs | 43 +++++++++++++++++--------------- src/views/checkbox.rs | 8 ++---- src/views/dialog.rs | 5 ++-- src/views/edit_view.rs | 24 ++++++++---------- src/views/id_view.rs | 7 +++--- src/views/layer.rs | 6 ++--- src/views/linear_layout.rs | 10 ++++---- src/views/list_view.rs | 43 ++++++++++++++++---------------- src/views/menu_popup.rs | 10 +++----- src/views/menubar.rs | 2 +- src/views/progress_bar.rs | 4 +-- src/views/slider_view.rs | 12 +++------ src/views/stack_view.rs | 10 ++++---- src/views/text_area.rs | 9 +++---- src/xy.rs | 2 +- 34 files changed, 233 insertions(+), 200 deletions(-) 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/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/curses/n.rs b/src/backend/curses/n.rs index 8e568ce..256fc3d 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,9 +1,9 @@ extern crate ncurses; -use backend; -use event::{Event, Key}; use self::super::find_closest; +use backend; +use event::{Event, Key}; use theme::{Color, ColorStyle, Effect}; use utf8; diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 25a8ba4..65d0d55 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -1,10 +1,10 @@ extern crate pancurses; -use backend; -use event::{Event, Key}; use self::super::find_closest; +use backend; +use event::{Event, Key}; use std::cell::Cell; use theme::{Color, ColorStyle, Effect}; use utf8; @@ -23,7 +23,8 @@ impl backend::Backend for Concrete { pancurses::cbreak(); pancurses::start_color(); pancurses::curs_set(0); - window.bkgd(pancurses::COLOR_PAIR(ColorStyle::Background.id() as pancurses::chtype)); + window.bkgd(pancurses::COLOR_PAIR(ColorStyle::Background.id() as + pancurses::chtype)); Concrete { window: window, @@ -66,7 +67,8 @@ impl backend::Backend for Concrete { self.current_style.set(current_style); // self.window.attroff(style); - self.window.attron(pancurses::COLOR_PAIR(current_style.id() as pancurses::chtype)); + self.window.attron(pancurses::COLOR_PAIR(current_style.id() as + pancurses::chtype)); } fn with_effect(&self, effect: Effect, f: F) { @@ -101,7 +103,9 @@ impl backend::Backend for Concrete { // TODO: wait for a very short delay. If more keys are // pipelined, it may be an escape sequence. pancurses::Input::Character('\u{7f}') | - pancurses::Input::Character('\u{8}') => Event::Key(Key::Backspace), + pancurses::Input::Character('\u{8}') => { + Event::Key(Key::Backspace) + } pancurses::Input::Character('\u{9}') => Event::Key(Key::Tab), pancurses::Input::Character('\u{1b}') => Event::Key(Key::Esc), pancurses::Input::Character(c) if 32 <= (c as u32) && 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/backend/termion.rs b/src/backend/termion.rs index f77c0f7..bb15999 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -2,14 +2,14 @@ extern crate termion; extern crate chan_signal; -use ::backend; -use chan; -use ::event::{Event, Key}; use self::termion::color as tcolor; use self::termion::event::Key as TKey; use self::termion::input::TermRead; use self::termion::raw::IntoRawMode; use self::termion::style as tstyle; +use backend; +use chan; +use event::{Event, Key}; use std::cell::Cell; use std::collections::BTreeMap; use std::fmt; @@ -17,7 +17,7 @@ use std::io::Write; use std::thread; use std::time; -use ::theme; +use theme; pub struct Concrete { terminal: termion::raw::RawTerminal<::std::io::Stdout>, 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 e6cefe4..3c868ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ extern crate num; #[macro_use] extern crate chan; + macro_rules! println_stderr( ($($arg:tt)*) => { { use ::std::io::Write; @@ -364,8 +365,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 /// @@ -380,15 +384,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) } /// Convenient method to use `find` with a `view::Selector::Id`. @@ -406,14 +413,17 @@ 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) } /// Adds a global callback. 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 36c41de..44a6943 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 c7f4f47..527b991 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(); @@ -70,7 +71,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/mod.rs b/src/view/mod.rs index 5614c0d..51481ab 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,6 +61,10 @@ 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 Printer; + +use direction::Direction; +use event::{Event, EventResult}; use std::any::Any; use vec::Vec2; @@ -122,8 +122,9 @@ 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 } /// This view is offered focus. Will it take it? @@ -150,17 +151,38 @@ 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 let Some(v) = v.downcast_mut::() { + *result_ref = Some(callback(v)); + } + }; + self.find_any(sel, Box::new(callback)); + } + result } } diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index b981bbe..457f03a 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,44 +20,46 @@ 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) -> R 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) -> R + 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)) } /// 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)) } /// 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)) } /// 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 `needs_relayout` method. fn wrap_needs_relayout(&self) -> bool { - self.get_view().needs_relayout() + self.with_view(|v| v.needs_relayout()) } } @@ -82,8 +84,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 { @@ -93,7 +96,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 @@ -117,12 +120,14 @@ 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) -> R where F: FnOnce(&Self::V) -> R { + f(&self.$v) } - fn get_view_mut(&mut self) -> &mut Self::V { - &mut self.$v + fn with_view_mut(&mut self, f: F) -> R + where F: FnOnce(&mut Self::V) -> R + { + 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 8f3a04c..325657f 100644 --- a/src/views/dialog.rs +++ b/src/views/dialog.rs @@ -399,7 +399,8 @@ 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); } } diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 734ef28..80a9b73 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -40,9 +40,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); /// })); /// @@ -239,9 +238,10 @@ impl EditView { // Look at the content before the cursor (we will print its tail). // From the end, count the length until we reach `available`. // Then sum the byte lengths. - let suffix_length = - simple_suffix(&self.content[self.offset..self.cursor], - available).length; + let suffix_length = simple_suffix(&self.content[self.offset.. + self.cursor], + available) + .length; self.offset = self.cursor - suffix_length; // Make sure the cursor is in view assert!(self.cursor >= self.offset); @@ -250,8 +250,8 @@ impl EditView { // If we have too much space if self.content[self.offset..].width() < self.last_length { - let suffix_length = simple_suffix(&self.content, - self.last_length - 1).length; + let suffix_length = + simple_suffix(&self.content, self.last_length - 1).length; self.offset = self.content.len() - suffix_length; } } @@ -392,9 +392,7 @@ impl View for EditView { Event::Key(Key::Enter) if self.on_submit.is_some() => { let cb = self.on_submit.clone().unwrap(); let content = self.content.clone(); - return EventResult::with_cb(move |s| { - cb(s, &content); - }); + return EventResult::with_cb(move |s| { cb(s, &content); }); } _ => return EventResult::Ignored, } @@ -407,9 +405,7 @@ impl View for EditView { let content = self.content.clone(); let cursor = self.cursor; - Callback::from_fn(move |s| { - cb(s, &content, cursor); - }) + Callback::from_fn(move |s| { cb(s, &content, cursor); }) }); EventResult::Consumed(cb) } diff --git a/src/views/id_view.rs b/src/views/id_view.rs index 73ae6c2..7784335 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 9c6a750..cc0ddad 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -375,10 +375,10 @@ 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))); + } } } diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 77a84df..7f3490c 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -132,16 +132,16 @@ impl ListView { fn move_focus(&mut self, n: usize, source: direction::Direction) -> EventResult { let i = if let Some(i) = - source.relative(direction::Orientation::Vertical) - .and_then(|rel| { - // The iterator starts at the focused element. - // We don't want that one. - self.iter_mut(true, rel) - .skip(1) - .filter_map(|p| try_focus(p, source)) - .take(n) - .last() - }) { + source.relative(direction::Orientation::Vertical) + .and_then(|rel| { + // The iterator starts at the focused element. + // We don't want that one. + self.iter_mut(true, rel) + .skip(1) + .filter_map(|p| try_focus(p, source)) + .take(n) + .last() + }) { i } else { return EventResult::Ignored; @@ -185,14 +185,12 @@ impl View for ListView { .max() .unwrap_or(0) + 1; - self.scrollbase.draw(printer, |printer, i| { - match self.children[i] { - Child::Row(ref label, ref view) => { - printer.print((0, 0), label); - view.draw(&printer.offset((offset, 0), i == self.focus)); - } - Child::Delimiter => (), + self.scrollbase.draw(printer, |printer, i| match self.children[i] { + Child::Row(ref label, ref view) => { + printer.print((0, 0), label); + view.draw(&printer.offset((offset, 0), i == self.focus)); } + Child::Delimiter => (), }); } @@ -298,11 +296,12 @@ 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))); + } } } 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/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/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 e48c01a..7c0feb6 100644 --- a/src/views/stack_view.rs +++ b/src/views/stack_view.rs @@ -180,10 +180,10 @@ 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))); + } } } 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/xy.rs b/src/xy.rs index 4f30323..4416dbf 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -56,7 +56,7 @@ impl XY { } } -impl XY { +impl XY { /// Returns a new `XY` with the axis `o` set to `value`. pub fn with_axis(&self, o: Orientation, value: T) -> Self { let mut new = self.clone(); From b0193b0ebc2bb9b42985ca6235d7801ec09ac4e1 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Feb 2017 11:57:28 -0800 Subject: [PATCH 2/8] Add RefCellView Wraps a view and provide interior mutability --- Cargo.toml | 19 +++++++++------ src/lib.rs | 1 + src/views/mod.rs | 2 ++ src/views/refcell_view.rs | 49 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/views/refcell_view.rs diff --git a/Cargo.toml b/Cargo.toml index 85bc834..abe3288 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [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" @@ -13,7 +13,11 @@ repository = "https://github.com/gyscos/Cursive" version = "0.4.0" [badges] -travis-ci = { repository = "gyscos/Cursive" } + +[badges.travis-ci] +repository = "gyscos/Cursive" + +[build-dependencies] [build-dependencies.skeptic] optional = true @@ -23,18 +27,19 @@ version = "0.6" chan = "0.1.18" num = "0.1" odds = "0.2" +owning_ref = "0.2.4" toml = "0.2" unicode-segmentation = "1.0" unicode-width = "0.1" -[dependencies.chan-signal] -optional = true -version = "0.1" - [dependencies.bear-lib-terminal] optional = true version = "1.3.1" +[dependencies.chan-signal] +optional = true +version = "0.1" + [dependencies.ncurses] features = ["wide"] optional = true @@ -55,8 +60,8 @@ skeptic = "0.6" [features] default = ["ncurses"] -termion-backend = ["termion", "chan-signal"] pancurses-backend = ["pancurses"] +termion-backend = ["termion", "chan-signal"] [lib] name = "cursive" diff --git a/src/lib.rs b/src/lib.rs index 3c868ef..cda5d79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ extern crate unicode_segmentation; extern crate unicode_width; extern crate odds; extern crate num; +extern crate owning_ref; #[macro_use] extern crate chan; 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/refcell_view.rs b/src/views/refcell_view.rs new file mode 100644 index 0000000..e412310 --- /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) -> R + where F: FnOnce(&Self::V) -> R + { + f(&*self.view.borrow()) + } + + fn with_view_mut(&mut self, f: F) -> R + where F: FnOnce(&mut Self::V) -> R + { + f(&mut *self.view.borrow_mut()) + } +} From 1de7dcd8198cfddf1af582f8fee1b2fcd5bf5ba0 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Feb 2017 12:15:43 -0800 Subject: [PATCH 3/8] Remove useless entries from Cargo.toml --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abe3288..7725dd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,9 @@ readme = "Readme.md" repository = "https://github.com/gyscos/Cursive" version = "0.4.0" -[badges] - [badges.travis-ci] repository = "gyscos/Cursive" -[build-dependencies] - [build-dependencies.skeptic] optional = true version = "0.6" From 1b8d109e9478b6f98bceeaea2a0923dba4e95932 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Feb 2017 15:20:41 -0800 Subject: [PATCH 4/8] Add `with_id_mut` and `find_id_mut` convenient methods. --- src/lib.rs | 12 ++++++++++++ src/view/identifiable.rs | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cda5d79..bd0d636 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -427,6 +427,18 @@ impl Cursive { 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) + } + /// Adds a global callback. /// /// Will be triggered on the given key press when no view catches it. 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. From b50d2f077fea0a077c9b98bee78d7c20d49e4011 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 8 Feb 2017 15:33:43 -0800 Subject: [PATCH 5/8] Make RefCellView re-entrant safe Don't crash if the view is already borrowed. --- src/view/view_wrapper.rs | 22 ++++++++++++---------- src/views/refcell_view.rs | 8 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index 457f03a..4533115 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -20,10 +20,10 @@ pub trait ViewWrapper { type V: View; /// Get an immutable reference to the wrapped view. - fn with_view(&self, f: F) -> R where F: FnOnce(&Self::V) -> R; + fn with_view(&self, f: F) -> Option where F: FnOnce(&Self::V) -> R; /// Get a mutable reference to the wrapped view. - fn with_view_mut(&mut self, f: F) -> R + fn with_view_mut(&mut self, f: F) -> Option where F: FnOnce(&mut Self::V) -> R; /// Wraps the `draw` method. @@ -33,12 +33,12 @@ pub trait ViewWrapper { /// Wraps the `required_size` method. fn wrap_required_size(&mut self, req: Vec2) -> Vec2 { - self.with_view_mut(|v| v.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.with_view_mut(|v| v.on_event(ch)) + self.with_view_mut(|v| v.on_event(ch)).unwrap_or(EventResult::Ignored) } /// Wraps the `layout` method. @@ -48,7 +48,7 @@ pub trait ViewWrapper { /// Wraps the `take_focus` method. fn wrap_take_focus(&mut self, source: Direction) -> bool { - self.with_view_mut(|v| v.take_focus(source)) + self.with_view_mut(|v| v.take_focus(source)).unwrap_or(false) } /// Wraps the `find` method. @@ -59,7 +59,7 @@ pub trait ViewWrapper { /// Wraps the `needs_relayout` method. fn wrap_needs_relayout(&self) -> bool { - self.with_view(|v| v.needs_relayout()) + self.with_view(|v| v.needs_relayout()).unwrap_or(true) } } @@ -120,14 +120,16 @@ macro_rules! wrap_impl { (self.$v:ident: $t:ty) => { type V = $t; - fn with_view(&self, f: F) -> R where F: FnOnce(&Self::V) -> R { - f(&self.$v) + fn with_view(&self, f: F) -> Option + where F: FnOnce(&Self::V) -> R + { + Some(f(&self.$v)) } - fn with_view_mut(&mut self, f: F) -> R + fn with_view_mut(&mut self, f: F) -> Option where F: FnOnce(&mut Self::V) -> R { - f(&mut self.$v) + Some(f(&mut self.$v)) } }; } diff --git a/src/views/refcell_view.rs b/src/views/refcell_view.rs index e412310..64f31d5 100644 --- a/src/views/refcell_view.rs +++ b/src/views/refcell_view.rs @@ -35,15 +35,15 @@ impl RefCellView { impl ViewWrapper for RefCellView { type V = T; - fn with_view(&self, f: F) -> R + fn with_view(&self, f: F) -> Option where F: FnOnce(&Self::V) -> R { - f(&*self.view.borrow()) + self.view.try_borrow().ok().map(|v| f(&*v)) } - fn with_view_mut(&mut self, f: F) -> R + fn with_view_mut(&mut self, f: F) -> Option where F: FnOnce(&mut Self::V) -> R { - f(&mut *self.view.borrow_mut()) + self.view.try_borrow_mut().ok().map(|mut v| f(&mut *v)) } } From cb3adc5baf0c7d7b1bf87530bcff93d15d1a72c2 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 26 Feb 2017 15:52:42 -0800 Subject: [PATCH 6/8] feat: `find_id` can now find views declared with `with_id_mut` --- src/view/mod.rs | 13 ++++++++----- src/views/text_view.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/view/mod.rs b/src/view/mod.rs index 51481ab..56201ce 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -65,9 +65,10 @@ use Printer; use direction::Direction; use event::{Event, EventResult}; -use std::any::Any; use vec::Vec2; +use views::RefCellView; +use std::any::Any; /// Main trait defining a view behaviour. pub trait View { @@ -122,8 +123,7 @@ pub trait View { /// Returns None if the path doesn't lead to a view. /// /// Default implementation always return `None`. - fn find_any<'a>(&mut self, _: &Selector, - _: Box) { + fn find_any<'a>(&mut self, _: &Selector, _: Box) { // TODO: FnMut -> FnOnce once it works } @@ -176,8 +176,11 @@ impl Finder for T { let mut callback = Some(callback); let callback = |v: &mut Any| if let Some(callback) = callback.take() { - if let Some(v) = v.downcast_mut::() { - *result_ref = Some(callback(v)); + 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)); 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(); From 23b0f9c168a06601ca7c55a22bc954890901f222 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 26 Feb 2017 15:53:12 -0800 Subject: [PATCH 7/8] docs: add refcell_view example --- examples/refcell_view.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 examples/refcell_view.rs diff --git a/examples/refcell_view.rs b/examples/refcell_view.rs new file mode 100644 index 0000000..abe7473 --- /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(); + let mut text = siv.find_id_mut::("match").unwrap(); + + // Directly compare references to edit_1 and edit_2. + text.set_content(if edit_1.get_content() == edit_2.get_content() { + "match" + } else { + "no match" + }); +} From 30f13fc260e454ba5650d119ad9b8bc88182a70a Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 26 Feb 2017 16:01:23 -0800 Subject: [PATCH 8/8] Edit refcell_view example to use `find_id` --- examples/refcell_view.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/refcell_view.rs b/examples/refcell_view.rs index abe7473..593d928 100644 --- a/examples/refcell_view.rs +++ b/examples/refcell_view.rs @@ -28,12 +28,12 @@ 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(); - let mut text = siv.find_id_mut::("match").unwrap(); // Directly compare references to edit_1 and edit_2. - text.set_content(if edit_1.get_content() == edit_2.get_content() { + 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" - }); + })); }