mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add ScreensView
This commit is contained in:
parent
11d4c865c6
commit
54882989e4
158
src/cursive.rs
158
src/cursive.rs
@ -8,7 +8,7 @@ use crossbeam_channel::{self, Receiver, Sender};
|
|||||||
|
|
||||||
use crate::backend;
|
use crate::backend;
|
||||||
use crate::direction;
|
use crate::direction;
|
||||||
use crate::event::{Callback, Event, EventResult};
|
use crate::event::{Event, EventResult};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
use crate::view::{self, Finder, IntoBoxedView, Position, View};
|
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
|
// How long we wait between two empty input polls
|
||||||
const INPUT_POLL_DELAY_MS: u64 = 30;
|
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.
|
/// Central part of the cursive library.
|
||||||
///
|
///
|
||||||
/// It initializes ncurses on creation and cleans up on drop.
|
/// 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.
|
/// It uses a list of screen, with one screen active at a time.
|
||||||
pub struct Cursive {
|
pub struct Cursive {
|
||||||
theme: theme::Theme,
|
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,
|
menubar: views::Menubar,
|
||||||
|
|
||||||
// Last layer sizes of the stack view.
|
// Last layer sizes of the stack view.
|
||||||
// If it changed, clear the screen.
|
// If it changed, clear the screen.
|
||||||
last_sizes: Vec<Vec2>,
|
last_sizes: Vec<Vec2>,
|
||||||
|
|
||||||
active_screen: ScreenId,
|
|
||||||
|
|
||||||
running: bool,
|
running: bool,
|
||||||
|
|
||||||
backend: Box<dyn backend::Backend>,
|
backend: Box<dyn backend::Backend>,
|
||||||
|
|
||||||
|
// Handle asynchronous callbacks
|
||||||
cb_source: Receiver<Box<dyn FnOnce(&mut Cursive) + Send>>,
|
cb_source: Receiver<Box<dyn FnOnce(&mut Cursive) + Send>>,
|
||||||
cb_sink: Sender<Box<dyn FnOnce(&mut Cursive) + Send>>,
|
cb_sink: Sender<Box<dyn FnOnce(&mut Cursive) + Send>>,
|
||||||
|
|
||||||
// User-provided data.
|
// User-provided data.
|
||||||
user_data: Box<dyn Any>,
|
user_data: Box<dyn Any>,
|
||||||
|
|
||||||
|
// Handle auto-refresh when no event is received.
|
||||||
fps: Option<NonZeroU32>,
|
fps: Option<NonZeroU32>,
|
||||||
boring_frame_count: u32,
|
boring_frame_count: u32,
|
||||||
}
|
}
|
||||||
@ -153,11 +152,11 @@ impl Cursive {
|
|||||||
|
|
||||||
backend_init().map(|backend| Cursive {
|
backend_init().map(|backend| Cursive {
|
||||||
theme,
|
theme,
|
||||||
screens: vec![views::StackView::new()],
|
root: views::OnEventView::new(views::ScreensView::single_screen(
|
||||||
|
views::StackView::new(),
|
||||||
|
)),
|
||||||
last_sizes: Vec::new(),
|
last_sizes: Vec::new(),
|
||||||
global_callbacks: HashMap::default(),
|
|
||||||
menubar: views::Menubar::new(),
|
menubar: views::Menubar::new(),
|
||||||
active_screen: 0,
|
|
||||||
running: true,
|
running: true,
|
||||||
cb_source,
|
cb_source,
|
||||||
cb_sink,
|
cb_sink,
|
||||||
@ -436,12 +435,12 @@ impl Cursive {
|
|||||||
.clear(self.theme.palette[theme::PaletteColor::Background]);
|
.clear(self.theme.palette[theme::PaletteColor::Background]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "toml")]
|
|
||||||
/// Loads a theme from the given file.
|
/// Loads a theme from the given file.
|
||||||
///
|
///
|
||||||
/// `filename` must point to a valid toml file.
|
/// `filename` must point to a valid toml file.
|
||||||
///
|
///
|
||||||
/// Must have the `toml` feature enabled.
|
/// Must have the `toml` feature enabled.
|
||||||
|
#[cfg(feature = "toml")]
|
||||||
pub fn load_theme_file<P: AsRef<Path>>(
|
pub fn load_theme_file<P: AsRef<Path>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
filename: P,
|
filename: P,
|
||||||
@ -449,12 +448,12 @@ impl Cursive {
|
|||||||
theme::load_theme_file(filename).map(|theme| self.set_theme(theme))
|
theme::load_theme_file(filename).map(|theme| self.set_theme(theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "toml")]
|
|
||||||
/// Loads a theme from the given string content.
|
/// Loads a theme from the given string content.
|
||||||
///
|
///
|
||||||
/// Content must be valid toml.
|
/// Content must be valid toml.
|
||||||
///
|
///
|
||||||
/// Must have the `toml` feature enabled.
|
/// Must have the `toml` feature enabled.
|
||||||
|
#[cfg(feature = "toml")]
|
||||||
pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> {
|
pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> {
|
||||||
theme::load_toml(content).map(|theme| self.set_theme(theme))
|
theme::load_toml(content).map(|theme| self.set_theme(theme))
|
||||||
}
|
}
|
||||||
@ -478,26 +477,24 @@ impl Cursive {
|
|||||||
|
|
||||||
/// Returns a reference to the currently active screen.
|
/// Returns a reference to the currently active screen.
|
||||||
pub fn screen(&self) -> &views::StackView {
|
pub fn screen(&self) -> &views::StackView {
|
||||||
let id = self.active_screen;
|
self.root.get_inner().screen().unwrap()
|
||||||
&self.screens[id]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the currently active screen.
|
/// Returns a mutable reference to the currently active screen.
|
||||||
pub fn screen_mut(&mut self) -> &mut views::StackView {
|
pub fn screen_mut(&mut self) -> &mut views::StackView {
|
||||||
let id = self.active_screen;
|
self.root.get_inner_mut().screen_mut().unwrap()
|
||||||
&mut self.screens[id]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the id of the currently active screen.
|
/// Returns the id of the currently active screen.
|
||||||
pub fn active_screen(&self) -> ScreenId {
|
pub fn active_screen(&self) -> ScreenId {
|
||||||
self.active_screen
|
self.root.get_inner().active_screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new screen, and returns its ID.
|
/// Adds a new screen, and returns its ID.
|
||||||
pub fn add_screen(&mut self) -> ScreenId {
|
pub fn add_screen(&mut self) -> ScreenId {
|
||||||
let res = self.screens.len();
|
self.root
|
||||||
self.screens.push(views::StackView::new());
|
.get_inner_mut()
|
||||||
res
|
.add_screen(views::StackView::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenient method to create a new screen, and set it as active.
|
/// 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.
|
/// Sets the active screen. Panics if no such screen exist.
|
||||||
pub fn set_screen(&mut self, screen_id: ScreenId) {
|
pub fn set_screen(&mut self, screen_id: ScreenId) {
|
||||||
if screen_id >= self.screens.len() {
|
self.root.get_inner_mut().set_active_screen(screen_id);
|
||||||
panic!(
|
|
||||||
"Tried to set an invalid screen ID: {}, but only {} \
|
|
||||||
screens present.",
|
|
||||||
screen_id,
|
|
||||||
self.screens.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.active_screen = screen_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to find the view pointed to by the given selector.
|
/// Tries to find the view pointed to by the given selector.
|
||||||
@ -555,7 +544,7 @@ impl Cursive {
|
|||||||
V: View + Any,
|
V: View + Any,
|
||||||
F: FnOnce(&mut V) -> R,
|
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.
|
/// 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`.
|
/// Moves the focus to the view identified by `sel`.
|
||||||
pub fn focus(&mut self, sel: &view::Selector<'_>) -> Result<(), ()> {
|
pub fn focus(&mut self, sel: &view::Selector<'_>) -> Result<(), ()> {
|
||||||
self.screen_mut().focus_view(sel)
|
self.root.focus_view(sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a global callback.
|
/// Adds a global callback.
|
||||||
@ -695,10 +684,44 @@ impl Cursive {
|
|||||||
where
|
where
|
||||||
F: FnMut(&mut Cursive) + 'static,
|
F: FnMut(&mut Cursive) + 'static,
|
||||||
{
|
{
|
||||||
self.global_callbacks
|
self.set_on_post_event(event.into(), cb);
|
||||||
.entry(event.into())
|
}
|
||||||
.or_insert_with(Vec::new)
|
|
||||||
.push(Callback::from_fn_mut(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.
|
/// Removes any callback tied to the given event.
|
||||||
@ -717,7 +740,17 @@ impl Cursive {
|
|||||||
E: Into<Event>,
|
E: Into<Event>,
|
||||||
{
|
{
|
||||||
let event = event.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.
|
/// Add a layer to the current screen.
|
||||||
@ -761,32 +794,12 @@ impl Cursive {
|
|||||||
self.screen_mut().reposition_layer(layer, position);
|
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.
|
/// Processes an event.
|
||||||
///
|
///
|
||||||
/// * If the menubar is active, it will be handled the event.
|
/// * If the menubar is active, it will be handled the event.
|
||||||
/// * The view tree will be handled the event.
|
/// * The view tree will be handled the event.
|
||||||
/// * If ignored, global_callbacks will be checked for this event.
|
/// * If ignored, global_callbacks will be checked for this event.
|
||||||
pub fn on_event(&mut self, event: 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 {
|
if let Event::Mouse {
|
||||||
event, position, ..
|
event, position, ..
|
||||||
} = event
|
} = 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() {
|
if self.menubar.receive_events() {
|
||||||
self.menubar.on_event(event).process(self);
|
self.menubar.on_event(event).process(self);
|
||||||
} else {
|
} else {
|
||||||
let offset = if self.menubar.autohide { 0 } else { 1 };
|
let offset = if self.menubar.autohide { 0 } else { 1 };
|
||||||
match self.screen_mut().on_event(event.relativized((0, offset))) {
|
|
||||||
// If the event was ignored,
|
let result =
|
||||||
// it is our turn to play with it.
|
View::on_event(&mut self.root, event.relativized((0, offset)));
|
||||||
EventResult::Ignored => self.on_ignored_event(event),
|
|
||||||
EventResult::Consumed(None) => (),
|
if let EventResult::Consumed(Some(cb)) = result {
|
||||||
EventResult::Consumed(Some(cb)) => cb(self),
|
cb(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -828,12 +836,15 @@ impl Cursive {
|
|||||||
let size = self.screen_size();
|
let size = self.screen_size();
|
||||||
let offset = if self.menubar.autohide { 0 } else { 1 };
|
let offset = if self.menubar.autohide { 0 } else { 1 };
|
||||||
let size = size.saturating_sub((0, offset));
|
let size = size.saturating_sub((0, offset));
|
||||||
self.screen_mut().layout(size);
|
self.root.layout(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self) {
|
fn draw(&mut self) {
|
||||||
|
// TODO: do not allocate in the default, fast path?
|
||||||
let sizes = self.screen().layer_sizes();
|
let sizes = self.screen().layer_sizes();
|
||||||
if self.last_sizes != 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.clear();
|
||||||
self.last_sizes = sizes;
|
self.last_sizes = sizes;
|
||||||
}
|
}
|
||||||
@ -845,10 +856,11 @@ impl Cursive {
|
|||||||
|
|
||||||
// Print the stackview background before the menubar
|
// Print the stackview background before the menubar
|
||||||
let offset = if self.menubar.autohide { 0 } else { 1 };
|
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
|
// Draw the currently active screen
|
||||||
// If the menubar is active, nothing else can be.
|
// If the menubar is active, nothing else can be.
|
||||||
@ -860,7 +872,7 @@ impl Cursive {
|
|||||||
|
|
||||||
// finally draw stackview layers
|
// finally draw stackview layers
|
||||||
// using variables from above
|
// 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.
|
/// Returns `true` until [`quit(&mut self)`] is called.
|
||||||
|
83
src/event.rs
83
src/event.rs
@ -15,7 +15,6 @@
|
|||||||
use crate::Cursive;
|
use crate::Cursive;
|
||||||
use crate::Vec2;
|
use crate::Vec2;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
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.
|
/// A trigger that only selects some types of events.
|
||||||
///
|
///
|
||||||
/// It is meant to be stored in views.
|
/// 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 {
|
impl EventTrigger {
|
||||||
/// Create a new `EventTrigger` using the given function as filter.
|
/// Create a new `EventTrigger` using the given function as filter.
|
||||||
@ -43,43 +48,66 @@ impl EventTrigger {
|
|||||||
where
|
where
|
||||||
F: 'static + Fn(&Event) -> bool,
|
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`.
|
/// Checks if this trigger applies to the given `Event`.
|
||||||
pub fn apply(&self, event: &Event) -> bool {
|
pub fn apply(&self, event: &Event) -> bool {
|
||||||
(self.0)(event)
|
(self.trigger)(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `EventTrigger` that only accepts arrow keys.
|
/// Returns an `EventTrigger` that only accepts arrow keys.
|
||||||
///
|
///
|
||||||
/// Only bare arrow keys without modifiers (Shift, Ctrl, Alt) will be accepted.
|
/// Only bare arrow keys without modifiers (Shift, Ctrl, Alt) will be accepted.
|
||||||
pub fn arrows() -> Self {
|
pub fn arrows() -> Self {
|
||||||
Self::from_fn(|e| match e {
|
Self::from_fn_and_tag(
|
||||||
Event::Key(Key::Left)
|
|e| match e {
|
||||||
| Event::Key(Key::Down)
|
Event::Key(Key::Left)
|
||||||
| Event::Key(Key::Up)
|
| Event::Key(Key::Down)
|
||||||
| Event::Key(Key::Right) => true,
|
| Event::Key(Key::Up)
|
||||||
_ => false,
|
| Event::Key(Key::Right) => true,
|
||||||
})
|
_ => false,
|
||||||
|
},
|
||||||
|
"arrows",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `EventTrigger` that only accepts mouse events.
|
/// Returns an `EventTrigger` that only accepts mouse events.
|
||||||
pub fn mouse() -> Self {
|
pub fn mouse() -> Self {
|
||||||
Self::from_fn(|e| match e {
|
Self::from_fn_and_tag(
|
||||||
Event::Mouse { .. } => true,
|
|e| match e {
|
||||||
_ => false,
|
Event::Mouse { .. } => true,
|
||||||
})
|
_ => false,
|
||||||
|
},
|
||||||
|
"mouse",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `EventTrigger` that accepts any event.
|
/// Returns an `EventTrigger` that accepts any event.
|
||||||
pub fn any() -> Self {
|
pub fn any() -> Self {
|
||||||
Self::from_fn(|_| true)
|
Self::from_fn_and_tag(|_| true, "any")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an `EventTrigger` that doesn't accept any event.
|
/// Returns an `EventTrigger` that doesn't accept any event.
|
||||||
pub fn none() -> Self {
|
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.
|
/// Returns an `EventTrigger` that applies if either `self` or `other` applies.
|
||||||
@ -88,13 +116,22 @@ impl EventTrigger {
|
|||||||
O: Into<EventTrigger>,
|
O: Into<EventTrigger>,
|
||||||
{
|
{
|
||||||
let other = other.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<Event> for EventTrigger {
|
impl From<Event> for EventTrigger {
|
||||||
fn from(event: Event) -> Self {
|
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
|
where
|
||||||
F: 'static + FnMut(&mut Cursive),
|
F: 'static + FnMut(&mut Cursive),
|
||||||
{
|
{
|
||||||
let cb = RefCell::new(f);
|
Self::from_fn(crate::immut1!(f))
|
||||||
|
|
||||||
Self::from_fn(move |s| {
|
|
||||||
if let Ok(mut cb) = cb.try_borrow_mut() {
|
|
||||||
(&mut *cb)(s);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a dummy callback that doesn't run anything.
|
/// Returns a dummy callback that doesn't run anything.
|
||||||
|
@ -60,6 +60,13 @@
|
|||||||
// It's not how windows work, so no need to use that.
|
// It's not how windows work, so no need to use that.
|
||||||
|
|
||||||
macro_rules! new_default(
|
macro_rules! new_default(
|
||||||
|
($c:ident<$t:ident>) => {
|
||||||
|
impl<$t> Default for $c<$t> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
($c:ty) => {
|
($c:ty) => {
|
||||||
impl Default for $c {
|
impl Default for $c {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -76,6 +76,7 @@ mod panel;
|
|||||||
mod progress_bar;
|
mod progress_bar;
|
||||||
mod radio;
|
mod radio;
|
||||||
mod resized_view;
|
mod resized_view;
|
||||||
|
mod screens_view;
|
||||||
mod scroll_view;
|
mod scroll_view;
|
||||||
mod select_view;
|
mod select_view;
|
||||||
mod shadow_view;
|
mod shadow_view;
|
||||||
@ -109,6 +110,7 @@ pub use self::panel::Panel;
|
|||||||
pub use self::progress_bar::ProgressBar;
|
pub use self::progress_bar::ProgressBar;
|
||||||
pub use self::radio::{RadioButton, RadioGroup};
|
pub use self::radio::{RadioButton, RadioGroup};
|
||||||
pub use self::resized_view::ResizedView;
|
pub use self::resized_view::ResizedView;
|
||||||
|
pub use self::screens_view::ScreensView;
|
||||||
pub use self::scroll_view::ScrollView;
|
pub use self::scroll_view::ScrollView;
|
||||||
pub use self::select_view::SelectView;
|
pub use self::select_view::SelectView;
|
||||||
pub use self::shadow_view::ShadowView;
|
pub use self::shadow_view::ShadowView;
|
||||||
|
@ -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.
|
/// Registers a callback when the given event is ignored by the child.
|
||||||
///
|
///
|
||||||
/// Chainable variant.
|
/// Chainable variant.
|
||||||
|
121
src/views/screens_view.rs
Normal file
121
src/views/screens_view.rs
Normal 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.
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user