mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Make backends pausable
This commit is contained in:
parent
01bb32d640
commit
2cd6d7a80c
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
229
cursive-core/src/cursive_run.rs
Normal file
229
cursive-core/src/cursive_run.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
//! ```
|
||||
|
@ -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| {
|
||||
|
@ -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")
|
||||
|
@ -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,
|
||||
|
@ -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"))
|
||||
|
@ -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?"));
|
||||
/// ```
|
||||
|
@ -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!"));
|
||||
/// ```
|
||||
|
@ -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()),
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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) {}
|
||||
}
|
@ -11,8 +11,6 @@
|
||||
#[cfg(unix)]
|
||||
mod resize;
|
||||
|
||||
pub mod dummy;
|
||||
|
||||
pub mod blt;
|
||||
pub mod crossterm;
|
||||
pub mod curses;
|
||||
|
@ -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 =
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
143
cursive/src/cursive_runnable.rs
Normal file
143
cursive/src/cursive_runnable.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
21
examples/src/bin/ctrl_c.rs
Normal file
21
examples/src/bin/ctrl_c.rs
Normal 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();
|
||||
}
|
@ -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).
|
||||
|
Loading…
Reference in New Issue
Block a user