mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Merge branch 'master' into scroll
This commit is contained in:
commit
5e1956b737
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ tags
|
||||
.ctags
|
||||
*.bk
|
||||
TODO.txt
|
||||
*.rustfmt
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -1,7 +1,7 @@
|
||||
extern crate cursive;
|
||||
|
||||
use cursive::Cursive;
|
||||
use cursive::views::TextView;
|
||||
use cursive::Cursive;
|
||||
|
||||
fn main() {
|
||||
let mut siv = Cursive::default();
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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"),
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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("> ")),
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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), "┤ ");
|
||||
|
@ -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]
|
||||
|
94
src/views/hideable_view.rs
Normal file
94
src/views/hideable_view.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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)),
|
||||
|
@ -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,
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user