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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
/// Backend using termion
pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
current_style: Cell<theme::ColorPair>,
@ -210,6 +211,7 @@ impl Effectable for theme::Effect {
}
impl Backend {
/// Creates a new termion-based backend.
pub fn init() -> Box<backend::Backend> {
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`.
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 {
/// Wraps the given function into a `Callback` object.
pub fn from_fn<F>(f: F) -> Self

View File

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

View File

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