mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add EventTrigger, refactor OnEventView
This commit is contained in:
parent
3f4719c148
commit
d9d34b4350
@ -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));
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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 {
|
||||
| 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 {
|
||||
| pancurses::BUTTON5_TRIPLE_CLICKED => {
|
||||
for _ in 0..3 {
|
||||
f(MouseEvent::Press(button));
|
||||
f(MouseEvent::Release(button));
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => debug!("Unknown event: {:032b}", bare_event),
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
85
src/event.rs
85
src/event.rs
@ -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
|
||||
|
@ -123,7 +123,6 @@ mod xy;
|
||||
mod div;
|
||||
mod utf8;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod backend;
|
||||
|
||||
pub use cursive::{CbFunc, Cursive, ScreenId};
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user