Add EventTrigger, refactor OnEventView

This commit is contained in:
Alexandre Bury 2018-11-09 10:40:06 -08:00
parent 3f4719c148
commit d9d34b4350
8 changed files with 193 additions and 69 deletions

View File

@ -26,6 +26,7 @@ enum ColorRole {
Background, Background,
} }
/// Backend using BearLibTerminal
pub struct Backend { pub struct Backend {
buttons_pressed: HashSet<MouseButton>, buttons_pressed: HashSet<MouseButton>,
mouse_position: Vec2, mouse_position: Vec2,
@ -35,6 +36,7 @@ pub struct Backend {
} }
impl Backend { impl Backend {
/// Creates a new BearLibTerminal-based backend.
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
terminal::open("Cursive", 80, 24); terminal::open("Cursive", 80, 24);
terminal::set(terminal::config::Window::empty().resizeable(true)); terminal::set(terminal::config::Window::empty().resizeable(true));

View File

@ -1,3 +1,4 @@
//! Ncurses-specific backend.
extern crate ncurses; extern crate ncurses;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -23,6 +24,7 @@ use vec::Vec2;
use self::super::split_i32; use self::super::split_i32;
use self::ncurses::mmask_t; use self::ncurses::mmask_t;
/// Backend using ncurses.
pub struct Backend { pub struct Backend {
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
@ -200,6 +202,7 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
} }
impl Backend { impl Backend {
/// Creates a new ncurses-based backend.
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());

View File

@ -1,3 +1,4 @@
//! Pancuses-specific backend.
extern crate pancurses; extern crate pancurses;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -22,6 +23,7 @@ use vec::Vec2;
use self::pancurses::mmask_t; use self::pancurses::mmask_t;
use super::split_i32; use super::split_i32;
/// Backend using pancurses.
pub struct Backend { pub struct Backend {
// Used // Used
current_style: Cell<ColorPair>, current_style: Cell<ColorPair>,
@ -84,7 +86,8 @@ impl InputParser {
pancurses::Input::Character(c) => Event::Char(c), pancurses::Input::Character(c) => Event::Char(c),
// TODO: Some key combos are not recognized by pancurses, // TODO: Some key combos are not recognized by pancurses,
// but are sent as Unknown. We could still parse them here. // but are sent as Unknown. We could still parse them here.
pancurses::Input::Unknown(code) => self.key_codes pancurses::Input::Unknown(code) => self
.key_codes
// pancurses does some weird keycode mapping // pancurses does some weird keycode mapping
.get(&(code + 256 + 48)) .get(&(code + 256 + 48))
.cloned() .cloned()
@ -286,6 +289,7 @@ fn find_closest_pair(pair: ColorPair) -> (i16, i16) {
} }
impl Backend { impl Backend {
/// Creates a new pancurses-based backend.
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
// We need to create this now, before ncurses initialization // We need to create this now, before ncurses initialization
// Otherwise ncurses starts its own signal handling and it's a mess. // Otherwise ncurses starts its own signal handling and it's a mess.
@ -523,18 +527,22 @@ where
| pancurses::BUTTON2_DOUBLE_CLICKED | pancurses::BUTTON2_DOUBLE_CLICKED
| pancurses::BUTTON3_DOUBLE_CLICKED | pancurses::BUTTON3_DOUBLE_CLICKED
| pancurses::BUTTON4_DOUBLE_CLICKED | pancurses::BUTTON4_DOUBLE_CLICKED
| pancurses::BUTTON5_DOUBLE_CLICKED => for _ in 0..2 { | pancurses::BUTTON5_DOUBLE_CLICKED => {
for _ in 0..2 {
f(MouseEvent::Press(button)); f(MouseEvent::Press(button));
f(MouseEvent::Release(button)); f(MouseEvent::Release(button));
}, }
}
pancurses::BUTTON1_TRIPLE_CLICKED pancurses::BUTTON1_TRIPLE_CLICKED
| pancurses::BUTTON2_TRIPLE_CLICKED | pancurses::BUTTON2_TRIPLE_CLICKED
| pancurses::BUTTON3_TRIPLE_CLICKED | pancurses::BUTTON3_TRIPLE_CLICKED
| pancurses::BUTTON4_TRIPLE_CLICKED | pancurses::BUTTON4_TRIPLE_CLICKED
| pancurses::BUTTON5_TRIPLE_CLICKED => for _ in 0..3 { | pancurses::BUTTON5_TRIPLE_CLICKED => {
for _ in 0..3 {
f(MouseEvent::Press(button)); f(MouseEvent::Press(button));
f(MouseEvent::Release(button)); f(MouseEvent::Release(button));
}, }
}
_ => debug!("Unknown event: {:032b}", bare_event), _ => debug!("Unknown event: {:032b}", bare_event),
} }
} }

View File

@ -8,12 +8,16 @@ use event::Event;
use theme; use theme;
use vec::Vec2; use vec::Vec2;
/// Dummy backend that does nothing and immediately exits.
///
/// Mostly used for testing.
pub struct Backend { pub struct Backend {
inner_sender: Sender<Option<Event>>, inner_sender: Sender<Option<Event>>,
inner_receiver: Receiver<Option<Event>>, inner_receiver: Receiver<Option<Event>>,
} }
impl Backend { impl Backend {
/// Creates a new dummy backend.
pub fn init() -> Box<backend::Backend> pub fn init() -> Box<backend::Backend>
where where
Self: Sized, Self: Sized,

View File

@ -31,6 +31,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
/// Backend using termion
pub struct Backend { pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>, terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
current_style: Cell<theme::ColorPair>, current_style: Cell<theme::ColorPair>,
@ -210,6 +211,7 @@ impl Effectable for theme::Effect {
} }
impl Backend { impl Backend {
/// Creates a new termion-based backend.
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
print!("{}", termion::cursor::Hide); print!("{}", termion::cursor::Hide);

View File

@ -29,6 +29,91 @@ pub struct Callback(Rc<Box<Fn(&mut Cursive)>>);
/// A boxed callback that can be run on `&mut Any`. /// A boxed callback that can be run on `&mut Any`.
pub type AnyCb<'a> = Box<FnMut(&mut Any) + 'a>; pub type AnyCb<'a> = Box<FnMut(&mut Any) + 'a>;
/// A trigger that only selects some types of events.
pub struct EventTrigger(Box<Fn(&Event) -> bool>);
impl EventTrigger {
/// Create a new `EventTrigger` using the given function as filter.
pub fn from_fn<F>(f: F) -> Self
where
F: 'static + Fn(&Event) -> bool,
{
EventTrigger(Box::new(f))
}
/// Checks if this trigger applies to the given `Event`.
pub fn apply(&self, event: &Event) -> bool {
(self.0)(event)
}
/// Returns an `EventTrigger` that only accepts arrow keys.
///
/// Only bare arrow keys without modifiers (Shift, Ctrl, Alt) will be accepted.
pub fn arrows() -> Self {
Self::from_fn(|e| match e {
Event::Key(Key::Left)
| Event::Key(Key::Down)
| Event::Key(Key::Up)
| Event::Key(Key::Right) => true,
_ => false,
})
}
/// Returns an `EventTrigger` that only accepts mouse events.
pub fn mouse() -> Self {
Self::from_fn(|e| match e {
Event::Mouse { .. } => true,
_ => false,
})
}
/// Returns an `EventTrigger` that accepts any event.
pub fn any() -> Self {
Self::from_fn(|_| true)
}
/// Returns an `EventTrigger` that doesn't accept any event.
pub fn none() -> Self {
Self::from_fn(|_| true)
}
/// Returns an `EventTrigger` that applies if either `self` or `other` applies.
pub fn or<O>(self, other: O) -> Self
where
O: Into<EventTrigger>,
{
let other = other.into();
Self::from_fn(move |e| self.apply(e) || other.apply(e))
}
}
impl From<Event> for EventTrigger {
fn from(event: Event) -> Self {
Self::from_fn(move |e| *e == event)
}
}
impl From<char> for EventTrigger {
fn from(c: char) -> Self {
Self::from(Event::from(c))
}
}
impl From<Key> for EventTrigger {
fn from(k: Key) -> Self {
Self::from(Event::from(k))
}
}
impl<F> From<F> for EventTrigger
where
F: 'static + Fn(&Event) -> bool,
{
fn from(f: F) -> Self {
Self::from_fn(f)
}
}
impl Callback { impl Callback {
/// Wraps the given function into a `Callback` object. /// Wraps the given function into a `Callback` object.
pub fn from_fn<F>(f: F) -> Self pub fn from_fn<F>(f: F) -> Self

View File

@ -123,7 +123,6 @@ mod xy;
mod div; mod div;
mod utf8; mod utf8;
#[doc(hidden)]
pub mod backend; pub mod backend;
pub use cursive::{CbFunc, Cursive, ScreenId}; pub use cursive::{CbFunc, Cursive, ScreenId};

View File

@ -1,5 +1,4 @@
use event::{Callback, Event, EventResult}; use event::{Callback, Event, EventResult, EventTrigger};
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use Cursive; use Cursive;
@ -10,10 +9,8 @@ use With;
/// This view registers a set of callbacks tied to specific events, to be run /// This view registers a set of callbacks tied to specific events, to be run
/// in certain conditions. /// in certain conditions.
/// ///
/// **Note**: only one callback can be registered per event. Trying to register /// * Some callbacks are called only for events ignored by the wrapped view.
/// a new one will replace any existing one for that event.
/// ///
/// * Some callbacks are called only for vents ignored by the wrapped view
/// (those registered by [`on_event`] or [`on_event_inner`]) /// (those registered by [`on_event`] or [`on_event_inner`])
/// * Others are processed first, and can control whether the child view should /// * Others are processed first, and can control whether the child view should
/// be given the event (those registered by [`on_pre_event`] or /// be given the event (those registered by [`on_pre_event`] or
@ -43,10 +40,10 @@ use With;
/// ``` /// ```
pub struct OnEventView<T: View> { pub struct OnEventView<T: View> {
view: T, view: T,
callbacks: HashMap<Event, Action<T>>, callbacks: Vec<(EventTrigger, Action<T>)>,
} }
type InnerCallback<T> = Rc<Box<Fn(&mut T) -> Option<EventResult>>>; type InnerCallback<T> = Rc<Box<Fn(&mut T, &Event) -> Option<EventResult>>>;
struct Action<T> { struct Action<T> {
phase: TriggerPhase, phase: TriggerPhase,
@ -73,19 +70,19 @@ impl<T: View> OnEventView<T> {
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
OnEventView { OnEventView {
view, view,
callbacks: HashMap::new(), callbacks: Vec::new(),
} }
} }
/// Registers a callback when the given event is ignored by the child. /// Registers a callback when the given event is ignored by the child.
/// ///
/// Chainable variant. /// Chainable variant.
pub fn on_event<F, E>(self, event: E, cb: F) -> Self pub fn on_event<F, E>(self, trigger: E, cb: F) -> Self
where where
E: Into<Event>, E: Into<EventTrigger>,
F: 'static + Fn(&mut Cursive), F: 'static + Fn(&mut Cursive),
{ {
self.with(|s| s.set_on_event(event, cb)) self.with(|s| s.set_on_event(trigger, cb))
} }
/// Registers a callback when the given event is received. /// Registers a callback when the given event is received.
@ -93,12 +90,12 @@ impl<T: View> OnEventView<T> {
/// The child will never receive this event. /// The child will never receive this event.
/// ///
/// Chainable variant. /// Chainable variant.
pub fn on_pre_event<F, E>(self, event: E, cb: F) -> Self pub fn on_pre_event<F, E>(self, trigger: E, cb: F) -> Self
where where
E: Into<Event>, E: Into<EventTrigger>,
F: 'static + Fn(&mut Cursive), F: 'static + Fn(&mut Cursive),
{ {
self.with(|s| s.set_on_pre_event(event, cb)) self.with(|s| s.set_on_pre_event(trigger, cb))
} }
/// Registers a callback when the given event is received. /// Registers a callback when the given event is received.
@ -111,12 +108,12 @@ impl<T: View> OnEventView<T> {
/// result. /// result.
/// ///
/// Chainable variant. /// Chainable variant.
pub fn on_pre_event_inner<F, E>(self, event: E, cb: F) -> Self pub fn on_pre_event_inner<F, E>(self, trigger: E, cb: F) -> Self
where where
E: Into<Event>, E: Into<EventTrigger>,
F: Fn(&mut T) -> Option<EventResult> + 'static, F: Fn(&mut T, &Event) -> Option<EventResult> + 'static,
{ {
self.with(|s| s.set_on_pre_event_inner(event, cb)) self.with(|s| s.set_on_pre_event_inner(trigger, cb))
} }
/// Registers a callback when the given event is ignored by the child. /// Registers a callback when the given event is ignored by the child.
@ -126,41 +123,43 @@ impl<T: View> OnEventView<T> {
/// If the result is not `None`, it will be processed as well. /// If the result is not `None`, it will be processed as well.
/// ///
/// Chainable variant. /// Chainable variant.
pub fn on_event_inner<F, E>(self, event: E, cb: F) -> Self pub fn on_event_inner<F, E>(self, trigger: E, cb: F) -> Self
where where
E: Into<Event>, E: Into<EventTrigger>,
F: Fn(&mut T) -> Option<EventResult> + 'static, F: Fn(&mut T, &Event) -> Option<EventResult> + 'static,
{ {
self.with(|s| s.set_on_event_inner(event, cb)) self.with(|s| s.set_on_event_inner(trigger, cb))
} }
/// Registers a callback when the given event is ignored by the child. /// Registers a callback when the given event is ignored by the child.
pub fn set_on_event<F, E>(&mut self, event: E, cb: F) pub fn set_on_event<F, E>(&mut self, trigger: E, cb: F)
where where
E: Into<Event>, E: Into<EventTrigger>,
F: Fn(&mut Cursive) + 'static, F: Fn(&mut Cursive) + 'static,
{ {
let cb = Callback::from_fn(cb); let cb = Callback::from_fn(cb);
let action = let action = move |_: &mut T, _: &Event| {
move |_: &mut T| Some(EventResult::Consumed(Some(cb.clone()))); Some(EventResult::Consumed(Some(cb.clone())))
};
self.set_on_event_inner(event, action); self.set_on_event_inner(trigger, action);
} }
/// Registers a callback when the given event is received. /// Registers a callback when the given event is received.
/// ///
/// The child will never receive this event. /// The child will never receive this event.
pub fn set_on_pre_event<F, E>(&mut self, event: E, cb: F) pub fn set_on_pre_event<F, E>(&mut self, trigger: E, cb: F)
where where
E: Into<Event>, E: Into<EventTrigger>,
F: 'static + Fn(&mut Cursive), F: 'static + Fn(&mut Cursive),
{ {
let cb = Callback::from_fn(cb); let cb = Callback::from_fn(cb);
// We want to clone the Callback every time we call the closure // We want to clone the Callback every time we call the closure
let action = let action = move |_: &mut T, _: &Event| {
move |_: &mut T| Some(EventResult::Consumed(Some(cb.clone()))); Some(EventResult::Consumed(Some(cb.clone())))
};
self.set_on_pre_event_inner(event, action); self.set_on_pre_event_inner(trigger, action);
} }
/// Registers a callback when the given event is received. /// Registers a callback when the given event is received.
@ -171,18 +170,18 @@ impl<T: View> OnEventView<T> {
/// usual. /// usual.
/// * Otherwise, it bypasses the child view and directly processes the /// * Otherwise, it bypasses the child view and directly processes the
/// result. /// result.
pub fn set_on_pre_event_inner<F, E>(&mut self, event: E, cb: F) pub fn set_on_pre_event_inner<F, E>(&mut self, trigger: E, cb: F)
where where
E: Into<Event>, E: Into<EventTrigger>,
F: Fn(&mut T) -> Option<EventResult> + 'static, F: Fn(&mut T, &Event) -> Option<EventResult> + 'static,
{ {
self.callbacks.insert( self.callbacks.push((
event.into(), trigger.into(),
Action { Action {
phase: TriggerPhase::BeforeChild, phase: TriggerPhase::BeforeChild,
callback: Rc::new(Box::new(cb)), callback: Rc::new(Box::new(cb)),
}, },
); ));
} }
/// Registers a callback when the given event is ignored by the child. /// Registers a callback when the given event is ignored by the child.
@ -190,18 +189,18 @@ impl<T: View> OnEventView<T> {
/// If the child view ignores the event, `cb` will be called with the /// If the child view ignores the event, `cb` will be called with the
/// child view as argument. /// child view as argument.
/// If the result is not `None`, it will be processed as well. /// If the result is not `None`, it will be processed as well.
pub fn set_on_event_inner<F, E>(&mut self, event: E, cb: F) pub fn set_on_event_inner<F, E>(&mut self, trigger: E, cb: F)
where where
E: Into<Event>, E: Into<EventTrigger>,
F: Fn(&mut T) -> Option<EventResult> + 'static, F: Fn(&mut T, &Event) -> Option<EventResult> + 'static,
{ {
self.callbacks.insert( self.callbacks.push((
event.into(), trigger.into(),
Action { Action {
phase: TriggerPhase::AfterChild, phase: TriggerPhase::AfterChild,
callback: Rc::new(Box::new(cb)), callback: Rc::new(Box::new(cb)),
}, },
); ));
} }
inner_getters!(self.view: T); inner_getters!(self.view: T);
@ -211,22 +210,44 @@ impl<T: View> ViewWrapper for OnEventView<T> {
wrap_impl!(self.view: T); wrap_impl!(self.view: T);
fn wrap_on_event(&mut self, event: Event) -> EventResult { fn wrap_on_event(&mut self, event: Event) -> EventResult {
let action = self.callbacks.get(&event).cloned(); // Until we have better closure capture, define captured members separately.
let pre_child = action let callbacks = &self.callbacks;
.as_ref() let view = &mut self.view;
.map(|a| a.phase == TriggerPhase::BeforeChild)
.unwrap_or(false);
if pre_child { // * First, check all pre-child callbacks. Combine them.
action // If any gets triggered and returns Some(...), stop right there.
.and_then(|a| (*a.callback)(&mut self.view)) // * Otherwise, give the event to the child view.
.unwrap_or_else(|| self.view.on_event(event)) // If it returns EventResult::Consumed, stop right there.
} else { // * Finally, check all post-child callbacks. Combine them.
self.view.on_event(event).or_else(|| { // And just return the result.
action
.and_then(|a| (*a.callback)(&mut self.view)) // First step: check pre-child
.unwrap_or(EventResult::Ignored) callbacks
.iter()
.filter(|&(_, action)| action.phase == TriggerPhase::BeforeChild)
.filter(|&(trigger, _)| trigger.apply(&event))
.filter_map(|(_, action)| (*action.callback)(view, &event))
.fold(None, |s, r| match s {
// Return `Some()` if any pre-callback was present.
None => Some(r),
Some(c) => Some(c.and(r)),
})
.unwrap_or_else(|| {
// If it was None, it means no pre-callback was triggered.
// So let's give the view a chance!
view.on_event(event.clone())
})
.or_else(|| {
// No pre-child, and the child itself ignored the event?
// Let's have a closer look then, shall we?
callbacks
.iter()
.filter(|&(_, action)| {
action.phase == TriggerPhase::AfterChild
})
.filter(|&(trigger, _)| trigger.apply(&event))
.filter_map(|(_, action)| (*action.callback)(view, &event))
.fold(EventResult::Ignored, EventResult::and)
}) })
} }
} }
}