Make backends pausable

This commit is contained in:
Alexandre Bury 2020-08-04 23:24:11 -07:00
parent 01bb32d640
commit 2cd6d7a80c
24 changed files with 634 additions and 421 deletions

View File

@ -38,14 +38,6 @@ pub trait Backend {
/// * `Some(event)` for each event to process.
fn poll_event(&mut self) -> Option<Event>;
// TODO: take `self` by value?
// Or implement Drop?
// Will change when implementing resumable backends
/// Prepares to close the backend.
///
/// This should clear any state in the terminal.
fn finish(&mut self);
/// Refresh the screen.
///
/// This will be called each frame after drawing has been done.
@ -130,8 +122,6 @@ impl Backend for Dummy {
"dummy"
}
fn finish(&mut self) {}
fn refresh(&mut self) {}
fn has_colors(&self) -> bool {

View File

@ -2,12 +2,13 @@ use std::any::Any;
use std::num::NonZeroU32;
#[cfg(feature = "toml")]
use std::path::Path;
use std::time::Duration;
use crossbeam_channel::{self, Receiver, Sender};
use crate::{
backend, direction,
backend,
cursive_run::CursiveRunner,
direction,
event::{Event, EventResult},
printer::Printer,
theme,
@ -18,9 +19,6 @@ use crate::{
static DEBUG_VIEW_NAME: &str = "_cursive_debug_view";
// How long we wait between two empty input polls
const INPUT_POLL_DELAY_MS: u64 = 30;
/// Central part of the cursive library.
///
/// It initializes ncurses on creation and cleans up on drop.
@ -36,14 +34,10 @@ pub struct Cursive {
menubar: views::Menubar,
// Last layer sizes of the stack view.
// If it changed, clear the screen.
last_sizes: Vec<Vec2>,
pub(crate) needs_clear: bool,
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>>,
@ -53,7 +47,6 @@ pub struct Cursive {
// Handle auto-refresh when no event is received.
fps: Option<NonZeroU32>,
boring_frame_count: u32,
}
/// Identifies a screen in the cursive root.
@ -71,24 +64,6 @@ pub type ScreenId = usize;
pub type CbSink = Sender<Box<dyn FnOnce(&mut Cursive) + Send>>;
impl Cursive {
/// Shortcut for `Cursive::try_new` with non-failible init function.
///
/// You probably don't want to use this function directly, unless you're
/// using a non-standard backend. Built-in backends have dedicated functions.
///
/// # Examples
///
/// ```rust,no_run
/// # use cursive_core::{Cursive, backend};
/// let siv = Cursive::new(backend::Dummy::init);
/// ```
pub fn new<F>(backend_init: F) -> Self
where
F: FnOnce() -> Box<dyn backend::Backend>,
{
Self::try_new::<_, ()>(|| Ok(backend_init())).unwrap()
}
/// Creates a new Cursive root, and initialize the back-end.
///
/// You probably don't want to use this function directly, unless you're
@ -96,42 +71,60 @@ impl Cursive {
/// [`CursiveExt`] trait.
///
/// [`CursiveExt`]: https://docs.rs/cursive/0/cursive/trait.CursiveExt.html
pub fn try_new<F, E>(backend_init: F) -> Result<Self, E>
where
F: FnOnce() -> Result<Box<dyn backend::Backend>, E>,
{
pub fn new() -> Self {
let theme = theme::load_default();
let (cb_sink, cb_source) = crossbeam_channel::unbounded();
let backend = backend_init()?;
let mut cursive = Cursive {
theme,
root: views::OnEventView::new(views::ScreensView::single_screen(
views::StackView::new(),
)),
last_sizes: Vec::new(),
menubar: views::Menubar::new(),
needs_clear: true,
running: true,
cb_source,
cb_sink,
backend,
fps: None,
boring_frame_count: 0,
user_data: Box::new(()),
};
cursive.reset_default_callbacks();
Ok(cursive)
cursive
}
/// Creates a new Cursive root using a [dummy backend].
///
/// Nothing will be output. This is mostly here for tests.
///
/// [dummy backend]: backend::Dummy
pub fn dummy() -> Self {
Self::new(backend::Dummy::init)
pub(crate) fn layout(&mut self, size: Vec2) {
let offset = if self.menubar.autohide { 0 } else { 1 };
let size = size.saturating_sub((0, offset));
self.root.layout(size);
}
pub(crate) fn draw(&mut self, size: Vec2, backend: &dyn backend::Backend) {
let printer = Printer::new(size, &self.theme, backend);
let selected = self.menubar.receive_events();
// Print the stackview background before the menubar
let offset = if self.menubar.autohide { 0 } else { 1 };
// The printer for the stackview
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.
// Draw the menubar?
if self.menubar.visible() {
let printer = printer.focused(self.menubar.receive_events());
self.menubar.draw(&printer);
}
// finally draw stackview layers
// using variables from above
self.root.get_inner().draw_fg(&sv_printer);
}
/// Sets some data to be stored in Cursive.
@ -162,7 +155,7 @@ impl Cursive {
/// # Examples
///
/// ```rust
/// let mut siv = cursive_core::Cursive::dummy();
/// let mut siv = cursive_core::Cursive::new();
///
/// // Start with a simple `Vec<i32>` as user data.
/// siv.set_user_data(vec![1i32, 2, 3]);
@ -231,7 +224,7 @@ impl Cursive {
///
/// ```rust
/// # use cursive_core::Cursive;
/// # let mut siv = Cursive::dummy();
/// # let mut siv = Cursive::new();
/// siv.add_global_callback('~', Cursive::toggle_debug_console);
/// ```
pub fn toggle_debug_console(&mut self) {
@ -264,7 +257,7 @@ impl Cursive {
///
/// ```rust
/// # use cursive_core::*;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// // quit() will be called during the next event cycle
/// siv.cb_sink().send(Box::new(|s| s.quit())).unwrap();
@ -298,7 +291,7 @@ impl Cursive {
/// # use cursive_core::traits::*;
/// # use cursive_core::menu::*;
/// #
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.menubar()
/// .add_subtree("File",
@ -361,8 +354,7 @@ impl Cursive {
///
/// Users rarely have to call this directly.
pub fn clear(&mut self) {
self.backend
.clear(self.theme.palette[theme::PaletteColor::Background]);
self.needs_clear = true;
}
/// Loads a theme from the given file.
@ -405,6 +397,14 @@ impl Cursive {
self.set_fps(if autorefresh { 30 } else { 0 });
}
/// Returns the current refresh rate, if any.
///
/// Returns `None` if no auto-refresh is set. Otherwise, returns the rate
/// in frames per second.
pub fn fps(&self) -> Option<NonZeroU32> {
self.fps
}
/// Returns a reference to the currently active screen.
pub fn screen(&self) -> &views::StackView {
self.root.get_inner().screen().unwrap()
@ -452,13 +452,13 @@ impl Cursive {
/// ```rust
/// # use cursive_core::{Cursive, views, view};
/// # use cursive_core::traits::*;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_layer(views::TextView::new("Text #1").with_name("text"));
///
/// siv.add_global_callback('p', |s| {
/// s.call_on(
/// &view::Selector::Id("text"),
/// &view::Selector::Name("text"),
/// |view: &mut views::TextView| {
/// view.set_content("Text #2");
/// },
@ -486,7 +486,7 @@ impl Cursive {
/// ```rust
/// # use cursive_core::{Cursive, views};
/// # use cursive_core::traits::*;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_layer(views::TextView::new("Text #1")
/// .with_name("text"));
@ -530,7 +530,7 @@ impl Cursive {
/// ```rust
/// # use cursive_core::Cursive;
/// # use cursive_core::views::{TextView, ViewRef};
/// # let mut siv = Cursive::dummy();
/// # let mut siv = Cursive::new();
/// use cursive_core::traits::Identifiable;
///
/// siv.add_layer(TextView::new("foo").with_name("id"));
@ -546,7 +546,7 @@ impl Cursive {
/// ```rust
/// # use cursive_core::Cursive;
/// # use cursive_core::views::{SelectView};
/// # let mut siv = Cursive::dummy();
/// # let mut siv = Cursive::new();
/// use cursive_core::traits::Identifiable;
///
/// let select = SelectView::new().item("zero", 0u32).item("one", 1u32);
@ -606,7 +606,7 @@ impl Cursive {
///
/// ```rust
/// # use cursive_core::*;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_global_callback('q', |s| s.quit());
/// ```
@ -702,7 +702,7 @@ impl Cursive {
///
/// ```rust
/// use cursive_core::Cursive;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_global_callback('q', |s| s.quit());
/// siv.clear_global_callbacks('q');
@ -731,7 +731,7 @@ impl Cursive {
///
/// ```rust
/// use cursive_core::{Cursive, views};
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_layer(views::TextView::new("Hello world!"));
/// ```
@ -799,52 +799,18 @@ impl Cursive {
}
}
/// Returns the size of the screen, in characters.
pub fn screen_size(&self) -> Vec2 {
self.backend.screen_size()
}
fn layout(&mut self) {
let size = self.screen_size();
let offset = if self.menubar.autohide { 0 } else { 1 };
let size = size.saturating_sub((0, offset));
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;
/// Try to process a single callback.
///
/// Returns `true` if a callback was processed, `false` if there was
/// nothing to process.
pub(crate) fn process_callback(&mut self) -> bool {
match self.cb_source.try_recv() {
Ok(cb) => {
cb(self);
true
}
_ => false,
}
let printer =
Printer::new(self.screen_size(), &self.theme, &*self.backend);
let selected = self.menubar.receive_events();
// Print the stackview background before the menubar
let offset = if self.menubar.autohide { 0 } else { 1 };
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.
// Draw the menubar?
if self.menubar.visible() {
let printer = printer.focused(self.menubar.receive_events());
self.menubar.draw(&printer);
}
// finally draw stackview layers
// using variables from above
self.root.get_inner().draw_fg(&sv_printer);
}
/// Returns `true` until [`quit(&mut self)`] is called.
@ -854,138 +820,65 @@ impl Cursive {
self.running
}
/// Runs the event loop.
/// Runs a dummy event loop.
///
/// It will wait for user input (key presses)
/// and trigger callbacks accordingly.
/// Initializes a dummy backend for the event loop.
pub fn run_dummy(&mut self) {
self.run_with(|| backend::Dummy::init())
}
/// Returns a new runner on the given backend.
///
/// Internally, it calls [`step(&mut self)`] until [`quit(&mut self)`] is
/// called.
/// Used to manually control the event loop. In most cases, running
/// `Cursive::run_with` will be easier.
///
/// After this function returns, you can call it again and it will start a
/// new loop.
/// The runner will borrow `self`; when dropped, it will clear out the
/// terminal, and the cursive instance will be ready for another run if
/// needed.
pub fn runner(
&mut self,
backend: Box<dyn backend::Backend>,
) -> CursiveRunner<&mut Self> {
CursiveRunner::new(self, backend)
}
/// Returns a new runner on the given backend.
///
/// [`step(&mut self)`]: #method.step
/// [`quit(&mut self)`]: #method.quit
pub fn run(&mut self) {
/// Used to manually control the event loop. In most cases, running
/// `Cursive::run_with` will be easier.
///
/// The runner will embed `self`; when dropped, it will clear out the
/// terminal, and the cursive instance will be dropped as well.
pub fn into_runner(
self,
backend: Box<dyn backend::Backend>,
) -> CursiveRunner<Self> {
CursiveRunner::new(self, backend)
}
/// Initialize the backend and runs the event loop.
///
/// Used for infallible backend initializers.
pub fn run_with<F>(&mut self, backend_init: F)
where
F: FnOnce() -> Box<dyn backend::Backend>,
{
self.try_run_with::<(), _>(|| Ok(backend_init())).unwrap();
}
/// Initialize the backend and runs the event loop.
///
/// Returns an error if initializing the backend fails.
pub fn try_run_with<E, F>(&mut self, backend_init: F) -> Result<(), E>
where
F: FnOnce() -> Result<Box<dyn backend::Backend>, E>,
{
self.running = true;
let mut runner = self.runner(backend_init()?);
self.refresh();
runner.run();
// And the big event loop begins!
while self.running {
self.step();
}
}
/// Performs a single step from the event loop.
///
/// Useful if you need tighter control on the event loop.
/// Otherwise, [`run(&mut self)`] might be more convenient.
///
/// Returns `true` if an input event or callback was received
/// during this step, and `false` otherwise.
///
/// [`run(&mut self)`]: #method.run
pub fn step(&mut self) -> bool {
let received_something = self.process_events();
self.post_events(received_something);
received_something
}
/// Performs the first half of `Self::step()`.
///
/// This is an advanced method for fine-tuned manual stepping;
/// you probably want [`run`][1] or [`step`][2].
///
/// This processes any pending event or callback. After calling this,
/// you will want to call [`post_events`][3] with the result from this
/// function.
///
/// Returns `true` if an event or callback was received,
/// and `false` otherwise.
///
/// [1]: Cursive::run()
/// [2]: Cursive::step()
/// [3]: Cursive::post_events()
pub fn process_events(&mut self) -> bool {
// Things are boring if nothing significant happened.
let mut boring = true;
// First, handle all available input
while let Some(event) = self.backend.poll_event() {
boring = false;
self.on_event(event);
if !self.running {
return true;
}
}
// Then, handle any available callback
while let Ok(cb) = self.cb_source.try_recv() {
boring = false;
cb(self);
if !self.running {
return true;
}
}
!boring
}
/// Performs the second half of `Self::step()`.
///
/// This is an advanced method for fine-tuned manual stepping;
/// you probably want [`run`][1] or [`step`][2].
///
/// You should call this after [`process_events`][3].
///
/// [1]: Cursive::run()
/// [2]: Cursive::step()
/// [3]: Cursive::process_events()
pub fn post_events(&mut self, received_something: bool) {
let boring = !received_something;
// How many times should we try if it's still boring?
// Total duration will be INPUT_POLL_DELAY_MS * repeats
// So effectively fps = 1000 / INPUT_POLL_DELAY_MS / repeats
if !boring
|| self
.fps
.map(|fps| 1000 / INPUT_POLL_DELAY_MS as u32 / fps.get())
.map(|repeats| self.boring_frame_count >= repeats)
.unwrap_or(false)
{
// We deserve to draw something!
if boring {
// We're only here because of a timeout.
self.on_event(Event::Refresh);
}
self.refresh();
}
if boring {
std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS));
self.boring_frame_count += 1;
}
}
/// Refresh the screen with the current view tree state.
pub fn refresh(&mut self) {
self.boring_frame_count = 0;
// Do we need to redraw everytime?
// Probably, actually.
// TODO: Do we need to re-layout everytime?
self.layout();
// TODO: Do we need to redraw every view every time?
// (Is this getting repetitive? :p)
self.draw();
self.backend.refresh();
Ok(())
}
/// Stops the event loop.
@ -998,13 +891,6 @@ impl Cursive {
// foo
}
/// Return the name of the backend used.
///
/// Mostly used for debugging.
pub fn backend_name(&self) -> &str {
self.backend.name()
}
/// Dump the current state of the Cursive root.
///
/// *It will clear out this `Cursive` instance* and save everything, including:
@ -1052,9 +938,3 @@ impl Cursive {
self.clear();
}
}
impl Drop for Cursive {
fn drop(&mut self) {
self.backend.finish();
}
}

View File

@ -0,0 +1,229 @@
use crate::{backend, event::Event, theme, Cursive, Vec2};
use std::borrow::{Borrow, BorrowMut};
use std::time::Duration;
// How long we wait between two empty input polls
const INPUT_POLL_DELAY_MS: u64 = 30;
/// Event loop runner for a cursive instance.
///
/// You can get one from `Cursive::runner`, then either call `.run()`, or
/// manually `.step()`.
pub struct CursiveRunner<C> {
siv: C,
backend: Box<dyn backend::Backend>,
boring_frame_count: u32,
// Last layer sizes of the stack view.
// If it changed, clear the screen.
last_sizes: Vec<Vec2>,
}
impl<C> std::ops::Deref for CursiveRunner<C>
where
C: Borrow<Cursive>,
{
type Target = Cursive;
fn deref(&self) -> &Cursive {
self.siv.borrow()
}
}
impl<C> std::ops::DerefMut for CursiveRunner<C>
where
C: BorrowMut<Cursive>,
{
fn deref_mut(&mut self) -> &mut Cursive {
self.siv.borrow_mut()
}
}
impl<C> CursiveRunner<C> {
pub(crate) fn new(siv: C, backend: Box<dyn backend::Backend>) -> Self {
CursiveRunner {
siv,
backend,
boring_frame_count: 0,
last_sizes: Vec::new(),
}
}
/// Returns the size of the screen, in characters.
fn screen_size(&self) -> Vec2 {
self.backend.screen_size()
}
/// Clean out the terminal and get back the wrapped object.
pub fn into_inner(self) -> C {
self.siv
}
}
impl<C> CursiveRunner<C>
where
C: BorrowMut<Cursive>,
{
fn layout(&mut self) {
let size = self.screen_size();
self.siv.borrow_mut().layout(size);
}
fn draw(&mut self) {
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;
}
if self.needs_clear {
self.backend.clear(
self.current_theme().palette[theme::PaletteColor::Background],
);
self.needs_clear = false;
}
let size = self.screen_size();
self.siv.borrow_mut().draw(size, &*self.backend);
}
/// Performs the first half of `Self::step()`.
///
/// This is an advanced method for fine-tuned manual stepping;
/// you probably want [`run`][1] or [`step`][2].
///
/// This processes any pending event or callback. After calling this,
/// you will want to call [`post_events`][3] with the result from this
/// function.
///
/// Returns `true` if an event or callback was received,
/// and `false` otherwise.
///
/// [1]: CursiveRun::run()
/// [2]: CursiveRun::step()
/// [3]: CursiveRun::post_events()
pub fn process_events(&mut self) -> bool {
// Things are boring if nothing significant happened.
let mut boring = true;
// First, handle all available input
while let Some(event) = self.backend.poll_event() {
boring = false;
self.on_event(event);
if !self.is_running() {
return true;
}
}
// Then, handle any available callback
while self.process_callback() {
boring = false;
if !self.is_running() {
return true;
}
}
!boring
}
/// Performs the second half of `Self::step()`.
///
/// This is an advanced method for fine-tuned manual stepping;
/// you probably want [`run`][1] or [`step`][2].
///
/// You should call this after [`process_events`][3].
///
/// [1]: Cursive::run()
/// [2]: Cursive::step()
/// [3]: Cursive::process_events()
pub fn post_events(&mut self, received_something: bool) {
let boring = !received_something;
// How many times should we try if it's still boring?
// Total duration will be INPUT_POLL_DELAY_MS * repeats
// So effectively fps = 1000 / INPUT_POLL_DELAY_MS / repeats
if !boring
|| self
.fps()
.map(|fps| 1000 / INPUT_POLL_DELAY_MS as u32 / fps.get())
.map(|repeats| self.boring_frame_count >= repeats)
.unwrap_or(false)
{
// We deserve to draw something!
if boring {
// We're only here because of a timeout.
self.on_event(Event::Refresh);
}
self.refresh();
}
if boring {
std::thread::sleep(Duration::from_millis(INPUT_POLL_DELAY_MS));
self.boring_frame_count += 1;
}
}
/// Refresh the screen with the current view tree state.
pub fn refresh(&mut self) {
self.boring_frame_count = 0;
// Do we need to redraw everytime?
// Probably, actually.
// TODO: Do we need to re-layout everytime?
self.layout();
// TODO: Do we need to redraw every view every time?
// (Is this getting repetitive? :p)
self.draw();
self.backend.refresh();
}
/// Return the name of the backend used.
///
/// Mostly used for debugging.
pub fn backend_name(&self) -> &str {
self.backend.name()
}
/// Performs a single step from the event loop.
///
/// Useful if you need tighter control on the event loop.
/// Otherwise, [`run(&mut self)`] might be more convenient.
///
/// Returns `true` if an input event or callback was received
/// during this step, and `false` otherwise.
///
/// [`run(&mut self)`]: #method.run
pub fn step(&mut self) -> bool {
let received_something = self.process_events();
self.post_events(received_something);
received_something
}
/// Runs the event loop.
///
/// It will wait for user input (key presses)
/// and trigger callbacks accordingly.
///
/// Internally, it calls [`step(&mut self)`] until [`quit(&mut self)`] is
/// called.
///
/// After this function returns, you can call it again and it will start a
/// new loop.
///
/// [`step(&mut self)`]: #method.step
/// [`quit(&mut self)`]: #method.quit
pub fn run(&mut self) {
self.refresh();
// And the big event loop begins!
while self.is_running() {
self.step();
}
}
}

View File

@ -52,6 +52,7 @@ pub mod traits;
pub mod vec;
mod cursive;
mod cursive_run;
mod dump;
mod printer;
mod rect;
@ -61,6 +62,7 @@ mod xy;
mod div;
pub use self::cursive::{CbSink, Cursive, ScreenId};
pub use self::cursive_run::CursiveRunner;
pub use self::dump::Dump;
pub use self::printer::Printer;
pub use self::rect::Rect;

View File

@ -144,7 +144,7 @@
//! method (or use [`theme::load_theme_file`] to aquire the theme object).
//!
//! ```rust,ignore
//! let mut siv = Cursive::dummy();
//! let mut siv = Cursive::new();
//! // Embed the theme with the binary.
//! siv.load_toml(include_str!("<path_to_theme_file>.toml")).unwrap();
//! ```

View File

@ -54,7 +54,7 @@ pub fn immutify<F: FnMut(&mut Cursive)>(
/// ```rust
/// # use cursive_core::{Cursive, immut1};
/// # fn main() {
/// # let mut siv = Cursive::dummy();
/// # let mut siv = Cursive::new();
/// let mut i = 0;
/// // `Cursive::add_global_callback` takes a `Fn(&mut Cursive)`
/// siv.add_global_callback('q', immut1!(move |s: &mut Cursive| {

View File

@ -19,7 +19,7 @@ pub trait Nameable: View + Sized {
/// # use cursive_core::view::Resizable;
/// use cursive_core::view::Nameable;
///
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
/// siv.add_layer(
/// TextView::new("foo")
/// .with_name("text")

View File

@ -34,7 +34,7 @@ pub type OnSubmit = dyn Fn(&mut Cursive, &str);
/// # use cursive_core::Cursive;
/// # use cursive_core::traits::*;
/// # use cursive_core::views::{Dialog, EditView, TextView};
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// // Create a dialog with an edit text and a button.
/// // The user can either hit the <Ok> button,

View File

@ -13,7 +13,7 @@ use crate::Printer;
/// use cursive_core::views::{Button, EnableableView, Checkbox, LinearLayout};
/// use cursive_core::traits::Identifiable;
///
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_layer(LinearLayout::vertical()
/// .child(EnableableView::new(Checkbox::new()).with_name("my_view"))

View File

@ -40,7 +40,7 @@ use std::rc::Rc;
/// .button("Quit", |s| s.quit()));
/// });
///
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
/// siv.add_layer(Dialog::around(time_select)
/// .title("How long is your wait?"));
/// ```

View File

@ -176,7 +176,7 @@ impl TextContentInner {
/// ```rust
/// # use cursive_core::Cursive;
/// # use cursive_core::views::TextView;
/// let mut siv = Cursive::dummy();
/// let mut siv = Cursive::new();
///
/// siv.add_layer(TextView::new("Hello world!"));
/// ```

View File

@ -236,15 +236,17 @@ impl Backend {
}
}
impl Drop for Backend {
fn drop(&mut self) {
terminal::close();
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"bear-lib-terminal"
}
fn finish(&mut self) {
terminal::close();
}
fn set_color(&self, color: ColorPair) -> ColorPair {
let current = ColorPair {
front: blt_colour_to_colour(state::foreground()),

View File

@ -273,18 +273,8 @@ impl Backend {
}
}
impl backend::Backend for Backend {
fn poll_event(&mut self) -> Option<Event> {
match poll(Duration::from_millis(1)) {
Ok(true) => match read() {
Ok(event) => Some(self.map_key(event)),
Err(e) => panic!("{:?}", e),
},
_ => None,
}
}
fn finish(&mut self) {
impl Drop for Backend {
fn drop(&mut self) {
// We have to execute the show cursor command at the `stdout`.
execute!(
io::stdout(),
@ -296,6 +286,18 @@ impl backend::Backend for Backend {
disable_raw_mode().unwrap();
}
}
impl backend::Backend for Backend {
fn poll_event(&mut self) -> Option<Event> {
match poll(Duration::from_millis(1)) {
Ok(true) => match read() {
Ok(event) => Some(self.map_key(event)),
Err(e) => panic!("{:?}", e),
},
_ => None,
}
}
fn refresh(&mut self) {
self.stdout_mut().flush().unwrap();

View File

@ -14,7 +14,7 @@ use crate::theme::{Color, ColorPair, Effect};
use crate::utf8;
use crate::Vec2;
use self::super::split_i32;
use super::split_i32;
// Use AHash instead of the slower SipHash
type HashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
@ -321,6 +321,13 @@ impl Backend {
}
}
impl Drop for Backend {
fn drop(&mut self) {
write_to_tty(b"\x1B[?1002l").unwrap();
ncurses::endwin();
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"ncurses"
@ -341,11 +348,6 @@ impl backend::Backend for Backend {
self.parse_next()
}
fn finish(&mut self) {
write_to_tty(b"\x1B[?1002l").unwrap();
ncurses::endwin();
}
fn set_color(&self, colors: ColorPair) -> ColorPair {
// eprintln!("Color used: {:?}", colors);
let current = self.current_style.get();

View File

@ -353,6 +353,14 @@ impl Backend {
}
}
impl Drop for Backend {
fn drop(&mut self) {
print!("\x1B[?1002l");
stdout().flush().expect("could not flush stdout");
pancurses::endwin();
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"pancurses"
@ -368,12 +376,6 @@ impl backend::Backend for Backend {
pancurses::has_colors()
}
fn finish(&mut self) {
print!("\x1B[?1002l");
stdout().flush().expect("could not flush stdout");
pancurses::endwin();
}
fn set_color(&self, colors: ColorPair) -> ColorPair {
let current = self.current_style.get();

View File

@ -1,58 +0,0 @@
//! Dummy backend
use crate::backend;
use crate::event::Event;
use crate::theme;
use crate::Vec2;
/// Dummy backend that does nothing and immediately exits.
///
/// Mostly used for testing.
pub struct Backend;
impl Backend {
/// Creates a new dummy backend.
pub fn init() -> Box<dyn backend::Backend>
where
Self: Sized,
{
Box::new(Backend)
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"dummy"
}
fn finish(&mut self) {}
fn refresh(&mut self) {}
fn has_colors(&self) -> bool {
false
}
fn screen_size(&self) -> Vec2 {
(1, 1).into()
}
fn poll_event(&mut self) -> Option<Event> {
Some(Event::Exit)
}
fn print_at(&self, _: Vec2, _: &str) {}
fn print_at_rep(&self, _pos: Vec2, _repetitions: usize, _text: &str) {}
fn clear(&self, _: theme::Color) {}
// This sets the Colours and returns the previous colours
// to allow you to set them back when you're done.
fn set_color(&self, colors: theme::ColorPair) -> theme::ColorPair {
// TODO: actually save a stack of colors?
colors
}
fn set_effect(&self, _: theme::Effect) {}
fn unset_effect(&self, _: theme::Effect) {}
}

View File

@ -11,8 +11,6 @@
#[cfg(unix)]
mod resize;
pub mod dummy;
pub mod blt;
pub mod crossterm;
pub mod curses;

View File

@ -84,8 +84,6 @@ impl backend::Backend for Backend {
}
}
fn finish(&mut self) {}
fn refresh(&mut self) {
let size = self.size.get();
let current_frame =

View File

@ -203,12 +203,8 @@ impl Backend {
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"termion"
}
fn finish(&mut self) {
impl Drop for Backend {
fn drop(&mut self) {
write!(
self.terminal.get_mut(),
"{}{}",
@ -226,6 +222,12 @@ impl backend::Backend for Backend {
)
.unwrap();
}
}
impl backend::Backend for Backend {
fn name(&self) -> &str {
"termion"
}
fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair {
let current_style = self.current_style.get();

View File

@ -7,26 +7,25 @@
/// ```rust,no_run
/// use cursive::{Cursive, CursiveExt};
///
/// // Use `Cursive::default()` to pick one of the enabled backends,
/// let mut siv = Cursive::new();
///
/// // Use `CursiveExt::run()` to pick one of the enabled backends,
/// // depending on cargo features.
/// let mut siv = Cursive::default();
/// siv.run();
///
/// // Or explicitly use a specific backend
/// #[cfg(feature = "ncurses-backend")]
/// let mut siv = Cursive::ncurses();
/// siv.run_ncurses().unwrap();
/// #[cfg(feature = "panncurses-backend")]
/// let mut siv = Cursive::pancurses();
/// siv.run_pancurses().unwrap();
/// #[cfg(feature = "termion-backend")]
/// let mut siv = Cursive::termion();
/// siv.run_termion().unwrap();
/// #[cfg(feature = "crossterm-backend")]
/// let mut siv = Cursive::crossterm();
/// siv.run_crossterm().unwrap();
/// #[cfg(feature = "blt-backend")]
/// let mut siv = Cursive::blt();
/// siv.run_blt();
/// ```
pub trait CursiveExt {
/// Type of the returned cursive root.
type Cursive;
/// Tries to use one of the enabled backends.
///
/// Will fallback to the dummy backend if no other backend feature is enabled.
@ -34,73 +33,71 @@ pub trait CursiveExt {
/// # Panics
///
/// If the backend initialization fails.
fn default() -> Self::Cursive;
fn run(&mut self);
/// Creates a new Cursive root using a ncurses backend.
#[cfg(feature = "ncurses-backend")]
fn ncurses() -> std::io::Result<Self::Cursive>;
fn run_ncurses(&mut self) -> std::io::Result<()>;
/// Creates a new Cursive root using a pancurses backend.
#[cfg(feature = "pancurses-backend")]
fn pancurses() -> std::io::Result<Self::Cursive>;
fn run_pancurses(&mut self) -> std::io::Result<()>;
/// Creates a new Cursive root using a termion backend.
#[cfg(feature = "termion-backend")]
fn termion() -> std::io::Result<Self::Cursive>;
fn run_termion(&mut self) -> std::io::Result<()>;
/// Creates a new Cursive root using a crossterm backend.
#[cfg(feature = "crossterm-backend")]
fn crossterm() -> Result<Self::Cursive, crossterm::ErrorKind>;
fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind>;
/// Creates a new Cursive root using a bear-lib-terminal backend.
#[cfg(feature = "blt-backend")]
fn blt() -> Self::Cursive;
fn run_blt(&mut self);
}
impl CursiveExt for cursive_core::Cursive {
type Cursive = Self;
fn default() -> Self::Cursive {
fn run(&mut self) {
cfg_if::cfg_if! {
if #[cfg(feature = "blt-backend")] {
Self::blt()
self.run_blt()
} else if #[cfg(feature = "termion-backend")] {
Self::termion().unwrap()
self.run_termion().unwrap()
} else if #[cfg(feature = "crossterm-backend")] {
Self::crossterm().unwrap()
self.run_crossterm().unwrap()
} else if #[cfg(feature = "pancurses-backend")] {
Self::pancurses().unwrap()
self.run_pancurses().unwrap()
} else if #[cfg(feature = "ncurses-backend")] {
Self::ncurses().unwrap()
self.run_ncurses().unwrap()
} else {
log::warn!("No built-it backend, falling back to Cursive::dummy().");
Self::dummy()
self.run_dummy()
}
}
}
#[cfg(feature = "ncurses-backend")]
fn ncurses() -> std::io::Result<Self> {
Self::try_new(crate::backends::curses::n::Backend::init)
fn run_ncurses(&mut self) -> std::io::Result<()> {
self.try_run_with(crate::backends::curses::n::Backend::init)
}
#[cfg(feature = "pancurses-backend")]
fn pancurses() -> std::io::Result<Self> {
Self::try_new(crate::backends::curses::pan::Backend::init)
fn run_pancurses(&mut self) -> std::io::Result<()> {
self.try_run_with(crate::backends::curses::pan::Backend::init)
}
#[cfg(feature = "termion-backend")]
fn termion() -> std::io::Result<Self> {
Self::try_new(crate::backends::termion::Backend::init)
fn run_termion(&mut self) -> std::io::Result<()> {
self.try_run_with(crate::backends::termion::Backend::init)
}
#[cfg(feature = "crossterm-backend")]
fn crossterm() -> Result<Self, crossterm::ErrorKind> {
Self::try_new(crate::backends::crossterm::Backend::init)
fn run_crossterm(&mut self) -> Result<(), crossterm::ErrorKind> {
self.try_run_with(crate::backends::crossterm::Backend::init)
}
#[cfg(feature = "blt-backend")]
fn blt() -> Self {
Self::new(crate::backends::blt::Backend::init)
fn run_blt(&mut self) {
self.run_with(crate::backends::blt::Backend::init)
}
}

View File

@ -0,0 +1,143 @@
use crate::{backend, backends, Cursive};
/// A runnable wrapper around `Cursive`, bundling the backend initializer.
///
/// This struct embeds both `Cursive` and a backend-initializer
/// (`FnMut() -> Result<dyn Backend>`), to provide a simple `.run()` method.
///
/// This lets you pick the backend when creating the Cursive root, rather than
/// when running it.
///
/// It implements `DerefMut<Target=Cursive>`, so you can use it just like a
/// regular `Cursive` object.
pub struct CursiveRunnable {
siv: Cursive,
backend_init: Box<
dyn FnMut() -> Result<
Box<dyn backend::Backend>,
Box<dyn std::error::Error>,
>,
>,
}
impl std::ops::Deref for CursiveRunnable {
type Target = Cursive;
fn deref(&self) -> &Cursive {
&self.siv
}
}
impl std::ops::DerefMut for CursiveRunnable {
fn deref_mut(&mut self) -> &mut Cursive {
&mut self.siv
}
}
fn boxed(e: impl std::error::Error + 'static) -> Box<dyn std::error::Error> {
Box::new(e)
}
impl CursiveRunnable {
/// Creates a new Cursive wrapper, using the given backend.
pub fn new<E, F>(mut backend_init: F) -> Self
where
E: std::error::Error + 'static,
F: FnMut() -> Result<Box<dyn backend::Backend>, E> + 'static,
{
let siv = Cursive::new();
let backend_init = Box::new(move || backend_init().map_err(boxed));
Self { siv, backend_init }
}
/// Runs the event loop with the registered backend initializer.
///
/// # Panics
///
/// If the backend initialization fails.
pub fn run(&mut self) {
self.try_run().unwrap();
}
/// Runs the event loop with the registered backend initializer.
pub fn try_run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.siv.try_run_with(&mut self.backend_init)
}
/// Creates a new Cursive wrapper using the dummy backend.
///
/// Nothing will actually be output when calling `.run()`.
pub fn dummy() -> Self {
Self::new::<std::convert::Infallible, _>(|| {
Ok(cursive_core::backend::Dummy::init())
})
}
/// Creates a new Cursive wrapper using the ncurses backend.
///
/// _Requires the `ncurses-backend` feature._
#[cfg(feature = "ncurses-backend")]
pub fn ncurses() -> Self {
Self::new(backends::curses::n::Backend::init)
}
/// Creates a new Cursive wrapper using the panncurses backend.
///
/// _Requires the `panncurses-backend` feature._
#[cfg(feature = "pancurses-backend")]
pub fn pancurses() -> Self {
Self::new(backends::curses::pan::Backend::init)
}
/// Creates a new Cursive wrapper using the termion backend.
///
/// _Requires the `termion-backend` feature._
#[cfg(feature = "termion-backend")]
pub fn termion() -> Self {
Self::new(backends::termion::Backend::init)
}
/// Creates a new Cursive wrapper using the crossterm backend.
///
/// _Requires the `crossterm-backend` feature._
#[cfg(feature = "crossterm-backend")]
pub fn crossterm() -> Self {
Self::new(backends::crossterm::Backend::init)
}
/// Creates a new Cursive wrapper using the bear-lib-terminal backend.
///
/// _Requires the `blt-backend` feature._
#[cfg(feature = "blt-backend")]
pub fn blt() -> Self {
Self::new(|| Ok(backends::blt::Backend::init()))
}
/// Creates a new Cursive wrapper using one of the available backends.
///
/// Picks the first backend enabled from the list:
/// * BearLibTerminal
/// * Termion
/// * Crossterm
/// * Pancurses
/// * Ncurses
/// * Dummy
pub fn default() -> Self {
cfg_if::cfg_if! {
if #[cfg(feature = "blt-backend")] {
Self::blt()
} else if #[cfg(feature = "termion-backend")] {
Self::termion()
} else if #[cfg(feature = "crossterm-backend")] {
Self::crossterm()
} else if #[cfg(feature = "pancurses-backend")] {
Self::pancurses()
} else if #[cfg(feature = "ncurses-backend")] {
Self::ncurses()
} else {
log::warn!("No built-it backend, falling back to Cursive::dummy().");
Self::dummy()
}
}
}
}

View File

@ -14,6 +14,21 @@
//! views and configuring their behaviours.
//! * Finally, the event loop is started by calling [`Cursive::run`].
//!
//! ## Examples
//!
//! ```rust,no_run
//! use cursive::{Cursive, CursiveExt};
//! use cursive::views::TextView;
//!
//! let mut siv = Cursive::new();
//!
//! siv.add_layer(TextView::new("Hello World!\nPress q to quit."));
//!
//! siv.add_global_callback('q', |s| s.quit());
//!
//! siv.run();
//! ```
//!
//! ## Views
//!
//! Views are the main components of a cursive interface.
@ -29,21 +44,6 @@
//! events. These functions usually take an `&mut Cursive` argument, allowing
//! them to modify the view tree at will.
//!
//! ## Examples
//!
//! ```rust
//! use cursive::Cursive;
//! use cursive::views::TextView;
//!
//! let mut siv = Cursive::dummy();
//!
//! siv.add_layer(TextView::new("Hello World!\nPress q to quit."));
//!
//! siv.add_global_callback('q', |s| s.quit());
//!
//! siv.run();
//! ```
//!
//! ## Debugging
//!
//! The `Cursive` root initializes the terminal on creation, and does cleanups
@ -71,7 +71,10 @@ mod utf8;
pub mod backends;
mod cursive_ext;
mod cursive_runnable;
pub use cursive_ext::CursiveExt;
pub use cursive_runnable::CursiveRunnable;
/// Creates a new Cursive root using one of the enabled backends.
///
@ -87,43 +90,43 @@ pub use cursive_ext::CursiveExt;
/// # Panics
///
/// If the backend initialization fails.
pub fn default() -> Cursive {
Cursive::default()
pub fn default() -> CursiveRunnable {
CursiveRunnable::default()
}
/// Creates a new Cursive root using a ncurses backend.
#[cfg(feature = "ncurses-backend")]
pub fn ncurses() -> std::io::Result<Cursive> {
Cursive::ncurses()
pub fn ncurses() -> CursiveRunnable {
CursiveRunnable::ncurses()
}
/// Creates a new Cursive root using a pancurses backend.
#[cfg(feature = "pancurses-backend")]
pub fn pancurses() -> std::io::Result<Cursive> {
Cursive::pancurses()
pub fn pancurses() -> CursiveRunnable {
CursiveRunnable::pancurses()
}
/// Creates a new Cursive root using a termion backend.
#[cfg(feature = "termion-backend")]
pub fn termion() -> std::io::Result<Cursive> {
Cursive::termion()
pub fn termion() -> CursiveRunnable {
CursiveRunnable::termion()
}
/// Creates a new Cursive root using a crossterm backend.
#[cfg(feature = "crossterm-backend")]
pub fn crossterm() -> Result<Cursive, crossterm::ErrorKind> {
Cursive::crossterm()
pub fn crossterm() -> CursiveRunnable {
CursiveRunnable::crossterm()
}
/// Creates a new Cursive root using a bear-lib-terminal backend.
#[cfg(feature = "blt-backend")]
pub fn blt() -> Cursive {
Cursive::blt()
pub fn blt() -> CursiveRunnable {
CursiveRunnable::blt()
}
/// Creates a new Cursive root using a dummy backend.
///
/// Nothing will be output. This is mostly here for tests.
pub fn dummy() -> Cursive {
Cursive::dummy()
pub fn dummy() -> CursiveRunnable {
CursiveRunnable::dummy()
}

View File

@ -0,0 +1,21 @@
use cursive::views;
fn main() {
let mut siv = cursive::default();
siv.clear_global_callbacks(cursive::event::Event::CtrlChar('c'));
siv.set_on_pre_event(cursive::event::Event::CtrlChar('c'), |s| {
s.add_layer(
views::Dialog::text("Do you want to quit?")
.button("Yes", |s| s.quit())
.button("No", |s| {
s.pop_layer();
}),
);
});
siv.add_layer(views::Dialog::text("Try pressing Ctrl-C!"));
siv.run();
}

View File

@ -24,7 +24,7 @@ pub mod tests {
use std::cell::RefCell;
pub struct BasicSetup {
siv: Cursive,
siv: CursiveRunner<Cursive>,
screen_stream: crossbeam_channel::Receiver<ObservedScreen>,
input: crossbeam_channel::Sender<Option<Event>>,
last_screen: RefCell<Option<ObservedScreen>>,
@ -61,7 +61,7 @@ pub mod tests {
let backend = backends::puppet::Backend::init(Some(size));
let sink = backend.stream();
let input = backend.input();
let mut siv = Cursive::new(|| backend);
let mut siv = Cursive::new().into_runner(backend);
// Let's add a ResizedView to keep the list at a reasonable size
// (it can scroll anyway).