diff --git a/src/cursive.rs b/src/cursive.rs index 5fa0b99..ba5c7b6 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -8,7 +8,7 @@ use crossbeam_channel::{self, Receiver, Sender}; use crate::backend; use crate::direction; -use crate::event::{Callback, Event, EventResult}; +use crate::event::{Event, EventResult}; use crate::printer::Printer; use crate::theme; use crate::view::{self, Finder, IntoBoxedView, Position, View}; @@ -20,9 +20,6 @@ static DEBUG_VIEW_NAME: &str = "_cursive_debug_view"; // How long we wait between two empty input polls const INPUT_POLL_DELAY_MS: u64 = 30; -// Use AHash instead of the slower SipHash -type HashMap = std::collections::HashMap; - /// Central part of the cursive library. /// /// It initializes ncurses on creation and cleans up on drop. @@ -32,26 +29,28 @@ type HashMap = std::collections::HashMap; /// It uses a list of screen, with one screen active at a time. pub struct Cursive { theme: theme::Theme, - screens: Vec, - global_callbacks: HashMap>, + + // The main view + root: views::OnEventView>, + menubar: views::Menubar, // Last layer sizes of the stack view. // If it changed, clear the screen. last_sizes: Vec, - active_screen: ScreenId, - running: bool, backend: Box, + // Handle asynchronous callbacks cb_source: Receiver>, cb_sink: Sender>, // User-provided data. user_data: Box, + // Handle auto-refresh when no event is received. fps: Option, boring_frame_count: u32, } @@ -153,11 +152,11 @@ impl Cursive { backend_init().map(|backend| Cursive { theme, - screens: vec![views::StackView::new()], + root: views::OnEventView::new(views::ScreensView::single_screen( + views::StackView::new(), + )), last_sizes: Vec::new(), - global_callbacks: HashMap::default(), menubar: views::Menubar::new(), - active_screen: 0, running: true, cb_source, cb_sink, @@ -436,12 +435,12 @@ impl Cursive { .clear(self.theme.palette[theme::PaletteColor::Background]); } - #[cfg(feature = "toml")] /// Loads a theme from the given file. /// /// `filename` must point to a valid toml file. /// /// Must have the `toml` feature enabled. + #[cfg(feature = "toml")] pub fn load_theme_file>( &mut self, filename: P, @@ -449,12 +448,12 @@ impl Cursive { theme::load_theme_file(filename).map(|theme| self.set_theme(theme)) } - #[cfg(feature = "toml")] /// Loads a theme from the given string content. /// /// Content must be valid toml. /// /// Must have the `toml` feature enabled. + #[cfg(feature = "toml")] pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> { theme::load_toml(content).map(|theme| self.set_theme(theme)) } @@ -478,26 +477,24 @@ impl Cursive { /// Returns a reference to the currently active screen. pub fn screen(&self) -> &views::StackView { - let id = self.active_screen; - &self.screens[id] + self.root.get_inner().screen().unwrap() } /// Returns a mutable reference to the currently active screen. pub fn screen_mut(&mut self) -> &mut views::StackView { - let id = self.active_screen; - &mut self.screens[id] + self.root.get_inner_mut().screen_mut().unwrap() } /// Returns the id of the currently active screen. pub fn active_screen(&self) -> ScreenId { - self.active_screen + self.root.get_inner().active_screen() } /// Adds a new screen, and returns its ID. pub fn add_screen(&mut self) -> ScreenId { - let res = self.screens.len(); - self.screens.push(views::StackView::new()); - res + self.root + .get_inner_mut() + .add_screen(views::StackView::new()) } /// Convenient method to create a new screen, and set it as active. @@ -509,15 +506,7 @@ impl Cursive { /// Sets the active screen. Panics if no such screen exist. pub fn set_screen(&mut self, screen_id: ScreenId) { - if screen_id >= self.screens.len() { - panic!( - "Tried to set an invalid screen ID: {}, but only {} \ - screens present.", - screen_id, - self.screens.len() - ); - } - self.active_screen = screen_id; + self.root.get_inner_mut().set_active_screen(screen_id); } /// Tries to find the view pointed to by the given selector. @@ -555,7 +544,7 @@ impl Cursive { V: View + Any, F: FnOnce(&mut V) -> R, { - self.screen_mut().call_on(sel, callback) + self.root.call_on(sel, callback) } /// Tries to find the view identified by the given id. @@ -676,7 +665,7 @@ impl Cursive { /// Moves the focus to the view identified by `sel`. pub fn focus(&mut self, sel: &view::Selector<'_>) -> Result<(), ()> { - self.screen_mut().focus_view(sel) + self.root.focus_view(sel) } /// Adds a global callback. @@ -695,10 +684,44 @@ impl Cursive { where F: FnMut(&mut Cursive) + 'static, { - self.global_callbacks - .entry(event.into()) - .or_insert_with(Vec::new) - .push(Callback::from_fn_mut(cb)); + self.set_on_post_event(event.into(), cb); + } + + /// Registers a callback for ignored events. + /// + /// This is the same as `add_global_callback`, but can register any `EventTrigger`. + pub fn set_on_post_event(&mut self, trigger: E, cb: F) + where + F: FnMut(&mut Cursive) + 'static, + E: Into, + { + self.root.set_on_event(trigger, crate::immut1!(cb)); + } + + /// Registers a priotity callback. + /// + /// If an event matches the given trigger, it will not be sent to the view + /// tree and will go to the given callback instead. + pub fn set_on_pre_event(&mut self, trigger: E, cb: F) + where + F: FnMut(&mut Cursive) + 'static, + E: Into, + { + self.root.set_on_pre_event(trigger, crate::immut1!(cb)); + } + + /// Sets the only global callback for the given event. + /// + /// Any other callback for this event will be removed. + /// + /// See also [`Cursive::add_global_callback`]. + pub fn set_global_callback>(&mut self, event: E, cb: F) + where + F: FnMut(&mut Cursive) + 'static, + { + let event = event.into(); + self.clear_global_callbacks(event.clone()); + self.add_global_callback(event, cb); } /// Removes any callback tied to the given event. @@ -717,7 +740,17 @@ impl Cursive { E: Into, { let event = event.into(); - self.global_callbacks.remove(&event); + self.root.clear_event(event); + } + + /// This resets the default callbacks. + /// + /// Currently this mostly includes exiting on Ctrl-C. + pub fn reset_default_callbacks(&mut self) { + self.set_on_pre_event(Event::CtrlChar('c'), |s| s.quit()); + self.set_on_pre_event(Event::Exit, |s| s.quit()); + + self.set_on_pre_event(Event::WindowResize, |s| s.clear()); } /// Add a layer to the current screen. @@ -761,32 +794,12 @@ impl Cursive { self.screen_mut().reposition_layer(layer, position); } - // Handles a key event when it was ignored by the current view - fn on_ignored_event(&mut self, event: Event) { - let cb_list = match self.global_callbacks.get(&event) { - None => return, - Some(cb_list) => cb_list.clone(), - }; - // Not from a view, so no viewpath here - for cb in cb_list { - cb(self); - } - } - /// Processes an event. /// /// * If the menubar is active, it will be handled the event. /// * The view tree will be handled the event. /// * If ignored, global_callbacks will be checked for this event. pub fn on_event(&mut self, event: Event) { - if event == Event::Exit { - self.quit(); - } - - if event == Event::WindowResize { - self.clear(); - } - if let Event::Mouse { event, position, .. } = event @@ -800,21 +813,16 @@ impl Cursive { } } - // Event dispatch order: - // * Focused element: - // * Menubar (if active) - // * Current screen (top layer) - // * Global callbacks if self.menubar.receive_events() { self.menubar.on_event(event).process(self); } else { let offset = if self.menubar.autohide { 0 } else { 1 }; - match self.screen_mut().on_event(event.relativized((0, offset))) { - // If the event was ignored, - // it is our turn to play with it. - EventResult::Ignored => self.on_ignored_event(event), - EventResult::Consumed(None) => (), - EventResult::Consumed(Some(cb)) => cb(self), + + let result = + View::on_event(&mut self.root, event.relativized((0, offset))); + + if let EventResult::Consumed(Some(cb)) = result { + cb(self); } } } @@ -828,12 +836,15 @@ impl Cursive { let size = self.screen_size(); let offset = if self.menubar.autohide { 0 } else { 1 }; let size = size.saturating_sub((0, offset)); - self.screen_mut().layout(size); + self.root.layout(size); } fn draw(&mut self) { + // TODO: do not allocate in the default, fast path? let sizes = self.screen().layer_sizes(); if self.last_sizes != sizes { + // TODO: Maybe we only need to clear if the _max_ size differs? + // Or if the positions change? self.clear(); self.last_sizes = sizes; } @@ -845,10 +856,11 @@ impl Cursive { // Print the stackview background before the menubar let offset = if self.menubar.autohide { 0 } else { 1 }; - let id = self.active_screen; - let sv_printer = printer.offset((0, offset)).focused(!selected); - self.screens[id].draw_bg(&sv_printer); + let sv_printer = printer.offset((0, offset)).focused(!selected); + self.root.draw(&sv_printer); + + self.root.get_inner().draw_bg(&sv_printer); // Draw the currently active screen // If the menubar is active, nothing else can be. @@ -860,7 +872,7 @@ impl Cursive { // finally draw stackview layers // using variables from above - self.screens[id].draw_fg(&sv_printer); + self.root.get_inner().draw_fg(&sv_printer); } /// Returns `true` until [`quit(&mut self)`] is called. diff --git a/src/event.rs b/src/event.rs index 8a73c34..8a89797 100644 --- a/src/event.rs +++ b/src/event.rs @@ -15,7 +15,6 @@ use crate::Cursive; use crate::Vec2; use std::any::Any; -use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; @@ -35,7 +34,13 @@ pub type AnyCb<'a> = &'a mut dyn FnMut(&mut dyn Any); /// A trigger that only selects some types of events. /// /// It is meant to be stored in views. -pub struct EventTrigger(Box bool>); +pub struct EventTrigger { + trigger: Box bool>, + tag: Box, +} + +trait AnyTag: Any + std::fmt::Debug {} +impl AnyTag for T where T: Any + std::fmt::Debug {} impl EventTrigger { /// Create a new `EventTrigger` using the given function as filter. @@ -43,43 +48,66 @@ impl EventTrigger { where F: 'static + Fn(&Event) -> bool, { - EventTrigger(Box::new(f)) + EventTrigger::from_fn_and_tag(f, "free function") + } + + /// Create a new `EventTrigger`. + pub fn from_fn_and_tag(f: F, tag: T) -> Self + where + F: 'static + Fn(&Event) -> bool, + T: Any + std::fmt::Debug, + { + EventTrigger { + trigger: Box::new(f), + tag: Box::new(tag), + } + } + + /// Check if this trigger has the given tag. + pub fn has_tag(&self, tag: &T) -> bool { + Any::downcast_ref::(&self.tag).map_or(false, |t| tag == t) } /// Checks if this trigger applies to the given `Event`. pub fn apply(&self, event: &Event) -> bool { - (self.0)(event) + (self.trigger)(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, - }) + Self::from_fn_and_tag( + |e| match e { + Event::Key(Key::Left) + | Event::Key(Key::Down) + | Event::Key(Key::Up) + | Event::Key(Key::Right) => true, + _ => false, + }, + "arrows", + ) } /// Returns an `EventTrigger` that only accepts mouse events. pub fn mouse() -> Self { - Self::from_fn(|e| match e { - Event::Mouse { .. } => true, - _ => false, - }) + Self::from_fn_and_tag( + |e| match e { + Event::Mouse { .. } => true, + _ => false, + }, + "mouse", + ) } /// Returns an `EventTrigger` that accepts any event. pub fn any() -> Self { - Self::from_fn(|_| true) + Self::from_fn_and_tag(|_| true, "any") } /// Returns an `EventTrigger` that doesn't accept any event. pub fn none() -> Self { - Self::from_fn(|_| false) + Self::from_fn_and_tag(|_| false, "none") } /// Returns an `EventTrigger` that applies if either `self` or `other` applies. @@ -88,13 +116,22 @@ impl EventTrigger { O: Into, { let other = other.into(); - Self::from_fn(move |e| self.apply(e) || other.apply(e)) + + let self_trigger = self.trigger; + let other_trigger = other.trigger; + let tag = (self.tag, "or", other.tag); + + Self::from_fn_and_tag( + move |e| self_trigger(e) || other_trigger(e), + tag, + ) } } impl From for EventTrigger { fn from(event: Event) -> Self { - Self::from_fn(move |e| *e == event) + let tag = event.clone(); + Self::from_fn_and_tag(move |e| *e == event, tag) } } @@ -137,13 +174,7 @@ impl Callback { where F: 'static + FnMut(&mut Cursive), { - let cb = RefCell::new(f); - - Self::from_fn(move |s| { - if let Ok(mut cb) = cb.try_borrow_mut() { - (&mut *cb)(s); - } - }) + Self::from_fn(crate::immut1!(f)) } /// Returns a dummy callback that doesn't run anything. diff --git a/src/lib.rs b/src/lib.rs index a834868..9a54209 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,13 @@ // It's not how windows work, so no need to use that. macro_rules! new_default( + ($c:ident<$t:ident>) => { + impl<$t> Default for $c<$t> { + fn default() -> Self { + Self::new() + } + } + }; ($c:ty) => { impl Default for $c { fn default() -> Self { diff --git a/src/views/mod.rs b/src/views/mod.rs index 25c524e..d9ebfb2 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -76,6 +76,7 @@ mod panel; mod progress_bar; mod radio; mod resized_view; +mod screens_view; mod scroll_view; mod select_view; mod shadow_view; @@ -109,6 +110,7 @@ pub use self::panel::Panel; pub use self::progress_bar::ProgressBar; pub use self::radio::{RadioButton, RadioGroup}; pub use self::resized_view::ResizedView; +pub use self::screens_view::ScreensView; pub use self::scroll_view::ScrollView; pub use self::select_view::SelectView; pub use self::shadow_view::ShadowView; diff --git a/src/views/on_event_view.rs b/src/views/on_event_view.rs index eddcced..157311b 100644 --- a/src/views/on_event_view.rs +++ b/src/views/on_event_view.rs @@ -74,6 +74,16 @@ impl OnEventView { } } + /// Remove all callbacks associated with the given event. + pub fn clear_event(&mut self, event: E) + where + E: Into, + { + let event = event.into(); + self.callbacks + .retain(move |&(ref trigger, _)| !trigger.has_tag(&event)); + } + /// Registers a callback when the given event is ignored by the child. /// /// Chainable variant. diff --git a/src/views/screens_view.rs b/src/views/screens_view.rs new file mode 100644 index 0000000..a29758b --- /dev/null +++ b/src/views/screens_view.rs @@ -0,0 +1,121 @@ +use crate::views::BoxedView; +use crate::View; + +/// Identifies a screen in the cursive root. +pub type ScreenId = usize; + +/// A view that can switch between different screens. +pub struct ScreensView { + screens: Vec, + active_screen: ScreenId, +} + +new_default!(ScreensView); + +impl ScreensView { + /// Creates a new empty `ScreensView`. + pub fn new() -> Self { + ScreensView { + screens: Vec::new(), + active_screen: 0, + } + } + + /// Creates a new `ScreensView` with a single screen. + pub fn single_screen(v: V) -> Self { + ScreensView { + screens: vec![v], + active_screen: 0, + } + } + + /// Returns a reference to the currently active screen. + /// + /// Returns `None` if there is no active screen. + pub fn screen(&self) -> Option<&V> { + self.screens.get(self.active_screen) + } + + /// Returns a mutable reference to the currently active screen. + pub fn screen_mut(&mut self) -> Option<&mut V> { + let id = self.active_screen; + self.screens.get_mut(id) + } + + /// Returns the id of the currently active screen. + pub fn active_screen(&self) -> ScreenId { + self.active_screen + } + + /// Adds a new screen, and returns its ID. + pub fn add_screen(&mut self, v: V) -> ScreenId { + let res = self.screens.len(); + self.screens.push(v); + res + } + + /// Convenient method to create a new screen, and set it as active. + pub fn add_active_screen(&mut self, v: V) -> ScreenId { + let res = self.add_screen(v); + self.set_active_screen(res); + res + } + + /// Sets the active screen. Panics if no such screen exist. + pub fn set_active_screen(&mut self, screen_id: ScreenId) { + if screen_id >= self.screens.len() { + panic!( + "Tried to set an invalid screen ID: {}, but only {} \ + screens present.", + screen_id, + self.screens.len() + ); + } + self.active_screen = screen_id; + } +} + +impl ScreensView { + /// Draws the background. + /// + /// This is mostly used internally by cursive. You probably just want + /// `View::draw`. + pub fn draw_bg(&self, printer: &crate::Printer) { + if let Some(screen) = self.screen() { + screen.draw_bg(printer); + } + } + + /// Draws the foreground. + /// + /// This is mostly used internally by cursive. You probably just want + /// `View::draw`. + pub fn draw_fg(&self, printer: &crate::Printer) { + if let Some(screen) = self.screen() { + screen.draw_fg(printer); + } + } +} + +impl crate::view::ViewWrapper for ScreensView +where + V: View, +{ + type V = V; + + fn with_view(&self, f: F) -> Option + where + F: FnOnce(&Self::V) -> R, + { + self.screen().map(f) + } + + fn with_view_mut(&mut self, f: F) -> Option + where + F: FnOnce(&mut Self::V) -> R, + { + self.screen_mut().map(f) + } + + // TODO: Should `focus_view` work cross-screens? Should `call_on_id`? Answer: yes. +}