cursive_core-0.4.6/.cargo_vcs_info.json0000644000000001520000000000100135310ustar { "git": { "sha1": "e592ade2f277ea95bc093a4319427ed82a5075b8" }, "path_in_vcs": "cursive-core" }cursive_core-0.4.6/Cargo.toml0000644000000045140000000000100115350ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.61" name = "cursive_core" version = "0.4.6" authors = ["Alexandre Bury "] build = false include = [ "src/**/*", "LICENSE", "README.md", ] autobins = false autoexamples = false autotests = false autobenches = false description = "Core components for the Cursive TUI" documentation = "https://docs.rs/cursive" readme = "README.md" keywords = [ "ncurses", "TUI", "UI", ] categories = [ "command-line-interface", "gui", ] license = "MIT" repository = "https://github.com/gyscos/cursive" [package.metadata.docs.rs] all-features = true [lib] name = "cursive_core" path = "src/lib.rs" [dependencies.ahash] version = "0.8" [dependencies.ansi-parser] version = "0.9.0" optional = true [dependencies.compact_str] version = "0.8.0" [dependencies.crossbeam-channel] version = "0.5" [dependencies.cursive-macros] version = "0.1.0" [dependencies.enum-map] version = "2.0" [dependencies.enumset] version = "1.1.4" [dependencies.inventory] version = "0.3.1" optional = true [dependencies.lazy_static] version = "1" [dependencies.log] version = "0.4" [dependencies.num] version = "0.4" default-features = false [dependencies.parking_lot] version = "0.12.1" features = ["arc_lock"] [dependencies.pulldown-cmark] version = "0.11" optional = true default-features = false [dependencies.serde_json] version = "1.0.85" [dependencies.time] version = "0.3" features = [ "local-offset", "formatting", ] [dependencies.toml] version = "0.8" optional = true [dependencies.unicode-segmentation] version = "1" [dependencies.unicode-width] version = "0.1" [dependencies.xi-unicode] version = "0.3" [dev-dependencies.serde_yaml] version = "0.9.11" [features] ansi = ["dep:ansi-parser"] builder = [ "dep:inventory", "cursive-macros/builder", ] default = [] doc-cfg = [] markdown = ["dep:pulldown-cmark"] cursive_core-0.4.6/Cargo.toml.orig000064400000000000000000000032411046102023000152120ustar 00000000000000[package] authors = ["Alexandre Bury "] categories = ["command-line-interface", "gui"] description = "Core components for the Cursive TUI" documentation = "https://docs.rs/cursive" keywords = ["ncurses", "TUI", "UI"] license = "MIT" name = "cursive_core" readme = "README.md" repository = "https://github.com/gyscos/cursive" version = "0.4.6" edition = "2021" rust-version = "1.61" include = ["src/**/*", "LICENSE", "README.md"] [package.metadata.docs.rs] all-features = true [dependencies] enum-map = "2.0" enumset = "1.1.4" # EnumSet::empty() became `const` in 1.1.4 log = "0.4" unicode-segmentation = "1" unicode-width = "0.1" xi-unicode = "0.3" crossbeam-channel = "0.5" lazy_static = "1" ahash = "0.8" serde_json = "1.0.85" parking_lot = { version = "0.12.1", features = ["arc_lock"] } compact_str = "0.8.0" [dependencies.cursive-macros] path = "../cursive-macros" version = "0.1.0" [dependencies.inventory] version = "0.3.1" optional = true [dependencies.ansi-parser] version = "0.9.0" optional = true [dependencies.time] version = "0.3" features = ["local-offset", "formatting"] [dependencies.toml] optional = true version = "0.8" [dependencies.num] default-features = false version = "0.4" [dependencies.pulldown-cmark] default-features = false optional = true version = "0.11" [features] default = [] doc-cfg = [] # Add doc-centric features builder = ["dep:inventory", "cursive-macros/builder"] # Enable building views from configs markdown = ["dep:pulldown-cmark"] # Enables a markdown-to-styled string parser ansi = ["dep:ansi-parser"] # Enables an ansi-to-styled string parser [lib] name = "cursive_core" [dev-dependencies] serde_yaml = "0.9.11" cursive_core-0.4.6/LICENSE000064400000000000000000000020421046102023000133260ustar 00000000000000Copyright (c) 2015 Alexandre Bury Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cursive_core-0.4.6/README.md000064400000000000000000000003301046102023000135760ustar 00000000000000# Cursive-core This crate is where most of cursive is defined, except for the backends. Third-party libraries are encouraged to depend on this instead of `cursive`, as it should have fewer semver breaking updates. cursive_core-0.4.6/src/align.rs000064400000000000000000000113601046102023000145530ustar 00000000000000//! Tools to control view alignment. /// Specifies the alignment along both horizontal and vertical directions. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Align { /// Horizontal alignment policy pub h: HAlign, /// Vertical alignment policy pub v: VAlign, } impl Align { /// Creates a new Align object from the given alignments. pub const fn new(h: HAlign, v: VAlign) -> Self { Align { h, v } } /// Creates a top-left alignment. pub const fn top_left() -> Self { Align::new(HAlign::Left, VAlign::Top) } /// Creates a top-right alignment. pub const fn top_right() -> Self { Align::new(HAlign::Right, VAlign::Top) } /// Creates a top-center alignment. pub const fn top_center() -> Self { Align::new(HAlign::Center, VAlign::Top) } /// Creates a bottom-left alignment. pub const fn bot_left() -> Self { Align::new(HAlign::Left, VAlign::Bottom) } /// Creates a bottom-right alignment. pub const fn bot_right() -> Self { Align::new(HAlign::Right, VAlign::Bottom) } /// Creates a bottom-center alignment. pub const fn bot_center() -> Self { Align::new(HAlign::Center, VAlign::Bottom) } /// Creates a center-right alignment. pub const fn center_left() -> Self { Align::new(HAlign::Left, VAlign::Center) } /// Creates a center-right alignment. pub const fn center_right() -> Self { Align::new(HAlign::Right, VAlign::Center) } /// Creates an alignment centered both horizontally and vertically. pub const fn center() -> Self { Align::new(HAlign::Center, VAlign::Center) } } impl std::str::FromStr for Align { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "top_left" => Self::top_left(), "top_center" => Self::top_center(), "top_right" => Self::top_right(), "center_left" => Self::center_left(), "center" => Self::center(), "center_right" => Self::center_right(), "bot_left" | "bottom_left" => Self::bot_left(), "bot_center" | "bottom_center" => Self::bot_center(), "bot_right" | "bottom_right" => Self::bot_right(), _ => return Err(()), }) } } /// Horizontal alignment #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum HAlign { /// Place the element to the left of available space Left, /// Place the element horizontally in the center of available space Center, /// Place the element to the right of available space Right, } impl std::str::FromStr for HAlign { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "left" | "Left" => Self::Left, "center" | "Center" => Self::Center, "right" | "Right" => Self::Right, _ => return Err(()), }) } } /// Vertical alignment #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VAlign { /// Place the element at the top of available space Top, /// Place the element vertically in the center of available space Center, /// Place the element at the bottom of available space Bottom, } impl std::str::FromStr for VAlign { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "Top" | "top" => Self::Top, "Center" | "center" => Self::Center, "Bottom" | "bottom" | "Bot" | "bot" => Self::Bottom, _ => return Err(()), }) } } impl HAlign { /// Returns the offset required to position a view. /// /// When drawing a view with size `content` when the available size is /// `container`, printing at the resulting offset will align the view as /// desired. pub const fn get_offset(&self, content: usize, container: usize) -> usize { if container < content { 0 } else { match *self { HAlign::Left => 0, HAlign::Center => (container - content) / 2, HAlign::Right => container - content, } } } } impl VAlign { /// Returns the offset required to position a view. /// /// When drawing a view with size `content` when the available size is /// `container`, printing at the resulting offset will align the view as /// desired. pub const fn get_offset(&self, content: usize, container: usize) -> usize { if container < content { 0 } else { match *self { VAlign::Top => 0, VAlign::Center => (container - content) / 2, VAlign::Bottom => container - content, } } } } cursive_core-0.4.6/src/backend.rs000064400000000000000000000104611046102023000150510ustar 00000000000000//! Define the backend trait for actual terminal interaction. //! //! Cursive doesn't print anything by itself: it delegates this job to a //! backend library, which handles all actual input and output. //! //! This module defines the [`Backend`] trait, to be implemented by actual //! types, usually using third-party libraries. //! //! [`Backend`]: trait.Backend.html use crate::event::Event; use crate::style; use crate::Vec2; /// Trait defining the required methods to be a backend. /// /// A backend is the interface between the abstract view tree and the actual /// input/output, like a terminal. /// /// It usually delegates the work to a terminal-handling library like ncurses /// or termion, or it can entirely simulate a terminal and show it as a /// graphical window (`BearLibTerminal`). /// /// When creating a new cursive tree with [`Cursive::new()`][1], you will need to /// provide a backend initializer - usually their `init()` function. /// /// Backends are responsible for handling input and converting it to [`Event`]. /// Input must be non-blocking, it will be polled regularly. /// /// [1]: ../struct.Cursive.html#method.new /// [`Event`]: ../event/enum.Event.html pub trait Backend { /// Polls the backend for any input. /// /// Should return immediately: /// * `None` if no event is currently available. /// * `Some(event)` for each event to process. fn poll_event(&mut self) -> Option; /// Sets the title for the backend. /// /// This usually sets the terminal window title. fn set_title(&mut self, title: String); /// Refresh the screen. /// /// This will be called each frame after drawing has been done. /// /// A backend could, for example, buffer any print command, and apply /// everything when refresh() is called. fn refresh(&mut self); /// Should return `true` if this backend supports colors. fn has_colors(&self) -> bool; /// Returns the screen size. fn screen_size(&self) -> Vec2; /// Move the cursor to the given cell. /// /// The next call to `print()` will start at this place. fn move_to(&self, pos: Vec2); /// Print some text at the current cursor. /// /// The cursor will then be moved past the end of the printed text. fn print(&self, text: &str); /// Clears the screen with the given color. fn clear(&self, color: style::Color); /// Starts using a new color. /// /// This should return the previously active color. /// /// Any call to `print_at` from now on should use the given color. fn set_color(&self, colors: style::ColorPair) -> style::ColorPair; /// Enables the given effect. /// /// Any call to `print_at` from now on should use the given effect. fn set_effect(&self, effect: style::Effect); /// Returns `true` if the backend has persistent output. /// /// If true, this means that output stays there between calls to /// `refresh()`, so only delta needs to be sent. fn is_persistent(&self) -> bool { false } /// Disables the given effect. fn unset_effect(&self, effect: style::Effect); /// Returns a name to identify the backend. /// /// Mostly used for debugging. fn name(&self) -> &str { "unknown" } } /// Dummy backend that does nothing and immediately exits. /// /// Mostly used for testing. pub struct Dummy; impl Dummy { /// Creates a new dummy backend. pub fn init() -> Box where Self: Sized, { Box::new(Dummy) } } impl Backend for Dummy { fn name(&self) -> &str { "dummy" } fn set_title(&mut self, _title: String) {} fn refresh(&mut self) {} fn has_colors(&self) -> bool { false } fn screen_size(&self) -> Vec2 { (1, 1).into() } fn poll_event(&mut self) -> Option { Some(Event::Exit) } fn move_to(&self, _: Vec2) {} fn print(&self, _: &str) {} fn clear(&self, _: style::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: style::ColorPair) -> style::ColorPair { // TODO: actually save a stack of colors? colors } fn set_effect(&self, _: style::Effect) {} fn unset_effect(&self, _: style::Effect) {} } cursive_core-0.4.6/src/buffer.rs000064400000000000000000000363031046102023000147360ustar 00000000000000//! Output buffer use crate::backend::Backend; use crate::style::ConcreteStyle; use crate::{Rect, Vec2}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; /// The width of a cell. /// /// Most characters are single-width. Some asian characters and emojis are double-width. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CellWidth { /// This character takes a single cell in the grid. Single, /// This character takes 2 cells in the grid (mostly for emojis and asian characters). Double, } impl Default for CellWidth { fn default() -> Self { CellWidth::Single } } impl CellWidth { /// Convert the width as returned from `UnicodeWidthStr::width()` into a `CellWidth`. /// /// # Panics /// /// If `width > 2`. pub fn from_usize(width: usize) -> Self { match width { 1 => CellWidth::Single, 2 => CellWidth::Double, n => panic!("expected width of 1 or 2 only. Got {n}."), } } /// Returns the width as a usize: 1 or 2. pub fn as_usize(self) -> usize { match self { CellWidth::Single => 1, CellWidth::Double => 2, } } /// Returns the width of the given grapheme. /// /// # Panics /// /// If `text` has a width > 2 (it means it is not a single grapheme). pub fn from_grapheme(text: &str) -> Self { Self::from_usize(text.width()) } } /// A single cell in a grid. /// /// Most characters use 1 cell in the grid. Some wide graphemes use 2 cells /// (mostly asian characters and some emojis). #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct Cell { /// Style used for this cell. style: ConcreteStyle, /// Text buffer for this cell. /// /// Most graphemes fit in a couple bytes, but there's theoretically no limit. text: compact_str::CompactString, /// Either 1 or 2. /// /// If it's 2, the next cell should be None. /// /// Should be equal to `text.width()` /// /// TODO: Use a smaller sized integer to reduce the memory footprint? width: CellWidth, } impl Cell { /// Returns the text content of this cell. /// /// This should be a single grapheme. pub fn text(&self) -> &str { &self.text } /// Returns the width of this cell: either 1 or 2. /// /// If this returns 2, then the next cell in the grid should be empty. pub fn width(&self) -> usize { self.width.as_usize() } /// Sets the style for this cell. pub fn set_style(&mut self, style: ConcreteStyle) { self.style = style; } /// Sets the content of this cell. /// /// `text` should be a single grapheme, with width 1 or 2. /// /// # Panics /// /// If `text.width() > 2`. pub fn set_text(&mut self, text: &str) { self.text.clear(); self.text.push_str(text); self.width = CellWidth::from_grapheme(text); } fn set(&mut self, style: ConcreteStyle, text: &str, width: CellWidth) { self.style = style; self.text.clear(); self.text.push_str(text); self.width = width; } } /// A buffer for printing stuff. /// /// Can be flushed out to the backend. /// /// The goal of the buffer is to accumulate all print operation for a refresh cycle, then flush it /// all to the backend in one go. This should improve performance by: /// * Reducing the amount of cursor movements. /// * Only flushing the diff from the previous screen. /// * Removing any delay during printing the screen that could result in tearing. pub struct PrintBuffer { // A cell can be `None` if the cell before was double-wide (or more). // It can also be `None` if this is the first time since last resize. // Where we are actively writing. active_buffer: Vec>, // Last buffer flushed. // // Used to compute the diff between active and frozen when flushing. frozen_buffer: Vec>, // This is an internal cache used to remember the last style flushed to the backend. current_style: ConcreteStyle, size: Vec2, } /// A view into a rectangular area of the buffer. pub struct Window<'a> { buffer: &'a mut PrintBuffer, viewport: Rect, } impl<'a> Window<'a> { /// Returns the cell at the given location. /// /// Returns `None` if the cell is empty because the previous one was double-wide. pub fn cell_at(&self, pos: Vec2) -> Option<&Cell> { let pos = self.absolute_pos(pos)?; self.buffer.cell_at(pos) } fn absolute_pos(&self, pos: Vec2) -> Option { if !pos.fits_in(self.viewport.size()) { return None; } Some(pos + self.viewport.top_left()) } /// Iterate on the rows of this window. pub fn rows(&self) -> impl Iterator]> { self.buffer .rows() .skip(self.viewport.top()) .take(self.viewport.height()) .map(|row| &row[self.viewport.left()..=self.viewport.right()]) } /// Returns the viewport this window is covering. pub fn viewport(&self) -> Rect { self.viewport } /// Returns the size of this window. pub fn size(&self) -> Vec2 { self.viewport.size() } /// Get mutable access to the style at the given cell, if any. pub fn style_at_mut(&mut self, pos: V) -> Option<&mut ConcreteStyle> where V: Into, { let pos = pos.into(); let pos = self.absolute_pos(pos)?; self.buffer.style_at_mut(pos) } } impl Default for PrintBuffer { fn default() -> Self { Self::new() } } impl PrintBuffer { /// Create a new empty print buffer. pub const fn new() -> Self { PrintBuffer { active_buffer: Vec::new(), frozen_buffer: Vec::new(), current_style: ConcreteStyle::terminal_default(), size: Vec2::ZERO, } } /// Iterate on the rows of this buffer. pub fn rows(&self) -> impl Iterator]> { self.active_buffer.chunks(self.size.x) } /// Clear this buffer. /// /// The buffer will be empty afterwards. pub fn clear(&mut self) { let len = self.size.x * self.size.y; let reset = |buffer: &mut Vec>| { buffer.clear(); buffer.resize_with(len, Default::default); }; reset(&mut self.active_buffer); reset(&mut self.frozen_buffer); } /// Fill the buffer with the given text and style. pub fn fill(&mut self, text: &str, style: impl Into) { let style = style.into(); let width = CellWidth::from_usize(text.width()); if width != CellWidth::Single { panic!("Filling the screen with double-wide characters is not currently supported."); } for cell in &mut *self.active_buffer { let cell = cell.get_or_insert_with(Default::default); cell.style = style; cell.text.clear(); cell.text.push_str(text); cell.width = width; } } /// Returns the current size of the buffer. pub fn size(&self) -> Vec2 { self.size } /// Resize the buffer to the given size. pub fn resize(&mut self, size: Vec2) { if self.size == size { return; } self.size = size; let len = size.x * size.y; self.active_buffer.clear(); self.frozen_buffer.clear(); self.active_buffer.resize_with(len, Default::default); self.frozen_buffer.resize_with(len, Default::default); } /// Print some text at the given location. pub fn print_at(&mut self, start: Vec2, text: &str, style: ConcreteStyle) { if !(start.strictly_lt(self.size)) { return; } // If the character before was double-wide, we need to remove it. // TODO: Do not re-compute the ID of the first cell twice? let id = self.cell_id(start); if self.active_buffer[id].is_none() && start.x > 0 { // If the previous character is double-wide, then this cell would be None. // So only check that if we're None to begin with. // Here `id - 1` is safe to compute since `start.x > 0`. if let Some(ref mut prev) = self.active_buffer[id - 1] { if prev.width == CellWidth::Double { prev.width = CellWidth::Single; prev.text.clear(); prev.text.push_str(" "); // Preserve style. } } } let mut pos = start; // We only consider "regular" graphemes that can be displayed. // // Control characters should not be displayed. fn is_control_char(g: &str) -> bool { g.chars() .all(|c| matches!(c, (..='\u{001F}') | ('\u{007F}'..='\u{009F}'))) } // Fill our active buffer // TODO: Use some WithWidth(&str, usize) to not re-compute width a thousand times for g in text.graphemes(true) { let width = g.width(); if width == 0 { // Any zero-width grapheme can be ignored. // With unicode-width < 0.1.13, this includes control chars. continue; } if is_control_char(g) { // With unicode-width >= 0.1.13, control chars have non-zero width (in // practice width = 1). debug_assert_eq!( 1, width, "Control character '{g:?}' should've had a width of 1" ); debug_assert_eq!(1, "\u{FFFD}".width(), "\u{FFFD} should've had a width of 1"); self.set_cell(pos, "\u{fffd}", CellWidth::from_usize(width), style); } else { self.set_cell(pos, g, CellWidth::from_usize(width), style); } pos.x += width; } } fn cell_id(&self, pos: Vec2) -> usize { pos.x + pos.y * self.size.x } /// Get mutable access to the style at the given cell, if any. /// /// Returns `None` if the previous cell was double-wide. pub fn style_at_mut(&mut self, pos: Vec2) -> Option<&mut ConcreteStyle> { let id = self.cell_id(pos); self.active_buffer[id].as_mut().map(|cell| &mut cell.style) } /// Returns the cell at the given location. pub fn cell_at(&self, pos: Vec2) -> Option<&Cell> { let id = self.cell_id(pos); self.active_buffer[id].as_ref() } /// Returns a mutable access to a sub-region from this buffer. pub fn window(&mut self, viewport: Rect) -> Option> { if !viewport.bottom_right().fits_in(self.size) { return None; } Some(Window { buffer: self, viewport, }) } /// Get the text at the given position /// /// Returns `None` if there is no text, because the previous cell was double-wide. pub fn cell_text(&self, pos: Vec2) -> Option<&str> { let id = self.cell_id(pos); self.active_buffer[id].as_ref().map(|cell| cell.text()) } /// Get the style at the given position. /// /// Returns `None` if there is no text, because the previous cell was double-wide. pub fn cell_style(&self, pos: Vec2) -> Option { let id = self.cell_id(pos); self.active_buffer[id].as_ref().map(|cell| cell.style) } /// Set a cell. /// /// width _must_ be grapheme.width(). fn set_cell(&mut self, pos: Vec2, grapheme: &str, width: CellWidth, style: ConcreteStyle) { debug_assert_eq!(width.as_usize(), grapheme.width()); let id = self.cell_id(pos); let cell = &mut self.active_buffer[id].get_or_insert_with(Default::default); cell.set(style, grapheme, width); // If this is a double-wide grapheme, mark the next cell as blocked. for dx in 1..width.as_usize() { if pos.x + dx >= self.size.x { break; } self.active_buffer[id + dx] = None; } } /// Flush out all accumulated prints so far. /// /// * Assumes the backend was representing `self.frozen_buffer`. /// * Ensures the backend now represents `self.active_buffer`. /// * Try to minimize the commands sent to the backend to achieve that. /// /// Afterwards, replace `self.frozen_buffer` with `self.active_buffer`. /// `self.active_buffer` should not be affected by this call. /// /// Note: this does _not_ call `backend.refresh()`! /// /// Ideally it should be possible to call `flush()` at any times, possibly repeatedly. /// /// (Successive calls should do nothing.) pub fn flush(&mut self, backend: &dyn Backend) { let terminal_width = self.size.x; let persistent = backend.is_persistent(); let mut current_pos = Vec2::zero(); backend.move_to(current_pos); for (i, (active, frozen)) in self .active_buffer .iter() .zip(self.frozen_buffer.iter()) .enumerate() { if persistent && active == frozen { // TODO (optim): it may be pricier to omit printing a letter but to then "move to" the // cell to the right. So there should be a price N for the jump, and wait until we see // N bytes without changes to actually jump. If it changes before that, flush the // unchanged bytes rather than the jump. // Let's not change this cell. continue; } // eprintln!("Non matching: {frozen:?} -> {active:?}"); // Skip empty cells. let Some(Cell { style, text, width }) = active else { continue; }; let x = i % terminal_width; let y = i / terminal_width; // Should we move? if current_pos != (x, y) { current_pos = Vec2::new(x, y); backend.move_to(current_pos); } // Make sure we have the correct style // eprintln!("Applying {style:?} over {:?} for {text} @ {x}:{y}", self.current_style); apply_diff(&self.current_style, style, backend); self.current_style = *style; backend.print(text); current_pos.x += width.as_usize(); // Assume we never wrap over? } // Keep the active buffer the same, because why not? // We could also flush it to Nones? self.frozen_buffer.clone_from_slice(&self.active_buffer); } } fn apply_diff(old: &ConcreteStyle, new: &ConcreteStyle, backend: &dyn Backend) { if old.color != new.color { // TODO: flush front/back colors separately? backend.set_color(new.color); } // Check the diff between two effect sets: // - Effects in new but not in old for effect in new.effects.iter() { if old.effects.contains(effect) { continue; } backend.set_effect(effect); } // - Effects in old but not in new for effect in old.effects.iter() { if new.effects.contains(effect) { continue; } backend.unset_effect(effect); } } cursive_core-0.4.6/src/builder/resolvable.rs000064400000000000000000001623241046102023000172540ustar 00000000000000use super::{Config, Context, Error, Object}; use crate::style::ConcreteEffects; use crate::views::BoxedView; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; use std::any::Any; /// Trait for types that can be resolved from a context. /// /// They can be loaded from a config (yaml), or from a stored value (`Box`). pub trait Resolvable { /// Build from a config (a JSON value). /// /// The default implementation always fails. fn from_config(config: &Config, _context: &Context) -> Result where Self: Sized, { Err(Error::CouldNotLoad { expected_type: std::any::type_name::().to_string(), config: config.clone(), }) } /// Build from an `Any` variable. /// /// Default implementation tries to downcast to `Self`. /// /// Override if you want to try downcasting to other types as well. fn from_any(any: Box) -> Option where Self: Sized + Any, { any.downcast().ok().map(|b| *b) } } type Resolver = fn(&Config, &Context) -> Result; fn try_all(config: &Config, context: &Context, fns: &[Resolver]) -> Result { let mut errors = Vec::new(); for f in fns { match f(config, context) { Ok(res) => return Ok(res), Err(err) => errors.push(err), } } Err(Error::AllVariantsFailed { config: config.clone(), errors, }) } fn resolve_from_str(config: &Config, context: &Context, err_map: F) -> Result where T: std::str::FromStr, F: FnOnce(T::Err) -> R, R: Into, { let str: String = context.resolve(config)?; str.parse() .map_err(|e| Error::invalid_config(err_map(e).into(), config)) } // Implement a trait for Fn(A, B), Fn(&A, B), Fn(A, &B), ... // We do this by going down a tree: // (D C B A) // (C B A) to handle the case for 3 args // ... // <> [] (D C B A) to start actual work for 4 args // [D] (C B A) | // [&D] (C B A) | Here we branch and recurse // [&mut D] (C B A) | // ... // [A B C D] () | // ... | // [A B &C D] () | Final implementations // ... | // [&mut A &mut B &mut C &mut D] () macro_rules! impl_fn_from_config { // Here is a graceful end for recursion. ( $trait:ident ( ) ) => { }; ( $trait:ident < $($letters:ident $(: ?$unbound:ident)?)* > [ $($args:ty)* ] ( ) ) => { // The leaf node is the actual implementation #[allow(coherence_leak_check)] impl $trait for Arc Res + Send + Sync> {} }; ( $trait:ident < $($letters:ident $(: ?$unbound:ident)?)* > [ $($args:ty)* ] ( $head:ident $($leftover:ident)* ) ) => { // Here we just branch per ref type impl_fn_from_config!( $trait < $head $($letters $(: ?$unbound)?)* > [ $head $($args)* ] ( $($leftover)* ) ); impl_fn_from_config!( $trait < $head: ?Sized $($letters $(: ?$unbound)?)* > [ & $head $($args)* ] ( $($leftover)* ) ); impl_fn_from_config!( $trait < $head: ?Sized $($letters $(: ?$unbound)?)* > [ &mut $head $($args)* ] ( $($leftover)* ) ); }; ( $trait:ident ( $head:ident $($leftover:ident)* ) ) => { // First, branch out both the true implementation and the level below. impl_fn_from_config!( $trait <> [] ( $head $($leftover)* ) ); impl_fn_from_config!( $trait ( $($leftover)* ) ); }; } /// A wrapper around a value that cannot be parsed from config, but can still be stored/retrieved /// in a context. /// /// This brings a `Resolvable` implementation that will always fail. #[derive(Clone, Debug, Eq, PartialEq)] pub struct NoConfig(pub T); impl NoConfig { /// Return the wrapped object. pub fn into_inner(self) -> T { self.0 } } impl From for NoConfig { fn from(t: T) -> Self { NoConfig(t) } } // Implement Resolvable for the wrapper, so we can resolve it. impl Resolvable for NoConfig { // We leave from_config as default (it'll always fail). // As stated in the name, this cannot be loaded from a Config. // But when loading from a variable, accept an unwrapped value. // // So users can store a `T` and load it as `NoConfig`, without having to implement // `Resolvable` for `T`. fn from_any(any: Box) -> Option where Self: Sized + Any, { // First try an actual NoConfig any.downcast() .map(|b| *b) // Then, try a bare T .or_else(|any| any.downcast::().map(|b| NoConfig(*b))) .ok() } } impl Resolvable for Option where T: Resolvable, { fn from_config(config: &Config, context: &Context) -> Result { if let Config::Null = config { Ok(None) } else { Ok(Some(T::from_config(config, context)?)) } } fn from_any(any: Box) -> Option where Self: Sized + Any, { // First try the option, then try bare T. any.downcast::() .map(|b| *b) .or_else(|any| T::from_any(any).map(|b| Some(b)).ok_or(())) .ok() } } impl Resolvable for Config { fn from_config(config: &Config, _context: &Context) -> Result { Ok(config.clone()) } } impl Resolvable for Object { fn from_config(config: &Config, _context: &Context) -> Result { config .as_object() .ok_or_else(|| Error::invalid_config("Expected an object", config)) .cloned() } } impl Resolvable for Box { fn from_config(config: &Config, context: &Context) -> Result { let boxed: BoxedView = context.build(config)?; Ok(boxed.unwrap()) } } impl Resolvable for BoxedView { fn from_config(config: &Config, context: &Context) -> Result { context.build(config) } } impl Resolvable for crate::style::BaseColor { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected base color") } } impl Resolvable for crate::style::Palette { fn from_config(config: &Config, context: &Context) -> Result { let mut palette = Self::default(); let config = config .as_object() .ok_or_else(|| Error::invalid_config("Expected object", config))?; for (key, value) in config { if let Ok(value) = context.resolve(value) { palette.set_color(key, value); } else if let Some(value) = value.as_object() { // We don't currently support namespace themes here. // ¯\_(ツ)_/¯ log::warn!( "Namespaces are not currently supported in configs. (When reading color for `{key}`: {value:?}.)" ); } } Ok(palette) } } impl Resolvable for crate::style::BorderStyle { fn from_config(config: &Config, context: &Context) -> Result { let borders: String = context.resolve(config)?; Ok(Self::from(&borders)) } } impl Resolvable for crate::style::PaletteStyle { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { resolve_from_str(config, context, |_| "Expected valid palette style") } } impl Resolvable for crate::style::StyleType { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { try_all( config, context, &[ |config, context| { let style = context.resolve::(config)?; Ok(crate::style::StyleType::Palette(style)) }, |config, context| { let style = context.resolve::(config)?; Ok(crate::style::StyleType::Style(style)) }, ], ) } } impl Resolvable for crate::style::Style { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { try_all( config, context, &[ |config, context| { // Option A: explicit effects and color let effects = context.resolve(&config["effects"])?; let color = context.resolve(&config["color"])?; Ok(crate::style::Style { effects, color }) }, |config, context| { // Option B: just a color style let style = context.resolve::(config)?; Ok(crate::style::Style::from(style)) }, |config, context| { // Option C: just effect(s) let effects = context.resolve::(config)?; Ok(crate::style::Style::from(effects)) }, ], ) } } impl Resolvable for crate::theme::Theme { fn from_config(config: &Config, context: &Context) -> Result { let mut theme = Self::default(); if let Some(shadow) = context.resolve(&config["shadow"])? { theme.shadow = shadow; } if let Some(borders) = context.resolve(&config["borders"])? { theme.borders = borders; } if let Some(palette) = context.resolve(&config["palette"])? { theme.palette = palette; } Ok(theme) } } // A bunch of `impl From` can easily implement Resolvable impl Resolvable for Box where T: 'static + Resolvable, { fn from_config(config: &Config, context: &Context) -> Result { Ok(Box::new(T::from_config(config, context)?)) } fn from_any(any: Box) -> Option { // First try a Box match any.downcast::().map(|b| *b) { Ok(res) => Some(res), // If it fails, try T::from_any (unboxed stored value) Err(any) => T::from_any(any).map(Into::into), } } } impl Resolvable for Arc where T: 'static + Resolvable, { fn from_config(config: &Config, context: &Context) -> Result { Ok(Arc::new(T::from_config(config, context)?)) } fn from_any(any: Box) -> Option { // First try a Arc match any.downcast::().map(|b| *b) { Ok(res) => Some(res), Err(any) => T::from_any(any).map(Into::into), } } } impl Resolvable for HashMap where K: 'static + std::str::FromStr + Eq + std::hash::Hash, T: 'static + Resolvable, { fn from_config(config: &Config, context: &Context) -> Result { let config = match config { Config::Null => return Ok(HashMap::new()), Config::Object(config) => config, // Missing value get an empty vec _ => return Err(Error::invalid_config("Expected object", config)), }; config .iter() .map(|(k, v)| { context.resolve(v).and_then(|v| { Ok(( k.parse::() .map_err(|_| Error::invalid_config("Error parsing key", config))?, v, )) }) }) .collect() } } impl Resolvable for Vec where T: 'static + Resolvable, { fn from_config(config: &Config, context: &Context) -> Result { let config = match config { Config::Array(config) => config, // Missing value get an empty vec Config::Null => return Ok(Vec::new()), _ => return Err(Error::invalid_config("Expected array", config)), }; config.iter().map(|v| context.resolve(v)).collect() } // TODO: Allow loading from `Vec>` and downcasting one by one? } impl Resolvable for [T; N] where T: 'static + Resolvable + Clone, { fn from_any(any: Box) -> Option where Self: Sized + Any, { // Allow storing a `Vec` with the correct size any.downcast() .map(|b| *b) .or_else(|any| { any.downcast::>() .ok() .and_then(|vec| (*vec).try_into().ok()) .ok_or(()) }) .ok() } fn from_config(config: &Config, context: &Context) -> Result { let vec = Vec::::from_config(config, context)?; vec.try_into() .map_err(|_| Error::invalid_config("Expected array of size {N}", config)) } } impl Resolvable for crate::style::Rgb { fn from_any(any: Box) -> Option { // Accept both Rgb and Rgb as stored values. any.downcast() .map(|b| *b) .or_else(|any| { any.downcast::>() .map(|rgb| rgb.as_f32()) }) .ok() } fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { // Try as a hex string? if let Ok(rgb) = context.resolve::(config) { if let Ok(rgb) = rgb.parse::>() { return Ok(rgb.as_f32()); } } // Allow storing a list of f32 or a list of u8. if let Ok(rgb) = context.resolve::<[u8; 3]>(config) { // Try u8 first. If it's all u8, trying as f32 would also work. return Ok(crate::style::Rgb::::from(rgb).as_f32()); } if let Ok(rgb) = context.resolve::<[f32; 3]>(config) { return Ok(Self::from(rgb)); } // TODO: Here too, try as u8 first, then again as f32. if let Some(rgb) = config.as_object().and_then(|config| { let r = config.get("r").or_else(|| config.get("R"))?; let g = config.get("g").or_else(|| config.get("G"))?; let b = config.get("b").or_else(|| config.get("B"))?; let r = context.resolve(r).ok()?; let g = context.resolve(g).ok()?; let b = context.resolve(b).ok()?; Some(Self { r, g, b }) }) { return Ok(rgb); } Err(Error::invalid_config( "Could not parse as a RGB color.", config, )) } } impl Resolvable for crate::style::Rgb { fn from_any(any: Box) -> Option where Self: Sized + Any, { // Accept both Rgb and Rgb as stored values. any.downcast() .map(|b| *b) .or_else(|any| { any.downcast::>() .map(|rgb| rgb.as_u8()) }) .ok() } fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { // Try as a hex string? if let Ok(rgb) = context.resolve::(config) { if let Ok(rgb) = rgb.parse::>() { return Ok(rgb); } } // Allow storing a list of f32 or a list of u8. if let Ok(rgb) = context.resolve::<[u8; 3]>(config) { // Try u8 first. If it's all u8, trying as f32 would also work. return Ok(Self::from(rgb)); } // TODO: Here too, try as u8 first, then again as f32. if let Some(rgb) = config.as_object().and_then(|config| { let r = config.get("r").or_else(|| config.get("R"))?; let g = config.get("g").or_else(|| config.get("G"))?; let b = config.get("b").or_else(|| config.get("B"))?; let r = context.resolve(r).ok()?; let g = context.resolve(g).ok()?; let b = context.resolve(b).ok()?; Some(Self { r, g, b }) }) { return Ok(rgb); } let rgb: [f32; 3] = context.resolve(config)?; Ok(crate::style::Rgb::::from(rgb).as_u8()) } } impl Resolvable for crate::style::gradient::Dynterpolator { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let config = config .as_object() .ok_or_else(|| Error::invalid_config("Expected object", config))?; match config .iter() .next() .map(|(key, config)| (key.as_str(), config)) .ok_or_else(|| Error::invalid_config("Expected non-empty object", config))? { ("radial", config) => { let radial: crate::style::gradient::Radial = context.resolve(config)?; Ok(Box::new(radial)) } ("angled", config) => { let angled: crate::style::gradient::Angled = context.resolve(config)?; Ok(Box::new(angled)) } ("bilinear", config) => { let bilinear: crate::style::gradient::Bilinear = context.resolve(config)?; Ok(Box::new(bilinear)) } // TODO: Allow external libraries to define their own blueprints to be used here? // Something like a type-map of blueprints?... (key, _) => Err(Error::invalid_config( format!("Received unsupported gradient type {key}."), config, )), } } } impl Resolvable for crate::style::gradient::Bilinear { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let top_left = context.resolve(&config["top_left"])?; let top_right = context.resolve(&config["top_right"])?; let bottom_right = context.resolve(&config["bottom_right"])?; let bottom_left = context.resolve(&config["bottom_left"])?; Ok(Self { top_left, top_right, bottom_right, bottom_left, }) } } impl Resolvable for crate::style::gradient::Angled { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let angle_rad = match context.resolve(&config["angle_rad"]) { Ok(angle_rad) => angle_rad, Err(err1) => match context.resolve::(&config["angle_deg"]) { Ok(angle_deg) => angle_deg * std::f32::consts::PI / 180f32, Err(err2) => { return Err(Error::AllVariantsFailed { config: config.clone(), errors: vec![err1, err2], }) } }, }; let gradient = context.resolve(&config["gradient"])?; Ok(Self { angle_rad, gradient, }) } } impl Resolvable for crate::style::gradient::Radial { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let center = context.resolve(&config["center"])?; let gradient = context.resolve(&config["gradient"])?; Ok(Self { center, gradient }) } } impl Resolvable for crate::style::gradient::Linear { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { use crate::style::Rgb; // Options: // - A list of Rgb (evenly spaced) // - A list of (f32, Rgb) // - An object with start, end, and optionally middle, with a list of (f32, Rgb) // - Some presets strings? Rainbow? match config { Config::Array(array) => { let mut errors = Vec::new(); match array .iter() .map(|config| context.resolve::>(config)) .collect::, _>>() { Ok(colors) => return Ok(Self::evenly_spaced(&colors)), Err(err) => errors.push(err), } match array .iter() .map(|config| context.resolve(config)) .collect::, _>>() { Ok(points) => return Ok(Self::new(points)), Err(err) => errors.push(err), } return Err(Error::AllVariantsFailed { config: config.clone(), errors, }); } Config::Object(object) => { if let (Some(start), Some(end)) = (object.get("start"), object.get("end")) { return Ok(Self::simple( context.resolve::>(start)?, context.resolve::>(end)?, )); } if let Some(points) = object.get("points") { let points = points .as_array() .ok_or_else(|| Error::invalid_config("Expected array", config))?; let points = points .iter() .map(|config| context.resolve(config)) .collect::, _>>()?; return Ok(Self::new(points)); } } Config::String(string) => match string.as_str() { // TODO: Allow external libs to define their own aliases to resolve here? "rainbow" => return Ok(Self::rainbow()), "black_to_white" | "black to white" => return Ok(Self::black_to_white()), _ => (), }, _ => (), } Err(Error::invalid_config( "Expected array, object or string", config, )) } } // ```yaml // color: red // color: // dark: red // color: // rgb: [1, 2, 4] // ``` impl Resolvable for crate::style::Color { fn from_config(config: &Config, context: &Context) -> Result { Ok(match config { Config::String(config) => Self::parse(config) .ok_or_else(|| Error::invalid_config("Could not parse color", config))?, Config::Object(config) => { // Possibly keywords: // - light // - dark // - rgb let (key, value) = config .iter() .next() .ok_or_else(|| Error::invalid_config("", config))?; match key.as_str() { "light" => Self::Light(context.resolve(value)?), "dark" => Self::Dark(context.resolve(value)?), "rgb" => { let array: [u8; 3] = context.resolve(value)?; Self::Rgb(array[0], array[1], array[2]) } _ => return Err(Error::invalid_config("Found unexpected key", config)), } } Config::Array(_) => { // Assume r, g, b let array: [u8; 3] = context.resolve(config)?; Self::Rgb(array[0], array[1], array[2]) } _ => return Err(Error::invalid_config("Found unsupported type", config)), }) } } impl Resolvable for crate::style::ConcreteStyle { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { // We _need_ colors for a concrete style, since "inherit parent" is not an option. try_all( config, context, &[ |config, context| { let color = context.resolve(config)?; Ok(Self { effects: ConcreteEffects::empty(), color, }) }, |config, context| { let effects = context .resolve::>(&config["effects"])? .unwrap_or_else(ConcreteEffects::empty); let color = context.resolve(&config["color"])?; Ok(Self { effects, color }) }, ], ) } } impl Resolvable for crate::style::ConcreteEffects { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { use crate::style::Effect; // Either a single effect string, or a list of effects try_all( config, context, &[ |config, context| { // Option A: a single effect as string let effect = resolve_from_str(config, context, |_| "Expected valid effect")?; { Ok(ConcreteEffects::only(effect)) } }, |config, context| { // Option B: a list of effects let effects = context.resolve::>(config)?; Ok(ConcreteEffects::from_iter(effects.iter().copied())) }, ], ) } } impl Resolvable for crate::style::Effects { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { use crate::style::{Effect, EffectStatus, Effects}; // Option A: a single effect as a string. if let Ok(effect) = resolve_from_str(config, context, |_| "Expected valid effect") { return Ok(Effects::only(effect)); } // Option B: a list of effects to enable. if let Ok(effects) = context.resolve::>(config) { let mut result = Effects::empty(); for effect in effects { result.insert(effect); } return Ok(result); } // Option C: a map of effect to status if let Ok(statuses) = context.resolve::>(config) { let mut result = Effects::empty(); for (key, value) in statuses { result.statuses[key] = value; } return Ok(result); } Err(Error::invalid_config( "Expected either an effect, a list of effects, or a map of effect statuses", config, )) } } impl Resolvable for crate::style::EffectStatus { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { resolve_from_str(config, context, |_| "Expected valid effect status") } } impl Resolvable for crate::style::Effect { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { resolve_from_str(config, context, |_| "Expected valid effect") } } impl Resolvable for crate::style::ColorPair { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { if let Ok(config_str) = context.resolve::(config) { if config_str == "terminal_default" { return Ok(Self::terminal_default()); } return Err(Error::invalid_config( "Expected {front, back} or terminal_default", config, )); } let front = context.resolve(&config["front"])?; let back = context.resolve(&config["back"])?; Ok(Self { front, back }) } } impl Resolvable for crate::style::PaletteColor { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Unrecognized palette color") } } impl Resolvable for crate::style::ColorType { fn from_config(config: &Config, context: &Context) -> Result { if let Ok(color) = context.resolve(config) { return Ok(Self::Color(color)); } match config { Config::String(config) => Self::from_str(config) .map_err(|_| Error::invalid_config("Unrecognized color type", config)), Config::Object(config) => { // Either a palette or a color? let (key, value) = config .iter() .next() .ok_or_else(|| Error::invalid_config("Found empty object", config))?; Ok(match key.as_str() { "palette" => Self::Palette(context.resolve(value)?), "color" => Self::Color(context.resolve(value)?), _ => { return Err(Error::invalid_config( format!("Found unrecognized key `{key}` in color type config"), config, )) } }) } _ => Err(Error::invalid_config("Expected string or object", config)), } } } impl Resolvable for crate::style::ColorStyle { fn from_config(config: &Config, context: &Context) -> Result { try_all( config, context, &[ |config, context| { let front = context.resolve(&config["front"])?; let back = context.resolve(&config["back"])?; Ok(crate::style::ColorStyle { front, back }) }, |config, context| { let front = context.resolve::(config)?; Ok(crate::style::ColorStyle::front(front)) }, ], ) } } impl Resolvable for crate::view::ScrollStrategy { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { resolve_from_str(config, context, |_| { "Expected one of keep_row, stick_to_top, stick_to_bottom" }) } } impl Resolvable for crate::view::Offset { fn from_config(config: &Config, context: &Context) -> Result { if let Some("center" | "Center") = config.as_str() { return Ok(Self::Center); } let config = config .as_object() .ok_or_else(|| Error::invalid_config("Expected `center` or an object.", config))?; let (key, value) = config .iter() .next() .ok_or_else(|| Error::invalid_config("Expected non-empty object.", config))?; match key.as_str() { "Absolute" | "absolute" => Ok(Self::Absolute(context.resolve(value)?)), "Parent" | "parent" => Ok(Self::Parent(context.resolve(value)?)), _ => Err(Error::invalid_config("Unexpected key `{key}`.", config)), } } } impl Resolvable for crate::view::SizeConstraint { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { Ok(match config { Config::String(config_str) => match config_str.as_str() { "Free" | "free" => Self::Free, "Full" | "full" => Self::Full, _ => { return Err(Error::invalid_config( "Expected `free` or `full` string, or object", config, )) } }, Config::Object(config_obj) => { if config_obj.len() != 1 { return Err(Error::invalid_config( "Expected object with a single `fixed`, `at_most` or `at_least` key", config, )); } let (key, value) = config_obj.iter().next().unwrap(); let value = context.resolve(value)?; match key.as_str() { "fixed" => Self::Fixed(value), "at_most" => Self::AtMost(value), "at_least" => Self::AtLeast(value), _ => { return Err(Error::invalid_config( "Expected `fixed`, `at_most` or `at_least` key", config, )) } } } _ => return Err(Error::invalid_config("Expected string or object", config)), }) } } impl Resolvable for crate::views::LayerPosition { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { fn option_a( config: &Config, context: &Context, ) -> Result { let value = context.resolve(&config["from_back"])?; Ok(crate::views::LayerPosition::FromBack(value)) } fn option_b( config: &Config, context: &Context, ) -> Result { let value = context.resolve(&config["from_front"])?; Ok(crate::views::LayerPosition::FromFront(value)) } option_a(config, context).or_else(|_| option_b(config, context)) } } impl Resolvable for String { fn from_config(config: &Config, _context: &Context) -> Result { match config { Config::String(config) => Ok(config.into()), Config::Bool(config) => Ok(format!("{config}")), Config::Number(n) => Ok(format!("{n}")), _ => Err(Error::invalid_config("Cannot resolve as string", config)), } } fn from_any(any: Box) -> Option where Self: Sized + Any, { // Allow either String or &'static str (for easy literal insertion) any.downcast() .map(|b| *b) .or_else(|any| any.downcast::<&'static str>().map(|str| String::from(*str))) .ok() } } impl Resolvable for (A, B) where A: Resolvable + 'static, B: Resolvable + 'static, { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let config = config .as_array() .ok_or_else(|| Error::invalid_config("Expected array", config))?; Ok((context.resolve(&config[0])?, context.resolve(&config[1])?)) } } impl Resolvable for (A, B, C) where A: Resolvable + 'static, B: Resolvable + 'static, C: Resolvable + 'static, { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let config = config .as_array() .ok_or_else(|| Error::invalid_config("Expected array", config))?; Ok(( context.resolve(&config[0])?, context.resolve(&config[1])?, context.resolve(&config[2])?, )) } } impl Resolvable for crate::utils::markup::StyledString { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { let text: String = context.resolve(config)?; Ok(Self::plain(text)) } fn from_any(any: Box) -> Option where Self: Sized + Any, { let any = match any.downcast::().map(|b| *b) { Ok(res) => return Some(res), Err(any) => any, }; any.downcast::().map(|b| Self::plain(*b)).ok() } } impl Resolvable for bool { fn from_config(config: &Config, _context: &Context) -> Result { config .as_bool() .ok_or_else(|| Error::invalid_config("Expected bool type", config)) } } macro_rules! resolve_float { ($ty:ty) => { impl Resolvable for $ty { fn from_any(any: Box) -> Option { // Accept both f32 and f64, just cast between them. any.downcast::() .map(|b| *b as Self) .or_else(|any| { any.downcast::() .map(|b| *b as Self) }) .ok() } fn from_config(config: &Config, _context: &Context) -> Result { config .as_f64() // This already handles converting from integers .map(|config| config as Self) .ok_or_else(|| Error::invalid_config(format!("Expected float value"), config)) } } }; } macro_rules! resolve_unsigned { ($ty:ty) => { impl Resolvable for $ty { fn from_any(any: Box) -> Option { // Accept any signed or unsigned integer, as long as it fits. any.downcast::() .map(|b| (*b).try_into().ok()) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .ok()? } fn from_config(config: &Config, _context: &Context) -> Result { config .as_u64() .and_then(|config| Self::try_from(config).ok()) .ok_or_else(|| { Error::invalid_config(format!("Expected unsigned <= {}", Self::MAX), config) }) } } }; } macro_rules! resolve_signed { ($ty:ty) => { impl Resolvable for $ty { fn from_any(any: Box) -> Option { // Accept any signed or unsigned integer, as long as it fits. any.downcast::() .map(|b| (*b).try_into().ok()) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .or_else(|any| any.downcast::().map(|b| (*b).try_into().ok())) .ok()? } fn from_config(config: &Config, _context: &Context) -> Result { config .as_i64() .and_then(|config| Self::try_from(config).ok()) .ok_or_else(|| { Error::invalid_config( format!("Expected {} <= unsigned <= {}", Self::MIN, Self::MAX,), config, ) }) } } }; } resolve_float!(f32); resolve_float!(f64); resolve_unsigned!(u8); resolve_unsigned!(u16); resolve_unsigned!(u32); resolve_unsigned!(u64); resolve_unsigned!(u128); resolve_unsigned!(usize); resolve_signed!(i8); resolve_signed!(i16); resolve_signed!(i32); resolve_signed!(i64); resolve_signed!(i128); resolve_signed!(isize); impl Resolvable for crate::XY { fn from_config(config: &Config, context: &Context) -> Result { Ok(match config { Config::Array(config) if config.len() == 2 => { let x = context.resolve(&config[0])?; let y = context.resolve(&config[1])?; crate::XY::new(x, y) } Config::Object(config) => { let x = context.resolve(&config["x"])?; let y = context.resolve(&config["y"])?; crate::XY::new(x, y) } // That one would require specialization? // Config::String(config) if config == "zero" => crate::Vec2::zero(), Config::String(config) if config == "zero" => { let zero = Config::from(0); let x = context.resolve(&zero)?; let y = context.resolve(&zero)?; crate::XY::new(x, y) } config => { return Err(Error::invalid_config( "Expected Array of length 2, object, or 'zero'.", config, )) } }) } } impl Resolvable for crate::Rect { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { // Option A: (top_left, bottom_right) fn option_a(config: &Config, context: &Context) -> Result { let top_left: crate::Vec2 = context.resolve(&config["top_left"])?; let bottom_right: crate::Vec2 = context.resolve(&config["bottom_right"])?; Ok(crate::Rect::from_corners(top_left, bottom_right)) } // Option B: (top_left, size) fn option_b(config: &Config, context: &Context) -> Result { let top_left: crate::Vec2 = context.resolve(&config["top_left"])?; let size: crate::Vec2 = context.resolve(&config["size"])?; Ok(crate::Rect::from_size(top_left, size)) } // Option C: (corners) fn option_c(config: &Config, context: &Context) -> Result { let corners: [crate::Vec2; 2] = context.resolve(&config["corners"])?; Ok(crate::Rect::from_corners(corners[0], corners[1])) } // Option D: (point) fn option_d(config: &Config, context: &Context) -> Result { let point: crate::Vec2 = context.resolve(&config["point"])?; Ok(crate::Rect::from_point(point)) } // TODO: automate trying several options, and return AllVariantsFailed option_a(config, context) .or_else(|_| option_b(config, context)) .or_else(|_| option_c(config, context)) .or_else(|_| option_d(config, context)) } } impl Resolvable for crate::direction::Orientation { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected horizontal or vertical") } } impl Resolvable for crate::direction::Direction { fn from_config(config: &Config, context: &Context) -> Result where Self: Sized, { resolve_from_str(config, context, |_| "Expected a valid direction") } } impl Resolvable for crate::direction::Relative { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected a relative direction") } } impl Resolvable for crate::direction::Absolute { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected an absolute direction") } } impl Resolvable for crate::view::Margins { fn from_config(config: &Config, context: &Context) -> Result { Ok(match config { Config::Object(config) => Self::lrtb( context.resolve(&config["left"])?, context.resolve(&config["right"])?, context.resolve(&config["top"])?, context.resolve(&config["bottom"])?, ), Config::Number(_) => { let n = context.resolve(config)?; Self::lrtb(n, n, n, n) } _ => return Err(Error::invalid_config("Expected object or number", config)), }) } } impl Resolvable for crate::align::Align { fn from_config(config: &Config, context: &Context) -> Result { // Try a string shortcut let option_a = |config, context| resolve_from_str(config, context, |_| "Unexpected align string"); let option_b = |config: &Config, context: &Context| { let h = context.resolve(&config["h"])?; let v = context.resolve(&config["v"])?; Ok(Self { h, v }) }; option_a(config, context).or_else(|_| option_b(config, context)) } } impl Resolvable for crate::align::VAlign { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected top, center or bottom") } } impl Resolvable for crate::align::HAlign { fn from_config(config: &Config, context: &Context) -> Result { resolve_from_str(config, context, |_| "Expected left, center or right") } } // TODO: This could be solved with NoConfig instead. // Implement Resolvable for all functions taking 4 or less arguments. // (They will all fail to deserialize, but at least we can call resolve() on them) // We could consider increasing that? It would probably increase compilation time, and clutter the // Resolvable doc page. Maybe behind a feature if people really need it? // (Ideally we wouldn't need it and we'd have a blanket implementation instead, but that may // require specialization.) impl_fn_from_config!(Resolvable (D C B A)); #[cfg(test)] mod tests { use crate::{ builder::{Config, Context}, utils::markup::StyledString, }; use serde_json::json; use super::Resolvable; fn check_resolves_from_conf(config: Config, result: R) where R: Resolvable + PartialEq + std::fmt::Debug + 'static, { let context = Context::new(); assert_eq!(result, context.resolve::(&config).unwrap()); } fn check_resolves_from_any(value: T, result: R) where T: Clone + Send + Sync + 'static, R: Resolvable + PartialEq + std::fmt::Debug + 'static, { let mut context = Context::new(); context.store("foo", value); let config = Config::String("$foo".into()); assert_eq!(result, context.resolve::(&config).unwrap()); } #[test] fn test_strings() { check_resolves_from_conf(json!("1"), String::from("1")); check_resolves_from_conf(json!(1), String::from("1")); check_resolves_from_conf(json!(true), String::from("true")); } #[test] fn test_integers() { fn check_integer_types(result: T) where T: Clone + Send + Sync + 'static + std::fmt::Debug + PartialEq + Resolvable, { check_resolves_from_any(1usize, result.clone()); check_resolves_from_any(1u8, result.clone()); check_resolves_from_any(1u16, result.clone()); check_resolves_from_any(1u32, result.clone()); check_resolves_from_any(1u64, result.clone()); check_resolves_from_any(1u128, result.clone()); check_resolves_from_any(1isize, result.clone()); check_resolves_from_any(1i8, result.clone()); check_resolves_from_any(1i16, result.clone()); check_resolves_from_any(1i32, result.clone()); check_resolves_from_any(1i64, result.clone()); check_resolves_from_any(1i128, result.clone()); check_resolves_from_conf(json!(1), result.clone()); } check_integer_types(1usize); check_integer_types(1u8); check_integer_types(1u16); check_integer_types(1u32); check_integer_types(1u64); check_integer_types(1u128); check_integer_types(1isize); check_integer_types(1i8); check_integer_types(1i16); check_integer_types(1i32); check_integer_types(1i64); check_integer_types(1i128); check_resolves_from_conf(json!(-1), -1i32); check_resolves_from_conf(json!(-1), -1isize); check_resolves_from_conf(json!(1), 1u32); check_resolves_from_conf(json!(1), 1u64); check_resolves_from_conf(json!(1), 1usize); check_resolves_from_conf(json!(0), 0u32); check_resolves_from_conf(json!(0), 0u64); } #[test] fn test_floats() { check_resolves_from_any(1.0f32, 1.0f32); check_resolves_from_any(1.0f32, 1.0f64); check_resolves_from_any(1.0f64, 1.0f32); check_resolves_from_any(1.0f64, 1.0f64); check_resolves_from_conf(json!(1), 1.0f32); check_resolves_from_conf(json!(1), 1.0f64); check_resolves_from_conf(json!(1.0), 1.0f32); check_resolves_from_conf(json!(1.0), 1.0f64); } #[test] fn test_vec() { // Vec to Vec check_resolves_from_any(vec![1u32, 2, 3], vec![1u32, 2, 3]); // Vec to Array check_resolves_from_any(vec![1u32, 2, 3], [1u32, 2, 3]); // Array to Array check_resolves_from_any([1u32, 2, 3], [1u32, 2, 3]); // Both array and Vec can resolve from a config array. check_resolves_from_conf(json!([1, 2, 3]), [1u32, 2, 3]); check_resolves_from_conf(json!([1, 2, 3]), vec![1u32, 2, 3]); } #[test] fn test_option() { check_resolves_from_any(Some(42u32), Some(42u32)); check_resolves_from_any(42u32, Some(42u32)); check_resolves_from_conf(json!(42), Some(42u32)); check_resolves_from_conf(json!(null), None::); } #[test] fn test_box() { check_resolves_from_any(Box::new(42u32), Box::new(42u32)); check_resolves_from_any(42u32, Box::new(42u32)); check_resolves_from_conf(json!(42), Box::new(42u32)); } #[test] fn test_arc() { use std::sync::Arc; check_resolves_from_any(Arc::new(42u32), Arc::new(42u32)); check_resolves_from_any(42u32, Arc::new(42u32)); check_resolves_from_conf(json!(42), Arc::new(42u32)); } #[test] fn test_rgb() { use crate::style::Rgb; // We can resolve both u8 and f32 from either u8 or f32 stored. check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0u8, 0u8, 255u8)); check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0u8, 0u8, 255u8)); check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0f32, 0f32, 1f32)); check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0f32, 0f32, 1f32)); // We can resolve both u8 and f32 from either integers or floats in json. check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0u8, 0u8, 255u8)); check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0f32, 0f32, 1f32)); check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0f32, 0f32, 1f32)); check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0u8, 0u8, 255u8)); check_resolves_from_conf(json!("blue"), Rgb::blue()); check_resolves_from_conf(json!("#0000FF"), Rgb::blue()); check_resolves_from_conf(json!("0x0000FF"), Rgb::blue()); check_resolves_from_conf(json!({"r": 0, "g": 0, "b": 255}), Rgb::blue()); } #[test] fn test_colors() { use crate::style::{BaseColor, Color, ColorPair, ColorStyle, ColorType}; check_resolves_from_conf(json!("green"), BaseColor::Green); check_resolves_from_conf(json!("dark red"), Color::Dark(BaseColor::Red)); check_resolves_from_conf(json!("terminal_default"), ColorPair::terminal_default()); check_resolves_from_conf( json!({"front": "red", "back": "light blue"}), ColorPair { front: Color::Dark(BaseColor::Red), back: Color::Light(BaseColor::Blue), }, ); check_resolves_from_conf(json!("inherit_parent"), ColorType::InheritParent); check_resolves_from_conf( json!({ "front": "inherit_parent", "back": "white", }), ColorStyle { front: ColorType::InheritParent, back: ColorType::Color(Color::Dark(BaseColor::White)), }, ); } #[test] fn test_align() { use crate::align::{Align, HAlign, VAlign}; check_resolves_from_conf(json!("left"), HAlign::Left); check_resolves_from_conf(json!("center"), HAlign::Center); check_resolves_from_conf(json!("right"), HAlign::Right); check_resolves_from_conf(json!("top"), VAlign::Top); check_resolves_from_conf(json!("center"), VAlign::Center); check_resolves_from_conf(json!("bottom"), VAlign::Bottom); check_resolves_from_conf(json!("top_center"), Align::top_center()); check_resolves_from_conf(json!({"v": "top", "h": "center"}), Align::top_center()); } #[test] fn test_directions() { use crate::direction::{Absolute, Direction, Orientation, Relative}; check_resolves_from_conf(json!("front"), Relative::Front); check_resolves_from_conf(json!("back"), Relative::Back); check_resolves_from_conf(json!("up"), Absolute::Up); check_resolves_from_conf(json!("down"), Absolute::Down); check_resolves_from_conf(json!("left"), Absolute::Left); check_resolves_from_conf(json!("right"), Absolute::Right); check_resolves_from_conf(json!("front"), Direction::Rel(Relative::Front)); check_resolves_from_conf(json!("down"), Direction::Abs(Absolute::Down)); check_resolves_from_conf(json!("none"), Direction::Abs(Absolute::None)); check_resolves_from_conf(json!("horizontal"), Orientation::Horizontal); check_resolves_from_conf(json!("vertical"), Orientation::Vertical); } #[test] fn test_rect() { use crate::Rect; check_resolves_from_conf( json!({"top_left": [0,0], "bottom_right": [4,2]}), Rect::from_corners((0, 0), (4, 2)), ); check_resolves_from_conf( json!({"top_left": [1,1], "size": [4,2]}), Rect::from_size((1, 1), (4, 2)), ); check_resolves_from_conf( json!({"corners": [[1,5], [4,2]]}), Rect::from_corners((1, 5), (4, 2)), ); check_resolves_from_conf(json!({"point": [4,2]}), Rect::from_point((4, 2))); } #[test] fn test_xy() { use crate::{Vec2, XY}; // Resolve zeroes check_resolves_from_conf(json!("zero"), Vec2::zero()); check_resolves_from_conf(json!("zero"), XY::::zero()); check_resolves_from_conf(json!("zero"), XY::new(0f32, 0f32)); // Resolve array and object versions check_resolves_from_conf(json!([4, 2]), Vec2::new(4, 2)); check_resolves_from_conf(json!({"x": 4, "y": 2}), Vec2::new(4, 2)); // Resolve strings check_resolves_from_conf( json!(["foo", "bar"]), XY::new(String::from("foo"), String::from("bar")), ); check_resolves_from_conf( json!({"x": "foo", "y": "bar"}), XY::new(String::from("foo"), String::from("bar")), ); } #[test] fn test_borderstyle() { use crate::style::BorderStyle; check_resolves_from_conf(json!("none"), BorderStyle::None); check_resolves_from_conf(json!("simple"), BorderStyle::Simple); check_resolves_from_conf(json!("outset"), BorderStyle::Outset); } #[test] fn test_styled_string() { check_resolves_from_any(String::from("foo"), StyledString::plain("foo")); check_resolves_from_any(StyledString::plain("foo"), StyledString::plain("foo")); check_resolves_from_conf(json!("foo"), StyledString::plain("foo")); } #[test] fn test_no_config() { use super::NoConfig; // Test how NoConfig lets you store and resolve types that are not `Resolvable`. #[derive(Clone, PartialEq, Eq, Debug)] struct Foo(i32); check_resolves_from_any(Foo(42), NoConfig(Foo(42))); } } cursive_core-0.4.6/src/builder.rs000064400000000000000000001075721046102023000151220ustar 00000000000000//! Build views from configuration. //! //! # Features //! //! This module is only active if the `builder` feature is enabled. Otherwise, types will still be //! exposed, blueprints can be defined, but they will be ignored. //! //! # Overview //! //! This module lets you build a view from a json-like config object. //! //! For example, this yaml could be parsed and used to build a basic TextView: //! //! ```yaml //! TextView: //! content: foo //! ``` //! //! Or, this slightly larger example could build a LinearLayout, relying on a `left_label` //! variable that would need to be fed: //! //! ```yaml //! LinearLayout: //! orientation: horizontal //! children: //! - TextView: $left_label //! - TextView: Center //! - Button: //! label: Right //! callback: $Cursive.quit //! ``` //! //! ## Configs //! //! Views are described using a `Config`, which is really just an alias for a `serde_json::Value`. //! Note that we use the json model here, but you could parse it from JSON, yaml, or almost any //! language supported by serde. //! //! ## Context //! //! A `Context` helps building views by providing variables that can be used by configs. They also //! keep a list of available blueprints. //! //! ## Blueprints //! //! At the core of the builder system, blueprints define _how_ to build views. //! //! A blueprint essentially ties a name to a function `fn(Config, Context) -> Result`. //! //! They are defined using macros - either manually (`manual_blueprint!`) or declaratively //! (`#[blueprint]`). When a `Context` is created, they are automatically gathered from all //! dependencies - so third party crates can define blueprints too. //! //! ## Resolving things //! //! Blueprints will need to parse various types from the config to build their views - strings, //! integers, callbacks, ... //! //! To do this, they will rely on the `Resolvable` trait. //! //! # Examples //! //! You can see the [`builder` example][builder.rs] and its [yaml config][config]. //! //! [builder.rs]: https://github.com/gyscos/cursive/blob/main/cursive/examples/builder.rs //! [config]: https://github.com/gyscos/cursive/blob/main/cursive/examples/builder.yaml #![cfg_attr(not(feature = "builder"), allow(unused))] mod resolvable; pub use self::resolvable::{NoConfig, Resolvable}; use crate::views::BoxedView; use parking_lot::Mutex; use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::any::Any; type MakerTrait = dyn Fn(&Config, &Context) -> Result + Send + Sync; /// Type of a trait-object that can build. /// /// Stored as variables. type Maker = Box>; type AnyMaker = Maker>; /// Type of a config item. pub type Config = serde_json::Value; /// Type of a config object. pub type Object = serde_json::Map; /// Can build a view from a config. pub type BareBuilder = fn(&serde_json::Value, &Context) -> Result; /// Boxed builder type BoxedBuilder = Box Result + Send + Sync>; /// Can build a wrapper from a config. pub type BareWrapperBuilder = fn(&serde_json::Value, &Context) -> Result; /// Boxed wrapper builder type BoxedWrapperBuilder = Box Result + Send + Sync>; /// Can wrap a view. pub type Wrapper = Box BoxedView + Send>; /// Can build a callback pub type BareVarBuilder = fn(&serde_json::Value, &Context) -> Result, Error>; /// Boxed variable builder /// /// If you store a variable of this type, when loading type `T`, it will run /// this builder and try to downcast the result to `T`. pub type BoxedVarBuilder = Arc Result, Error> + Send + Sync>; /// Everything needed to prepare a view from a config. /// /// - Current blueprints /// - Any stored variables/callbacks /// /// Cheap to clone (uses `Arc` internally). #[derive(Clone)] pub struct Context { // TODO: Merge variables and blueprints? // TODO: Use RefCell? Or even Arc? // So we can still modify the context when sub-context are alive. variables: Arc, blueprints: Arc, } impl std::fmt::Debug for Context { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let vars: Vec<_> = self.variables.keys().collect(); let blueprints: Vec<_> = self.blueprints.keys().collect(); let wrappers: Vec<_> = self.blueprints.wrapper_keys().collect(); write!(f, "Variables: {vars:?}, ")?; write!(f, "Blueprints: {blueprints:?}, ")?; write!(f, "Wrappers: {wrappers:?}")?; Ok(()) } } struct Blueprints { blueprints: HashMap, wrappers: HashMap, parent: Option>, } /// Wrapper around a value that makes it Cloneable, but can only be resolved once. pub struct ResolveOnce(Arc>>); /// Return a variable-maker (for use in store_with) pub fn resolve_once(value: T) -> impl Fn(&Config, &Context) -> Result where T: Send, { let value = Mutex::new(Some(value)); move |_, _| { value .lock() .take() .ok_or_else(|| Error::MakerFailed("variable was already resolved".to_string())) } } impl ResolveOnce { /// Create a new `ResolveOnce` which will resolve, once, to the given value. pub fn new(value: T) -> Self { Self(Arc::new(Mutex::new(Some(value)))) } /// Take the value from self. pub fn take(&self) -> Option { self.0.lock().take() } /// Check if there is a value still to be resolved in self. pub fn is_some(&self) -> bool { self.0.lock().is_some() } } impl Blueprints { fn wrapper_keys(&self) -> impl Iterator { self.wrappers .keys() .chain(self.parent.iter().flat_map(|parent| { let parent: Box> = Box::new(parent.wrapper_keys()); parent })) } fn keys(&self) -> impl Iterator { self.blueprints .keys() .chain(self.parent.iter().flat_map(|parent| { let parent: Box> = Box::new(parent.keys()); parent })) } fn build(&self, name: &str, config: &Config, context: &Context) -> Result { if let Some(blueprint) = self.blueprints.get(name) { (blueprint)(config, context) .map_err(|e| Error::BlueprintFailed(name.into(), Box::new(e))) } else { match self.parent { Some(ref parent) => parent.build(name, config, context), None => Err(Error::BlueprintNotFound(name.into())), } } } fn build_wrapper( &self, name: &str, config: &Config, context: &Context, ) -> Result { if let Some(blueprint) = self.wrappers.get(name) { (blueprint)(config, context) .map_err(|e| Error::BlueprintFailed(name.into(), Box::new(e))) } else { match self.parent { Some(ref parent) => parent.build_wrapper(name, config, context), None => Err(Error::BlueprintNotFound(name.into())), } } } } enum VarEntry { // Proxy variable used for sub-templates Proxy(Arc), // Regular variable set by user or blueprint // // Set by user: // - Clone-able // - Maker(AnyMaker), // Embedded config by intermediate node Config(Config), // Optional: store blueprints separately? } impl VarEntry { fn proxy(var_name: impl Into) -> Self { VarEntry::Proxy(Arc::new(var_name.into())) } fn maker(maker: AnyMaker) -> Self { VarEntry::Maker(maker) } fn config(config: impl Into) -> Self { VarEntry::Config(config.into()) } } struct Variables { variables: HashMap, // If something is not found in this scope, try the next one! parent: Option>, } /// Error during config parsing. #[derive(Debug)] #[non_exhaustive] pub enum Error { /// The configuration was invalid. InvalidConfig { /// Description of the issue message: String, /// Optional offending config object config: Config, }, /// Found no variable with the given name. NoSuchVariable(String), /// Found a variable, but with a different type than expected. IncorrectVariableType { /// Name of the offending variable name: String, /// Expected type expected_type: String, }, /// Could not load the given config. CouldNotLoad { /// Expected type expected_type: String, /// Config value that could not be parsed config: Config, }, /// All variants from a multi-variant blueprint failed. AllVariantsFailed { /// Config value that could not be parsed config: Config, /// List of errors for the blueprint variants. errors: Vec, }, /// A blueprint was not found BlueprintNotFound(String), /// A blueprint failed to run. /// /// This is in direct cause to an error in an actual blueprint. BlueprintFailed(String, Box), /// A maker failed to produce a value. MakerFailed(String), /// We failed to resolve a value. /// /// It means we failed to load it as a variable, and also failed to load it as a config. ResolveFailed { /// Failure while resolving the value as a variable. var_failure: Box, /// Failure while resolving the value as a config. config_failure: Box, }, } impl Error { /// Convenient method to create an error from a message and a problematic config. pub fn invalid_config, C: Clone + Into>( message: S, config: &C, ) -> Self { let message = message.into(); let config = config.clone().into(); Error::InvalidConfig { message, config } } } /// Error caused by an invalid config. #[derive(Debug)] pub struct ConfigError { /// Variable names present more than once in the config. /// /// Each variable can only be read once. pub duplicate_vars: HashSet, /// Variable names not registered in the context before loading the config. pub missing_vars: HashSet, } impl ConfigError { /// Creates a config error if any issue is detected. fn from(duplicate_vars: HashSet, missing_vars: HashSet) -> Result<(), Self> { if duplicate_vars.is_empty() && missing_vars.is_empty() { Ok(()) } else { Err(Self { duplicate_vars, missing_vars, }) } } } // Parse the given config, and yields all the variable names found. fn inspect_variables(config: &Config, on_var: &mut F) { match config { Config::String(name) => { if let Some(name) = name.strip_prefix('$') { on_var(name); } } Config::Array(array) => { for value in array { inspect_variables(value, on_var); } } Config::Object(object) => { for value in object.values() { inspect_variables(value, on_var); } } _ => (), } } new_default!(Context); impl Context { /// Prepare a new context using registered blueprints. pub fn new() -> Self { // Collect a distributed set of blueprints. #[cfg(feature = "builder")] let blueprints = inventory::iter::() .map(|blueprint| blueprint.as_tuple()) .collect(); #[cfg(not(feature = "builder"))] let blueprints = Default::default(); // for (blueprint, _) in &blueprints { // eprintln!("{blueprint:?}"); // } #[cfg(feature = "builder")] let wrappers = inventory::iter::() .map(|blueprint| blueprint.as_tuple()) .collect(); #[cfg(not(feature = "builder"))] let wrappers = Default::default(); // Store callback blueprints as variables for now. #[cfg(feature = "builder")] let variables = inventory::iter::() .map(|blueprint| blueprint.as_tuple()) .collect(); #[cfg(not(feature = "builder"))] let variables = Default::default(); let blueprints = Arc::new(Blueprints { blueprints, wrappers, parent: None, }); let variables = Arc::new(Variables { variables, parent: None, }); Self { blueprints, variables, } } /// Resolve a value. /// /// Needs to be a reference to a variable. pub fn resolve_as_var(&self, config: &Config) -> Result { // Use same strategy as for blueprints: always include a "config", potentially null if let Some(name) = parse_var(config) { // log::info!("Trying to load variable {name:?}"); // Option 1: a simple variable name. self.load(name, &Config::Null) } else if let Some(config) = config.as_object() { // Option 2: an object with a key (variable name pointing to a cb // blueprint) and a body (config for the blueprint). let (key, value) = config.iter().next().ok_or_else(|| Error::InvalidConfig { message: "Expected non-empty body".into(), config: config.clone().into(), })?; let key = key.strip_prefix('$').ok_or_else(|| Error::InvalidConfig { message: format!("Expected variable as $key; but found {key}."), config: config.clone().into(), })?; self.load(key, value) } else { // Here we did not find anything that looks like a variable. // Let's just bubble up, and hope we can use resolve() instead. Err(Error::CouldNotLoad { expected_type: std::any::type_name::().into(), config: config.clone(), }) } } fn resolve_as_builder(&self, config: &Config) -> Result { // TODO: return a cheap error here, no allocation let config = config .as_object() .ok_or_else(|| Error::invalid_config("Expected string", config))?; // Option 2: an object with a key (variable name pointing to a cb // blueprint) and a body (config for the blueprint). let (key, value) = config.iter().next().ok_or_else(|| Error::InvalidConfig { message: "Expected non-empty body".into(), config: config.clone().into(), })?; let key = key.strip_prefix('$').ok_or_else(|| Error::InvalidConfig { message: "Expected variable as key".into(), config: config.clone().into(), })?; self.load(key, value) } /// Resolve a value straight from the config. /// /// This will not attempt to load the value as a variable if the config is a string. /// /// Note however that while loading as a config, it may still resolve nested values as /// variables. pub fn resolve_as_config(&self, config: &Config) -> Result { // First, it could be a variable pointing to a config from a template view if let Some(name) = parse_var(config) { // Option 1: a simple variable name. if let Ok(var) = self.load(name, &Config::Null) { return Ok(var); } } if let Ok(value) = self.resolve_as_builder(config) { return Ok(value); } T::from_config(config, self) } /// Resolve a value pub fn resolve(&self, config: &Config) -> Result { let var_failure = Box::new(match self.resolve_as_var(config) { Ok(value) => return Ok(value), Err(err) => err, }); let config_failure = Box::new(match self.resolve_as_config(config) { Ok(value) => return Ok(value), Err(err) => err, }); Err(Error::ResolveFailed { var_failure, config_failure, }) } /// Resolve a value, using the given default if the key is missing. pub fn resolve_or( &self, config: &Config, if_missing: T, ) -> Result { Ok(self.resolve::>(config)?.unwrap_or(if_missing)) } fn store_entry(&mut self, name: impl Into, entry: VarEntry) { // This will fail if there are any sub_context alive. // On the other hand, would anyone store variables after the fact? let name = name.into(); if let Some(variables) = Arc::get_mut(&mut self.variables) { variables.store(name, entry); } else { log::error!("Context was not available to store variable `{name}`."); } } /// Store a new variable that can only be resolved once. pub fn store_once(&mut self, name: impl Into, value: T) where T: Send + 'static, { self.store_with(name, resolve_once(value)); } /// Store a new variable maker. pub fn store_with( &mut self, name: impl Into, maker: impl 'static + Fn(&Config, &Context) -> Result + Send + Sync, ) { let name = name.into(); // eprintln!( // "Storing {name} with {:?} (for {})", // std::any::TypeId::of::(), // std::any::type_name::() // ); let maker: AnyMaker = Box::new(move |config, context| { let res: T = (maker)(config, context)?; // eprintln!("Generated type ID: {:?}", res.type_id()); let b: Box = Box::new(res); // eprintln!("Boxed type ID: {:?}", b.as_ref().type_id()); Ok(b) }); self.store_entry(name, VarEntry::Maker(maker)); } /// Store a new variable for resolution. /// /// Can be a callback, a usize, ... pub fn store(&mut self, name: S, value: T) where S: Into, T: Clone + Send + Sync + 'static, { self.store_with(name, move |_, _| Ok(value.clone())); } /// Store a view for resolution. /// /// The view can be resolved as a `BoxedView`. /// /// Note that this will only resolve this view _once_. /// /// If the view should be resolved more than that, consider calling `store_with` and /// re-constructing a `BoxedView` (maybe by cloning your view) every time. pub fn store_view(&mut self, name: S, view: T) where S: Into, T: crate::view::IntoBoxedView, { self.store_once(name, BoxedView::new(view.into_boxed_view())); } /// Store a new config. pub fn store_config(&mut self, name: impl Into, config: impl Into) { self.store_entry(name, VarEntry::config(config)); } /// Store a new variable proxy. pub fn store_proxy(&mut self, name: impl Into, new_name: impl Into) { self.store_entry(name, VarEntry::proxy(new_name)); } /// Register a new blueprint _for this context only_. pub fn register_blueprint(&mut self, name: impl Into, blueprint: F) where F: Fn(&Config, &Context) -> Result + 'static + Send + Sync, { if let Some(blueprints) = Arc::get_mut(&mut self.blueprints) { blueprints .blueprints .insert(name.into(), Box::new(blueprint)); } } /// Register a new wrapper blueprint _for this context only_. pub fn register_wrapper_blueprint(&mut self, name: impl Into, blueprint: F) where F: Fn(&Config, &Context) -> Result + 'static + Send + Sync, { if let Some(blueprints) = Arc::get_mut(&mut self.blueprints) { blueprints.wrappers.insert(name.into(), Box::new(blueprint)); } } /* /// Loads a variable of the given type. /// /// This does not require Resolvable on `T`, but will also not try to deserialize. /// /// It should be used for types that simply cannot be parsed from config, like closures. pub fn load_as_var( &self, name: &str, config: &Config, ) -> Result { self.variables.call_on_any( name, self.on_maker(name, config), |config| self.resolve_as_var(config), ) } */ // Helper function to implement load_as_var and load fn on_maker( &self, maker: &AnyMaker, name: &str, config: &Config, ) -> Result { let res: Box = (maker)(config, self)?; // eprintln!( // "Trying to load {name} as {:?} (for {})", // std::any::TypeId::of::(), // std::any::type_name::() // ); // eprintln!( // "Loading var `{name}`: found type ID {:?}", // res.as_ref().type_id() // ); T::from_any(res).ok_or_else(|| { // It was not the right type :( Error::IncorrectVariableType { name: name.into(), expected_type: std::any::type_name::().into(), } }) } /// Loads a variable of the given type. /// /// If a variable with this name is found but is a Config, tries to deserialize it. /// /// Note: `config` can be `&Config::Null` for loading simple variables. pub fn load(&self, name: &str, config: &Config) -> Result { self.variables.call_on_any( name, |maker| self.on_maker(maker, name, config), |config| self.resolve(config), ) } /// Build a wrapper with the given config pub fn build_wrapper(&self, config: &Config) -> Result { // Expect a single key let (key, value) = match config { Config::String(key) => (key, &Config::Null), Config::Object(config) => config.into_iter().next().ok_or(Error::InvalidConfig { message: "Expected non-empty object".into(), config: config.clone().into(), })?, _ => { return Err(Error::InvalidConfig { message: "Expected string or object".into(), config: config.clone(), }) } }; let wrapper = self.blueprints.build_wrapper(key, value, self)?; Ok(wrapper) } /// Validate a config. /// /// Returns an error if any variable is missing or used more than once. pub fn validate(&self, config: &Config) -> Result<(), ConfigError> { let mut vars = HashSet::new(); let mut duplicates = HashSet::new(); inspect_variables(config, &mut |variable| { if !vars.insert(variable.to_string()) { // Error! We found a duplicate! duplicates.insert(variable.to_string()); } }); let not_found: HashSet = vars .into_iter() .filter(|var| !self.variables.variables.contains_key(var)) .collect(); ConfigError::from(duplicates, not_found) } fn get_wrappers(&self, config: &Config) -> Result, Error> { fn get_with(config: &Config) -> Option<&Vec> { config.as_object()?.get("with")?.as_array() } let with = match get_with(config) { Some(with) => with, None => return Ok(Vec::new()), }; with.iter().map(|with| self.build_wrapper(with)).collect() } /// Build a new view from the given config. pub fn build(&self, config: &Config) -> Result { let (key, value) = match config { // Some views can be built from a null config. Config::String(name) => (name, &serde_json::Value::Null), // Most view require a full object. Config::Object(config) => { // Expect a single key config.iter().next().ok_or(Error::InvalidConfig { message: "Expected non-empty object".into(), config: config.clone().into(), })? } _ => { return Err(Error::InvalidConfig { message: "Expected object or string.".into(), config: config.clone(), }) } }; let with = self.get_wrappers(value)?; let mut view = self.blueprints.build(key, value, self)?; // Now, apply optional wrappers for wrapper in with { view = (wrapper)(view); } Ok(view) } /// Prepare a new context with some variable overrides. pub fn sub_context(&self, f: F) -> Context where F: FnOnce(&mut Context), { let variables = Arc::new(Variables { variables: HashMap::new(), parent: Some(Arc::clone(&self.variables)), }); let blueprints = Arc::new(Blueprints { blueprints: HashMap::new(), wrappers: HashMap::new(), parent: Some(Arc::clone(&self.blueprints)), }); let mut context = Context { blueprints, variables, }; f(&mut context); context } /// Builds a view from a template config. /// /// `template` should be a config describing a view, potentially using variables. /// Any value in `config` will be stored as a variable when rendering the template. pub fn build_template(&self, config: &Config, template: &Config) -> Result { let res = self .sub_context(|c| { if let Some(config) = config.as_object() { for (key, value) in config.iter() { // If value is a variable, resolve it first. if let Some(var) = parse_var(value) { c.store_proxy(key, var); } else { c.store_config(key, value.clone()); } } } else { c.store_config(".", config.clone()); } }) .build(template)?; Ok(res) } } fn parse_var(value: &Config) -> Option<&str> { value.as_str().and_then(|s| s.strip_prefix('$')) } impl Variables { fn keys(&self) -> impl Iterator { self.variables .keys() .chain(self.parent.iter().flat_map(|parent| { let parent: Box> = Box::new(parent.keys()); parent })) } /// Store a new variable for interpolation. /// /// Can be a callback, a usize, ... fn store(&mut self, name: S, value: VarEntry) where S: Into, { let name = name.into(); // eprintln!( // "Storing {name} with type {} (ID {:?})", // std::any::type_name::(), // std::any::TypeId::of::(), // ); self.variables.insert(name, value); } fn call_on_any( &self, name: &str, mut on_maker: OnMaker, mut on_config: OnConfig, ) -> Result where OnConfig: FnMut(&Config) -> Result, OnMaker: FnMut(&AnyMaker) -> Result, T: 'static, { let new_name = match self.variables.get(name) { None => None, Some(VarEntry::Proxy(proxy)) => Some(Arc::clone(proxy)), Some(VarEntry::Maker(maker)) => return (on_maker)(maker), Some(VarEntry::Config(config)) => return (on_config)(config), }; let name = new_name .as_ref() .map(|s| s.as_ref().as_str()) .unwrap_or(name); self.parent.as_ref().map_or_else( || Err(Error::NoSuchVariable(name.into())), |parent| parent.call_on_any(name, on_maker, on_config), ) } } /// Describes how to build a callback. pub struct CallbackBlueprint { /// Name used in config file to use this callback. /// /// The config file will include an extra $ at the beginning. pub name: &'static str, /// Function to run this blueprint. pub builder: BareVarBuilder, } impl CallbackBlueprint { fn as_tuple(&self) -> (String, VarEntry) { let cb: AnyMaker = Box::new(self.builder); (self.name.into(), VarEntry::maker(cb)) } } /// Describes how to build a view. pub struct Blueprint { /// Name used in config file to use this blueprint. pub name: &'static str, /// Function to run this blueprint. pub builder: BareBuilder, } impl Blueprint { fn as_tuple(&self) -> (String, BoxedBuilder) { (self.name.into(), Box::new(self.builder)) } } /// Describes how to build a view wrapper. pub struct WrapperBlueprint { /// Name used in config file to use this wrapper. pub name: &'static str, /// Function to run this blueprint. pub builder: BareWrapperBuilder, } impl WrapperBlueprint { fn as_tuple(&self) -> (String, BoxedWrapperBuilder) { (self.name.into(), Box::new(self.builder)) } } #[cfg(feature = "builder")] inventory::collect!(Blueprint); #[cfg(feature = "builder")] inventory::collect!(CallbackBlueprint); #[cfg(feature = "builder")] inventory::collect!(WrapperBlueprint); #[cfg(not(feature = "builder"))] #[macro_export] /// Define a blueprint to build this view from a config file. macro_rules! manual_blueprint { ($name:ident from $config_builder:expr) => {}; (with $name:ident, $builder:expr) => {}; ($name:ident, $builder:expr) => {}; } #[cfg(feature = "builder")] #[macro_export] /// Define a blueprint to manually build this view from a config file. /// /// Note: this is entirely ignored (not even type-checked) if the `builder` feature is not /// enabled. /// /// There are 3 variants of this macro: /// /// * `manual_blueprint!(Identifier, |config, context| make_the_view(...))` /// This registers the recipe under `Identifier`, and uses the given closure to build /// the view. /// * `manual_blueprint!(Identifier from { parse_some_config(...) })` /// This register under `Identifier` a recipe that forwards the creation to another /// config using [`Context::build_template`]. /// * `manual_blueprint`(with Identifier, |config, context| Ok(|view| wrap_the_view(view, ...)))` /// This register a "with" blueprint under `Identifier`, which will prepare a view wrapper. macro_rules! manual_blueprint { // Remember to keep the inactive version above in sync ($name:ident from $config_builder:expr) => { $crate::submit! { $crate::builder::Blueprint { name: stringify!($name), builder: |config, context| { let template = $config_builder; context.build_template(config, &template) }, } } }; (with $name:ident, $builder:expr) => { $crate::submit! { $crate::builder::WrapperBlueprint { name: stringify!($name), builder: |config, context| { let builder: fn(&$crate::reexports::serde_json::Value, &$crate::builder::Context) -> Result<_, $crate::builder::Error> = $builder; let wrapper = (builder)(config, context)?; Ok(Box::new(move |view| { let view = (wrapper)(view); $crate::views::BoxedView::boxed(view) })) } } } }; ($name:ident, $builder:expr) => { $crate::submit! { $crate::builder::Blueprint { name: stringify!($name), builder: |config, context| { let builder: fn(&$crate::reexports::serde_json::Value, &$crate::builder::Context) -> Result<_,$crate::builder::Error> = $builder; (builder)(config, context).map($crate::views::BoxedView::boxed) }, } } }; } #[cfg(not(feature = "builder"))] #[macro_export] /// Define a macro for a variable builder. macro_rules! fn_blueprint { ($name: expr, $builder:expr) => {}; } #[cfg(feature = "builder")] #[macro_export] /// Define a macro for a variable builder. macro_rules! fn_blueprint { // Remember to keep the inactive version above in sync ($name: expr, $builder:expr) => { $crate::submit! { $crate::builder::CallbackBlueprint { name: $name, builder: |config, context| { let builder: fn(&::serde_json::Value, &$crate::builder::Context) -> Result<_, $crate::builder::Error> = $builder; Ok(Box::new((builder)(config, context)?)) }, } } }; } // Simple blueprint allowing to use variables as views, and attach a `with` clause. manual_blueprint!(View, |config, context| { let view: BoxedView = context.resolve(&config["view"])?; Ok(view) }); // TODO: A $format blueprint that parses a f-string and renders variables in there. // Will need to look for various "string-able" types as variables. // (String mostly, maybe integers) // Probably needs regex crate to parse the template. fn_blueprint!("concat", |config, context| { let values = config .as_array() .ok_or_else(|| Error::invalid_config("Expected array", config))?; values .iter() .map(|value| { // log::info!("Resolving {value:?}"); context.resolve::(value) }) .collect::>() }); fn_blueprint!("cursup", |config, context| { let text: String = context.resolve(config)?; Ok(crate::utils::markup::cursup::parse(text)) }); #[cfg(feature = "builder")] #[cfg(test)] mod tests { #[test] fn test_load_config() { use crate::view::Finder; let config = r#" LinearLayout: children: - TextView: content: $foo with: - name: text - DummyView - TextView: bar - LinearLayout: orientation: horizontal children: - TextView: "Age?" - DummyView - EditView: with: - name: edit with: - full_screen "#; let foo = "Foo"; let config: crate::builder::Config = serde_yaml::from_str(config).unwrap(); let mut context = crate::builder::Context::new(); // Here we're still missing the $foo variable. assert!(context.validate(&config).is_err()); context.store("foo", foo.to_string()); // Now everything is find. context.validate(&config).unwrap(); // Build the view from the config let mut res = context.build(&config).unwrap(); // The top-level view should be a full-screen view assert!(res .downcast_ref::>() .is_some()); // The view should be reachable by name let content = res .call_on_name("text", |v: &mut crate::views::TextView| v.get_content()) .unwrap(); assert_eq!(content.source(), foo); } } cursive_core-0.4.6/src/cursive_root.rs000064400000000000000000000744401046102023000162140ustar 00000000000000use std::any::Any; use std::num::NonZeroU32; #[cfg(feature = "toml")] use std::path::Path; use crossbeam_channel::{self, Receiver, Sender}; use parking_lot::RwLock; use crate::{ backend, cursive_run::CursiveRunner, direction, event::{Event, EventResult}, printer::Printer, theme, view::{self, Finder, IntoBoxedView, Position, View, ViewNotFound}, views::{self, LayerPosition}, Dump, Vec2, }; static DEBUG_VIEW_NAME: &str = "_cursive_debug_view"; type RootView = views::OnEventView>; type BackendCallback = dyn FnOnce(&mut dyn backend::Backend); type Callback = dyn FnOnce(&mut Cursive) + Send; /// Central part of the cursive library. /// /// It initializes ncurses on creation and cleans up on drop. /// To use it, you should populate it with views, layouts, and callbacks, /// then start the event loop with `run()`. /// /// It uses a list of screen, with one screen active at a time. pub struct Cursive { theme: theme::Theme, // The main view root: RootView, menubar: views::Menubar, pub(crate) needs_clear: bool, running: bool, // Handle asynchronous callbacks cb_source: Receiver>, cb_sink: Sender>, last_size: Vec2, // User-provided data. user_data: Box, // Handle auto-refresh when no event is received. fps: Option, // List of callbacks to run on the backend. // The current assumption is that we only add calls here during event processing. pub(crate) backend_calls: Vec>, } /// Identifies a screen in the cursive root. pub type ScreenId = usize; /// Convenient alias to the result of `Cursive::cb_sink`. /// /// # Notes /// /// Callbacks need to be `Send`, which can be limiting in some cases. /// /// In some case [`send_wrapper`] may help you work around that. /// /// [`send_wrapper`]: https://crates.io/crates/send_wrapper pub type CbSink = Sender>; new_default!(Cursive); impl Cursive { /// Creates a new Cursive root, and initialize the back-end. /// /// You probably don't want to use this function directly, unless you're /// using a non-standard backend. Built-in backends have dedicated functions in the /// [`CursiveExt`] trait. /// /// [`CursiveExt`]: https://docs.rs/cursive/0/cursive/trait.CursiveExt.html pub fn new() -> Self { let theme = theme::load_default(); let (cb_sink, cb_source) = crossbeam_channel::unbounded(); let mut cursive = Cursive { theme, root: views::OnEventView::new(views::ScreensView::single_screen( views::StackView::new(), )), menubar: views::Menubar::new(), last_size: Vec2::zero(), needs_clear: true, running: true, cb_source, cb_sink, fps: None, user_data: Box::new(()), backend_calls: Vec::new(), }; cursive.reset_default_callbacks(); cursive } /// Returns the screen size given in the last layout phase. /// /// Note: this will return `(0, 0)` before the first layout phase. pub fn screen_size(&self) -> Vec2 { self.last_size } pub(crate) fn layout(&mut self, size: Vec2) { self.last_size = size; let offset = usize::from(!self.menubar.autohide); let size = size.saturating_sub((0, offset)); self.root.layout(size); } pub(crate) fn draw(&mut self, buffer: &RwLock) { let size = buffer.read().size(); let printer = Printer::new(size, &self.theme, buffer); if self.needs_clear { printer.clear(); self.needs_clear = false; } let selected = self.menubar.receive_events(); let offset = usize::from(!self.menubar.autohide); // The printer for the stackview let sv_printer = printer.offset((0, offset)).focused(!selected); // Print the stackview background (the blue background) before the menubar self.root.get_inner().draw_bg(&sv_printer); // Draw the currently active screen // If the menubar is active, nothing else can be. if self.menubar.visible() { let printer = printer.focused(self.menubar.receive_events()); printer.with_color(crate::style::ColorStyle::primary(), |printer| { self.menubar.draw(printer) }); } // Finally draw stackview layers self.root.get_inner().draw_fg(&sv_printer); } /// Sets some data to be stored in Cursive. /// /// It can later on be accessed with `Cursive::user_data()` pub fn set_user_data(&mut self, user_data: T) { self.user_data = Box::new(user_data); } /// Attempts to access the user-provided data. /// /// If some data was set previously with the same type, returns a /// reference to it. /// /// If nothing was set or if the type is different, returns `None`. pub fn user_data(&mut self) -> Option<&mut T> { self.user_data.downcast_mut() } /// Attempts to take by value the current user-data. /// /// If successful, this will replace the current user-data with the unit /// type `()`. /// /// If the current user data is not of the requested type, `None` will be /// returned. /// /// # Examples /// /// ```rust /// let mut siv = cursive_core::Cursive::new(); /// /// // Start with a simple `Vec` as user data. /// siv.set_user_data(vec![1i32, 2, 3]); /// assert_eq!(siv.user_data::>(), Some(&mut vec![1i32, 2, 3])); /// /// // Let's mutate the data a bit. /// siv.with_user_data(|numbers: &mut Vec| numbers.push(4)); /// /// // If mutable reference is not enough, we can take the data by value. /// let data: Vec = siv.take_user_data().unwrap(); /// assert_eq!(data, vec![1i32, 2, 3, 4]); /// /// // At this point the user data was removed and is no longer available. /// assert_eq!(siv.user_data::>(), None); /// ``` pub fn take_user_data(&mut self) -> Option { // Start by taking the user data and replacing it with a dummy. let user_data = std::mem::replace(&mut self.user_data, Box::new(())); // Downcast the data to the requested type. // If it works, unbox it. // It if doesn't, take it back. user_data .downcast() .map_err(|user_data| { // If we asked for the wrong type, put it back. self.user_data = user_data; }) .map(|boxed| *boxed) .ok() } /// Runs the given closure on the stored user data, if any. /// /// If no user data was supplied, or if the type is different, nothing /// will be run. /// /// Otherwise, the result will be returned. pub fn with_user_data(&mut self, f: F) -> Option where F: FnOnce(&mut T) -> R, T: Any, { self.user_data().map(f) } /// Sets the title for the terminal window. /// /// Note that not all backends support this. pub fn set_window_title>(&mut self, title: S) { let title = title.into(); self.backend_calls .push(Box::new(move |backend| backend.set_title(title))); } /// Show the debug console. /// /// Currently, this will show logs if [`logger::init()`](crate::logger::init()) was called. pub fn show_debug_console(&mut self) { self.add_layer( views::Dialog::around( views::ScrollView::new(views::NamedView::new( DEBUG_VIEW_NAME, views::DebugView::new(), )) .scroll_x(true), ) .title("Debug console"), ); } /// Show the debug console, or hide it if it's already visible. /// /// # Examples /// /// ```rust /// # use cursive_core::Cursive; /// # let mut siv = Cursive::new(); /// siv.add_global_callback('~', Cursive::toggle_debug_console); /// ``` pub fn toggle_debug_console(&mut self) { if let Some(pos) = self.screen_mut().find_layer_from_name(DEBUG_VIEW_NAME) { self.screen_mut().remove_layer(pos); } else { self.show_debug_console(); } } /// Returns a sink for asynchronous callbacks. /// /// Returns the sender part of a channel, that allows to send /// callbacks to `self` from other threads. /// /// Callbacks will be executed in the order /// of arrival on the next event cycle. /// /// # Notes /// /// Callbacks need to be `Send`, which can be limiting in some cases. /// /// In some case [`send_wrapper`] may help you work around that. /// /// [`send_wrapper`]: https://crates.io/crates/send_wrapper /// /// # Examples /// /// ```rust /// # use cursive_core::*; /// let mut siv = Cursive::new(); /// /// // quit() will be called during the next event cycle /// siv.cb_sink().send(Box::new(|s| s.quit())).unwrap(); /// ``` pub fn cb_sink(&self) -> &CbSink { &self.cb_sink } /// Selects the menubar. pub fn select_menubar(&mut self) { if let Ok(res) = self.menubar.take_focus(direction::Direction::none()) { res.process(self); } } /// Sets the menubar autohide feature. /// /// * When enabled (default), the menu is only visible when selected. /// * When disabled, the menu is always visible and reserves the top row. pub fn set_autohide_menu(&mut self, autohide: bool) { self.menubar.autohide = autohide; } /// Access the menu tree used by the menubar. /// /// This allows to add menu items to the menubar. /// /// # Examples /// /// ```rust /// # use cursive_core::{Cursive, event}; /// # use cursive_core::views::{Dialog}; /// # use cursive_core::traits::*; /// # use cursive_core::menu; /// # /// let mut siv = Cursive::new(); /// /// siv.menubar() /// .add_subtree( /// "File", /// menu::Tree::new() /// .leaf("New", |s| s.add_layer(Dialog::info("New file!"))) /// .subtree( /// "Recent", /// menu::Tree::new().with(|tree| { /// for i in 1..100 { /// tree.add_leaf(format!("Item {}", i), |_| ()) /// } /// }), /// ) /// .delimiter() /// .with(|tree| { /// for i in 1..10 { /// tree.add_leaf(format!("Option {}", i), |_| ()); /// } /// }) /// .delimiter() /// .leaf("Quit", |s| s.quit()), /// ) /// .add_subtree( /// "Help", /// menu::Tree::new() /// .subtree( /// "Help", /// menu::Tree::new() /// .leaf("General", |s| s.add_layer(Dialog::info("Help message!"))) /// .leaf("Online", |s| s.add_layer(Dialog::info("Online help?"))), /// ) /// .leaf("About", |s| s.add_layer(Dialog::info("Cursive v0.0.0"))), /// ); /// /// siv.add_global_callback(event::Key::Esc, |s| s.select_menubar()); /// ``` pub fn menubar(&mut self) -> &mut views::Menubar { &mut self.menubar } /// Returns the currently used theme. pub fn current_theme(&self) -> &theme::Theme { &self.theme } /// Modifies the current theme. /// /// Shortcut to get the [`Cursive::current_theme()`], /// then run [`Cursive::set_theme()`]. pub fn with_theme(&mut self, f: F) { f(&mut self.theme); self.clear(); } /// Sets the current theme. pub fn set_theme(&mut self, theme: theme::Theme) { self.theme = theme; self.clear(); } /// Updates the current theme. pub fn update_theme(&mut self, f: impl FnOnce(&mut theme::Theme)) { // We don't just expose a `current_theme_mut` because we may want to // run some logic _after_ setting the theme. // Though right now, it's only clearing the screen, so... let mut theme = self.theme.clone(); f(&mut theme); self.set_theme(theme); } /// Clears the screen. /// /// Users rarely have to call this directly. pub fn clear(&mut self) { self.needs_clear = true; } /// Loads a theme from the given file. /// /// `filename` must point to a valid toml file. /// /// Must have the `toml` feature enabled. #[cfg(feature = "toml")] pub fn load_theme_file>(&mut self, filename: P) -> Result<(), theme::Error> { theme::load_theme_file(filename).map(|theme| self.set_theme(theme)) } /// Loads a theme from the given string content. /// /// Content must be valid toml. /// /// Must have the `toml` feature enabled. #[cfg(feature = "toml")] pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> { theme::load_toml(content).map(|theme| self.set_theme(theme)) } /// Sets the refresh rate, in frames per second. /// /// Note that the actual frequency is not guaranteed. /// /// Between 0 and 30. Call with `fps = 0` to disable (default value). pub fn set_fps(&mut self, fps: u32) { self.fps = NonZeroU32::new(fps); } /// Enables or disables automatic refresh of the screen. /// /// This is a shortcut to call `set_fps` with `30` or `0` depending on /// `autorefresh`. pub fn set_autorefresh(&mut self, autorefresh: bool) { 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 { self.fps } /// Returns a reference to the currently active screen. pub fn screen(&self) -> &views::StackView { self.root.get_inner().screen().unwrap() } /// Returns a mutable reference to the currently active screen. pub fn screen_mut(&mut self) -> &mut views::StackView { self.root.get_inner_mut().screen_mut().unwrap() } /// Returns the id of the currently active screen. pub fn active_screen(&self) -> ScreenId { self.root.get_inner().active_screen() } /// Adds a new screen, and returns its ID. pub fn add_screen(&mut self) -> ScreenId { self.root .get_inner_mut() .add_screen(views::StackView::new()) } /// Convenient method to create a new screen, and set it as active. pub fn add_active_screen(&mut self) -> ScreenId { let res = self.add_screen(); self.set_screen(res); res } /// Sets the active screen. Panics if no such screen exist. pub fn set_screen(&mut self, screen_id: ScreenId) { self.root.get_inner_mut().set_active_screen(screen_id); } /// Tries to find the view pointed to by the given selector. /// /// Runs a closure on the view once it's found, and return the /// result. /// /// If the view is not found, or if it is not of the asked type, /// returns None. /// /// # Examples /// /// ```rust /// # use cursive_core::{Cursive, views, view}; /// # use cursive_core::traits::*; /// 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::Name("text"), /// |view: &mut views::TextView| { /// view.set_content("Text #2"); /// }, /// ); /// }); /// ``` pub fn call_on(&mut self, sel: &view::Selector, callback: F) -> Option where V: View, F: FnOnce(&mut V) -> R, { self.root.call_on(sel, callback) } /// Tries to find the view identified by the given name. /// /// Convenient method to use `call_on` with a [`view::Selector::Name`]. /// /// # Examples /// /// ```rust /// # use cursive_core::{Cursive, views}; /// # use cursive_core::traits::*; /// 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_name("text", |view: &mut views::TextView| { /// view.set_content("Text #2"); /// }); /// }); /// ``` pub fn call_on_name(&mut self, name: &str, callback: F) -> Option where V: View, F: FnOnce(&mut V) -> R, { self.call_on(&view::Selector::Name(name), callback) } /// Call the given closure on all views with the given name and the correct type. pub fn call_on_all_named(&mut self, name: &str, callback: F) where V: View, F: FnMut(&mut V), { self.root.call_on_all(&view::Selector::Name(name), callback); } /// Convenient method to find a view wrapped in [`NamedView`]. /// /// This looks for a `NamedView` with the given name, and return /// a [`ViewRef`] to the wrapped view. The `ViewRef` implements /// `DerefMut`, so you can treat it just like a `&mut T`. /// /// # Examples /// /// ```rust /// # use cursive_core::Cursive; /// # use cursive_core::views::{TextView, ViewRef}; /// # let mut siv = Cursive::new(); /// use cursive_core::traits::Nameable; /// /// siv.add_layer(TextView::new("foo").with_name("id")); /// /// // Could be called in a callback /// let mut view: ViewRef = siv.find_name("id").unwrap(); /// view.set_content("bar"); /// ``` /// /// Note that you must specify the exact type for the view you're after; for example, using the /// wrong item type in a `SelectView` will not find anything: /// /// ```rust /// # use cursive_core::Cursive; /// # use cursive_core::views::{SelectView}; /// # let mut siv = Cursive::new(); /// use cursive_core::traits::Nameable; /// /// let select = SelectView::new().item("zero", 0u32).item("one", 1u32); /// siv.add_layer(select.with_name("select")); /// /// // Specifying a wrong type will not return anything. /// assert!(siv.find_name::>("select").is_none()); /// /// // Omitting the type will use the default type, in this case `String`. /// assert!(siv.find_name::("select").is_none()); /// /// // But with the correct type, it works fine. /// assert!(siv.find_name::>("select").is_some()); /// ``` /// /// [`NamedView`]: views::NamedView /// [`ViewRef`]: views::ViewRef pub fn find_name(&mut self, id: &str) -> Option> where V: View, { self.call_on_name(id, views::NamedView::::get_mut) } /// Moves the focus to the view identified by `name`. /// /// Convenient method to call `focus` with a [`view::Selector::Name`]. pub fn focus_name(&mut self, name: &str) -> Result { self.focus(&view::Selector::Name(name)) } /// Moves the focus to the view identified by `sel`. pub fn focus(&mut self, sel: &view::Selector) -> Result { self.root.focus_view(sel) } /// Adds a global callback. /// /// Will be triggered on the given key press when no view catches it. /// /// # Examples /// /// ```rust /// # use cursive_core::*; /// let mut siv = Cursive::new(); /// /// siv.add_global_callback('q', |s| s.quit()); /// ``` pub fn add_global_callback>(&mut self, event: E, cb: F) where F: FnMut(&mut Cursive) + 'static + Send + Sync, { self.set_on_post_event(event.into(), cb); } /// Registers a callback for ignored events. /// /// This is the same as `add_global_callback`, but can register any `EventTrigger`. pub fn set_on_post_event(&mut self, trigger: E, cb: F) where F: FnMut(&mut Cursive) + 'static + Send + Sync, E: Into, { self.root.set_on_event(trigger, crate::immut1!(cb)); } /// Registers a priority 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. /// /// Note that regular "post-event" callbacks will also be skipped for /// these events. pub fn set_on_pre_event(&mut self, trigger: E, cb: F) where F: FnMut(&mut Cursive) + 'static + Send + Sync, E: Into, { self.root.set_on_pre_event(trigger, crate::immut1!(cb)); } /// Registers an inner priority callback. /// /// See [`OnEventView`] for more information. /// /// [`OnEventView`]: crate::views::OnEventView::set_on_pre_event_inner() pub fn set_on_pre_event_inner(&mut self, trigger: E, cb: F) where E: Into, F: Fn(&Event) -> Option + 'static + Send + Sync, { self.root .set_on_pre_event_inner(trigger, move |_, event| cb(event)); } /// Registers an inner callback. /// /// See [`OnEventView`] for more information. /// /// [`OnEventView`]: crate::views::OnEventView::set_on_event_inner() pub fn set_on_event_inner(&mut self, trigger: E, cb: F) where E: Into, F: Fn(&Event) -> Option + 'static + Send + Sync, { self.root .set_on_event_inner(trigger, move |_, event| cb(event)); } /// Sets the only global callback for the given event. /// /// Any other callback for this event will be removed. /// /// See also [`Cursive::add_global_callback`]. pub fn set_global_callback>(&mut self, event: E, cb: F) where F: FnMut(&mut Cursive) + 'static + Send + Sync, { let event = event.into(); self.clear_global_callbacks(event.clone()); self.add_global_callback(event, cb); } /// Fetches the type name of a view in the tree. pub fn debug_name(&mut self, name: &str) -> Option<&'static str> { let mut result = None; self.root.call_on_any( &view::Selector::Name(name), &mut |v: &mut dyn crate::View| result = Some(v.type_name()), ); result } /// Removes any callback tied to the given event. /// /// # Examples /// /// ```rust /// use cursive_core::Cursive; /// let mut siv = Cursive::new(); /// /// siv.add_global_callback('q', |s| s.quit()); /// siv.clear_global_callbacks('q'); /// ``` pub fn clear_global_callbacks(&mut self, event: E) where E: Into, { let event = event.into(); self.root.clear_event(event); } /// Clear all currently-registered global callbacks. /// /// You may want to call `reset_default_callbacks()` afterwards. pub fn clear_all_global_callbacks(&mut self) { self.root.clear_callbacks(); } /// This resets the default callbacks. /// /// Currently this mostly includes exiting on Ctrl-C, and handling window resize. 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. /// /// # Examples /// /// ```rust /// use cursive_core::{views, Cursive}; /// let mut siv = Cursive::new(); /// /// siv.add_layer(views::TextView::new("Hello world!")); /// ``` pub fn add_layer(&mut self, view: T) where T: IntoBoxedView, { self.screen_mut().add_layer(view); } /// Adds a new full-screen layer to the current screen. /// /// Fullscreen layers have no shadow. pub fn add_fullscreen_layer(&mut self, view: T) where T: IntoBoxedView, { self.screen_mut().add_fullscreen_layer(view); } /// Convenient method to remove a layer from the current screen. pub fn pop_layer(&mut self) -> Option> { self.screen_mut().pop_layer() } /// Convenient stub forwarding layer repositioning. pub fn reposition_layer(&mut self, layer: LayerPosition, position: Position) { self.screen_mut().reposition_layer(layer, position); } /// Processes an event. /// /// * If the menubar is active, it will be handled the event. /// * The view tree will be handled the event. /// * If ignored, global_callbacks will be checked for this event. pub fn on_event(&mut self, event: Event) { if let Event::Mouse { event, position, .. } = event { if event.grabs_focus() && !self.menubar.autohide && !self.menubar.has_submenu() && position.y == 0 { self.select_menubar(); } } if self.menubar.receive_events() { self.menubar.on_event(event).process(self); } else { let offset = usize::from(!self.menubar.autohide); let result = View::on_event(&mut self.root, event.relativized((0, offset))); if let EventResult::Consumed(Some(cb)) = result { cb(self); } } } /// 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, } } /// Returns `true` until [`quit(&mut self)`] is called. /// /// [`quit(&mut self)`]: #method.quit pub fn is_running(&self) -> bool { self.running } /// Runs a dummy event loop. /// /// 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. /// /// Used to manually control the event loop. In most cases, running /// `Cursive::run_with` will be easier. /// /// 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) -> CursiveRunner<&mut Self> { self.running = true; CursiveRunner::new(self, backend) } /// Returns a new runner on the given backend. /// /// 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) -> CursiveRunner { CursiveRunner::new(self, backend) } /// Initialize the backend and runs the event loop. /// /// Used for infallible backend initializers. pub fn run_with(&mut self, backend_init: F) where F: FnOnce() -> Box, { 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(&mut self, backend_init: F) -> Result<(), E> where F: FnOnce() -> Result, E>, { let mut runner = self.runner(backend_init()?); runner.run(); Ok(()) } /// Stops the event loop. pub fn quit(&mut self) { self.running = false; } /// Does not do anything. pub fn noop(&mut self) { // foo } /// Dump the current state of the Cursive root. /// /// *It will clear out this `Cursive` instance* and save everything, including: /// * The view tree /// * Callbacks /// * Menubar /// * User data /// * Callback sink /// /// After calling this, the cursive object will be as if newly created. pub fn dump(&mut self) -> crate::Dump { let (cb_sink, cb_source) = crossbeam_channel::unbounded(); let root = views::OnEventView::new(views::ScreensView::single_screen(views::StackView::new())); Dump { cb_sink: std::mem::replace(&mut self.cb_sink, cb_sink), cb_source: std::mem::replace(&mut self.cb_source, cb_source), fps: self.fps.take(), menubar: std::mem::take(&mut self.menubar), root_view: std::mem::replace(&mut self.root, root), theme: std::mem::take(&mut self.theme), user_data: std::mem::replace(&mut self.user_data, Box::new(())), } } /// Restores the state from a previous dump. /// /// This will discard everything from this `Cursive` instance. /// In particular: /// * All current views will be dropped, replaced by the dump. /// * All callbacks will be replaced. /// * Menubar will be replaced. /// * User Data will be replaced. /// * The callback channel will be replaced - any previous call to /// `cb_sink` on this instance will be disconnected. pub fn restore(&mut self, dump: Dump) { self.cb_sink = dump.cb_sink; self.cb_source = dump.cb_source; self.fps = dump.fps; self.menubar = dump.menubar; self.root = dump.root_view; self.theme = dump.theme; self.user_data = dump.user_data; self.clear(); } } // Callback blueprint crate::fn_blueprint!("Cursive.quit", |_config, _context| { let cb: std::sync::Arc = std::sync::Arc::new(|s| s.quit()); Ok(cb) }); cursive_core-0.4.6/src/cursive_run.rs000064400000000000000000000154271046102023000160350ustar 00000000000000use crate::{backend, buffer, event, Cursive, Vec2}; use parking_lot::RwLock; 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()`. /// /// The `C` type is usually either `Cursive` or `&mut Cursive`. pub struct CursiveRunner { siv: C, backend: Box, buffer: RwLock, boring_frame_count: u32, // Last layer sizes of the stack view. // If it changed, clear the screen. last_sizes: Vec, } impl std::ops::Deref for CursiveRunner where C: Borrow, { type Target = Cursive; fn deref(&self) -> &Cursive { self.siv.borrow() } } impl std::ops::DerefMut for CursiveRunner where C: BorrowMut, { fn deref_mut(&mut self) -> &mut Cursive { self.siv.borrow_mut() } } impl CursiveRunner { /// Creates a new cursive runner wrapper. pub fn new(siv: C, backend: Box) -> Self { CursiveRunner { siv, backend, buffer: RwLock::new(buffer::PrintBuffer::new()), 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 CursiveRunner where C: BorrowMut, { fn layout(&mut self) { let size = self.screen_size(); self.siv.borrow_mut().layout(size); } // Process any backend-requiring calls accumulated by the Cursive root. fn process_pending_backend_calls(&mut self) { let calls = std::mem::take(&mut self.backend_calls); for call in calls { (call)(&mut *self.backend); } } 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; } self.buffer.write().resize(self.screen_size()); self.siv.borrow_mut().draw(&self.buffer); self.buffer.write().flush(&*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]: CursiveRunner::run() /// [2]: CursiveRunner::step() /// [3]: CursiveRunner::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); self.process_pending_backend_calls(); 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]: CursiveRunner::run() /// [2]: CursiveRunner::step() /// [3]: CursiveRunner::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::Event::Refresh); self.process_pending_backend_calls(); } 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 every time? // Probably, actually. // TODO: Do we need to re-layout every time? 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(); } } } cursive_core-0.4.6/src/direction.rs000064400000000000000000000267531046102023000154550ustar 00000000000000//! Direction-related structures. //! //! This module defines two main concepts: [`Orientation`] and [`Direction`]. //! //! [`Orientation`]: enum.Orientation.html //! [`Direction`]: enum.Direction.html //! //! ### Orientation //! //! `Orientation` is a simple enum that can take two values: //! `Horizontal` or `Vertical`. //! //! ### Direction //! //! `Direction` is a bit more complex, and can be of two kinds: //! //! * Absolute direction: left, up, right, or down //! * Relative direction: front or back. Its actual direction depends on the //! orientation. //! //! Usually, "front" refers to the "forward" direction, or the "next" //! element. For example, for a vertical `LinearLayout`, "front" would refer //! to the "down" direction. //! //! This is mostly relevant when referring to change of focus. Hitting the //! `Tab` key would usually cycle focus in the "front" direction, while //! using the arrow keys would use absolute directions instead. use crate::Vec2; use crate::XY; /// Describes a vertical or horizontal orientation for a view. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Orientation { /// Horizontal orientation Horizontal, /// Vertical orientation Vertical, } impl std::str::FromStr for Orientation { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "Vertical" | "vertical" => Self::Vertical, "Horizontal" | "horizontal" => Self::Horizontal, _ => return Err(()), }) } } impl Orientation { /// Returns a `XY(Horizontal, Vertical)`. pub const fn pair() -> XY { XY::new(Orientation::Horizontal, Orientation::Vertical) } /// Returns the component of `v` corresponding to this orientation. /// /// (`Horizontal` will return the x value, /// and `Vertical` will return the y value.) pub fn get(self, v: &XY) -> T { v.get(self).clone() } /// Returns the other orientation. #[must_use] pub const fn swap(self) -> Self { match self { Orientation::Horizontal => Orientation::Vertical, Orientation::Vertical => Orientation::Horizontal, } } // /// Returns a reference to the component of the given vector // /// corresponding to this orientation. // pub const fn get_ref(self, v: &XY) -> &T { // match self { // Orientation::Horizontal => &v.x, // Orientation::Vertical => &v.y, // } // } /// Returns a mutable reference to the component of the given vector /// corresponding to this orientation. /// /// # Examples /// ```rust /// # use cursive_core::XY; /// # use cursive_core::direction::Orientation; /// let o = Orientation::Horizontal; /// let mut xy = XY::new(1, 2); /// *o.get_mut(&mut xy) = 42; /// /// assert_eq!(xy, XY::new(42, 2)); /// ``` pub fn get_mut(self, v: &mut XY) -> &mut T { match self { Orientation::Horizontal => &mut v.x, Orientation::Vertical => &mut v.y, } } /// Same as [`Self::get_mut()`]. #[deprecated] pub fn get_ref(self, v: &mut XY) -> &mut T { self.get_mut(v) } /// Takes an iterator on sizes, and stack them in the current orientation, /// returning the size of the required bounding box. /// /// * For an horizontal view, returns `(Sum(x), Max(y))`. /// * For a vertical view, returns `(Max(x), Sum(y))`. pub fn stack>(self, iter: T) -> Vec2 { match self { Orientation::Horizontal => iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(&b)), Orientation::Vertical => iter.fold(Vec2::zero(), |a, b| a.stack_vertical(&b)), } } /// Creates a new `Vec2` with `main_axis` in `self`'s axis, and /// `second_axis` for the other axis. /// /// # Examples /// /// ```rust /// # use cursive_core::direction::Orientation; /// # use cursive_core::Vec2; /// let o = Orientation::Horizontal; /// let vec = o.make_vec(1, 2); /// /// assert_eq!(vec, Vec2::new(1, 2)); /// /// let o = Orientation::Vertical; /// let vec = o.make_vec(1, 2); /// /// assert_eq!(vec, Vec2::new(2, 1)); /// ``` pub const fn make_vec(self, main_axis: usize, second_axis: usize) -> Vec2 { Vec2::from_major_minor(self, main_axis, second_axis) } } /// Represents a direction, either absolute or orientation-dependent. /// /// * Absolute directions are Up, Down, Left, and Right. /// * Relative directions are Front and Back. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Direction { /// An absolute direction. Abs(Absolute), /// A direction relative to the current orientation. Rel(Relative), } impl std::str::FromStr for Direction { type Err = (); fn from_str(s: &str) -> Result { s.parse() .map(Direction::Abs) .or_else(|_| s.parse().map(Direction::Rel)) } } impl Direction { /// Returns the relative direction for the given orientation. /// /// Some combination have no corresponding relative position. For example, /// `Direction::Abs(Up)` means nothing for `Orientation::Horizontal`. pub const fn relative(self, orientation: Orientation) -> Option { match self { Direction::Abs(abs) => abs.relative(orientation), Direction::Rel(rel) => Some(rel), } } /// Returns the absolute direction in the given `orientation`. pub const fn absolute(self, orientation: Orientation) -> Absolute { match self { Direction::Abs(abs) => abs, Direction::Rel(rel) => rel.absolute(orientation), } } /// Returns the direction opposite `self`. #[must_use] pub const fn opposite(self) -> Self { match self { Direction::Abs(abs) => Direction::Abs(abs.opposite()), Direction::Rel(rel) => Direction::Rel(rel.swap()), } } /// Shortcut to create `Direction::Rel(Relative::Back)` pub const fn back() -> Self { Direction::Rel(Relative::Back) } /// Shortcut to create `Direction::Rel(Relative::Front)` pub const fn front() -> Self { Direction::Rel(Relative::Front) } /// Shortcut to create `Direction::Abs(Absolute::Left)` pub const fn left() -> Self { Direction::Abs(Absolute::Left) } /// Shortcut to create `Direction::Abs(Absolute::Right)` pub const fn right() -> Self { Direction::Abs(Absolute::Right) } /// Shortcut to create `Direction::Abs(Absolute::Up)` pub const fn up() -> Self { Direction::Abs(Absolute::Up) } /// Shortcut to create `Direction::Abs(Absolute::Down)` pub const fn down() -> Self { Direction::Abs(Absolute::Down) } /// Shortcut to create `Direction::Abs(Absolute::None)` pub const fn none() -> Self { Direction::Abs(Absolute::None) } } /// Direction relative to an orientation. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Relative { // TODO: handle right-to-left? (Arabic, ...) /// Front relative direction. /// /// * Horizontally, this means `Left` /// * Vertically, this means `Up` Front, /// Back relative direction. /// /// * Horizontally, this means `Right` /// * Vertically, this means `Down`. Back, } impl std::str::FromStr for Relative { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "Front" | "front" => Self::Front, "Back" | "back" => Self::Back, _ => return Err(()), }) } } impl Relative { /// Returns the absolute direction in the given `orientation`. pub const fn absolute(self, orientation: Orientation) -> Absolute { match (orientation, self) { (Orientation::Horizontal, Relative::Front) => Absolute::Left, (Orientation::Horizontal, Relative::Back) => Absolute::Right, (Orientation::Vertical, Relative::Front) => Absolute::Up, (Orientation::Vertical, Relative::Back) => Absolute::Down, } } /// Picks one of the two values in a tuple. /// /// First one is `self` is `Front`, second one if `self` is `Back`. pub fn pick(self, (front, back): (T, T)) -> T { match self { Relative::Front => front, Relative::Back => back, } } /// Returns the other relative direction. #[must_use] pub const fn swap(self) -> Self { match self { Relative::Front => Relative::Back, Relative::Back => Relative::Front, } } /// Returns the position of `a` relative to `b`. /// /// If `a < b`, it would be `Front`. /// If `a > b`, it would be `Back`. /// If `a == b`, returns `None`. pub const fn a_to_b(a: usize, b: usize) -> Option { // TODO: use std::cmp::Ordering once const trait are a thing. match (a < b, a == b) { (_, true) => None, (true, false) => Some(Relative::Front), (false, false) => Some(Relative::Back), } } } /// Absolute direction (up, down, left, right). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Absolute { /// Left Left, /// Up Up, /// Right Right, /// Down Down, /// No real direction. /// /// Used when the "direction" is across layers for instance. None, } impl std::str::FromStr for Absolute { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "Left" | "left" => Self::Left, "Up" | "up" => Self::Up, "Right" | "right" => Self::Right, "Down" | "down" => Self::Down, "None" | "none" => Self::None, _ => return Err(()), }) } } impl Absolute { /// Returns the relative direction for the given orientation. /// /// Returns `None` when the direction does not apply to the given /// orientation (ex: `Left` and `Vertical`). pub const fn relative(self, orientation: Orientation) -> Option { match (orientation, self) { (Orientation::Horizontal, Absolute::Left) | (Orientation::Vertical, Absolute::Up) => { Some(Relative::Front) } (Orientation::Horizontal, Absolute::Right) | (Orientation::Vertical, Absolute::Down) => Some(Relative::Back), _ => None, } } /// Returns the direction opposite `self`. #[must_use] pub const fn opposite(self) -> Self { match self { Absolute::Left => Absolute::Right, Absolute::Right => Absolute::Left, Absolute::Up => Absolute::Down, Absolute::Down => Absolute::Up, Absolute::None => Absolute::None, } } /// Splits this absolute direction into an orientation and relative direction. /// /// For example, `Right` will give `(Horizontal, Back)`. pub const fn split(self) -> (Orientation, Relative) { match self { Absolute::Left => (Orientation::Horizontal, Relative::Front), Absolute::Right => (Orientation::Horizontal, Relative::Back), Absolute::Up => (Orientation::Vertical, Relative::Front), Absolute::Down => (Orientation::Vertical, Relative::Back), // TODO: Remove `Absolute::None` Absolute::None => panic!("None direction not supported here"), } } } cursive_core-0.4.6/src/div.rs000064400000000000000000000003461046102023000142450ustar 00000000000000use num::Num; /// Integer division that rounds up. pub fn div_up(p: T, q: T) -> T where T: Num + Clone, { let d = p.clone() / q.clone(); if p % q == T::zero() { d } else { T::one() + d } } cursive_core-0.4.6/src/dump.rs000064400000000000000000000012251046102023000144250ustar 00000000000000use crate::{theme::Theme, views, Cursive}; use crossbeam_channel::{Receiver, Sender}; use std::any::Any; use std::num::NonZeroU32; type Callback = dyn FnOnce(&mut Cursive) + Send; /// Represents a dump of everything from a `Cursive` instance. /// /// See [`Cursive::dump()`](../cursive.html#method.dump) pub struct Dump { pub(crate) cb_sink: Sender>, pub(crate) cb_source: Receiver>, pub(crate) fps: Option, pub(crate) menubar: views::Menubar, pub(crate) root_view: views::OnEventView>, pub(crate) theme: Theme, pub(crate) user_data: Box, } cursive_core-0.4.6/src/event.rs000064400000000000000000000375511046102023000146140ustar 00000000000000//! User-input events and their effects. //! //! * Every user input the application receives is converted to an [`Event`]. //! * Each event is then given to the root, and descends the view tree down to //! the view currently in focus, through the [`on_event`] method. //! * If the view consumes the event, it may return a callback to be //! executed. //! * Otherwise, it ignores the event, and the view parent can in turn //! choose to consume it or not. //! * If no view consumes the event, the [global callback] table is checked. //! //! [`Event`]: enum.Event.html //! [`on_event`]: ../trait.View.html#method.on_event //! [global callback]: ../struct.Cursive.html#method.add_global_callback use crate::Cursive; use crate::Vec2; use std::any::Any; use std::ops::Deref; use std::sync::Arc; /// Callback is a function that can be triggered by an event. /// It has a mutable access to the cursive root. /// /// It is meant to be stored in views. #[derive(Clone)] pub struct Callback(Arc); // TODO: remove the Box when Box -> Arc is possible /// A callback that can be run on `&mut dyn View`. /// /// It is meant to be used as parameter in `View::call_on_any`, and not much else. pub type AnyCb<'a> = &'a mut dyn FnMut(&mut dyn crate::view::View); /// A trigger that only selects some types of events. /// /// It is meant to be stored in views. pub struct EventTrigger { trigger: Box bool + Send + Sync>, tag: Box, } trait AnyTag: Any + std::fmt::Debug { fn as_any(&self) -> &dyn Any; } impl AnyTag for T where T: Any + std::fmt::Debug, { fn as_any(&self) -> &dyn Any { self } } impl EventTrigger { /// Create a new `EventTrigger` using the given function as filter. pub fn from_fn(f: F) -> Self where F: 'static + Fn(&Event) -> bool + Send + Sync, { EventTrigger::from_fn_and_tag(f, "free function") } /// Create a new `EventTrigger`. pub fn from_fn_and_tag(f: F, tag: T) -> Self where F: 'static + Fn(&Event) -> bool + Send + Sync, T: Any + std::fmt::Debug + Send + Sync, { let tag = Box::new(tag); let trigger = Box::new(f); EventTrigger { trigger, tag } } /// Check if this trigger has the given tag. /// /// # Examples /// /// ```rust /// use cursive_core::event::{Event, EventTrigger}; /// /// let event = Event::CtrlChar('c'); /// let trigger: EventTrigger = event.clone().into(); /// assert!( /// trigger.has_tag(&event), /// "Trigger does not recognize its own tag." /// ); /// ``` pub fn has_tag(&self, tag: &T) -> bool { (*self.tag) .as_any() .downcast_ref::() .map_or(false, |t| tag == t) } /// Checks if this trigger applies to the given `Event`. pub fn apply(&self, event: &Event) -> bool { (self.trigger)(event) } /// Returns an `EventTrigger` that only accepts arrow keys. /// /// Only bare arrow keys without modifiers (Shift, Ctrl, Alt) will be accepted. pub fn arrows() -> Self { Self::from_fn_and_tag( |e| { matches!( e, Event::Key(Key::Left) | Event::Key(Key::Down) | Event::Key(Key::Up) | Event::Key(Key::Right) ) }, "arrows", ) } /// Returns an `EventTrigger` that only accepts mouse events. pub fn mouse() -> Self { Self::from_fn_and_tag(|e| matches!(e, Event::Mouse { .. }), "mouse") } /// Returns an `EventTrigger` that accepts any event. pub fn any() -> Self { Self::from_fn_and_tag(|_| true, "any") } /// Returns an `EventTrigger` that doesn't accept any event. pub fn none() -> Self { Self::from_fn_and_tag(|_| false, "none") } /// Returns an `EventTrigger` that applies if either `self` or `other` applies. #[must_use] pub fn or(self, other: O) -> Self where O: Into, { let other = other.into(); let self_trigger = self.trigger; let other_trigger = other.trigger; let tag = (self.tag, "or", other.tag); Self::from_fn_and_tag(move |e| self_trigger(e) || other_trigger(e), tag) } } impl From for EventTrigger { fn from(event: Event) -> Self { let tag = event.clone(); Self::from_fn_and_tag(move |e| *e == event, tag) } } impl From for EventTrigger { fn from(c: char) -> Self { Self::from(Event::from(c)) } } impl From for EventTrigger { fn from(k: Key) -> Self { Self::from(Event::from(k)) } } impl From for EventTrigger where F: 'static + Fn(&Event) -> bool + Send + Sync, { fn from(f: F) -> Self { Self::from_fn(f) } } impl Callback { /// Wraps the given function into a `Callback` object. pub fn from_fn(f: F) -> Self where F: 'static + Fn(&mut Cursive) + Send + Sync, { Callback(Arc::new(move |siv| { f(siv); })) } /// Wrap a `FnMut` into a `Callback` object. /// /// If this methods tries to call itself, nested calls will be no-ops. pub fn from_fn_mut(f: F) -> Self where F: 'static + FnMut(&mut Cursive) + Send, { Self::from_fn(crate::immut1!(f)) } /// Wrap a `FnOnce` into a `Callback` object. /// /// After being called once, the callback will become a no-op. pub fn from_fn_once(f: F) -> Self where F: 'static + FnOnce(&mut Cursive) + Send, { Self::from_fn_mut(crate::once1!(f)) } /// Returns a dummy callback that doesn't run anything. pub fn dummy() -> Self { Callback::from_fn(|_| ()) } } impl Deref for Callback { type Target = dyn Fn(&mut Cursive) + 'static; fn deref(&self) -> &Self::Target { &*self.0 } } impl From> for Callback { fn from(f: Arc) -> Self { Callback(f) } } impl From> for Callback { fn from(f: Box) -> Self { Callback(Arc::from(f)) } } /// Answer to an event notification. /// The event can be consumed or ignored. pub enum EventResult { /// The event was ignored. The parent can keep handling it. Ignored, /// The event was consumed. An optional callback to run is attached. Consumed(Option), // TODO: make this a FnOnce? } impl EventResult { /// Convenient method to create `Consumed(Some(f))` pub fn with_cb(f: F) -> Self where F: 'static + Fn(&mut Cursive) + Send + Sync, { EventResult::Consumed(Some(Callback::from_fn(f))) } /// Convenient method to create `Consumed(Some(f))` /// /// After being called once, the callback will become a no-op. pub fn with_cb_once(f: F) -> Self where F: 'static + FnOnce(&mut Cursive) + Send, { EventResult::Consumed(Some(Callback::from_fn_once(f))) } /// Convenient method to create `Consumed(None)` pub fn consumed() -> Self { EventResult::Consumed(None) } /// Returns `true` if `self` is `EventResult::Consumed`. pub fn is_consumed(&self) -> bool { matches!(*self, EventResult::Consumed(_)) } /// Returns `true` if `self` contains a callback. pub fn has_callback(&self) -> bool { matches!(*self, EventResult::Consumed(Some(_))) } /// Process this result if it is a callback. /// /// Does nothing otherwise. pub fn process(self, s: &mut Cursive) { if let EventResult::Consumed(Some(cb)) = self { cb(s); } } /// Returns `self` if it is not `EventResult::Ignored`, otherwise returns `f()`. #[must_use] pub fn or_else(self, f: F) -> Self where F: FnOnce() -> EventResult, { match self { EventResult::Ignored => f(), other => other, } } /// Returns an event result that combines `self` and `other`. #[must_use] pub fn and(self, other: Self) -> Self { match (self, other) { (EventResult::Ignored, result) | (result, EventResult::Ignored) => result, (EventResult::Consumed(None), EventResult::Consumed(cb)) | (EventResult::Consumed(cb), EventResult::Consumed(None)) => EventResult::Consumed(cb), (EventResult::Consumed(Some(cb1)), EventResult::Consumed(Some(cb2))) => { EventResult::with_cb(move |siv| { (cb1)(siv); (cb2)(siv); }) } } } } /// A non-character key on the keyboard #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] pub enum Key { /// Both Enter (or Return) and numpad Enter Enter, /// Tabulation key Tab, /// Backspace key Backspace, /// Escape key Esc, /// Left arrow Left, /// Right arrow Right, /// Up arrow Up, /// Down arrow Down, /// Insert key Ins, /// Delete key Del, /// Home key Home, /// End key End, /// Page Up key PageUp, /// Page Down key PageDown, /// Pause Break key PauseBreak, /// The 5 in the center of the keypad, when numlock is disabled. NumpadCenter, /// F0 key F0, /// F1 key F1, /// F2 key F2, /// F3 key F3, /// F4 key F4, /// F5 key F5, /// F6 key F6, /// F7 key F7, /// F8 key F8, /// F9 key F9, /// F10 key F10, /// F11 key F11, /// F12 key F12, } impl Key { /// Returns the function key corresponding to the given number /// /// 1 -> F1, etc... /// /// # Panics /// /// If `n == 0 || n > 12` pub fn from_f(n: u8) -> Key { match n { 0 => Key::F0, 1 => Key::F1, 2 => Key::F2, 3 => Key::F3, 4 => Key::F4, 5 => Key::F5, 6 => Key::F6, 7 => Key::F7, 8 => Key::F8, 9 => Key::F9, 10 => Key::F10, 11 => Key::F11, 12 => Key::F12, _ => panic!("unknown function key: F{n}"), } } } /// One of the buttons present on the mouse #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] #[non_exhaustive] pub enum MouseButton { /// The left button, used for main actions. Left, /// Middle button, probably the wheel. Often pastes text in X11 on linux. Middle, /// The right button, for special actions. Right, /// Fourth button if the mouse supports it. Button4, /// Fifth button if the mouse supports it. Button5, // TODO: handle more buttons? Wheel left/right? #[doc(hidden)] Other, } /// Represents a possible event sent by the mouse. #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] pub enum MouseEvent { /// A button was pressed. Press(MouseButton), /// A button was released. Release(MouseButton), /// A button is being held. Hold(MouseButton), /// The wheel was moved up. WheelUp, /// The wheel was moved down. WheelDown, } impl MouseEvent { /// Returns the button used by this event, if any. /// /// Returns `None` if `self` is `WheelUp` or `WheelDown`. pub fn button(self) -> Option { match self { MouseEvent::Press(btn) | MouseEvent::Release(btn) | MouseEvent::Hold(btn) => Some(btn), _ => None, } } /// Returns `true` if `self` is an event that can grab focus. /// /// This includes `Press`, `WheelUp` and `WheelDown`. /// /// It does _not_ include `Release` or `Hold`. /// /// It means you should be able to grab a scroll bar, and move the mouse /// away from the view, without actually changing the focus. pub fn grabs_focus(self) -> bool { matches!( self, MouseEvent::Press(_) | MouseEvent::WheelUp | MouseEvent::WheelDown ) } } /// Represents an event as seen by the application. #[derive(PartialEq, Eq, Clone, Hash, Debug)] pub enum Event { /// Event fired when the window is resized. WindowResize, /// Event fired when the view is about to lose focus. FocusLost, /// Event fired regularly when a auto-refresh is set. Refresh, // TODO: have Char(modifier, char) and Key(modifier, key) enums? /// A character was entered (includes numbers, punctuation, ...). Char(char), /// A character was entered with the Ctrl key pressed. CtrlChar(char), /// A character was entered with the Alt key pressed. AltChar(char), /// A non-character key was pressed. Key(Key), /// A non-character key was pressed with the Shift key pressed. Shift(Key), /// A non-character key was pressed with the Alt key pressed. Alt(Key), /// A non-character key was pressed with the Shift and Alt keys pressed. AltShift(Key), /// A non-character key was pressed with the Ctrl key pressed. Ctrl(Key), /// A non-character key was pressed with the Ctrl and Shift keys pressed. CtrlShift(Key), /// A non-character key was pressed with the Ctrl and Alt keys pressed. CtrlAlt(Key), /// A mouse event was sent. Mouse { /// Position of the top-left corner of the view receiving this event. offset: Vec2, /// Position of the mouse when this event was fired. position: Vec2, /// The mouse event itself. event: MouseEvent, }, // TODO: use a backend-dependent type for the unknown values? /// An unknown event was received. Unknown(Vec), // Maybe add a `Custom(Arc)` ? // Having a doc-hidden event prevents people from having exhaustive // matches, allowing us to add events in the future. // // In addition we may not want people to listen to the exit event? #[doc(hidden)] /// The application is about to exit. Exit, } impl Event { /// Returns the character, if `self` is a char event. pub fn char(&self) -> Option { match *self { Event::Char(c) => Some(c), Event::AltChar(c) => Some(c), Event::CtrlChar(c) => Some(c), _ => None, } } /// Returns the position of the mouse, if `self` is a mouse event. pub fn mouse_position(&self) -> Option { if let Event::Mouse { position, .. } = *self { Some(position) } else { None } } /// Returns a mutable reference to the position of the mouse. /// /// Returns `None` if `self` is not a mouse event. pub fn mouse_position_mut(&mut self) -> Option<&mut Vec2> { if let Event::Mouse { ref mut position, .. } = *self { Some(position) } else { None } } /// Update `self` with the given offset. /// /// If `self` is a mouse event, adds `top_left` to its offset. /// Otherwise, do nothing. pub fn relativize(&mut self, top_left: V) where V: Into, { if let Event::Mouse { ref mut offset, .. } = *self { *offset = *offset + top_left; } } /// Returns a cloned, relativized event. /// /// If `self` is a mouse event, adds `top_left` to its offset. /// Otherwise, returns a simple clone. #[must_use] pub fn relativized(&self, top_left: V) -> Self where V: Into, { let mut result = self.clone(); result.relativize(top_left); result } } impl From for Event { fn from(c: char) -> Event { Event::Char(c) } } impl From for Event { fn from(k: Key) -> Event { Event::Key(k) } } cursive_core-0.4.6/src/lib.rs000064400000000000000000000040121046102023000142230ustar 00000000000000//! # Cursive-core //! //! This library defines the core components for the Cursive TUI. //! //! The main purpose of `cursive-core` is to write third-party libraries to work with Cursive. //! //! If you are building an end-user application, then [`cursive`] is probably what you want. //! //! [`cursive`]: https://docs.rs/cursive #![deny(missing_docs)] #![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] macro_rules! new_default( ($c:ident<$t:ident>) => { impl<$t> Default for $c<$t> { fn default() -> Self { Self::new() } } }; ($c:ident) => { impl Default for $c { fn default() -> Self { Self::new() } } }; ($c:ident<$t:ident: Default>) => { impl <$t> Default for $c<$t> where $t: Default { fn default() -> Self { Self::new($t::default()) } } }; ); /// Re-export crates used in the public API pub mod reexports { pub use ahash; pub use crossbeam_channel; pub use enumset; pub use log; pub use time; #[cfg(feature = "toml")] pub use toml; #[cfg(feature = "ansi")] pub use ansi_parser; pub use serde_json; } // use crate as cursive; pub use cursive_macros::{blueprint, callback_helpers}; #[macro_use] pub mod utils; #[macro_use] pub mod view; #[macro_use] pub mod views; pub mod align; pub mod backend; pub mod direction; pub mod event; pub mod logger; pub mod menu; pub mod style; pub mod theme; pub mod traits; pub mod vec; #[cfg(feature = "builder")] pub use inventory::submit; #[macro_use] pub mod builder; pub mod buffer; mod cursive_root; mod cursive_run; mod dump; mod printer; mod rect; mod with; mod xy; mod div; pub use self::cursive_root::{CbSink, Cursive, ScreenId}; pub use self::cursive_run::CursiveRunner; pub use self::dump::Dump; pub use self::printer::Printer; pub use self::rect::Rect; pub use self::vec::Vec2; pub use self::view::View; pub use self::with::With; pub use self::xy::XY; cursive_core-0.4.6/src/logger.rs000064400000000000000000000110601046102023000147350ustar 00000000000000//! Logging utilities. use lazy_static::lazy_static; use std::cmp::Ord; use std::collections::VecDeque; use std::str::FromStr; use std::sync::{Mutex, RwLock}; /// Saves all log records in a global deque. /// /// Uses a `DebugView` to access it. /// /// # Examples /// /// Set log levels from env vars /// /// ``` /// # use cursive_core::*; /// logger::set_filter_levels_from_env(); /// logger::init(); /// ``` /// /// Set log levels explicitly. /// /// ``` /// # use cursive_core::*; /// # use log::LevelFilter; /// logger::set_internal_filter_level(LevelFilter::Warn); /// logger::set_external_filter_level(LevelFilter::Debug); /// logger::init(); /// ``` pub struct CursiveLogger; lazy_static! { /// Circular buffer for logs. Use it to implement [`DebugView`]. /// /// [`DebugView`]: ../views/struct.DebugView.html pub static ref LOGS: Mutex> = Mutex::new(VecDeque::with_capacity(1_000)); // Log filter level for log messages from within cursive static ref INT_FILTER_LEVEL: RwLock = RwLock::new(log::LevelFilter::Trace); // Log filter level for log messages from sources outside of cursive static ref EXT_FILTER_LEVEL: RwLock = RwLock::new(log::LevelFilter::Trace); } /// Sets the internal log filter level. pub fn set_internal_filter_level(level: log::LevelFilter) { *INT_FILTER_LEVEL.write().unwrap() = level; } /// Sets the external log filter level. pub fn set_external_filter_level(level: log::LevelFilter) { *EXT_FILTER_LEVEL.write().unwrap() = level; } /// Sets log filter levels based on environment variables `RUST_LOG` and `CURSIVE_LOG`. /// If `RUST_LOG` is set, then both internal and external log levels are set to match. /// If `CURSIVE_LOG` is set, then the internal log level is set to match with precedence over /// `RUST_LOG`. pub fn set_filter_levels_from_env() { if let Ok(rust_log) = std::env::var("RUST_LOG") { match log::LevelFilter::from_str(&rust_log) { Ok(filter_level) => { set_internal_filter_level(filter_level); set_external_filter_level(filter_level); } Err(e) => log::warn!("Could not parse RUST_LOG: {}", e), } } if let Ok(cursive_log) = std::env::var("CURSIVE_LOG") { match log::LevelFilter::from_str(&cursive_log) { Ok(filter_level) => { set_internal_filter_level(filter_level); } Err(e) => log::warn!("Could not parse CURSIVE_LOG: {}", e), } } } /// A log record. pub struct Record { /// Log level used for this record pub level: log::Level, /// Time this message was logged pub time: time::OffsetDateTime, /// Message content pub message: String, } /// Log a record in cursive's log queue. pub fn log(record: &log::Record) { let mut logs = LOGS.lock().unwrap(); // TODO: customize the format? Use colors? Save more info? if logs.len() == logs.capacity() { logs.pop_front(); } logs.push_back(Record { level: record.level(), message: format!("{}", record.args()), time: time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc()), }); } impl log::Log for CursiveLogger { fn enabled(&self, metadata: &log::Metadata) -> bool { if metadata.target().starts_with("cursive_core::") { metadata.level() <= *INT_FILTER_LEVEL.read().unwrap() } else { metadata.level() <= *EXT_FILTER_LEVEL.read().unwrap() } } fn log(&self, record: &log::Record) { if self.enabled(record.metadata()) { log(record); } } fn flush(&self) {} } /// Initialize the Cursive logger. /// /// Make sure this is the only logger your are using. /// /// Use a [`DebugView`](crate::views::DebugView) to see the logs, or use /// [`Cursive::toggle_debug_console()`](crate::Cursive::toggle_debug_console()). pub fn init() { log::set_max_level((*INT_FILTER_LEVEL.read().unwrap()).max(*EXT_FILTER_LEVEL.read().unwrap())); // This will panic if `set_logger` was already called. log::set_logger(&CursiveLogger).unwrap(); } /// Return a logger that stores records in cursive's log queue. /// /// These logs can then be read by a [`DebugView`](crate::views::DebugView). /// /// An easier alternative might be to use [`init()`]. pub fn get_logger() -> CursiveLogger { CursiveLogger } /// Adds `n` more entries to cursive's log queue. /// /// Most of the time you don't need to use this directly. pub fn reserve_logs(n: usize) { LOGS.lock().unwrap().reserve(n); } cursive_core-0.4.6/src/menu.rs000064400000000000000000000255561046102023000144410ustar 00000000000000//! Build menu trees. //! //! Menus are a way to arrange many actions in groups of more manageable size. //! //! A menu can be seen as a [`Tree`]. It has a list of children: //! //! * Leaf nodes are made of a label and a callback //! * Sub-trees are made of a label, and another `Tree`. //! * Delimiters are just there to separate groups of related children. //! //! The [menubar] is the main way to show menus. //! //! [`Tree`]: struct.Tree.html //! [menubar]: ../struct.Cursive.html#method.menubar use crate::utils::markup::PlainStr; use crate::utils::span::{SpannedStr, SpannedText as _}; use crate::{event::Callback, style::Style, utils::markup::StyledString, Cursive, With}; use std::sync::Arc; static DELIMITER: PlainStr = PlainStr::new_with_width("│", 1); /// Root of a menu tree. #[derive(Default, Clone)] pub struct Tree { /// Menu items pub children: Vec, } /// Node in the menu tree. #[derive(Clone)] pub enum Item { /// Actionnable button with a label. Leaf { /// Text displayed for this entry. label: StyledString, /// Callback to run when the entry is selected. cb: Callback, /// Whether this item is enabled. /// /// Disabled items cannot be selected and are displayed grayed out. enabled: bool, }, /// Sub-menu with a label. Subtree { /// Text displayed for this entry. label: StyledString, /// Subtree under this item. tree: Arc, /// Whether this item is enabled. /// /// Disabled items cannot be selected and are displayed grayed out. enabled: bool, }, /// Delimiter without a label. Delimiter, } impl Item { /// Create a new leaf menu item. pub fn leaf(label: S, cb: F) -> Self where S: Into, F: 'static + Fn(&mut Cursive) + Send + Sync, { let label = label.into(); let cb = Callback::from_fn(cb); let enabled = true; Item::Leaf { label, cb, enabled } } /// Create a new subtree menu item. pub fn subtree(label: S, tree: Tree) -> Self where S: Into, { let label = label.into(); let tree = Arc::new(tree); let enabled = true; Item::Subtree { label, tree, enabled, } } /// Returns the label for this item. /// /// Returns a vertical bar string if `self` is a delimiter. pub fn label(&self) -> &str { match *self { Item::Delimiter => DELIMITER.source(), Item::Leaf { ref label, .. } | Item::Subtree { ref label, .. } => label.source(), } } /// Returns the styled lable for this item /// /// Returns a vertical bar string if `self` is a delimiter. pub fn styled_label(&self) -> SpannedStr