Add ScreensView

This commit is contained in:
Alexandre Bury 2020-01-26 14:43:24 -08:00
parent 11d4c865c6
commit 54882989e4
6 changed files with 282 additions and 99 deletions

View File

@ -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<K, V> = std::collections::HashMap<K, V, ahash::ABuildHasher>;
/// Central part of the cursive library.
///
/// It initializes ncurses on creation and cleans up on drop.
@ -32,26 +29,28 @@ type HashMap<K, V> = std::collections::HashMap<K, V, ahash::ABuildHasher>;
/// It uses a list of screen, with one screen active at a time.
pub struct Cursive {
theme: theme::Theme,
screens: Vec<views::StackView>,
global_callbacks: HashMap<Event, Vec<Callback>>,
// The main view
root: views::OnEventView<views::ScreensView<views::StackView>>,
menubar: views::Menubar,
// Last layer sizes of the stack view.
// If it changed, clear the screen.
last_sizes: Vec<Vec2>,
active_screen: ScreenId,
running: bool,
backend: Box<dyn backend::Backend>,
// Handle asynchronous callbacks
cb_source: Receiver<Box<dyn FnOnce(&mut Cursive) + Send>>,
cb_sink: Sender<Box<dyn FnOnce(&mut Cursive) + Send>>,
// User-provided data.
user_data: Box<dyn Any>,
// Handle auto-refresh when no event is received.
fps: Option<NonZeroU32>,
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<P: AsRef<Path>>(
&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<F, E>(&mut self, trigger: E, cb: F)
where
F: FnMut(&mut Cursive) + 'static,
E: Into<crate::event::EventTrigger>,
{
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<F, E>(&mut self, trigger: E, cb: F)
where
F: FnMut(&mut Cursive) + 'static,
E: Into<crate::event::EventTrigger>,
{
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<F, E: Into<Event>>(&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<Event>,
{
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.

View File

@ -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<dyn Fn(&Event) -> bool>);
pub struct EventTrigger {
trigger: Box<dyn Fn(&Event) -> bool>,
tag: Box<dyn AnyTag>,
}
trait AnyTag: Any + std::fmt::Debug {}
impl<T> 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, T>(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<T: PartialEq + 'static>(&self, tag: &T) -> bool {
Any::downcast_ref::<T>(&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<EventTrigger>,
{
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<Event> 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.

View File

@ -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 {

View File

@ -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;

View File

@ -74,6 +74,16 @@ impl<T: View> OnEventView<T> {
}
}
/// Remove all callbacks associated with the given event.
pub fn clear_event<E>(&mut self, event: E)
where
E: Into<Event>,
{
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.

121
src/views/screens_view.rs Normal file
View File

@ -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<V = BoxedView> {
screens: Vec<V>,
active_screen: ScreenId,
}
new_default!(ScreensView<V>);
impl<V> ScreensView<V> {
/// 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<crate::views::StackView> {
/// 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<V> crate::view::ViewWrapper for ScreensView<V>
where
V: View,
{
type V = V;
fn with_view<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&Self::V) -> R,
{
self.screen().map(f)
}
fn with_view_mut<F, R>(&mut self, f: F) -> Option<R>
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.
}