From d9d34b435041d968b5e8b40e844e6f0674917b5d Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Fri, 9 Nov 2018 10:40:06 -0800 Subject: [PATCH] Add EventTrigger, refactor OnEventView --- src/backend/blt.rs | 2 + src/backend/curses/n.rs | 3 + src/backend/curses/pan.rs | 26 ++++--- src/backend/dummy.rs | 4 ++ src/backend/termion.rs | 2 + src/event.rs | 85 +++++++++++++++++++++++ src/lib.rs | 1 - src/views/on_event_view.rs | 139 +++++++++++++++++++++---------------- 8 files changed, 193 insertions(+), 69 deletions(-) diff --git a/src/backend/blt.rs b/src/backend/blt.rs index ded8b51..18a476a 100644 --- a/src/backend/blt.rs +++ b/src/backend/blt.rs @@ -26,6 +26,7 @@ enum ColorRole { Background, } +/// Backend using BearLibTerminal pub struct Backend { buttons_pressed: HashSet, mouse_position: Vec2, @@ -35,6 +36,7 @@ pub struct Backend { } impl Backend { + /// Creates a new BearLibTerminal-based backend. pub fn init() -> Box { terminal::open("Cursive", 80, 24); terminal::set(terminal::config::Window::empty().resizeable(true)); diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 88eded8..43ba48d 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -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, @@ -200,6 +202,7 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> { } impl Backend { + /// Creates a new ncurses-based backend. pub fn init() -> Box { let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index c7c318a..5002b32 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -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, @@ -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 { // 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), } } diff --git a/src/backend/dummy.rs b/src/backend/dummy.rs index 54b03b4..c8444ac 100644 --- a/src/backend/dummy.rs +++ b/src/backend/dummy.rs @@ -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>, inner_receiver: Receiver>, } impl Backend { + /// Creates a new dummy backend. pub fn init() -> Box where Self: Sized, diff --git a/src/backend/termion.rs b/src/backend/termion.rs index f0c6186..1d333c2 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -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>>, current_style: Cell, @@ -210,6 +211,7 @@ impl Effectable for theme::Effect { } impl Backend { + /// Creates a new termion-based backend. pub fn init() -> Box { print!("{}", termion::cursor::Hide); diff --git a/src/event.rs b/src/event.rs index 64759c6..2fd8737 100644 --- a/src/event.rs +++ b/src/event.rs @@ -29,6 +29,91 @@ pub struct Callback(Rc>); /// A boxed callback that can be run on `&mut Any`. pub type AnyCb<'a> = Box; +/// A trigger that only selects some types of events. +pub struct EventTrigger(Box bool>); + +impl EventTrigger { + /// Create a new `EventTrigger` using the given function as filter. + pub fn from_fn(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(self, other: O) -> Self + where + O: Into, + { + let other = other.into(); + Self::from_fn(move |e| self.apply(e) || other.apply(e)) + } +} + +impl From for EventTrigger { + fn from(event: Event) -> Self { + Self::from_fn(move |e| *e == event) + } +} + +impl From for EventTrigger { + fn from(c: char) -> Self { + Self::from(Event::from(c)) + } +} + +impl From for EventTrigger { + fn from(k: Key) -> Self { + Self::from(Event::from(k)) + } +} + +impl From 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) -> Self diff --git a/src/lib.rs b/src/lib.rs index 85576f5..878a141 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,7 +123,6 @@ mod xy; mod div; mod utf8; -#[doc(hidden)] pub mod backend; pub use cursive::{CbFunc, Cursive, ScreenId}; diff --git a/src/views/on_event_view.rs b/src/views/on_event_view.rs index ae86df3..1094e33 100644 --- a/src/views/on_event_view.rs +++ b/src/views/on_event_view.rs @@ -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 { view: T, - callbacks: HashMap>, + callbacks: Vec<(EventTrigger, Action)>, } -type InnerCallback = Rc Option>>; +type InnerCallback = Rc Option>>; struct Action { phase: TriggerPhase, @@ -73,19 +70,19 @@ impl OnEventView { 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(self, event: E, cb: F) -> Self + pub fn on_event(self, trigger: E, cb: F) -> Self where - E: Into, + E: Into, 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 OnEventView { /// The child will never receive this event. /// /// Chainable variant. - pub fn on_pre_event(self, event: E, cb: F) -> Self + pub fn on_pre_event(self, trigger: E, cb: F) -> Self where - E: Into, + E: Into, 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 OnEventView { /// result. /// /// Chainable variant. - pub fn on_pre_event_inner(self, event: E, cb: F) -> Self + pub fn on_pre_event_inner(self, trigger: E, cb: F) -> Self where - E: Into, - F: Fn(&mut T) -> Option + 'static, + E: Into, + F: Fn(&mut T, &Event) -> Option + '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 OnEventView { /// If the result is not `None`, it will be processed as well. /// /// Chainable variant. - pub fn on_event_inner(self, event: E, cb: F) -> Self + pub fn on_event_inner(self, trigger: E, cb: F) -> Self where - E: Into, - F: Fn(&mut T) -> Option + 'static, + E: Into, + F: Fn(&mut T, &Event) -> Option + '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(&mut self, event: E, cb: F) + pub fn set_on_event(&mut self, trigger: E, cb: F) where - E: Into, + E: Into, 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(&mut self, event: E, cb: F) + pub fn set_on_pre_event(&mut self, trigger: E, cb: F) where - E: Into, + E: Into, 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 OnEventView { /// usual. /// * Otherwise, it bypasses the child view and directly processes the /// result. - pub fn set_on_pre_event_inner(&mut self, event: E, cb: F) + pub fn set_on_pre_event_inner(&mut self, trigger: E, cb: F) where - E: Into, - F: Fn(&mut T) -> Option + 'static, + E: Into, + F: Fn(&mut T, &Event) -> Option + '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 OnEventView { /// 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(&mut self, event: E, cb: F) + pub fn set_on_event_inner(&mut self, trigger: E, cb: F) where - E: Into, - F: Fn(&mut T) -> Option + 'static, + E: Into, + F: Fn(&mut T, &Event) -> Option + '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 ViewWrapper for OnEventView { 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) }) - } } }