Merge branch 'master' into scroll

This commit is contained in:
Alexandre Bury 2018-06-15 23:21:10 -07:00
commit 5e1956b737
51 changed files with 581 additions and 189 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ tags
.ctags .ctags
*.bk *.bk
TODO.txt TODO.txt
*.rustfmt

View File

@ -17,7 +17,6 @@ repository = "gyscos/Cursive"
enum-map = "0.2.24" enum-map = "0.2.24"
enumset = "0.3.3" enumset = "0.3.3"
log = "0.4" log = "0.4"
num = "0.1"
owning_ref = "0.3.3" owning_ref = "0.3.3"
toml = "0.4" toml = "0.4"
unicode-segmentation = "1.0" unicode-segmentation = "1.0"
@ -25,6 +24,10 @@ unicode-width = "0.1"
xi-unicode = "0.1.0" xi-unicode = "0.1.0"
libc = "0.2" libc = "0.2"
[dependencies.num]
default-features = false
version = "0.1"
[dependencies.maplit] [dependencies.maplit]
optional = true optional = true
version = "1.0.0" version = "1.0.0"

View File

@ -1,9 +1,9 @@
extern crate cursive; extern crate cursive;
use cursive::{Cursive, Printer};
use cursive::theme::{Color, ColorStyle}; use cursive::theme::{Color, ColorStyle};
use cursive::view::Boxable; use cursive::view::Boxable;
use cursive::views::Canvas; use cursive::views::Canvas;
use cursive::{Cursive, Printer};
// This example will draw a colored square with a gradient. // This example will draw a colored square with a gradient.
// //

View File

@ -1,7 +1,7 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::views::{Dialog, TextView}; use cursive::views::{Dialog, TextView};
use cursive::Cursive;
fn main() { fn main() {
// Creates the cursive root - required for every application. // Creates the cursive root - required for every application.

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Dialog, EditView, TextView}; use cursive::views::{Dialog, EditView, TextView};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,7 +1,7 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::views::TextView; use cursive::views::TextView;
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,9 +1,9 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::align::HAlign; use cursive::align::HAlign;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Dialog, DummyView, LinearLayout, TextView}; use cursive::views::{Dialog, DummyView, LinearLayout, TextView};
use cursive::Cursive;
// This example uses a LinearLayout to stick multiple views next to each other. // This example uses a LinearLayout to stick multiple views next to each other.

View File

@ -1,9 +1,10 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Checkbox, Dialog, EditView, LinearLayout, ListView, use cursive::views::{
SelectView, TextView}; Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView, TextView,
};
use cursive::Cursive;
// This example uses a ListView. // This example uses a ListView.
// //

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::{Cursive, Printer};
use cursive::traits::*; use cursive::traits::*;
use cursive::vec::Vec2; use cursive::vec::Vec2;
use cursive::{Cursive, Printer};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;

View File

@ -1,9 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::align::HAlign; use cursive::align::HAlign;
use cursive::view::Boxable;
use cursive::views::{Dialog, Panel, TextView}; use cursive::views::{Dialog, Panel, TextView};
use cursive::Cursive;
fn main() { fn main() {
// Read some long text from a file. // Read some long text from a file.
@ -18,11 +17,10 @@ fn main() {
// and will adapt to the terminal size. // and will adapt to the terminal size.
siv.add_fullscreen_layer( siv.add_fullscreen_layer(
Dialog::around(Panel::new(TextView::new(content))) Dialog::around(Panel::new(TextView::new(content)))
.title("Unicode and wide-character support")
// This is the alignment for the button // This is the alignment for the button
.h_align(HAlign::Center) .h_align(HAlign::Center)
.button("Quit", |s| s.quit()) .button("Quit", |s| s.quit()),
.title("Unicode and wide-character support")
.full_screen(),
); );
// Show a popup on top of the view. // Show a popup on top of the view.
siv.add_layer(Dialog::info( siv.add_layer(Dialog::info(

View File

@ -1,12 +1,12 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::theme::BaseColor; use cursive::theme::BaseColor;
use cursive::theme::Color; use cursive::theme::Color;
use cursive::theme::Effect; use cursive::theme::Effect;
use cursive::theme::Style; use cursive::theme::Style;
use cursive::utils::markup::StyledString; use cursive::utils::markup::StyledString;
use cursive::views::{Dialog, TextView}; use cursive::views::{Dialog, TextView};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,10 +1,10 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::event::Key; use cursive::event::Key;
use cursive::menu::MenuTree; use cursive::menu::MenuTree;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::Dialog; use cursive::views::Dialog;
use cursive::Cursive;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
// This examples shows how to configure and use a menubar at the top of the // This examples shows how to configure and use a menubar at the top of the

View File

@ -3,13 +3,13 @@ extern crate rand;
mod game; mod game;
use cursive::Cursive;
use cursive::Printer;
use cursive::direction::Direction; use cursive::direction::Direction;
use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; use cursive::event::{Event, EventResult, MouseButton, MouseEvent};
use cursive::theme::{BaseColor, Color, ColorStyle}; use cursive::theme::{BaseColor, Color, ColorStyle};
use cursive::vec::Vec2; use cursive::vec::Vec2;
use cursive::views::{Button, Dialog, LinearLayout, Panel, SelectView}; use cursive::views::{Button, Dialog, LinearLayout, Panel, SelectView};
use cursive::Cursive;
use cursive::Printer;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,9 +1,9 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::traits::*; use cursive::traits::*;
use cursive::view::{Offset, Position}; use cursive::view::{Offset, Position};
use cursive::views::{Dialog, OnEventView, TextView}; use cursive::views::{Dialog, OnEventView, TextView};
use cursive::Cursive;
// This example modifies a view after creation. // This example modifies a view after creation.

View File

@ -1,9 +1,9 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::view::Position; use cursive::view::Position;
use cursive::views::LayerPosition; use cursive::views::LayerPosition;
use cursive::views::TextView; use cursive::views::TextView;
use cursive::Cursive;
/// Moves top layer by the specified amount /// Moves top layer by the specified amount
fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) { fn move_top(c: &mut Cursive, x_in: isize, y_in: isize) {

View File

@ -1,10 +1,10 @@
extern crate cursive; extern crate cursive;
extern crate rand; extern crate rand;
use cursive::Cursive;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView};
use cursive::utils::Counter; use cursive::utils::Counter;
use cursive::views::{Button, Dialog, LinearLayout, ProgressBar, TextView};
use cursive::Cursive;
use rand::Rng; use rand::Rng;
use std::cmp::min; use std::cmp::min;
use std::thread; use std::thread;

View File

@ -1,7 +1,7 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup}; use cursive::views::{Dialog, DummyView, LinearLayout, RadioGroup};
use cursive::Cursive;
// This example uses radio buttons. // This example uses radio buttons.

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::view::{Boxable, Identifiable}; use cursive::view::{Boxable, Identifiable};
use cursive::views::{Dialog, EditView, LinearLayout, TextView}; use cursive::views::{Dialog, EditView, LinearLayout, TextView};
use cursive::Cursive;
// This example shows a way to access multiple views at the same time. // This example shows a way to access multiple views at the same time.

View File

@ -1,10 +1,10 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::align::HAlign; use cursive::align::HAlign;
use cursive::event::EventResult; use cursive::event::EventResult;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Dialog, OnEventView, SelectView, TextView}; use cursive::views::{Dialog, OnEventView, SelectView, TextView};
use cursive::Cursive;
// We'll use a SelectView here. // We'll use a SelectView here.
// //

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Dialog, SliderView}; use cursive::views::{Dialog, SliderView};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::theme::{Color, PaletteColor, Theme}; use cursive::theme::{Color, PaletteColor, Theme};
use cursive::views::TextView; use cursive::views::TextView;
use cursive::Cursive;
// This example sets the background color to the terminal default. // This example sets the background color to the terminal default.
// //

View File

@ -1,9 +1,9 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::event::{Event, Key}; use cursive::event::{Event, Key};
use cursive::traits::*; use cursive::traits::*;
use cursive::views::{Dialog, EditView, OnEventView, TextArea}; use cursive::views::{Dialog, EditView, OnEventView, TextArea};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();
@ -37,10 +37,10 @@ fn main() {
.min_width(10), .min_width(10),
) )
.button("Ok", |s| { .button("Ok", |s| {
let text = s.call_on_id( let text =
"edit", s.call_on_id("edit", |view: &mut EditView| {
|view: &mut EditView| view.get_content(), view.get_content()
).unwrap(); }).unwrap();
find(s, &text); find(s, &text);
}) })
.dismiss_button("Cancel"), .dismiss_button("Cancel"),

View File

@ -1,7 +1,7 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::views::{Dialog, TextView}; use cursive::views::{Dialog, TextView};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -1,8 +1,8 @@
extern crate cursive; extern crate cursive;
use cursive::Cursive;
use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle}; use cursive::theme::{BaseColor, BorderStyle, Color, ColorStyle};
use cursive::views::{Dialog, EditView, LinearLayout, TextView}; use cursive::views::{Dialog, EditView, LinearLayout, TextView};
use cursive::Cursive;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();

View File

@ -3,10 +3,10 @@ extern crate pretty_bytes;
use std::io; use std::io;
use cursive::Cursive;
use cursive::traits::{Boxable, With}; use cursive::traits::{Boxable, With};
use cursive::utils; use cursive::utils;
use cursive::views::{Canvas, Dialog, LinearLayout, ProgressBar}; use cursive::views::{Canvas, Dialog, LinearLayout, ProgressBar};
use cursive::Cursive;
use pretty_bytes::converter::convert; use pretty_bytes::converter::convert;
use std::thread; use std::thread;
use std::time; use std::time;
@ -31,10 +31,7 @@ fn main() {
let meta = std::fs::metadata(&source).unwrap(); let meta = std::fs::metadata(&source).unwrap();
// If possible, read the file size to have a progress bar. // If possible, read the file size to have a progress bar.
let len = meta.len(); 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), None => (None, None),
}; };
@ -68,9 +65,7 @@ fn main() {
} }
// When we're done, shut down the application // When we're done, shut down the application
cb_sink cb_sink.send(Box::new(|s: &mut Cursive| s.quit())).unwrap();
.send(Box::new(|s: &mut Cursive| s.quit()))
.unwrap();
}); });
// Add a single view: progress status // Add a single view: progress status

View File

@ -156,7 +156,8 @@ impl Backend {
let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0; let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL 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) | ncurses::BUTTON_CTRL)
as mmask_t; as mmask_t;

View File

@ -117,7 +117,8 @@ impl Backend {
let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0; let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0;
let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL 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; | pancurses::BUTTON_CTRL) as mmask_t;
let make_event = |event| Event::Mouse { let make_event = |event| Event::Mouse {

View File

@ -105,7 +105,7 @@ impl Cursive {
pub fn new(backend: Box<backend::Backend>) -> Self { pub fn new(backend: Box<backend::Backend>) -> Self {
let theme = theme::load_default(); let theme = theme::load_default();
// theme.activate(&mut backend); // 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(); let (tx, rx) = mpsc::channel();
@ -283,8 +283,8 @@ impl Cursive {
/// Loads a theme from the given string content. /// Loads a theme from the given string content.
/// ///
/// Content must be valid toml. /// Content must be valid toml.
pub fn load_theme(&mut self, content: &str) -> Result<(), theme::Error> { pub fn load_toml(&mut self, content: &str) -> Result<(), theme::Error> {
self.set_theme(try!(theme::load_theme(content))); self.set_theme(try!(theme::load_toml(content)));
Ok(()) Ok(())
} }
@ -670,7 +670,8 @@ impl Cursive {
event, position, .. event, position, ..
} = event } = event
{ {
if event.grabs_focus() && !self.menubar.autohide if event.grabs_focus()
&& !self.menubar.autohide
&& !self.menubar.has_submenu() && !self.menubar.has_submenu()
&& position.y == 0 && position.y == 0
{ {

View File

@ -17,7 +17,7 @@ use with::With;
/// ///
/// The part of the content it will print is defined by `content_offset` /// The part of the content it will print is defined by `content_offset`
/// and `size`. /// and `size`.
pub struct Printer<'a> { pub struct Printer<'a, 'b> {
/// Offset into the window this printer should start drawing at. /// Offset into the window this printer should start drawing at.
/// ///
/// A print request at `x` will really print at `x + offset`. /// A print request at `x` will really print at `x + offset`.
@ -46,10 +46,10 @@ pub struct Printer<'a> {
pub theme: &'a Theme, pub theme: &'a Theme,
/// Backend used to actually draw things /// 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 { fn clone(&self) -> Self {
Printer { Printer {
offset: self.offset, 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. /// Creates a new printer on the given window.
/// ///
/// But nobody needs to know that. /// But nobody needs to know that.
#[doc(hidden)] #[doc(hidden)]
pub fn new<T: Into<Vec2>>( pub fn new<T: Into<Vec2>>(
size: T, theme: &'a Theme, backend: &'a Backend, size: T, theme: &'a Theme, backend: &'b Backend,
) -> Self { ) -> Self {
let size = size.into(); let size = size.into();
Printer { Printer {
@ -298,6 +298,24 @@ impl<'a> Printer<'a> {
self.backend.unset_effect(effect); 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<F>(&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 /// Call the given closure with a modified printer
/// that will apply each given effect on prints. /// that will apply each given effect on prints.
pub fn with_effects<F>(&self, effects: EnumSet<Effect>, f: F) pub fn with_effects<F>(&self, effects: EnumSet<Effect>, f: F)

View File

@ -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<Self> { pub(crate) fn parse(value: &str) -> Option<Self> {
Some(match value { Some(match value {
"black" => Color::Dark(BaseColor::Black), "black" => Color::Dark(BaseColor::Black),
@ -149,11 +156,15 @@ impl Color {
let r = load_hex(&value[0..l]) * multiplier; let r = load_hex(&value[0..l]) * multiplier;
let g = load_hex(&value[l..2 * l]) * multiplier; let g = load_hex(&value[l..2 * l]) * multiplier;
let b = load_hex(&value[2 * l..3 * l]) * multiplier; let b = load_hex(&value[2 * l..3 * l]) * multiplier;
Some(Color::Rgb(r as u8, g as u8, b as u8)) Some(Color::Rgb(r as u8, g as u8, b as u8))
} else if value.len() == 3 { } else if value.len() == 3 {
// RGB values between 0 and 5 maybe? // RGB values between 0 and 5 maybe?
// Like 050 for green
let rgb: Vec<_> = let rgb: Vec<_> =
value.chars().map(|c| c as i16 - '0' as i16).collect(); 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) { if rgb.iter().all(|&i| i >= 0 && i < 6) {
Some(Color::RgbLowRes( Some(Color::RgbLowRes(
rgb[0] as u8, rgb[0] as u8,

View File

@ -169,7 +169,7 @@ pub use self::color::{BaseColor, Color};
pub use self::color_pair::ColorPair; pub use self::color_pair::ColorPair;
pub use self::color_style::{ColorStyle, ColorType}; pub use self::color_style::{ColorStyle, ColorType};
pub use self::effect::Effect; pub use self::effect::Effect;
pub use self::palette::{default_palette, Palette, PaletteColor}; pub use self::palette::{Palette, PaletteColor};
pub use self::style::Style; pub use self::style::Style;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
@ -193,13 +193,13 @@ impl Default for Theme {
Theme { Theme {
shadow: true, shadow: true,
borders: BorderStyle::Simple, borders: BorderStyle::Simple,
palette: default_palette(), palette: Palette::default(),
} }
} }
} }
impl Theme { 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") { if let Some(&toml::Value::Boolean(shadow)) = table.get("shadow") {
self.shadow = shadow; self.shadow = shadow;
} }
@ -209,7 +209,7 @@ impl Theme {
} }
if let Some(&toml::Value::Table(ref table)) = table.get("colors") { 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<P: AsRef<Path>>(filename: P) -> Result<Theme, Error> {
content content
}; };
load_theme(&content) load_toml(&content)
} }
/// Loads a theme string and sets it as active. /// Loads a theme string and sets it as active.
pub fn load_theme(content: &str) -> Result<Theme, Error> { pub fn load_toml(content: &str) -> Result<Theme, Error> {
let table = toml::de::from_str(content)?; let table = toml::de::from_str(content)?;
let mut theme = Theme::default(); let mut theme = Theme::default();
theme.load(&table); theme.load_toml(&table);
Ok(theme) Ok(theme)
} }

View File

@ -2,6 +2,9 @@ use super::Color;
use enum_map::EnumMap; use enum_map::EnumMap;
use toml; use toml;
use std::collections::HashMap;
use std::ops::{Index, IndexMut};
/// Color configuration for the application. /// Color configuration for the application.
/// ///
/// Assign each color role an actual color. /// Assign each color role an actual color.
@ -11,17 +14,122 @@ use toml;
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # use cursive::theme; /// # use cursive::theme::Palette;
/// use cursive::theme::PaletteColor::*; /// use cursive::theme::PaletteColor::*;
/// use cursive::theme::Color::*; /// use cursive::theme::Color::*;
/// use cursive::theme::BaseColor::*; /// use cursive::theme::BaseColor::*;
/// ///
/// let mut palette = theme::default_palette(); /// let mut palette = Palette::default();
/// ///
/// assert_eq!(palette[Background], Dark(Blue)); /// assert_eq!(palette[Background], Dark(Blue));
/// palette[Shadow] = Light(Red); /// palette[Shadow] = Light(Red);
/// ``` /// ```
pub type Palette = EnumMap<PaletteColor, Color>; #[derive(PartialEq, Eq, Clone, Debug)]
pub struct Palette {
basic: EnumMap<PaletteColor, Color>,
custom: HashMap<String, PaletteNode>,
}
/// 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<String, PaletteNode>),
}
// Basic usage: only use basic colors
impl Index<PaletteColor> 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<PaletteColor> 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<String, PaletteNode>,
) {
self.custom
.insert(key.to_string(), PaletteNode::Namespace(namespace));
}
}
/// Returns the default palette for a cursive application. /// Returns the default palette for a cursive application.
/// ///
@ -35,57 +143,84 @@ pub type Palette = EnumMap<PaletteColor, Color>;
/// * `TitleSecondary` => `Dark(Yellow)` /// * `TitleSecondary` => `Dark(Yellow)`
/// * `Highlight` => `Dark(Red)` /// * `Highlight` => `Dark(Red)`
/// * `HighlightInactive` => `Dark(Blue)` /// * `HighlightInactive` => `Dark(Blue)`
pub fn default_palette() -> Palette { impl Default for Palette {
use self::PaletteColor::*; fn default() -> Palette {
use theme::BaseColor::*; use self::PaletteColor::*;
use theme::Color::*; use theme::BaseColor::*;
use theme::Color::*;
enum_map!{ Palette {
Background => Dark(Blue), basic: enum_map!{
Shadow => Dark(Black), Background => Dark(Blue),
View => Dark(White), Shadow => Dark(Black),
Primary => Dark(Black), View => Dark(White),
Secondary => Dark(Blue), Primary => Dark(Black),
Tertiary => Dark(White), Secondary => Dark(Blue),
TitlePrimary => Dark(Red), Tertiary => Dark(White),
TitleSecondary => Dark(Yellow), TitlePrimary => Dark(Red),
Highlight => Dark(Red), TitleSecondary => Dark(Yellow),
HighlightInactive => Dark(Blue), Highlight => Dark(Red),
HighlightInactive => Dark(Blue),
},
custom: HashMap::new(),
}
} }
} }
// Iterate over a toml
fn iterate_toml<'a>(
table: &'a toml::value::Table,
) -> impl Iterator<Item = (&'a str, PaletteNode)> + '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`. /// 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? // TODO: use serde for that?
// Problem: toml-rs doesn't do well with Enums... // Problem: toml-rs doesn't do well with Enums...
load_color(
&mut palette[PaletteColor::Background], for (key, value) in iterate_toml(table) {
table.get("background"), match value {
); PaletteNode::Color(color) => palette.set_color(key, color),
load_color(&mut palette[PaletteColor::Shadow], table.get("shadow")); PaletteNode::Namespace(map) => palette.add_namespace(key, map),
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"),
);
} }
/// Color entry in a palette. /// Color entry in a palette.
@ -121,25 +256,3 @@ impl PaletteColor {
palette[self] 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
}
}

View File

@ -61,18 +61,19 @@ where
// `current_width` is the width of everything // `current_width` is the width of everything
// before the next token, including any space. // before the next token, including any space.
let mut current_width = 0; let mut current_width = 0;
let sum: usize = iter.take_while(|token| { let sum: usize =
let width = token.width(); iter.take_while(|token| {
if current_width + width > available_width { let width = token.width();
false if current_width + width > available_width {
} else { false
// Include the delimiter after this token. } else {
current_width += width; // Include the delimiter after this token.
current_width += delimiter_width; current_width += width;
true current_width += delimiter_width;
} true
}).map(|token| token.len() + delimiter_len) }
.sum(); }).map(|token| token.len() + delimiter_len)
.sum();
// We counted delimiter once too many times, // We counted delimiter once too many times,
// but only if the iterator was non empty. // but only if the iterator was non empty.

View File

@ -1,11 +1,22 @@
use super::segment::Segment; use super::segment::Segment;
/// Non-splittable piece of text. /// Non-splittable piece of text.
///
/// It is made of a list of segments of text.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chunk { pub struct Chunk {
/// Total width of this chunk.
pub width: usize, pub width: usize,
/// This is the segments this chunk contains.
pub segments: Vec<Segment>, pub segments: Vec<Segment>,
/// Hard stops are non-optional line breaks (newlines).
pub hard_stop: bool, 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, pub ends_with_space: bool,
} }

View File

@ -115,7 +115,7 @@ where
end, end,
}], }],
hard_stop: false, 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); assert!(width <= self.width);
// Concatenate all segments // Concatenate all segments

View File

@ -73,10 +73,12 @@ impl<'a> Iterator for Parser<'a> {
self.stack.push(Style::from(Effect::Italic)) self.stack.push(Style::from(Effect::Italic))
} }
Tag::Header(level) => { Tag::Header(level) => {
return Some(self.literal(format!( return Some(
"{} ", self.literal(format!(
header(level as usize) "{} ",
))) header(level as usize)
)),
)
} }
Tag::Rule => return Some(self.literal("---")), Tag::Rule => return Some(self.literal("---")),
Tag::BlockQuote => return Some(self.literal("> ")), Tag::BlockQuote => return Some(self.literal("> ")),

View File

@ -46,7 +46,8 @@ impl<T: View> Finder for T {
*result_ref = *result_ref =
v.downcast_mut::<V>().map(|v| callback(v)); v.downcast_mut::<V>().map(|v| callback(v));
} else if v.is::<IdView<V>>() { } else if v.is::<IdView<V>>() {
*result_ref = v.downcast_mut::<IdView<V>>() *result_ref = v
.downcast_mut::<IdView<V>>()
.and_then(|v| v.with_view_mut(callback)); .and_then(|v| v.with_view_mut(callback));
} }
} }

View File

@ -195,7 +195,8 @@ impl<T: View> ViewWrapper for BoxView<T> {
let req = self.size.zip_map(req, SizeConstraint::available); let req = self.size.zip_map(req, SizeConstraint::available);
let child_size = self.view.required_size(req); let child_size = self.view.required_size(req);
let result = self.size let result = self
.size
.zip_map(child_size.zip(req), SizeConstraint::result); .zip_map(child_size.zip(req), SizeConstraint::result);
debug!("{:?}", result); debug!("{:?}", result);

View File

@ -340,7 +340,8 @@ impl Dialog {
// Current horizontal position of the next button we'll draw. // Current horizontal position of the next button we'll draw.
// Sum of the sizes + len-1 for margins // Sum of the sizes + len-1 for margins
let width = self.buttons let width = self
.buttons
.iter() .iter()
.map(|button| button.button.size.x) .map(|button| button.button.size.x)
.sum::<usize>() .sum::<usize>()
@ -350,7 +351,8 @@ impl Dialog {
return None; return None;
} }
let mut offset = overhead.left let mut offset = overhead.left
+ self.align + self
.align
.h .h
.get_offset(width, printer.size.x - overhead.horizontal()); .get_offset(width, printer.size.x - overhead.horizontal());
@ -381,7 +383,8 @@ impl Dialog {
fn draw_content(&self, printer: &Printer, buttons_height: usize) { fn draw_content(&self, printer: &Printer, buttons_height: usize) {
// What do we have left? // 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(); + self.padding.combined();
let inner_size = match printer.size.checked_sub(taken) { let inner_size = match printer.size.checked_sub(taken) {
@ -403,7 +406,8 @@ impl Dialog {
} }
let spacing = 3; //minimum distance to borders let spacing = 3; //minimum distance to borders
let x = spacing let x = spacing
+ self.title_position + self
.title_position
.get_offset(len, printer.size.x - 2 * spacing); .get_offset(len, printer.size.x - 2 * spacing);
printer.with_high_border(false, |printer| { printer.with_high_border(false, |printer| {
printer.print((x - 2, 0), ""); printer.print((x - 2, 0), "");

View File

@ -616,15 +616,20 @@ impl View for EditView {
return EventResult::Consumed(Some(self.insert(ch))); return EventResult::Consumed(Some(self.insert(ch)));
} }
// TODO: handle ctrl-key? // TODO: handle ctrl-key?
Event::Key(Key::Home) => self.cursor = 0, Event::Key(Key::Home) => self.set_cursor(0),
Event::Key(Key::End) => self.cursor = self.content.len(), 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 => { Event::Key(Key::Left) if self.cursor > 0 => {
let len = self.content[..self.cursor] let len = self.content[..self.cursor]
.graphemes(true) .graphemes(true)
.last() .last()
.unwrap() .unwrap()
.len(); .len();
self.cursor -= len; let cursor = self.cursor - len;
self.set_cursor(cursor);
} }
Event::Key(Key::Right) if self.cursor < self.content.len() => { Event::Key(Key::Right) if self.cursor < self.content.len() => {
let len = self.content[self.cursor..] let len = self.content[self.cursor..]
@ -632,7 +637,8 @@ impl View for EditView {
.next() .next()
.unwrap() .unwrap()
.len(); .len();
self.cursor += len; let cursor = self.cursor + len;
self.set_cursor(cursor);
} }
Event::Key(Key::Backspace) if self.cursor > 0 => { Event::Key(Key::Backspace) if self.cursor > 0 => {
let len = self.content[..self.cursor] let len = self.content[..self.cursor]

View File

@ -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<V> {
view: V,
visible: bool,
}
impl<V> HideableView<V> {
/// 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<V: View> ViewWrapper for HideableView<V> {
type V = V;
fn with_view<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&Self::V) -> R,
{
if self.visible {
Some(f(&self.view))
} else {
None
}
}
fn with_view_mut<F, R>(&mut self, f: F) -> Option<R>
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<FnMut(&mut Any) + 'a>,
) {
// We always run callbacks, even when invisible.
self.view.call_on_any(selector, callback)
}
fn into_inner(self) -> Result<Self::V, Self>
where
Self: Sized,
Self::V: Sized,
{
Ok(self.view)
}
}

View File

@ -92,7 +92,8 @@ impl<T: View + 'static> ViewWrapper for IdView<T> {
fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> { fn wrap_focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
match selector { match selector {
&Selector::Id(id) if id == self.id => Ok(()), &Selector::Id(id) if id == self.id => Ok(()),
s => self.view s => self
.view
.try_borrow_mut() .try_borrow_mut()
.map_err(|_| ()) .map_err(|_| ())
.and_then(|mut v| v.deref_mut().focus_view(s)), .and_then(|mut v| v.deref_mut().focus_view(s)),

View File

@ -195,7 +195,8 @@ impl LinearLayout {
} }
fn children_are_sleeping(&self) -> bool { fn children_are_sleeping(&self) -> bool {
!self.children !self
.children
.iter() .iter()
.map(Child::as_view) .map(Child::as_view)
.any(View::needs_relayout) .any(View::needs_relayout)
@ -357,7 +358,8 @@ impl View for LinearLayout {
} }
// First, make a naive scenario: everything will work fine. // First, make a naive scenario: everything will work fine.
let ideal_sizes: Vec<Vec2> = self.children let ideal_sizes: Vec<Vec2> = self
.children
.iter_mut() .iter_mut()
.map(|c| c.required_size(req)) .map(|c| c.required_size(req))
.collect(); .collect();
@ -381,7 +383,8 @@ impl View for LinearLayout {
// See how they like it that way. // See how they like it that way.
// This is, hopefully, the absolute minimum these views will accept. // This is, hopefully, the absolute minimum these views will accept.
let min_sizes: Vec<Vec2> = self.children let min_sizes: Vec<Vec2> = self
.children
.iter_mut() .iter_mut()
.map(|c| c.required_size(budget_req)) .map(|c| c.required_size(budget_req))
.collect(); .collect();
@ -461,7 +464,8 @@ impl View for LinearLayout {
// Let's ask everyone one last time. Everyone should be happy. // Let's ask everyone one last time. Everyone should be happy.
// (But they may ask more on the other axis.) // (But they may ask more on the other axis.)
let final_sizes: Vec<Vec2> = self.children let final_sizes: Vec<Vec2> = self
.children
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.map(|(i, c)| c.required_size(final_lengths[i])) .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? // In what order will we iterate on the children?
let rel = source.relative(self.orientation); let rel = source.relative(self.orientation);
// We activate from_focus only if coming from the "sides". // We activate from_focus only if coming from the "sides".
let i = if let Some(i) = self.iter_mut( let i = if let Some(i) = self
rel.is_none(), .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front))
rel.unwrap_or(direction::Relative::Front), .filter_map(|p| try_focus(p, source))
).filter_map(|p| try_focus(p, source))
.next() .next()
{ {
// ... we can't update `self.focus` here, // ... we can't update `self.focus` here,

View File

@ -283,14 +283,16 @@ impl View for ListView {
fn required_size(&mut self, req: Vec2) -> Vec2 { fn required_size(&mut self, req: Vec2) -> Vec2 {
// We'll show 2 columns: the labels, and the views. // We'll show 2 columns: the labels, and the views.
let label_width = self.children let label_width = self
.children
.iter() .iter()
.map(ListChild::label) .map(ListChild::label)
.map(UnicodeWidthStr::width) .map(UnicodeWidthStr::width)
.max() .max()
.unwrap_or(0); .unwrap_or(0);
let view_size = self.children let view_size = self
.children
.iter_mut() .iter_mut()
.filter_map(ListChild::view) .filter_map(ListChild::view)
.map(|v| v.required_size(req).x) .map(|v| v.required_size(req).x)
@ -310,7 +312,8 @@ impl View for ListView {
self.scrollbase.set_heights(size.y, self.children.len()); self.scrollbase.set_heights(size.y, self.children.len());
// We'll show 2 columns: the labels, and the views. // We'll show 2 columns: the labels, and the views.
let label_width = self.children let label_width = self
.children
.iter() .iter()
.map(ListChild::label) .map(ListChild::label)
.map(UnicodeWidthStr::width) .map(UnicodeWidthStr::width)
@ -320,7 +323,8 @@ impl View for ListView {
let spacing = 1; let spacing = 1;
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 }; 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); .saturating_sub(label_width + spacing + scrollbar_width);
debug!("Available: {}", available); debug!("Available: {}", available);
@ -439,10 +443,9 @@ impl View for ListView {
fn take_focus(&mut self, source: direction::Direction) -> bool { fn take_focus(&mut self, source: direction::Direction) -> bool {
let rel = source.relative(direction::Orientation::Vertical); let rel = source.relative(direction::Orientation::Vertical);
let i = if let Some(i) = self.iter_mut( let i = if let Some(i) = self
rel.is_none(), .iter_mut(rel.is_none(), rel.unwrap_or(direction::Relative::Front))
rel.unwrap_or(direction::Relative::Front), .filter_map(|p| try_focus(p, source))
).filter_map(|p| try_focus(p, source))
.next() .next()
{ {
i i
@ -464,7 +467,8 @@ impl View for ListView {
} }
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
if let Some(i) = self.children if let Some(i) = self
.children
.iter_mut() .iter_mut()
.enumerate() .enumerate()
.filter_map(|(i, v)| v.view().map(|v| (i, v))) .filter_map(|(i, v)| v.view().map(|v| (i, v)))

View File

@ -138,7 +138,8 @@ impl MenuPopup {
fn make_subtree_cb(&self, tree: &Rc<MenuTree>) -> EventResult { fn make_subtree_cb(&self, tree: &Rc<MenuTree>) -> EventResult {
let tree = Rc::clone(tree); let tree = Rc::clone(tree);
let max_width = 4 + self.menu let max_width = 4 + self
.menu
.children .children
.iter() .iter()
.map(Self::item_width) .map(Self::item_width)
@ -249,7 +250,8 @@ impl View for MenuPopup {
fn required_size(&mut self, req: Vec2) -> Vec2 { fn required_size(&mut self, req: Vec2) -> Vec2 {
// We can't really shrink our items here, so it's not flexible. // We can't really shrink our items here, so it's not flexible.
let w = 4 + self.menu let w = 4 + self
.menu
.children .children
.iter() .iter()
.map(Self::item_width) .map(Self::item_width)

View File

@ -342,7 +342,8 @@ impl View for Menubar {
.checked_sub(offset) .checked_sub(offset)
.and_then(|pos| self.child_at(pos.x)) .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() && self.root.children[child].is_leaf()
{ {
return self.select_child(false); return self.select_child(false);
@ -372,7 +373,8 @@ impl View for Menubar {
// We add 2 to the length of every label for marin. // We add 2 to the length of every label for marin.
// Also, we add 1 at the beginning. // Also, we add 1 at the beginning.
// (See the `draw()` method) // (See the `draw()` method)
let width = self.root let width = self
.root
.children .children
.iter() .iter()
.map(|item| item.label().len() + 2) .map(|item| item.label().len() + 2)

View File

@ -42,6 +42,7 @@ mod checkbox;
mod dialog; mod dialog;
mod dummy; mod dummy;
mod edit_view; mod edit_view;
mod hideable_view;
mod id_view; mod id_view;
mod layer; mod layer;
mod linear_layout; mod linear_layout;
@ -70,6 +71,7 @@ pub use self::checkbox::Checkbox;
pub use self::dialog::{Dialog, DialogFocus}; pub use self::dialog::{Dialog, DialogFocus};
pub use self::dummy::DummyView; pub use self::dummy::DummyView;
pub use self::edit_view::EditView; pub use self::edit_view::EditView;
pub use self::hideable_view::HideableView;
pub use self::id_view::{IdView, ViewRef}; pub use self::id_view::{IdView, ViewRef};
pub use self::layer::Layer; pub use self::layer::Layer;
pub use self::linear_layout::LinearLayout; pub use self::linear_layout::LinearLayout;

View File

@ -527,7 +527,8 @@ impl<T: 'static> SelectView<T> {
// the list when we reach the end. // the list when we reach the end.
// This is achieved by chaining twice the iterator // This is achieved by chaining twice the iterator
let iter = self.items.iter().chain(self.items.iter()); 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) .skip(self.focus() + 1)
.find(|&(_, item)| item.label.starts_with(c)) .find(|&(_, item)| item.label.starts_with(c))
{ {
@ -735,7 +736,8 @@ impl<T: 'static> View for SelectView<T> {
// Items here are not compressible. // Items here are not compressible.
// So no matter what the horizontal requirements are, // So no matter what the horizontal requirements are,
// we'll still return our longest item. // we'll still return our longest item.
let w = self.items let w = self
.items
.iter() .iter()
.map(|item| item.label.width()) .map(|item| item.label.width())
.max() .max()

View File

@ -56,8 +56,10 @@ impl Placement {
enum ChildWrapper<T: View> { enum ChildWrapper<T: View> {
// Some views include a shadow around. // Some views include a shadow around.
Shadow(ShadowView<Layer<T>>), Shadow(ShadowView<Layer<T>>),
// Some include a background.
Backfilled(Layer<T>),
// Some views don't (fullscreen views mostly) // Some views don't (fullscreen views mostly)
Plain(Layer<T>), Plain(T),
} }
impl<T: View> ChildWrapper<T> { impl<T: View> ChildWrapper<T> {
@ -67,7 +69,11 @@ impl<T: View> ChildWrapper<T> {
ChildWrapper::Shadow(shadow) => { ChildWrapper::Shadow(shadow) => {
shadow.into_inner().ok().unwrap().into_inner().ok().unwrap() 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<T: View> ChildWrapper<T> {
pub fn get_inner(&self) -> &View { pub fn get_inner(&self) -> &View {
match *self { match *self {
ChildWrapper::Shadow(ref shadow) => shadow.get_inner().get_inner(), 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<T: View> ChildWrapper<T> {
ChildWrapper::Shadow(ref mut shadow) => { ChildWrapper::Shadow(ref mut shadow) => {
shadow.get_inner_mut().get_inner_mut() 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<T: View> View for ChildWrapper<T> {
fn draw(&self, printer: &Printer) { fn draw(&self, printer: &Printer) {
match *self { match *self {
ChildWrapper::Shadow(ref v) => v.draw(printer), ChildWrapper::Shadow(ref v) => v.draw(printer),
ChildWrapper::Backfilled(ref v) => v.draw(printer),
ChildWrapper::Plain(ref v) => v.draw(printer), ChildWrapper::Plain(ref v) => v.draw(printer),
} }
} }
@ -104,6 +115,7 @@ impl<T: View> View for ChildWrapper<T> {
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
match *self { match *self {
ChildWrapper::Shadow(ref mut v) => v.on_event(event), 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), ChildWrapper::Plain(ref mut v) => v.on_event(event),
} }
} }
@ -111,6 +123,7 @@ impl<T: View> View for ChildWrapper<T> {
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
match *self { match *self {
ChildWrapper::Shadow(ref mut v) => v.layout(size), ChildWrapper::Shadow(ref mut v) => v.layout(size),
ChildWrapper::Backfilled(ref mut v) => v.layout(size),
ChildWrapper::Plain(ref mut v) => v.layout(size), ChildWrapper::Plain(ref mut v) => v.layout(size),
} }
} }
@ -118,6 +131,7 @@ impl<T: View> View for ChildWrapper<T> {
fn required_size(&mut self, size: Vec2) -> Vec2 { fn required_size(&mut self, size: Vec2) -> Vec2 {
match *self { match *self {
ChildWrapper::Shadow(ref mut v) => v.required_size(size), 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), ChildWrapper::Plain(ref mut v) => v.required_size(size),
} }
} }
@ -125,6 +139,7 @@ impl<T: View> View for ChildWrapper<T> {
fn take_focus(&mut self, source: Direction) -> bool { fn take_focus(&mut self, source: Direction) -> bool {
match *self { match *self {
ChildWrapper::Shadow(ref mut v) => v.take_focus(source), 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), ChildWrapper::Plain(ref mut v) => v.take_focus(source),
} }
} }
@ -134,6 +149,9 @@ impl<T: View> View for ChildWrapper<T> {
ChildWrapper::Shadow(ref mut v) => { ChildWrapper::Shadow(ref mut v) => {
v.call_on_any(selector, callback) v.call_on_any(selector, callback)
} }
ChildWrapper::Backfilled(ref mut v) => {
v.call_on_any(selector, callback)
}
ChildWrapper::Plain(ref mut v) => { ChildWrapper::Plain(ref mut v) => {
v.call_on_any(selector, callback) v.call_on_any(selector, callback)
} }
@ -143,6 +161,7 @@ impl<T: View> View for ChildWrapper<T> {
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
match *self { match *self {
ChildWrapper::Shadow(ref mut v) => v.focus_view(selector), 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), ChildWrapper::Plain(ref mut v) => v.focus_view(selector),
} }
} }
@ -181,7 +200,7 @@ impl StackView {
{ {
let boxed = ViewBox::boxed(view); let boxed = ViewBox::boxed(view);
self.layers.push(Child { self.layers.push(Child {
view: ChildWrapper::Plain(Layer::new(boxed)), view: ChildWrapper::Backfilled(Layer::new(boxed)),
size: Vec2::zero(), size: Vec2::zero(),
placement: Placement::Fullscreen, placement: Placement::Fullscreen,
virgin: true, virgin: true,
@ -267,6 +286,16 @@ impl StackView {
self.with(|s| s.add_fullscreen_layer(view)) self.with(|s| s.add_fullscreen_layer(view))
} }
/// Adds a new transparent layer on top of the stack.
///
/// Chainable variant.
pub fn transparent_layer<T>(self, view: T) -> Self
where
T: IntoBoxedView,
{
self.with(|s| s.add_transparent_layer(view))
}
/// Adds a view on top of the stack. /// Adds a view on top of the stack.
pub fn add_layer_at<T>(&mut self, position: Position, view: T) pub fn add_layer_at<T>(&mut self, position: Position, view: T)
where 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<T>(&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<T>(&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. /// Adds a view on top of the stack.
/// ///
/// Chainable variant. /// Chainable variant.
@ -612,4 +663,52 @@ mod tests {
assert!(stack.pop_layer().is_none()); 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::<ViewBox>()
.unwrap()
.with_view(|v| v.as_any().is::<TextView>())
.unwrap()
);
assert!(
stack
.get(LayerPosition::FromBack(0))
.unwrap()
.as_any()
.downcast_ref::<ViewBox>()
.unwrap()
.with_view(|v| v.as_any().is::<TextView>())
.unwrap()
);
assert!(
stack
.get_mut(LayerPosition::FromFront(0))
.unwrap()
.as_any_mut()
.downcast_mut::<ViewBox>()
.unwrap()
.with_view_mut(|v| v.as_any_mut().is::<TextView>())
.unwrap()
);
assert!(
stack
.get_mut(LayerPosition::FromBack(0))
.unwrap()
.as_any_mut()
.downcast_mut::<ViewBox>()
.unwrap()
.with_view_mut(|v| v.as_any_mut().is::<TextView>())
.unwrap()
);
}
} }

View File

@ -442,7 +442,8 @@ impl View for TextArea {
debug!("{:?}", self.rows); debug!("{:?}", self.rows);
let scroll_width = if self.rows.len() > constraint.y { 1 } else { 0 }; let scroll_width = if self.rows.len() > constraint.y { 1 } else { 0 };
Vec2::new( Vec2::new(
scroll_width + 1 scroll_width
+ 1
+ self.rows.iter().map(|r| r.width).max().unwrap_or(1), + self.rows.iter().map(|r| r.width).max().unwrap_or(1),
self.rows.len(), self.rows.len(),
) )

View File

@ -421,7 +421,8 @@ impl TextView {
} }
// Desired width, including the scrollbar_width. // Desired width, including the scrollbar_width.
self.width = self.rows self.width = self
.rows
.iter() .iter()
.map(|row| row.width) .map(|row| row.width)
.max() .max()