diff --git a/.gitignore b/.gitignore index 80dd7e1..a6d358e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tags .ctags *.bk TODO.txt +*.rustfmt diff --git a/Cargo.toml b/Cargo.toml index e098758..a43e4b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ repository = "gyscos/Cursive" enum-map = "0.2.24" enumset = "0.3.3" log = "0.4" -num = "0.1" owning_ref = "0.3.3" toml = "0.4" unicode-segmentation = "1.0" @@ -25,6 +24,10 @@ unicode-width = "0.1" xi-unicode = "0.1.0" libc = "0.2" +[dependencies.num] +default-features = false +version = "0.1" + [dependencies.maplit] optional = true version = "1.0.0" diff --git a/examples/colors.rs b/examples/colors.rs index bba7f15..7f97b8c 100644 --- a/examples/colors.rs +++ b/examples/colors.rs @@ -1,9 +1,9 @@ extern crate cursive; -use cursive::{Cursive, Printer}; use cursive::theme::{Color, ColorStyle}; use cursive::view::Boxable; use cursive::views::Canvas; +use cursive::{Cursive, Printer}; // This example will draw a colored square with a gradient. // diff --git a/examples/dialog.rs b/examples/dialog.rs index 126ad32..bf8ce28 100644 --- a/examples/dialog.rs +++ b/examples/dialog.rs @@ -1,7 +1,7 @@ extern crate cursive; -use cursive::Cursive; use cursive::views::{Dialog, TextView}; +use cursive::Cursive; fn main() { // Creates the cursive root - required for every application. diff --git a/examples/edit.rs b/examples/edit.rs index b6d695f..bcafbb5 100644 --- a/examples/edit.rs +++ b/examples/edit.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::traits::*; use cursive::views::{Dialog, EditView, TextView}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/hello_world.rs b/examples/hello_world.rs index fb5fae7..e0ba7c6 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,7 +1,7 @@ extern crate cursive; -use cursive::Cursive; use cursive::views::TextView; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/linear.rs b/examples/linear.rs index 1d38f9d..bc0fa1f 100644 --- a/examples/linear.rs +++ b/examples/linear.rs @@ -1,9 +1,9 @@ extern crate cursive; -use cursive::Cursive; use cursive::align::HAlign; use cursive::traits::*; use cursive::views::{Dialog, DummyView, LinearLayout, TextView}; +use cursive::Cursive; // This example uses a LinearLayout to stick multiple views next to each other. diff --git a/examples/list_view.rs b/examples/list_view.rs index a87ed11..0c04251 100644 --- a/examples/list_view.rs +++ b/examples/list_view.rs @@ -1,9 +1,10 @@ extern crate cursive; -use cursive::Cursive; use cursive::traits::*; -use cursive::views::{Checkbox, Dialog, EditView, LinearLayout, ListView, - SelectView, TextView}; +use cursive::views::{ + Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView, TextView, +}; +use cursive::Cursive; // This example uses a ListView. // diff --git a/examples/logs.rs b/examples/logs.rs index 74268f5..839e7b1 100644 --- a/examples/logs.rs +++ b/examples/logs.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::{Cursive, Printer}; use cursive::traits::*; use cursive::vec::Vec2; +use cursive::{Cursive, Printer}; use std::collections::VecDeque; use std::sync::mpsc; use std::thread; diff --git a/examples/lorem.rs b/examples/lorem.rs index a76a9e3..4c4a0ca 100644 --- a/examples/lorem.rs +++ b/examples/lorem.rs @@ -1,9 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::align::HAlign; -use cursive::view::Boxable; use cursive::views::{Dialog, Panel, TextView}; +use cursive::Cursive; fn main() { // Read some long text from a file. @@ -18,11 +17,10 @@ fn main() { // and will adapt to the terminal size. siv.add_fullscreen_layer( Dialog::around(Panel::new(TextView::new(content))) + .title("Unicode and wide-character support") // This is the alignment for the button .h_align(HAlign::Center) - .button("Quit", |s| s.quit()) - .title("Unicode and wide-character support") - .full_screen(), + .button("Quit", |s| s.quit()), ); // Show a popup on top of the view. siv.add_layer(Dialog::info( diff --git a/examples/markup.rs b/examples/markup.rs index 6eac432..6eec9af 100644 --- a/examples/markup.rs +++ b/examples/markup.rs @@ -1,12 +1,12 @@ extern crate cursive; -use cursive::Cursive; use cursive::theme::BaseColor; use cursive::theme::Color; use cursive::theme::Effect; use cursive::theme::Style; use cursive::utils::markup::StyledString; use cursive::views::{Dialog, TextView}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/menubar.rs b/examples/menubar.rs index fb063ff..1482de5 100644 --- a/examples/menubar.rs +++ b/examples/menubar.rs @@ -1,10 +1,10 @@ extern crate cursive; -use cursive::Cursive; use cursive::event::Key; use cursive::menu::MenuTree; use cursive::traits::*; use cursive::views::Dialog; +use cursive::Cursive; use std::sync::atomic::{AtomicUsize, Ordering}; // This examples shows how to configure and use a menubar at the top of the diff --git a/examples/mines/main.rs b/examples/mines/main.rs index c4ef63f..4a0ab85 100644 --- a/examples/mines/main.rs +++ b/examples/mines/main.rs @@ -3,13 +3,13 @@ extern crate rand; mod game; -use cursive::Cursive; -use cursive::Printer; use cursive::direction::Direction; use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; use cursive::theme::{BaseColor, Color, ColorStyle}; use cursive::vec::Vec2; use cursive::views::{Button, Dialog, LinearLayout, Panel, SelectView}; +use cursive::Cursive; +use cursive::Printer; fn main() { let mut siv = Cursive::default(); diff --git a/examples/mutation.rs b/examples/mutation.rs index db65c26..32cf39b 100644 --- a/examples/mutation.rs +++ b/examples/mutation.rs @@ -1,9 +1,9 @@ extern crate cursive; -use cursive::Cursive; use cursive::traits::*; use cursive::view::{Offset, Position}; use cursive::views::{Dialog, OnEventView, TextView}; +use cursive::Cursive; // This example modifies a view after creation. diff --git a/examples/position.rs b/examples/position.rs index cf4dff4..4957102 100644 --- a/examples/position.rs +++ b/examples/position.rs @@ -1,9 +1,9 @@ extern crate cursive; -use cursive::Cursive; use cursive::view::Position; use cursive::views::LayerPosition; use cursive::views::TextView; +use cursive::Cursive; /// Moves top layer by the specified amount fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) { diff --git a/examples/progress.rs b/examples/progress.rs index 98dd55c..0853b77 100644 --- a/examples/progress.rs +++ b/examples/progress.rs @@ -1,10 +1,10 @@ extern crate cursive; extern crate rand; -use cursive::Cursive; use cursive::traits::*; -use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView}; use cursive::utils::Counter; +use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView}; +use cursive::Cursive; use rand::Rng; use std::cmp::min; use std::thread; diff --git a/examples/radio.rs b/examples/radio.rs index f7ccfbc..ef3b060 100644 --- a/examples/radio.rs +++ b/examples/radio.rs @@ -1,7 +1,7 @@ extern crate cursive; -use cursive::Cursive; use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup}; +use cursive::Cursive; // This example uses radio buttons. diff --git a/examples/refcell_view.rs b/examples/refcell_view.rs index a6ec910..81aaa9a 100644 --- a/examples/refcell_view.rs +++ b/examples/refcell_view.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::view::{Boxable, Identifiable}; use cursive::views::{Dialog, EditView, LinearLayout, TextView}; +use cursive::Cursive; // This example shows a way to access multiple views at the same time. diff --git a/examples/select.rs b/examples/select.rs index d930be6..961481a 100644 --- a/examples/select.rs +++ b/examples/select.rs @@ -1,10 +1,10 @@ extern crate cursive; -use cursive::Cursive; use cursive::align::HAlign; use cursive::event::EventResult; use cursive::traits::*; use cursive::views::{Dialog, OnEventView, SelectView, TextView}; +use cursive::Cursive; // We'll use a SelectView here. // diff --git a/examples/slider.rs b/examples/slider.rs index b84a2e3..8442c7f 100644 --- a/examples/slider.rs +++ b/examples/slider.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::traits::*; use cursive::views::{Dialog, SliderView}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/terminal_default.rs b/examples/terminal_default.rs index 57f4c36..eb96433 100644 --- a/examples/terminal_default.rs +++ b/examples/terminal_default.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::theme::{Color, PaletteColor, Theme}; use cursive::views::TextView; +use cursive::Cursive; // This example sets the background color to the terminal default. // diff --git a/examples/text_area.rs b/examples/text_area.rs index b1e768e..e78b7f3 100644 --- a/examples/text_area.rs +++ b/examples/text_area.rs @@ -1,9 +1,9 @@ extern crate cursive; -use cursive::Cursive; use cursive::event::{Event, Key}; use cursive::traits::*; use cursive::views::{Dialog, EditView, OnEventView, TextArea}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); @@ -37,10 +37,10 @@ fn main() { .min_width(10), ) .button("Ok", |s| { - let text = s.call_on_id( - "edit", - |view: &mut EditView| view.get_content(), - ).unwrap(); + let text = + s.call_on_id("edit", |view: &mut EditView| { + view.get_content() + }).unwrap(); find(s, &text); }) .dismiss_button("Cancel"), diff --git a/examples/theme.rs b/examples/theme.rs index 3494961..2454f07 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -1,7 +1,7 @@ extern crate cursive; -use cursive::Cursive; use cursive::views::{Dialog, TextView}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/theme_manual.rs b/examples/theme_manual.rs index 1af3533..dc5061b 100644 --- a/examples/theme_manual.rs +++ b/examples/theme_manual.rs @@ -1,8 +1,8 @@ extern crate cursive; -use cursive::Cursive; use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle}; use cursive::views::{Dialog, EditView, LinearLayout, TextView}; +use cursive::Cursive; fn main() { let mut siv = Cursive::default(); diff --git a/examples/vpv.rs b/examples/vpv.rs index c3ba833..50b693d 100644 --- a/examples/vpv.rs +++ b/examples/vpv.rs @@ -3,10 +3,10 @@ extern crate pretty_bytes; use std::io; -use cursive::Cursive; use cursive::traits::{Boxable, With}; use cursive::utils; use cursive::views::{Canvas, Dialog, LinearLayout, ProgressBar}; +use cursive::Cursive; use pretty_bytes::converter::convert; use std::thread; use std::time; @@ -31,10 +31,7 @@ fn main() { let meta = std::fs::metadata(&source).unwrap(); // If possible, read the file size to have a progress bar. let len = meta.len(); - ( - Some(source), - if len > 0 { Some(len) } else { None }, - ) + (Some(source), if len > 0 { Some(len) } else { None }) } None => (None, None), }; @@ -68,9 +65,7 @@ fn main() { } // When we're done, shut down the application - cb_sink - .send(Box::new(|s: &mut Cursive| s.quit())) - .unwrap(); + cb_sink.send(Box::new(|s: &mut Cursive| s.quit())).unwrap(); }); // Add a single view: progress status diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index da0d98a..aae0bb4 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -156,7 +156,8 @@ impl Backend { let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0; let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0; - mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT + mevent.bstate &= !(ncurses::BUTTON_SHIFT + | ncurses::BUTTON_ALT | ncurses::BUTTON_CTRL) as mmask_t; diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 6d6c0ff..e39e005 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -117,7 +117,8 @@ impl Backend { let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0; let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0; - mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT + mevent.bstate &= !(pancurses::BUTTON_SHIFT + | pancurses::BUTTON_ALT | pancurses::BUTTON_CTRL) as mmask_t; let make_event = |event| Event::Mouse { diff --git a/src/cursive.rs b/src/cursive.rs index 40762f6..87265d7 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -105,7 +105,7 @@ impl Cursive { pub fn new(backend: Box) -> Self { let theme = theme::load_default(); // theme.activate(&mut backend); - // let theme = theme::load_theme("assets/style.toml").unwrap(); + // let theme = theme::load_toml("assets/style.toml").unwrap(); let (tx, rx) = mpsc::channel(); @@ -283,8 +283,8 @@ impl Cursive { /// Loads a theme from the given string content. /// /// Content must be valid toml. - pub fn load_theme(&mut self, content: &str) -> Result<(), theme::Error> { - self.set_theme(try!(theme::load_theme(content))); + pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> { + self.set_theme(try!(theme::load_toml(content))); Ok(()) } @@ -670,7 +670,8 @@ impl Cursive { event, position, .. } = event { - if event.grabs_focus() && !self.menubar.autohide + if event.grabs_focus() + && !self.menubar.autohide && !self.menubar.has_submenu() && position.y == 0 { diff --git a/src/printer.rs b/src/printer.rs index f1ab417..7d05698 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -17,7 +17,7 @@ use with::With; /// /// The part of the content it will print is defined by `content_offset` /// and `size`. -pub struct Printer<'a> { +pub struct Printer<'a, 'b> { /// Offset into the window this printer should start drawing at. /// /// A print request at `x` will really print at `x + offset`. @@ -46,10 +46,10 @@ pub struct Printer<'a> { pub theme: &'a Theme, /// Backend used to actually draw things - backend: &'a Backend, + backend: &'b Backend, } -impl<'a> Clone for Printer<'a> { +impl<'a, 'b> Clone for Printer<'a, 'b> { fn clone(&self) -> Self { Printer { offset: self.offset, @@ -63,13 +63,13 @@ impl<'a> Clone for Printer<'a> { } } -impl<'a> Printer<'a> { +impl<'a, 'b> Printer<'a, 'b> { /// Creates a new printer on the given window. /// /// But nobody needs to know that. #[doc(hidden)] pub fn new>( - size: T, theme: &'a Theme, backend: &'a Backend, + size: T, theme: &'a Theme, backend: &'b Backend, ) -> Self { let size = size.into(); Printer { @@ -298,6 +298,24 @@ impl<'a> Printer<'a> { self.backend.unset_effect(effect); } + /// Call the given closure with a modified printer + /// that will apply the given theme on prints. + pub fn with_theme(&self, theme: &Theme, f: F) + where + F: FnOnce(&Printer), + { + let new_printer = Printer { + offset: self.offset, + size: self.size, + focused: self.focused, + theme: theme, + backend: self.backend, + output_size: self.output_size, + content_offset: self.content_offset, + }; + f(&new_printer); + } + /// Call the given closure with a modified printer /// that will apply each given effect on prints. pub fn with_effects(&self, effects: EnumSet, f: F) diff --git a/src/theme/color.rs b/src/theme/color.rs index 43fe6f1..9c84543 100644 --- a/src/theme/color.rs +++ b/src/theme/color.rs @@ -114,6 +114,13 @@ impl Color { } } + /// Parse a string into a color. + /// + /// Examples: + /// * `"red"` becomes `Color::Dark(BaseColor::Red)` + /// * `"light green"` becomes `Color::Light(BaseColor::Green)` + /// * `"default"` becomes `Color::TerminalDefault` + /// * `"#123456"` becomes `Color::Rgb(0x12, 0x34, 0x56)` pub(crate) fn parse(value: &str) -> Option { Some(match value { "black" => Color::Dark(BaseColor::Black), @@ -149,11 +156,15 @@ impl Color { let r = load_hex(&value[0..l]) * multiplier; let g = load_hex(&value[l..2 * l]) * multiplier; let b = load_hex(&value[2 * l..3 * l]) * multiplier; + Some(Color::Rgb(r as u8, g as u8, b as u8)) } else if value.len() == 3 { // RGB values between 0 and 5 maybe? + // Like 050 for green let rgb: Vec<_> = value.chars().map(|c| c as i16 - '0' as i16).collect(); + + assert_eq!(rgb.len(), 3); if rgb.iter().all(|&i| i >= 0 && i < 6) { Some(Color::RgbLowRes( rgb[0] as u8, diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 3051e75..ddfe2e6 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -169,7 +169,7 @@ pub use self::color::{BaseColor, Color}; pub use self::color_pair::ColorPair; pub use self::color_style::{ColorStyle, ColorType}; pub use self::effect::Effect; -pub use self::palette::{default_palette, Palette, PaletteColor}; +pub use self::palette::{Palette, PaletteColor}; pub use self::style::Style; use std::fs::File; use std::io; @@ -193,13 +193,13 @@ impl Default for Theme { Theme { shadow: true, borders: BorderStyle::Simple, - palette: default_palette(), + palette: Palette::default(), } } } impl Theme { - fn load(&mut self, table: &toml::value::Table) { + fn load_toml(&mut self, table: &toml::value::Table) { if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") { self.shadow = shadow; } @@ -209,7 +209,7 @@ impl Theme { } if let Some(&toml::Value::Table(ref table)) = table.get("colors") { - palette::load_table(&mut self.palette, table); + palette::load_toml(&mut self.palette, table); } } } @@ -244,15 +244,15 @@ pub fn load_theme_file>(filename: P) -> Result { content }; - load_theme(&content) + load_toml(&content) } /// Loads a theme string and sets it as active. -pub fn load_theme(content: &str) -> Result { +pub fn load_toml(content: &str) -> Result { let table = toml::de::from_str(content)?; let mut theme = Theme::default(); - theme.load(&table); + theme.load_toml(&table); Ok(theme) } diff --git a/src/theme/palette.rs b/src/theme/palette.rs index 4273839..2fb344e 100644 --- a/src/theme/palette.rs +++ b/src/theme/palette.rs @@ -2,6 +2,9 @@ use super::Color; use enum_map::EnumMap; use toml; +use std::collections::HashMap; +use std::ops::{Index, IndexMut}; + /// Color configuration for the application. /// /// Assign each color role an actual color. @@ -11,17 +14,122 @@ use toml; /// # Example /// /// ```rust -/// # use cursive::theme; +/// # use cursive::theme::Palette; /// use cursive::theme::PaletteColor::*; /// use cursive::theme::Color::*; /// use cursive::theme::BaseColor::*; /// -/// let mut palette = theme::default_palette(); +/// let mut palette = Palette::default(); /// /// assert_eq!(palette[Background], Dark(Blue)); /// palette[Shadow] = Light(Red); /// ``` -pub type Palette = EnumMap; +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Palette { + basic: EnumMap, + custom: HashMap, +} + +/// A node in the palette tree. +/// +/// This describes a value attached to a custom keyword in the palette. +/// +/// This can either be a color, or a nested namespace with its own mapping. +#[derive(PartialEq, Eq, Clone, Debug)] +pub enum PaletteNode { + /// A single color. + Color(Color), + /// A group of values bundled in the same namespace. + /// + /// Namespaces can be merged in the palette with `Palette::merge`. + Namespace(HashMap), +} + +// Basic usage: only use basic colors +impl Index for Palette { + type Output = Color; + + fn index(&self, palette_color: PaletteColor) -> &Color { + &self.basic[palette_color] + } +} + +// We can alter existing color if needed (but why?...) +impl IndexMut for Palette { + fn index_mut(&mut self, palette_color: PaletteColor) -> &mut Color { + &mut self.basic[palette_color] + } +} + +impl Palette { + /// Returns a custom color from this palette. + /// + /// Returns `None` if the given key was not found. + pub fn custom<'a>(&'a self, key: &str) -> Option<&'a Color> { + self.custom.get(key).and_then(|node| { + if let &PaletteNode::Color(ref color) = node { + Some(color) + } else { + None + } + }) + } + + /// Returns a new palette where the given namespace has been merged. + /// + /// All values in the namespace will override previous values. + pub fn merge(&self, namespace: &str) -> Palette { + let mut result = self.clone(); + + if let Some(&PaletteNode::Namespace(ref palette)) = + self.custom.get(namespace) + { + // Merge `result` and `palette` + for (key, value) in palette.iter() { + match *value { + PaletteNode::Color(color) => result.set_color(key, color), + PaletteNode::Namespace(ref map) => { + result.add_namespace(key, map.clone()) + } + } + } + } + + result + } + + /// Sets the color for the given key. + /// + /// This will update either the basic palette or the custom values. + pub fn set_color(&mut self, key: &str, color: Color) { + use theme::PaletteColor::*; + + match key { + "background" => self.basic[Background] = color, + "shadow" => self.basic[Shadow] = color, + "view" => self.basic[View] = color, + "primary" => self.basic[Primary] = color, + "secondary" => self.basic[Secondary] = color, + "tertiary" => self.basic[Tertiary] = color, + "title_primary" => self.basic[TitlePrimary] = color, + "title_secondary" => self.basic[TitleSecondary] = color, + "highlight" => self.basic[Highlight] = color, + "highlight_inactive" => self.basic[HighlightInactive] = color, + other => { + self.custom + .insert(other.to_string(), PaletteNode::Color(color)); + } + } + } + + /// Adds a color namespace to this palette. + pub fn add_namespace( + &mut self, key: &str, namespace: HashMap, + ) { + self.custom + .insert(key.to_string(), PaletteNode::Namespace(namespace)); + } +} /// Returns the default palette for a cursive application. /// @@ -35,57 +143,84 @@ pub type Palette = EnumMap; /// * `TitleSecondary` => `Dark(Yellow)` /// * `Highlight` => `Dark(Red)` /// * `HighlightInactive` => `Dark(Blue)` -pub fn default_palette() -> Palette { - use self::PaletteColor::*; - use theme::BaseColor::*; - use theme::Color::*; +impl Default for Palette { + fn default() -> Palette { + use self::PaletteColor::*; + use theme::BaseColor::*; + use theme::Color::*; - enum_map!{ - Background => Dark(Blue), - Shadow => Dark(Black), - View => Dark(White), - Primary => Dark(Black), - Secondary => Dark(Blue), - Tertiary => Dark(White), - TitlePrimary => Dark(Red), - TitleSecondary => Dark(Yellow), - Highlight => Dark(Red), - HighlightInactive => Dark(Blue), + Palette { + basic: enum_map!{ + Background => Dark(Blue), + Shadow => Dark(Black), + View => Dark(White), + Primary => Dark(Black), + Secondary => Dark(Blue), + Tertiary => Dark(White), + TitlePrimary => Dark(Red), + TitleSecondary => Dark(Yellow), + Highlight => Dark(Red), + HighlightInactive => Dark(Blue), + }, + custom: HashMap::new(), + } } } +// Iterate over a toml +fn iterate_toml<'a>( + table: &'a toml::value::Table, +) -> impl Iterator + 'a { + table.iter().flat_map(|(key, value)| { + let node = match value { + toml::Value::Table(table) => { + // This should define a new namespace + // Treat basic colors as simple string. + // We'll convert them back in the merge method. + let map = iterate_toml(table) + .map(|(key, value)| (key.to_string(), value)) + .collect(); + // Should we only return something if it's non-empty? + Some(PaletteNode::Namespace(map)) + } + toml::Value::Array(colors) => { + // This should be a list of colors - just pick the first valid one. + colors + .iter() + .flat_map(toml::Value::as_str) + .flat_map(Color::parse) + .map(PaletteNode::Color) + .next() + } + toml::Value::String(color) => { + // This describe a new color - easy! + Color::parse(color).map(PaletteNode::Color) + } + other => { + // Other - error? + debug!( + "Found unexpected value in theme: {} = {:?}", + key, other + ); + None + } + }; + + node.map(|node| (key.as_str(), node)) + }) +} + /// Fills `palette` with the colors from the given `table`. -pub(crate) fn load_table(palette: &mut Palette, table: &toml::value::Table) { +pub(crate) fn load_toml(palette: &mut Palette, table: &toml::value::Table) { // TODO: use serde for that? // Problem: toml-rs doesn't do well with Enums... - load_color( - &mut palette[PaletteColor::Background], - table.get("background"), - ); - load_color(&mut palette[PaletteColor::Shadow], table.get("shadow")); - load_color(&mut palette[PaletteColor::View], table.get("view")); - load_color(&mut palette[PaletteColor::Primary], table.get("primary")); - load_color( - &mut palette[PaletteColor::Secondary], - table.get("secondary"), - ); - load_color(&mut palette[PaletteColor::Tertiary], table.get("tertiary")); - load_color( - &mut palette[PaletteColor::TitlePrimary], - table.get("title_primary"), - ); - load_color( - &mut palette[PaletteColor::TitleSecondary], - table.get("title_secondary"), - ); - load_color( - &mut palette[PaletteColor::Highlight], - table.get("highlight"), - ); - load_color( - &mut palette[PaletteColor::HighlightInactive], - table.get("highlight_inactive"), - ); + + for (key, value) in iterate_toml(table) { + match value { + PaletteNode::Color(color) => palette.set_color(key, color), + PaletteNode::Namespace(map) => palette.add_namespace(key, map), + } + } } /// Color entry in a palette. @@ -121,25 +256,3 @@ impl PaletteColor { palette[self] } } - -/// Parses `value` and fills `target` if it's a valid color. -fn load_color(target: &mut Color, value: Option<&toml::Value>) -> bool { - if let Some(value) = value { - match *value { - toml::Value::String(ref value) => { - if let Some(color) = Color::parse(value) { - *target = color; - true - } else { - false - } - } - toml::Value::Array(ref array) => { - array.iter().any(|item| load_color(target, Some(item))) - } - _ => false, - } - } else { - false - } -} diff --git a/src/utils/lines/simple/mod.rs b/src/utils/lines/simple/mod.rs index 553c6a3..e9719ca 100644 --- a/src/utils/lines/simple/mod.rs +++ b/src/utils/lines/simple/mod.rs @@ -61,18 +61,19 @@ where // `current_width` is the width of everything // before the next token, including any space. let mut current_width = 0; - let sum: usize = iter.take_while(|token| { - let width = token.width(); - if current_width + width > available_width { - false - } else { - // Include the delimiter after this token. - current_width += width; - current_width += delimiter_width; - true - } - }).map(|token| token.len() + delimiter_len) - .sum(); + let sum: usize = + iter.take_while(|token| { + let width = token.width(); + if current_width + width > available_width { + false + } else { + // Include the delimiter after this token. + current_width += width; + current_width += delimiter_width; + true + } + }).map(|token| token.len() + delimiter_len) + .sum(); // We counted delimiter once too many times, // but only if the iterator was non empty. diff --git a/src/utils/lines/spans/chunk.rs b/src/utils/lines/spans/chunk.rs index 838317a..bd87783 100644 --- a/src/utils/lines/spans/chunk.rs +++ b/src/utils/lines/spans/chunk.rs @@ -1,11 +1,22 @@ use super::segment::Segment; /// Non-splittable piece of text. +/// +/// It is made of a list of segments of text. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Chunk { + /// Total width of this chunk. pub width: usize, + + /// This is the segments this chunk contains. pub segments: Vec, + + /// Hard stops are non-optional line breaks (newlines). pub hard_stop: bool, + + /// If a chunk of text ends in a space, it can be compressed a bit. + /// + /// (We can omit the space if it would result in a perfect fit.) pub ends_with_space: bool, } diff --git a/src/utils/lines/spans/lines_iterator.rs b/src/utils/lines/spans/lines_iterator.rs index cc9d8ac..ebfaff5 100644 --- a/src/utils/lines/spans/lines_iterator.rs +++ b/src/utils/lines/spans/lines_iterator.rs @@ -115,7 +115,7 @@ where end, }], hard_stop: false, - ends_with_space: false, + ends_with_space: false, // should we? } }) }); @@ -147,7 +147,19 @@ where } } - let width = chunks.iter().map(|c| c.width).sum(); + // We can know text was wrapped if the stop was optional, + // and there's more coming. + let text_wrap = !chunks.last().map(|c| c.hard_stop).unwrap_or(true) + && self.iter.peek().is_some(); + + // If we had to break a line in two, then at least pretent we're + // taking the full width. + let width = if text_wrap { + self.width + } else { + chunks.iter().map(|c| c.width).sum() + }; + assert!(width <= self.width); // Concatenate all segments diff --git a/src/utils/markup/markdown.rs b/src/utils/markup/markdown.rs index 781b5fe..715eef4 100644 --- a/src/utils/markup/markdown.rs +++ b/src/utils/markup/markdown.rs @@ -73,10 +73,12 @@ impl<'a> Iterator for Parser<'a> { self.stack.push(Style::from(Effect::Italic)) } Tag::Header(level) => { - return Some(self.literal(format!( - "{} ", - header(level as usize) - ))) + return Some( + self.literal(format!( + "{} ", + header(level as usize) + )), + ) } Tag::Rule => return Some(self.literal("---")), Tag::BlockQuote => return Some(self.literal("> ")), diff --git a/src/view/finder.rs b/src/view/finder.rs index 2e81423..89f2e3f 100644 --- a/src/view/finder.rs +++ b/src/view/finder.rs @@ -46,7 +46,8 @@ impl Finder for T { *result_ref = v.downcast_mut::().map(|v| callback(v)); } else if v.is::>() { - *result_ref = v.downcast_mut::>() + *result_ref = v + .downcast_mut::>() .and_then(|v| v.with_view_mut(callback)); } } diff --git a/src/views/box_view.rs b/src/views/box_view.rs index c7ff9a6..823e96c 100644 --- a/src/views/box_view.rs +++ b/src/views/box_view.rs @@ -195,7 +195,8 @@ impl ViewWrapper for BoxView { let req = self.size.zip_map(req, SizeConstraint::available); let child_size = self.view.required_size(req); - let result = self.size + let result = self + .size .zip_map(child_size.zip(req), SizeConstraint::result); debug!("{:?}", result); diff --git a/src/views/dialog.rs b/src/views/dialog.rs index e19e6a8..3cea558 100644 --- a/src/views/dialog.rs +++ b/src/views/dialog.rs @@ -340,7 +340,8 @@ impl Dialog { // Current horizontal position of the next button we'll draw. // Sum of the sizes + len-1 for margins - let width = self.buttons + let width = self + .buttons .iter() .map(|button| button.button.size.x) .sum::() @@ -350,7 +351,8 @@ impl Dialog { return None; } let mut offset = overhead.left - + self.align + + self + .align .h .get_offset(width, printer.size.x - overhead.horizontal()); @@ -381,7 +383,8 @@ impl Dialog { fn draw_content(&self, printer: &Printer, buttons_height: usize) { // What do we have left? - let taken = Vec2::new(0, buttons_height) + self.borders.combined() + let taken = Vec2::new(0, buttons_height) + + self.borders.combined() + self.padding.combined(); let inner_size = match printer.size.checked_sub(taken) { @@ -403,7 +406,8 @@ impl Dialog { } let spacing = 3; //minimum distance to borders let x = spacing - + self.title_position + + self + .title_position .get_offset(len, printer.size.x - 2 * spacing); printer.with_high_border(false, |printer| { printer.print((x - 2, 0), "┤ "); diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index eb506e5..3a46668 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -616,15 +616,20 @@ impl View for EditView { return EventResult::Consumed(Some(self.insert(ch))); } // TODO: handle ctrl-key? - Event::Key(Key::Home) => self.cursor = 0, - Event::Key(Key::End) => self.cursor = self.content.len(), + Event::Key(Key::Home) => self.set_cursor(0), + Event::Key(Key::End) => { + // When possible, NLL to the rescue! + let len = self.content.len(); + self.set_cursor(len); + } Event::Key(Key::Left) if self.cursor > 0 => { let len = self.content[..self.cursor] .graphemes(true) .last() .unwrap() .len(); - self.cursor -= len; + let cursor = self.cursor - len; + self.set_cursor(cursor); } Event::Key(Key::Right) if self.cursor < self.content.len() => { let len = self.content[self.cursor..] @@ -632,7 +637,8 @@ impl View for EditView { .next() .unwrap() .len(); - self.cursor += len; + let cursor = self.cursor + len; + self.set_cursor(cursor); } Event::Key(Key::Backspace) if self.cursor > 0 => { let len = self.content[..self.cursor] diff --git a/src/views/hideable_view.rs b/src/views/hideable_view.rs new file mode 100644 index 0000000..cab64b7 --- /dev/null +++ b/src/views/hideable_view.rs @@ -0,0 +1,94 @@ +use view::{Selector, View, ViewWrapper}; +use With; + +use std::any::Any; + +/// Wrapper around another view that can be hidden at will. +/// +/// By default, it simply forwards all calls to the inner view. +/// +/// When hidden (with `HideableView::hide()`), it will appear as a zero-sized +/// invisible view, will not take focus and will not accept input. +/// +/// It can be made visible again with `HideableView::unhide()`. +pub struct HideableView { + view: V, + visible: bool, +} + +impl HideableView { + /// Creates a new HideableView around `view`. + /// + /// It will be visible by default. + pub fn new(view: V) -> Self { + HideableView { + view, + visible: true, + } + } + + /// Sets the visibility for this view. + pub fn set_visible(&mut self, visible: bool) { + self.visible = visible; + } + + /// Sets the visibility for this view to `false`. + pub fn hide(&mut self) { + self.set_visible(false); + } + + /// Sets the visibility for this view to `true`. + pub fn unhide(&mut self) { + self.set_visible(true); + } + + /// Sets the visibility for this view to `false`. + /// + /// Chainable variant. + pub fn hidden(self) -> Self { + self.with(Self::hide) + } + + inner_getters!(self.view: V); +} + +impl ViewWrapper for HideableView { + type V = V; + + fn with_view(&self, f: F) -> Option + where + F: FnOnce(&Self::V) -> R, + { + if self.visible { + Some(f(&self.view)) + } else { + None + } + } + + fn with_view_mut(&mut self, f: F) -> Option + where + F: FnOnce(&mut Self::V) -> R, + { + if self.visible { + Some(f(&mut self.view)) + } else { + None + } + } + + fn wrap_call_on_any<'a>( + &mut self, selector: &Selector, callback: Box, + ) { + // We always run callbacks, even when invisible. + self.view.call_on_any(selector, callback) + } + + fn into_inner(self) -> Result + where + Self: Sized, + Self::V: Sized, + { + Ok(self.view) + } +} diff --git a/src/views/id_view.rs b/src/views/id_view.rs index 703e265..9f196f4 100644 --- a/src/views/id_view.rs +++ b/src/views/id_view.rs @@ -92,7 +92,8 @@ impl ViewWrapper for IdView { fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> { match selector { &Selector::Id(id) if id == self.id => Ok(()), - s => self.view + s => self + .view .try_borrow_mut() .map_err(|_| ()) .and_then(|mut v| v.deref_mut().focus_view(s)), diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 219705d..ca4a19a 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -195,7 +195,8 @@ impl LinearLayout { } fn children_are_sleeping(&self) -> bool { - !self.children + !self + .children .iter() .map(Child::as_view) .any(View::needs_relayout) @@ -357,7 +358,8 @@ impl View for LinearLayout { } // First, make a naive scenario: everything will work fine. - let ideal_sizes: Vec = self.children + let ideal_sizes: Vec = self + .children .iter_mut() .map(|c| c.required_size(req)) .collect(); @@ -381,7 +383,8 @@ impl View for LinearLayout { // See how they like it that way. // This is, hopefully, the absolute minimum these views will accept. - let min_sizes: Vec = self.children + let min_sizes: Vec = self + .children .iter_mut() .map(|c| c.required_size(budget_req)) .collect(); @@ -461,7 +464,8 @@ impl View for LinearLayout { // Let's ask everyone one last time. Everyone should be happy. // (But they may ask more on the other axis.) - let final_sizes: Vec = self.children + let final_sizes: Vec = self + .children .iter_mut() .enumerate() .map(|(i, c)| c.required_size(final_lengths[i])) @@ -481,10 +485,9 @@ impl View for LinearLayout { // In what order will we iterate on the children? let rel = source.relative(self.orientation); // We activate from_focus only if coming from the "sides". - let i = if let Some(i) = self.iter_mut( - rel.is_none(), - rel.unwrap_or(direction::Relative::Front), - ).filter_map(|p| try_focus(p, source)) + let i = if let Some(i) = self + .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front)) + .filter_map(|p| try_focus(p, source)) .next() { // ... we can't update `self.focus` here, diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 660b4a0..90c7d94 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -283,14 +283,16 @@ impl View for ListView { fn required_size(&mut self, req: Vec2) -> Vec2 { // We'll show 2 columns: the labels, and the views. - let label_width = self.children + let label_width = self + .children .iter() .map(ListChild::label) .map(UnicodeWidthStr::width) .max() .unwrap_or(0); - let view_size = self.children + let view_size = self + .children .iter_mut() .filter_map(ListChild::view) .map(|v| v.required_size(req).x) @@ -310,7 +312,8 @@ impl View for ListView { self.scrollbase.set_heights(size.y, self.children.len()); // We'll show 2 columns: the labels, and the views. - let label_width = self.children + let label_width = self + .children .iter() .map(ListChild::label) .map(UnicodeWidthStr::width) @@ -320,7 +323,8 @@ impl View for ListView { let spacing = 1; let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 }; - let available = size.x + let available = size + .x .saturating_sub(label_width + spacing + scrollbar_width); debug!("Available: {}", available); @@ -439,10 +443,9 @@ impl View for ListView { fn take_focus(&mut self, source: direction::Direction) -> bool { let rel = source.relative(direction::Orientation::Vertical); - let i = if let Some(i) = self.iter_mut( - rel.is_none(), - rel.unwrap_or(direction::Relative::Front), - ).filter_map(|p| try_focus(p, source)) + let i = if let Some(i) = self + .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front)) + .filter_map(|p| try_focus(p, source)) .next() { i @@ -464,7 +467,8 @@ impl View for ListView { } fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { - if let Some(i) = self.children + if let Some(i) = self + .children .iter_mut() .enumerate() .filter_map(|(i, v)| v.view().map(|v| (i, v))) diff --git a/src/views/menu_popup.rs b/src/views/menu_popup.rs index 4f1c8e1..4118af3 100644 --- a/src/views/menu_popup.rs +++ b/src/views/menu_popup.rs @@ -138,7 +138,8 @@ impl MenuPopup { fn make_subtree_cb(&self, tree: &Rc) -> EventResult { let tree = Rc::clone(tree); - let max_width = 4 + self.menu + let max_width = 4 + self + .menu .children .iter() .map(Self::item_width) @@ -249,7 +250,8 @@ impl View for MenuPopup { fn required_size(&mut self, req: Vec2) -> Vec2 { // We can't really shrink our items here, so it's not flexible. - let w = 4 + self.menu + let w = 4 + self + .menu .children .iter() .map(Self::item_width) diff --git a/src/views/menubar.rs b/src/views/menubar.rs index 1cc0f25..3c3b472 100644 --- a/src/views/menubar.rs +++ b/src/views/menubar.rs @@ -342,7 +342,8 @@ impl View for Menubar { .checked_sub(offset) .and_then(|pos| self.child_at(pos.x)) { - if self.focus == child && btn == MouseButton::Left + if self.focus == child + && btn == MouseButton::Left && self.root.children[child].is_leaf() { return self.select_child(false); @@ -372,7 +373,8 @@ impl View for Menubar { // We add 2 to the length of every label for marin. // Also, we add 1 at the beginning. // (See the `draw()` method) - let width = self.root + let width = self + .root .children .iter() .map(|item| item.label().len() + 2) diff --git a/src/views/mod.rs b/src/views/mod.rs index 69c63f2..e64c1b6 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -42,6 +42,7 @@ mod checkbox; mod dialog; mod dummy; mod edit_view; +mod hideable_view; mod id_view; mod layer; mod linear_layout; @@ -70,6 +71,7 @@ pub use self::checkbox::Checkbox; pub use self::dialog::{Dialog, DialogFocus}; pub use self::dummy::DummyView; pub use self::edit_view::EditView; +pub use self::hideable_view::HideableView; pub use self::id_view::{IdView, ViewRef}; pub use self::layer::Layer; pub use self::linear_layout::LinearLayout; diff --git a/src/views/select_view.rs b/src/views/select_view.rs index b6c7c75..70b609c 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -527,7 +527,8 @@ impl SelectView { // the list when we reach the end. // This is achieved by chaining twice the iterator let iter = self.items.iter().chain(self.items.iter()); - if let Some((i, _)) = iter.enumerate() + if let Some((i, _)) = iter + .enumerate() .skip(self.focus() + 1) .find(|&(_, item)| item.label.starts_with(c)) { @@ -735,7 +736,8 @@ impl View for SelectView { // Items here are not compressible. // So no matter what the horizontal requirements are, // we'll still return our longest item. - let w = self.items + let w = self + .items .iter() .map(|item| item.label.width()) .max() diff --git a/src/views/stack_view.rs b/src/views/stack_view.rs index 1396e3f..1464a3b 100644 --- a/src/views/stack_view.rs +++ b/src/views/stack_view.rs @@ -56,8 +56,10 @@ impl Placement { enum ChildWrapper { // Some views include a shadow around. Shadow(ShadowView>), + // Some include a background. + Backfilled(Layer), // Some views don't (fullscreen views mostly) - Plain(Layer), + Plain(T), } impl ChildWrapper { @@ -67,7 +69,11 @@ impl ChildWrapper { ChildWrapper::Shadow(shadow) => { shadow.into_inner().ok().unwrap().into_inner().ok().unwrap() } - ChildWrapper::Plain(layer) => layer.into_inner().ok().unwrap(), + // Layer::into_inner can never fail. + ChildWrapper::Backfilled(background) => { + background.into_inner().ok().unwrap() + } + ChildWrapper::Plain(layer) => layer, } } } @@ -77,7 +83,8 @@ impl ChildWrapper { pub fn get_inner(&self) -> &View { match *self { ChildWrapper::Shadow(ref shadow) => shadow.get_inner().get_inner(), - ChildWrapper::Plain(ref layer) => layer.get_inner(), + ChildWrapper::Backfilled(ref background) => background.get_inner(), + ChildWrapper::Plain(ref layer) => layer, } } @@ -87,7 +94,10 @@ impl ChildWrapper { ChildWrapper::Shadow(ref mut shadow) => { shadow.get_inner_mut().get_inner_mut() } - ChildWrapper::Plain(ref mut layer) => layer.get_inner_mut(), + ChildWrapper::Backfilled(ref mut background) => { + background.get_inner_mut() + } + ChildWrapper::Plain(ref mut layer) => layer, } } } @@ -97,6 +107,7 @@ impl View for ChildWrapper { fn draw(&self, printer: &Printer) { match *self { ChildWrapper::Shadow(ref v) => v.draw(printer), + ChildWrapper::Backfilled(ref v) => v.draw(printer), ChildWrapper::Plain(ref v) => v.draw(printer), } } @@ -104,6 +115,7 @@ impl View for ChildWrapper { fn on_event(&mut self, event: Event) -> EventResult { match *self { ChildWrapper::Shadow(ref mut v) => v.on_event(event), + ChildWrapper::Backfilled(ref mut v) => v.on_event(event), ChildWrapper::Plain(ref mut v) => v.on_event(event), } } @@ -111,6 +123,7 @@ impl View for ChildWrapper { fn layout(&mut self, size: Vec2) { match *self { ChildWrapper::Shadow(ref mut v) => v.layout(size), + ChildWrapper::Backfilled(ref mut v) => v.layout(size), ChildWrapper::Plain(ref mut v) => v.layout(size), } } @@ -118,6 +131,7 @@ impl View for ChildWrapper { fn required_size(&mut self, size: Vec2) -> Vec2 { match *self { ChildWrapper::Shadow(ref mut v) => v.required_size(size), + ChildWrapper::Backfilled(ref mut v) => v.required_size(size), ChildWrapper::Plain(ref mut v) => v.required_size(size), } } @@ -125,6 +139,7 @@ impl View for ChildWrapper { fn take_focus(&mut self, source: Direction) -> bool { match *self { ChildWrapper::Shadow(ref mut v) => v.take_focus(source), + ChildWrapper::Backfilled(ref mut v) => v.take_focus(source), ChildWrapper::Plain(ref mut v) => v.take_focus(source), } } @@ -134,6 +149,9 @@ impl View for ChildWrapper { ChildWrapper::Shadow(ref mut v) => { v.call_on_any(selector, callback) } + ChildWrapper::Backfilled(ref mut v) => { + v.call_on_any(selector, callback) + } ChildWrapper::Plain(ref mut v) => { v.call_on_any(selector, callback) } @@ -143,6 +161,7 @@ impl View for ChildWrapper { fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { match *self { ChildWrapper::Shadow(ref mut v) => v.focus_view(selector), + ChildWrapper::Backfilled(ref mut v) => v.focus_view(selector), ChildWrapper::Plain(ref mut v) => v.focus_view(selector), } } @@ -181,7 +200,7 @@ impl StackView { { let boxed = ViewBox::boxed(view); self.layers.push(Child { - view: ChildWrapper::Plain(Layer::new(boxed)), + view: ChildWrapper::Backfilled(Layer::new(boxed)), size: Vec2::zero(), placement: Placement::Fullscreen, virgin: true, @@ -267,6 +286,16 @@ impl StackView { self.with(|s| s.add_fullscreen_layer(view)) } + /// Adds a new transparent layer on top of the stack. + /// + /// Chainable variant. + pub fn transparent_layer(self, view: T) -> Self + where + T: IntoBoxedView, + { + self.with(|s| s.add_transparent_layer(view)) + } + /// Adds a view on top of the stack. pub fn add_layer_at(&mut self, position: Position, view: T) where @@ -286,6 +315,28 @@ impl StackView { }); } + /// Adds a transparent view on top of the stack in the center of the screen. + pub fn add_transparent_layer(&mut self, view: T) + where + T: IntoBoxedView, + { + self.add_transparent_layer_at(Position::center(), view); + } + + /// Adds a transparent view on top of the stack. + pub fn add_transparent_layer_at(&mut self, position: Position, view: T) + where + T: IntoBoxedView, + { + let boxed = ViewBox::boxed(view); + self.layers.push(Child { + view: ChildWrapper::Plain(boxed), + size: Vec2::new(0, 0), + placement: Placement::Floating(position), + virgin: true, + }); + } + /// Adds a view on top of the stack. /// /// Chainable variant. @@ -612,4 +663,52 @@ mod tests { assert!(stack.pop_layer().is_none()); } + + #[test] + fn get() { + let mut stack = StackView::new() + .layer(TextView::new("1")) + .layer(TextView::new("2")); + + assert!( + stack + .get(LayerPosition::FromFront(0)) + .unwrap() + .as_any() + .downcast_ref::() + .unwrap() + .with_view(|v| v.as_any().is::()) + .unwrap() + ); + assert!( + stack + .get(LayerPosition::FromBack(0)) + .unwrap() + .as_any() + .downcast_ref::() + .unwrap() + .with_view(|v| v.as_any().is::()) + .unwrap() + ); + assert!( + stack + .get_mut(LayerPosition::FromFront(0)) + .unwrap() + .as_any_mut() + .downcast_mut::() + .unwrap() + .with_view_mut(|v| v.as_any_mut().is::()) + .unwrap() + ); + assert!( + stack + .get_mut(LayerPosition::FromBack(0)) + .unwrap() + .as_any_mut() + .downcast_mut::() + .unwrap() + .with_view_mut(|v| v.as_any_mut().is::()) + .unwrap() + ); + } } diff --git a/src/views/text_area.rs b/src/views/text_area.rs index ca38a46..f63380c 100644 --- a/src/views/text_area.rs +++ b/src/views/text_area.rs @@ -442,7 +442,8 @@ impl View for TextArea { debug!("{:?}", self.rows); let scroll_width = if self.rows.len() > constraint.y { 1 } else { 0 }; Vec2::new( - scroll_width + 1 + scroll_width + + 1 + self.rows.iter().map(|r| r.width).max().unwrap_or(1), self.rows.len(), ) diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 1eb0b63..3a2c8fc 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -421,7 +421,8 @@ impl TextView { } // Desired width, including the scrollbar_width. - self.width = self.rows + self.width = self + .rows .iter() .map(|row| row.width) .max()