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
*.bk
TODO.txt
*.rustfmt

View File

@ -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"

View File

@ -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.
//

View File

@ -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.

View File

@ -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();

View File

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

View File

@ -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.

View File

@ -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.
//

View File

@ -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;

View File

@ -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(

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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.

View File

@ -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.

View File

@ -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.
//

View File

@ -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();

View File

@ -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.
//

View File

@ -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"),

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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 {

View File

@ -105,7 +105,7 @@ impl Cursive {
pub fn new(backend: Box<backend::Backend>) -> 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
{

View File

@ -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<T: Into<Vec2>>(
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<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
/// that will apply each given effect on prints.
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> {
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,

View File

@ -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<P: AsRef<Path>>(filename: P) -> Result<Theme, Error> {
content
};
load_theme(&content)
load_toml(&content)
}
/// 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 mut theme = Theme::default();
theme.load(&table);
theme.load_toml(&table);
Ok(theme)
}

View File

@ -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<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.
///
@ -35,57 +143,84 @@ pub type Palette = EnumMap<PaletteColor, Color>;
/// * `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<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`.
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
}
}

View File

@ -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.

View File

@ -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<Segment>,
/// 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,
}

View File

@ -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

View File

@ -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("> ")),

View File

@ -46,7 +46,8 @@ impl<T: View> Finder for T {
*result_ref =
v.downcast_mut::<V>().map(|v| callback(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));
}
}

View File

@ -195,7 +195,8 @@ impl<T: View> ViewWrapper for BoxView<T> {
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);

View File

@ -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::<usize>()
@ -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), "");

View File

@ -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]

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<(), ()> {
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)),

View File

@ -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<Vec2> = self.children
let ideal_sizes: Vec<Vec2> = 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<Vec2> = self.children
let min_sizes: Vec<Vec2> = 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<Vec2> = self.children
let final_sizes: Vec<Vec2> = 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,

View File

@ -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)))

View File

@ -138,7 +138,8 @@ impl MenuPopup {
fn make_subtree_cb(&self, tree: &Rc<MenuTree>) -> 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)

View File

@ -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)

View File

@ -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;

View File

@ -527,7 +527,8 @@ impl<T: 'static> SelectView<T> {
// 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<T: 'static> View for SelectView<T> {
// 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()

View File

@ -56,8 +56,10 @@ impl Placement {
enum ChildWrapper<T: View> {
// Some views include a shadow around.
Shadow(ShadowView<Layer<T>>),
// Some include a background.
Backfilled(Layer<T>),
// Some views don't (fullscreen views mostly)
Plain(Layer<T>),
Plain(T),
}
impl<T: View> ChildWrapper<T> {
@ -67,7 +69,11 @@ impl<T: View> ChildWrapper<T> {
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<T: View> ChildWrapper<T> {
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<T: View> ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T: View> View for ChildWrapper<T> {
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<T>(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<T>(&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<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.
///
/// 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::<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);
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(),
)

View File

@ -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()