Add ViewPath and Cursive::find

Callbacks now include a path to the triggering view.
The Cursive root can find the View corresponding to a ViewPath.
In the future, ViewPaths may be returned when creating the layout.
This commit is contained in:
Alexandre Bury 2015-05-15 17:56:38 -07:00
parent 6cd2a28966
commit e17ca97136
10 changed files with 128 additions and 30 deletions

View File

@ -2,9 +2,12 @@
use std::rc::Rc;
use ::Cursive;
use view::ViewPath;
/// Callback is a function that can be triggered by an event.
/// It has a mutable access to the cursive root.
pub type Callback = Box<Fn(&mut super::Cursive)>;
pub type Callback = Box<Fn(&mut Cursive, &ViewPath)>;
/// Answer to an event notification.
/// The event can be consumed or ignored.
@ -12,5 +15,5 @@ pub enum EventResult {
/// The event was ignored. The parent can keep handling it.
Ignored,
/// The event was consumed. An optionnal callback to run is attached.
Consumed(Option<Rc<Callback>>),
Consumed(Option<Rc<Callback>>, ViewPath),
}

View File

@ -20,26 +20,23 @@
//! siv.run();
//! }
//! ```
extern crate ncurses;
pub mod event;
pub mod view;
pub mod printer;
pub mod vec2;
mod box_view;
mod stack_view;
mod text_view;
mod div;
use std::any::Any;
use std::rc::Rc;
use std::collections::HashMap;
use vec2::Vec2;
use view::View;
use printer::Printer;
use stack_view::StackView;
use view::{StackView,ViewPath};
use event::{EventResult,Callback};
@ -47,6 +44,7 @@ use event::{EventResult,Callback};
pub type ScreenId = usize;
/// Central part of the cursive library.
///
/// It initializes ncurses on creation and cleans up on drop.
/// To use it, you should populate it with views, layouts and callbacks,
/// then start the event loop with run().
@ -110,9 +108,23 @@ impl Cursive {
self.active_screen = screen_id;
}
fn find_any(&mut self, path: &ViewPath) -> Option<&mut Any> {
self.screen_mut().find(path)
}
/// Tries to find the view pointed to by the given path.
/// If the view is not found, or if it is not of the asked type,
/// it returns None.
pub fn find<V: View + Any>(&mut self, path: &ViewPath) -> Option<&mut V> {
match self.find_any(path) {
None => None,
Some(b) => b.downcast_mut::<V>(),
}
}
/// Adds a global callback, triggered on the given key press when no view catches it.
pub fn add_global_callback<F>(&mut self, key: i32, cb: F)
where F: Fn(&mut Cursive) + 'static
where F: Fn(&mut Cursive, &ViewPath) + 'static
{
self.global_callbacks.insert(key, Rc::new(Box::new(cb)));
}
@ -128,9 +140,11 @@ impl Cursive {
None => return,
Some(cb) => cb.clone(),
};
cb(self);
// Not from a view, so no viewpath here
cb(self, &ViewPath::new());
}
/// Returns the size of the screen, in characters.
pub fn screen_size(&self) -> Vec2 {
let mut x: i32 = 0;
let mut y: i32 = 0;
@ -180,12 +194,13 @@ impl Cursive {
// If the event was ignored, it is our turn to play with it.
match self.screen_mut().on_key_event(ch) {
EventResult::Ignored => self.on_key_event(ch),
EventResult::Consumed(None) => (),
EventResult::Consumed(Some(cb)) => cb(self),
EventResult::Consumed(None, _) => (),
EventResult::Consumed(Some(cb), path) => cb(self, &path),
}
}
}
/// Stops the event loop.
pub fn quit(&mut self) {
self.running = false;
}

View File

@ -7,9 +7,13 @@ fn main() {
let mut siv = Cursive::new();
// We can quit by pressing q
siv.add_global_callback('q' as i32, |s| s.quit());
siv.add_global_callback('q' as i32, |s,_| s.quit());
siv.add_layer(TextView::new("Hello World!\nPress q to quit the application."));
siv.add_global_callback('a' as i32, |s, _| {
//
});
siv.run();
}

View File

@ -1,12 +0,0 @@
use super::View;
pub trait ToView {
fn to_view(self) -> Box<View>;
}
impl<'a> ToView for &'a str {
fn to_view(self) -> Box<View> {
Box::new(TextView::new(self))
}
}

View File

@ -1,6 +1,6 @@
use event::EventResult;
use vec2::{Vec2,ToVec2};
use view::{View,SizeRequest};
use super::{View,SizeRequest};
use printer::Printer;
/// BoxView is a wrapper around an other view, with a given minimum size.

View File

@ -0,0 +1,59 @@
use std::collections::HashMap;
use std::rc::Rc;
use ::Cursive;
use event::{EventResult,Callback};
use vec2::{Vec2};
use super::{View,SizeRequest,ViewPath};
use printer::Printer;
/// A simple wrapper view that catches some ignored event from its child.
///
/// Events ignored by its child without a callback will stay ignored.
pub struct KeyEventView {
content: Box<View>,
callbacks: HashMap<i32, Rc<Callback>>,
}
impl KeyEventView {
/// Wraps the given view in a new KeyEventView.
pub fn new<V: View + 'static>(view: V) -> Self {
KeyEventView {
content: Box::new(view),
callbacks: HashMap::new(),
}
}
/// Registers a callback when the given key is ignored by the child.
pub fn register<F>(mut self, key: i32, cb: F) -> Self
where F: Fn(&mut Cursive, &ViewPath) + 'static
{
self.callbacks.insert(key, Rc::new(Box::new(cb)));
self
}
}
impl View for KeyEventView {
fn on_key_event(&mut self, ch: i32) -> EventResult {
match self.content.on_key_event(ch) {
EventResult::Ignored => match self.callbacks.get(&ch) {
None => EventResult::Ignored,
Some(cb) => EventResult::Consumed(Some(cb.clone()), ViewPath::new()),
},
EventResult::Consumed(cb, path) => EventResult::Consumed(cb, path),
}
}
fn draw(&self, printer: &Printer) {
self.content.draw(printer)
}
fn get_min_size(&self, req: SizeRequest) -> Vec2 {
self.content.get_min_size(req)
}
fn layout(&mut self, size: Vec2) {
self.content.layout(size);
}
}

View File

@ -1,11 +1,20 @@
//! Defines various views to use when creating the layout.
mod box_view;
mod stack_view;
mod text_view;
mod key_event_view;
mod view_path;
use std::any::Any;
pub use self::view_path::ViewPath;
pub use self::key_event_view::KeyEventView;
pub use self::box_view::BoxView;
pub use self::stack_view::StackView;
pub use self::text_view::TextView;
use event::EventResult;
pub use box_view::BoxView;
pub use stack_view::StackView;
pub use text_view::TextView;
use vec2::Vec2;
use printer::Printer;
@ -43,4 +52,9 @@ pub trait View {
/// Draws the view within the given bounds.
fn draw(&self, &Printer);
/// Finds the view pointed to by the given path.
/// Returns None if the path doesn't lead to a view.
fn find(&mut self, &ViewPath) -> Option<&mut Any> { None }
}

15
src/view/view_path.rs Normal file
View File

@ -0,0 +1,15 @@
/// Represents a path to a single view in the layout.
pub struct ViewPath {
/// List of turns to make on decision nodes when descending the view tree.
/// Simple nodes (with one fixed child) are skipped.
pub path: Vec<usize>,
}
impl ViewPath {
/// Creates a new empty path.
pub fn new() -> Self {
ViewPath {
path: Vec::new(),
}
}
}