Merge branch 'master' into callback_preemption

This commit is contained in:
Alexandre Bury 2018-06-16 11:03:39 -07:00
commit 83bcf4fb72
94 changed files with 2152 additions and 603 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"
@ -26,6 +25,10 @@ xi-unicode = "0.1.0"
libc = "0.2" libc = "0.2"
chan = "0.1" chan = "0.1"
[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.
// //
@ -19,7 +19,11 @@ use cursive::views::Canvas;
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();
siv.add_layer(Canvas::new(()).with_draw(draw).fixed_size((20, 10))); siv.add_layer(
Canvas::new(())
.with_draw(draw)
.fixed_size((20, 10)),
);
siv.add_global_callback('q', |s| s.quit()); siv.add_global_callback('q', |s| s.quit());

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,15 +1,19 @@
extern crate cursive; extern crate cursive;
use cursive::{Cursive, Printer};
use cursive::event::{Event, EventResult}; use cursive::event::{Event, EventResult};
use cursive::traits::*; use cursive::traits::*;
use cursive::{Cursive, Printer};
// This example define a custom view that prints any event it receives. // This example define a custom view that prints any event it receives.
// This is a handy way to check the input received by cursive. // This is a handy way to check the input received by cursive.
fn main() { fn main() {
let mut siv = Cursive::default(); let mut siv = Cursive::default();
siv.add_layer(KeyCodeView::new(10).full_width().fixed_height(10)); siv.add_layer(
KeyCodeView::new(10)
.full_width()
.fixed_height(10),
);
siv.run(); siv.run();
} }

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;
@ -87,8 +87,11 @@ impl View for BufferView {
fn draw(&self, printer: &Printer) { fn draw(&self, printer: &Printer) {
// Print the end of the buffer // Print the end of the buffer
for (i, line) in for (i, line) in self.buffer
self.buffer.iter().rev().take(printer.size.y).enumerate() .iter()
.rev()
.take(printer.size.y)
.enumerate()
{ {
printer.print((0, printer.size.y - 1 - i), line); printer.print((0, printer.size.y - 1 - i), line);
} }

View File

@ -1,18 +1,21 @@
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();
let mut styled = StyledString::plain("Isn't "); let mut styled = StyledString::plain("Isn't ");
styled.append(StyledString::styled("that ", Color::Dark(BaseColor::Red))); styled.append(StyledString::styled(
"that ",
Color::Dark(BaseColor::Red),
));
styled.append(StyledString::styled( styled.append(StyledString::styled(
"cool?", "cool?",
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold), Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),

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

@ -64,7 +64,8 @@ impl Board {
} }
fn get_mut(&mut self, pos: Vec2) -> Option<&mut Cell> { fn get_mut(&mut self, pos: Vec2) -> Option<&mut Cell> {
self.cell_id(pos).map(move |i| &mut self.cells[i]) self.cell_id(pos)
.map(move |i| &mut self.cells[i])
} }
pub fn cell_id(&self, pos: Vec2) -> Option<usize> { pub fn cell_id(&self, pos: Vec2) -> Option<usize> {

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();
@ -189,7 +189,9 @@ impl cursive::view::View for BoardView {
Cell::Unknown => "[]", Cell::Unknown => "[]",
Cell::Flag => "()", Cell::Flag => "()",
Cell::Visible(n) => { Cell::Visible(n) => {
[" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"][n] [
" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"
][n]
} }
}; };

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;
@ -87,7 +87,11 @@ fn phase_2(s: &mut Cursive) {
// Let's prepare the progress bars... // Let's prepare the progress bars...
let mut linear = LinearLayout::vertical(); let mut linear = LinearLayout::vertical();
for c in &counters { for c in &counters {
linear.add_child(ProgressBar::new().max(n_max).with_value(c.clone())); linear.add_child(
ProgressBar::new()
.max(n_max)
.with_value(c.clone()),
);
} }
s.pop_layer(); s.pop_layer();

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.
@ -38,6 +38,10 @@ fn on_edit(siv: &mut Cursive, _content: &str, _cursor: usize) {
let matches = edit_1.get_content() == edit_2.get_content(); let matches = edit_1.get_content() == edit_2.get_content();
siv.call_on_id("match", |v: &mut TextView| { siv.call_on_id("match", |v: &mut TextView| {
v.set_content(if matches { "match" } else { "no match" }) v.set_content(if matches {
"match"
} else {
"no match"
})
}); });
} }

39
examples/scroll.rs Normal file
View File

@ -0,0 +1,39 @@
extern crate cursive;
use cursive::traits::Boxable;
use cursive::views::{Button, Canvas, Dialog, LinearLayout, ScrollView};
use cursive::Printer;
fn main() {
let mut siv = cursive::Cursive::default();
siv.add_layer(
Dialog::around(
ScrollView::new(
LinearLayout::vertical()
.child(Button::new("Foo", |s| {
s.add_layer(Dialog::info("Ah"))
}))
.child(
Canvas::new(()).with_draw(draw).fixed_size((120, 40)),
)
.child(Button::new("Bar", |s| {
s.add_layer(Dialog::info("Uh"))
})),
).scroll_x(true),
).fixed_size((60, 30)),
);
siv.add_global_callback('q', |s| s.quit());
siv.run();
}
fn draw(_: &(), p: &Printer) {
for x in 0..p.size.x {
for y in 0..p.size.y {
let c = (x + 6 * y) % 10;
p.print((x, y), &format!("{}", c));
}
}
}

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();
@ -19,7 +19,9 @@ fn main() {
); );
// We'll add a find feature! // We'll add a find feature!
siv.add_layer(Dialog::info("Hint: press Ctrl-F to find in text!")); siv.add_layer(Dialog::info(
"Hint: press Ctrl-F to find in text!",
));
siv.add_global_callback(Event::CtrlChar('f'), |s| { siv.add_global_callback(Event::CtrlChar('f'), |s| {
// When Ctrl-F is pressed, show the Find popup. // When Ctrl-F is pressed, show the Find popup.
@ -35,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,12 +1,13 @@
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();
// You can load a theme from a file at runtime for fast development. // You can load a theme from a file at runtime for fast development.
siv.load_theme_file("assets/style.toml").unwrap(); siv.load_theme_file("assets/style.toml")
.unwrap();
// Or you can directly load it from a string for easy deployment. // Or you can directly load it from a string for easy deployment.
// siv.load_theme(include_str!("../assets/style.toml")).unwrap(); // siv.load_theme(include_str!("../assets/style.toml")).unwrap();

View File

@ -1,14 +1,16 @@
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();
let layout = LinearLayout::vertical() let layout = LinearLayout::vertical()
.child(TextView::new("This is a dynamic theme example!")) .child(TextView::new(
"This is a dynamic theme example!",
))
.child(EditView::new().content("Woo! colors!").style( .child(EditView::new().content("Woo! colors!").style(
ColorStyle::new( ColorStyle::new(
Color::Rgb(200, 150, 150), Color::Rgb(200, 150, 150),

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),
}; };

View File

@ -12,7 +12,7 @@ pub struct Align {
impl Align { impl Align {
/// Creates a new Align object from the given alignments. /// Creates a new Align object from the given alignments.
pub fn new(h: HAlign, v: VAlign) -> Self { pub fn new(h: HAlign, v: VAlign) -> Self {
Align { h: h, v: v } Align { h, v }
} }
/// Creates a top-left alignment. /// Creates a top-left alignment.

View File

@ -5,10 +5,11 @@
extern crate bear_lib_terminal; extern crate bear_lib_terminal;
use self::bear_lib_terminal::Color as BltColor;
use self::bear_lib_terminal::geometry::Size; use self::bear_lib_terminal::geometry::Size;
use self::bear_lib_terminal::terminal::{self, state, Event as BltEvent, use self::bear_lib_terminal::terminal::{
KeyCode}; self, state, Event as BltEvent, KeyCode,
};
use self::bear_lib_terminal::Color as BltColor;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use std::collections::HashSet; use std::collections::HashSet;
@ -108,7 +109,7 @@ impl Backend {
} }
fn blt_keycode_to_ev( fn blt_keycode_to_ev(
&mut self, kc: KeyCode, shift: bool, ctrl: bool &mut self, kc: KeyCode, shift: bool, ctrl: bool,
) -> Event { ) -> Event {
match kc { match kc {
KeyCode::F1 KeyCode::F1
@ -238,12 +239,12 @@ impl backend::Backend for Backend {
fn set_color(&self, color: ColorPair) -> ColorPair { fn set_color(&self, color: ColorPair) -> ColorPair {
let current = ColorPair { let current = ColorPair {
front: blt_colour_to_colour(state::foreground()), front: blt_colour_to_colour(state::foreground()),
back: blt_colour_to_colour(state::background()) back: blt_colour_to_colour(state::background()),
}; };
let fg = colour_to_blt_colour(color.front, ColorRole::Foreground); let fg = colour_to_blt_colour(color.front, ColorRole::Foreground);
let bg = colour_to_blt_colour(color.back, ColorRole::Background); let bg = colour_to_blt_colour(color.back, ColorRole::Background);
terminal::set_colors(fg, bg); terminal::set_colors(fg, bg);
current current
@ -255,14 +256,14 @@ impl backend::Backend for Backend {
Effect::Bold Effect::Bold
| Effect::Italic | Effect::Italic
| Effect::Underline | Effect::Underline
| Effect::Simple => {}, | Effect::Simple => {}
// TODO: how to do this correctly?` // TODO: how to do this correctly?`
// BLT itself doesn't do this kind of thing, // BLT itself doesn't do this kind of thing,
// we'd need the colours in our position, // we'd need the colours in our position,
// but `f()` can do whatever // but `f()` can do whatever
Effect::Reverse => terminal::set_colors( Effect::Reverse => {
state::background(), state::foreground() terminal::set_colors(state::background(), state::foreground())
), }
} }
} }
@ -272,11 +273,11 @@ impl backend::Backend for Backend {
Effect::Bold Effect::Bold
| Effect::Italic | Effect::Italic
| Effect::Underline | Effect::Underline
| Effect::Simple => {}, | Effect::Simple => {}
// The process of reversing is the same as unreversing // The process of reversing is the same as unreversing
Effect::Reverse => terminal::set_colors( Effect::Reverse => {
state::background(), state::foreground() terminal::set_colors(state::background(), state::foreground())
), }
} }
} }

View File

@ -63,7 +63,7 @@ where
fn find_closest_pair(pair: &ColorPair, max_colors: i16) -> (i16, i16) { fn find_closest_pair(pair: &ColorPair, max_colors: i16) -> (i16, i16) {
( (
find_closest(&pair.front,max_colors), find_closest(&pair.front, max_colors),
find_closest(&pair.back, max_colors), find_closest(&pair.back, max_colors),
) )
} }
@ -95,7 +95,7 @@ fn find_closest(color: &Color, max_colors: i16) -> i16 {
// (r - 8) / 10 = n // (r - 8) / 10 = n
// //
let n = (r - 8) / 10; let n = (r - 8) / 10;
(232 + n) as i16 i16::from(232 + n)
} else { } else {
// Generic RGB // Generic RGB
let r = 6 * u16::from(r) / 256; let r = 6 * u16::from(r) / 256;

View File

@ -1,7 +1,7 @@
extern crate ncurses; extern crate ncurses;
use self::ncurses::mmask_t;
use self::super::split_i32; use self::super::split_i32;
use self::ncurses::mmask_t;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
@ -206,10 +206,9 @@ impl Backend {
Box::new(c) Box::new(c)
} }
/// Save a new color pair. /// Save a new color pair.
fn insert_color( fn insert_color(
&self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16) &self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16),
) -> i16 { ) -> i16 {
let n = 1 + pairs.len() as i16; let n = 1 + pairs.len() as i16;
@ -250,7 +249,6 @@ impl Backend {
ncurses::attron(style); ncurses::attron(style);
} }
} }
impl backend::Backend for Backend { impl backend::Backend for Backend {

View File

@ -1,7 +1,7 @@
extern crate pancurses; extern crate pancurses;
use self::pancurses::mmask_t;
use self::super::split_i32; use self::super::split_i32;
use self::pancurses::mmask_t;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -307,9 +307,7 @@ impl Backend {
/// Save a new color pair. /// Save a new color pair.
fn insert_color( fn insert_color(
&self, &self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16),
pairs: &mut HashMap<(i16,i16), i32>,
(front, back): (i16, i16),
) -> i32 { ) -> i32 {
let n = 1 + pairs.len() as i32; let n = 1 + pairs.len() as i32;
@ -351,6 +349,68 @@ impl Backend {
self.window.attron(style); self.window.attron(style);
} }
<<<<<<< HEAD
=======
fn parse_mouse_event(&mut self) -> Event {
let mut mevent = match pancurses::getmouse() {
Err(code) => return Event::Unknown(split_i32(code)),
Ok(event) => event,
};
let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT 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;
mevent.bstate &= !(pancurses::BUTTON_SHIFT
| pancurses::BUTTON_ALT
| pancurses::BUTTON_CTRL) as mmask_t;
let make_event = |event| Event::Mouse {
offset: Vec2::zero(),
position: Vec2::new(mevent.x as usize, mevent.y as usize),
event: event,
};
if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t {
// The event is either a mouse drag event,
// or a weird double-release event. :S
self.last_mouse_button
.map(MouseEvent::Hold)
.map(&make_event)
.unwrap_or_else(|| {
debug!("We got a mouse drag, but no last mouse pressed?");
Event::Unknown(vec![])
})
} else {
// Identify the button
let mut bare_event = mevent.bstate & ((1 << 25) - 1);
let mut event = None;
while bare_event != 0 {
let single_event = 1 << bare_event.trailing_zeros();
bare_event ^= single_event;
// Process single_event
on_mouse_event(single_event, |e| {
if event.is_none() {
event = Some(e);
} else {
self.event_queue.push(make_event(e));
}
});
}
if let Some(event) = event {
if let Some(btn) = event.button() {
self.last_mouse_button = Some(btn);
}
make_event(event)
} else {
debug!("No event parsed?...");
Event::Unknown(vec![])
}
}
}
>>>>>>> master
} }
impl backend::Backend for Backend { impl backend::Backend for Backend {

View File

@ -1,7 +1,7 @@
//! Dummy backend //! Dummy backend
use backend; use backend;
use theme;
use event; use event;
use theme;
use vec::Vec2; use vec::Vec2;
use std::time::Duration; use std::time::Duration;

View File

@ -4,7 +4,7 @@
//! backend library, which handles all actual input and output. //! backend library, which handles all actual input and output.
//! //!
//! This module defines the `Backend` trait, as well as a few implementations //! This module defines the `Backend` trait, as well as a few implementations
//! using some common libraries. Each of those included backends needs a //! using some common libraries. Each of those included backends needs a
//! corresonding feature to be enabled. //! corresonding feature to be enabled.
use event; use event;
@ -18,9 +18,9 @@ use std::time::Duration;
pub mod dummy; pub mod dummy;
pub mod termion;
pub mod blt; pub mod blt;
pub mod curses; pub mod curses;
pub mod termion;
/// Trait defining the required methods to be a backend. /// Trait defining the required methods to be a backend.
pub trait Backend { pub trait Backend {
@ -72,7 +72,6 @@ pub trait Backend {
/// Enables the given effect. /// Enables the given effect.
fn set_effect(&self, effect: theme::Effect); fn set_effect(&self, effect: theme::Effect);
/// Disables the given effect. /// Disables the given effect.
fn unset_effect(&self, effect: theme::Effect); fn unset_effect(&self, effect: theme::Effect);
} }

View File

@ -133,7 +133,7 @@ impl Cursive {
Cursive { Cursive {
fps: 0, fps: 0,
theme: theme, theme,
screens: vec![views::StackView::new()], screens: vec![views::StackView::new()],
last_sizes: Vec::new(), last_sizes: Vec::new(),
global_callbacks: HashMap::new(), global_callbacks: HashMap::new(),
@ -309,8 +309,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(())
} }
@ -649,7 +649,7 @@ impl Cursive {
// Print the stackview background before the menubar // Print the stackview background before the menubar
let offset = if self.menubar.autohide { 0 } else { 1 }; let offset = if self.menubar.autohide { 0 } else { 1 };
let id = self.active_screen; let id = self.active_screen;
let sv_printer = printer.offset((0, offset), !selected); let sv_printer = printer.offset((0, offset)).focused(!selected);
self.screens[id].draw_bg(&sv_printer); self.screens[id].draw_bg(&sv_printer);
@ -657,11 +657,7 @@ impl Cursive {
// If the menubar is active, nothing else can be. // If the menubar is active, nothing else can be.
// Draw the menubar? // Draw the menubar?
if self.menubar.visible() { if self.menubar.visible() {
let printer = printer.sub_printer( let printer = printer.focused(self.menubar.receive_events());
Vec2::zero(),
printer.size,
self.menubar.receive_events(),
);
self.menubar.draw(&printer); self.menubar.draw(&printer);
} }

View File

@ -15,8 +15,8 @@
//! * Relative direction: front or back. //! * Relative direction: front or back.
//! Its actual direction depends on the orientation. //! Its actual direction depends on the orientation.
use XY;
use vec::Vec2; use vec::Vec2;
use XY;
/// Describes a vertical or horizontal orientation for a view. /// Describes a vertical or horizontal orientation for a view.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
@ -28,6 +28,11 @@ pub enum Orientation {
} }
impl Orientation { impl Orientation {
/// Returns a `XY(Horizontal, Vertical)`.
pub fn pair() -> XY<Orientation> {
XY::new(Orientation::Horizontal, Orientation::Vertical)
}
/// Returns the component of `v` corresponding to this orientation. /// Returns the component of `v` corresponding to this orientation.
/// ///
/// (`Horizontal` will return the x value, /// (`Horizontal` will return the x value,

View File

@ -13,10 +13,11 @@
//! [global callback](../struct.Cursive.html#method.add_global_callback) //! [global callback](../struct.Cursive.html#method.add_global_callback)
//! table is checked. //! table is checked.
use Cursive; use std::any::Any;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use vec::Vec2; use vec::Vec2;
use Cursive;
/// Callback is a function that can be triggered by an event. /// Callback is a function that can be triggered by an event.
/// It has a mutable access to the cursive root. /// It has a mutable access to the cursive root.
@ -24,6 +25,9 @@ use vec::Vec2;
pub struct Callback(Rc<Box<Fn(&mut Cursive)>>); pub struct Callback(Rc<Box<Fn(&mut Cursive)>>);
// TODO: remove the Box when Box<T: Sized> -> Rc<T> is possible // TODO: remove the Box when Box<T: Sized> -> Rc<T> is possible
/// A boxed callback that can be run on `&mut Any`.
pub type AnyCb<'a> = Box<FnMut(&mut Any) + 'a>;
impl Callback { impl Callback {
/// Wraps the given function into a `Callback` object. /// Wraps the given function into a `Callback` object.
pub fn from_fn<F>(f: F) -> Self pub fn from_fn<F>(f: F) -> Self
@ -343,6 +347,20 @@ impl Event {
} }
} }
/// Returns a mutable reference to the position of the mouse/
///
/// Returns `None` if `self` is not a mouse event.
pub fn mouse_position_mut(&mut self) -> Option<&mut Vec2> {
if let Event::Mouse {
ref mut position, ..
} = *self
{
Some(position)
} else {
None
}
}
/// Update `self` with the given offset. /// Update `self` with the given offset.
/// ///
/// If `self` is a mouse event, adds `top_left` to its offset. /// If `self` is a mouse event, adds `top_left` to its offset.

View File

@ -74,8 +74,8 @@ extern crate chan;
#[macro_use] #[macro_use]
extern crate maplit; extern crate maplit;
extern crate num;
extern crate libc; extern crate libc;
extern crate num;
extern crate owning_ref; extern crate owning_ref;
extern crate toml; extern crate toml;
extern crate unicode_segmentation; extern crate unicode_segmentation;
@ -98,20 +98,20 @@ pub mod event;
#[macro_use] #[macro_use]
pub mod view; pub mod view;
pub mod views; pub mod align;
pub mod vec; pub mod direction;
pub mod menu;
pub mod rect; pub mod rect;
pub mod theme; pub mod theme;
pub mod align;
pub mod menu;
pub mod direction;
pub mod utils; pub mod utils;
pub mod vec;
pub mod views;
// This probably doesn't need to be public? // This probably doesn't need to be public?
mod cursive; mod cursive;
mod printer; mod printer;
mod xy;
mod with; mod with;
mod xy;
mod div; mod div;
mod utf8; mod utf8;

View File

@ -12,10 +12,10 @@
//! //!
//! [menubar]: ../struct.Cursive.html#method.menubar //! [menubar]: ../struct.Cursive.html#method.menubar
use Cursive;
use With;
use event::Callback; use event::Callback;
use std::rc::Rc; use std::rc::Rc;
use Cursive;
use With;
/// Root of a menu tree. /// Root of a menu tree.
#[derive(Default, Clone)] #[derive(Default, Clone)]

View File

@ -1,46 +1,84 @@
//! Makes drawing on ncurses windows easier. //! Provide higher-level abstraction to draw things on backends.
use backend::Backend; use backend::Backend;
use direction::Orientation;
use enumset::EnumSet; use enumset::EnumSet;
use std::cell::Cell;
use std::cmp::min; use std::cmp::min;
use std::rc::Rc;
use theme::{BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme}; use theme::{BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use utils::lines::simple::prefix; use unicode_width::UnicodeWidthStr;
use utils::lines::simple::{prefix, suffix};
use vec::Vec2; use vec::Vec2;
use with::With;
/// Convenient interface to draw on a subset of the screen. /// Convenient interface to draw on a subset of the screen.
pub struct Printer<'a> { ///
/// The area it can print on is defined by `offset` and `size`.
///
/// The part of the content it will print is defined by `content_offset`
/// and `size`.
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`.
pub offset: Vec2, pub offset: Vec2,
/// Size of the area we are allowed to draw on. /// Size of the area we are allowed to draw on.
///
/// Anything outside of this should be discarded.
pub output_size: Vec2,
/// Size allocated to the view.
///
/// This should be the same value as the one given in the last call to
/// `View::layout`.
pub size: Vec2, pub size: Vec2,
/// Offset into the view for this printer.
///
/// A print request `x`, will really print at `x - content_offset`.
pub content_offset: Vec2,
/// Whether the view to draw is currently focused or not. /// Whether the view to draw is currently focused or not.
pub focused: bool, pub focused: bool,
/// Currently used theme /// Currently used theme
pub theme: &'a Theme, pub theme: &'a Theme,
/// `true` if nothing has been drawn yet.
new: Rc<Cell<bool>>,
/// Backend used to actually draw things /// Backend used to actually draw things
backend: &'a Backend, backend: &'b Backend,
} }
impl<'a> Printer<'a> { impl<'a, 'b> Clone for Printer<'a, 'b> {
fn clone(&self) -> Self {
Printer {
offset: self.offset,
content_offset: self.content_offset,
output_size: self.output_size,
size: self.size,
focused: self.focused,
theme: self.theme,
backend: self.backend,
}
}
}
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();
Printer { Printer {
offset: Vec2::zero(), offset: Vec2::zero(),
size: size.into(), content_offset: Vec2::zero(),
output_size: size,
size,
focused: true, focused: true,
theme, theme,
new: Rc::new(Cell::new(true)),
backend, backend,
} }
} }
@ -55,61 +93,152 @@ impl<'a> Printer<'a> {
.clear(self.theme.palette[PaletteColor::Background]); .clear(self.theme.palette[PaletteColor::Background]);
} }
/// Returns `true` if nothing has been printed yet.
pub fn is_new(&self) -> bool {
self.new.get()
}
// TODO: use &mut self? We don't *need* it, but it may make sense. // TODO: use &mut self? We don't *need* it, but it may make sense.
// We don't want people to start calling prints in parallel? // We don't want people to start calling prints in parallel?
/// Prints some text at the given position relative to the window. /// Prints some text at the given position relative to the window.
pub fn print<S: Into<Vec2>>(&self, pos: S, text: &str) { pub fn print<S: Into<Vec2>>(&self, start: S, text: &str) {
self.new.set(false); // Where we are asked to start printing. Oh boy.
let start = start.into();
let p = pos.into(); // We accept requests between `content_offset` and
if p.y >= self.size.y || p.x >= self.size.x { // `content_offset + output_size`.
if !(start < (self.output_size + self.content_offset)) {
return; return;
} }
// If start < content_offset, part of the text will not be visible.
// This is the part of the text that's hidden:
// (It should always be smaller than the content offset)
let hidden_part = self.content_offset.saturating_sub(start);
if hidden_part.y > 0 {
// Since we are printing a single line, there's nothing we can do.
return;
}
let text_width = text.width();
// If we're waaaay too far left, just give up.
if hidden_part.x > text_width {
return;
}
// We have to drop hidden_part.x width from the start of the string.
// prefix() may be too short if there's a double-width character.
// So instead, keep the suffix and drop the prefix.
// TODO: use a different prefix method that is *at least* the width
// (and not *at most*)
let tail =
suffix(text.graphemes(true), text_width - hidden_part.x, "");
let skipped_len = text.len() - tail.length;
let skipped_width = text_width - tail.width;
assert_eq!(text[..skipped_len].width(), skipped_width);
// This should be equal most of the time, except when there's a double
// character preventing us from splitting perfectly.
assert!(skipped_width >= hidden_part.x);
// Drop part of the text, and move the cursor correspondingly.
let text = &text[skipped_len..];
let start = start + (skipped_width, 0);
assert!(start.fits(self.content_offset));
// What we did before should guarantee that this won't overflow.
let start = start - self.content_offset;
// Do we have enough room for the entire line? // Do we have enough room for the entire line?
let room = self.size.x - p.x; let room = self.output_size.x - start.x;
// Drop the end of the text if it's too long
// We want the number of CHARACTERS, not bytes. // We want the number of CHARACTERS, not bytes.
// (Actually we want the "width" of the string, see unicode-width) // (Actually we want the "width" of the string, see unicode-width)
let prefix_len = prefix(text.graphemes(true), room, "").length; let prefix_len = prefix(text.graphemes(true), room, "").length;
let text = &text[..prefix_len]; let text = &text[..prefix_len];
assert!(text.width() <= room);
let p = p + self.offset; let start = start + self.offset;
self.backend.print_at(p, text); self.backend.print_at(start, text);
} }
/// Prints a vertical line using the given character. /// Prints a vertical line using the given character.
pub fn print_vline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) { pub fn print_vline<T: Into<Vec2>>(
self.new.set(false); &self, start: T, height: usize, c: &str,
) {
let start = start.into();
let p = start.into(); // Here again, we can abort if we're trying to print too far right or
if p.y > self.size.y || p.x > self.size.x { // too low.
if !start.fits_in(self.output_size + self.content_offset) {
return; return;
} }
let len = min(len, self.size.y - p.y);
let p = p + self.offset; // hidden_part describes how far to the top left of the viewport we are.
for y in 0..len { let hidden_part = self.content_offset.saturating_sub(start);
self.backend.print_at(p + (0,y), c); if hidden_part.x > 0 || hidden_part.y >= height {
// We're printing a single column, so we can't do much here.
return;
}
// Skip `hidden_part`
let start = start + hidden_part;
assert!(start.fits(self.content_offset));
let height = height - hidden_part.y;
// What we did before ensures this won't overflow.
let start = start - self.content_offset;
// Don't go overboard
let height = min(height, self.output_size.y - start.y);
let start = start + self.offset;
for y in 0..height {
self.backend.print_at(start + (0, y), c);
}
}
/// Prints a line using the given character.
pub fn print_line<T: Into<Vec2>>(
&self, orientation: Orientation, start: T, length: usize, c: &str,
) {
match orientation {
Orientation::Vertical => self.print_vline(start, length, c),
Orientation::Horizontal => self.print_hline(start, length, c),
} }
} }
/// Prints a horizontal line using the given character. /// Prints a horizontal line using the given character.
pub fn print_hline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) { pub fn print_hline<T: Into<Vec2>>(&self, start: T, width: usize, c: &str) {
self.new.set(false); let start = start.into();
let p = start.into(); // Nothing to be done if the start if too far to the bottom/right
if p.y > self.size.y || p.x > self.size.x { if !start.fits_in(self.output_size + self.content_offset) {
return; return;
} }
let len = min(len, self.size.x - p.x);
let text: String = ::std::iter::repeat(c).take(len).collect();
let p = p + self.offset; let hidden_part = self.content_offset.saturating_sub(start);
self.backend.print_at(p, &text); if hidden_part.y > 0 || hidden_part.x >= width {
// We're printing a single line, so we can't do much here.
return;
}
// Skip `hidden_part`
let start = start + hidden_part;
assert!(start.fits(self.content_offset));
let width = width - hidden_part.x;
// Don't go too far
let start = start - self.content_offset;
// Don't write too much if we're close to the end
let width = min(width, (self.output_size.x - start.x) / c.width());
// Could we avoid allocating?
let text: String = ::std::iter::repeat(c).take(width).collect();
let start = start + self.offset;
self.backend.print_at(start, &text);
} }
/// Call the given closure with a colored printer, /// Call the given closure with a colored printer,
@ -149,8 +278,6 @@ impl<'a> Printer<'a> {
let color = style.color; let color = style.color;
let effects = style.effects; let effects = style.effects;
// eprintln!("{:?}", effects);
if let Some(color) = color { if let Some(color) = color {
self.with_color(color, |printer| { self.with_color(color, |printer| {
printer.with_effects(effects, f); printer.with_effects(effects, f);
@ -171,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)
@ -205,10 +350,8 @@ impl<'a> Printer<'a> {
/// printer.print_box((0,0), (6,4), false); /// printer.print_box((0,0), (6,4), false);
/// ``` /// ```
pub fn print_box<T: Into<Vec2>, S: Into<Vec2>>( pub fn print_box<T: Into<Vec2>, S: Into<Vec2>>(
&self, start: T, size: S, invert: bool &self, start: T, size: S, invert: bool,
) { ) {
self.new.set(false);
let start = start.into(); let start = start.into();
let size = size.into(); let size = size.into();
@ -293,37 +436,98 @@ impl<'a> Printer<'a> {
} }
/// Prints a horizontal delimiter with side border `├` and `┤`. /// Prints a horizontal delimiter with side border `├` and `┤`.
pub fn print_hdelim<T: Into<Vec2>>(&self, start: T, len: usize) { pub fn print_hdelim<T>(&self, start: T, len: usize)
where
T: Into<Vec2>,
{
let start = start.into(); let start = start.into();
self.print(start, ""); self.print(start, "");
self.print_hline(start + (1, 0), len.saturating_sub(2), ""); self.print_hline(start + (1, 0), len.saturating_sub(2), "");
self.print(start + (len.saturating_sub(1), 0), ""); self.print(start + (len.saturating_sub(1), 0), "");
} }
/// Returns a printer on a subset of this one's area. /// Returns a sub-printer with the given offset.
pub fn sub_printer<S: Into<Vec2>, T: Into<Vec2>>( ///
&'a self, offset: S, size: T, focused: bool /// It will print in an area slightly to the bottom/right.
) -> Printer<'a> { pub fn offset<S>(&self, offset: S) -> Printer
let size = size.into(); where
let offset = offset.into().or_min(self.size); S: Into<Vec2>,
let available = if !offset.fits_in(self.size) { {
Vec2::zero() let offset = offset.into();
} else { self.clone().with(|s| {
Vec2::min(self.size - offset, size) // If we are drawing a part of the content,
}; // let's reduce this first.
Printer { let consumed = Vec2::min(s.content_offset, offset);
offset: self.offset + offset,
// We can't be larger than what remains let offset = offset - consumed;
size: available, s.content_offset = s.content_offset - consumed;
focused: self.focused && focused,
theme: self.theme, s.offset = s.offset + offset;
backend: self.backend,
new: Rc::clone(&self.new), s.output_size = s.output_size.saturating_sub(offset);
} s.size = s.size.saturating_sub(offset);
})
} }
/// Returns a sub-printer with the given offset. /// Returns a new sub-printer inheriting the given focus.
pub fn offset<S: Into<Vec2>>(&self, offset: S, focused: bool) -> Printer { ///
self.sub_printer(offset, self.size, focused) /// If `self` is focused and `focused == true`, the child will be focused.
///
/// Otherwise, he will be unfocused.
pub fn focused(&self, focused: bool) -> Self {
self.clone().with(|s| {
s.focused &= focused;
})
}
/// Returns a new sub-printer with a cropped area.
///
/// The new printer size will be the minimum of `size` and its current size.
///
/// Any size reduction happens at the bottom-right.
pub fn cropped<S>(&self, size: S) -> Self
where
S: Into<Vec2>,
{
self.clone().with(|s| {
let size = size.into();
s.output_size = Vec2::min(s.output_size, size);
s.size = Vec2::min(s.size, size);
})
}
/// Returns a new sub-printer with a shrinked area.
///
/// The printer size will be reduced by the given border from the bottom-right.
pub fn shrinked<S>(&self, borders: S) -> Self
where
S: Into<Vec2>,
{
self.cropped(self.size.saturating_sub(borders))
}
/// Returns a new sub-printer with a content offset.
pub fn content_offset<S>(&self, offset: S) -> Self
where
S: Into<Vec2>,
{
self.clone().with(|s| {
s.content_offset = s.content_offset + offset;
})
}
/// Returns a sub-printer with a different inner size.
///
/// This will not change the actual output size, but will appear bigger to
/// users of this printer.
///
/// Useful to give to children who think they're big, but really aren't.
pub fn inner_size<S>(&self, size: S) -> Self
where
S: Into<Vec2>,
{
self.clone().with(|s| {
s.size = size.into();
})
} }
} }

View File

@ -1,5 +1,5 @@
//! Rectangles on the 2D character grid. //! Rectangles on the 2D character grid.
use std::ops::Add;
use vec::Vec2; use vec::Vec2;
/// A non-empty rectangle on the 2D grid. /// A non-empty rectangle on the 2D grid.
@ -7,7 +7,9 @@ use vec::Vec2;
/// ///
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect { pub struct Rect {
/// Top-left corner, inclusive
top_left: Vec2, top_left: Vec2,
/// Bottom-right corner, inclusive
bottom_right: Vec2, bottom_right: Vec2,
} }
@ -21,6 +23,18 @@ where
} }
} }
impl<T> Add<T> for Rect
where
T: Into<Vec2>,
{
type Output = Rect;
fn add(mut self, rhs: T) -> Self {
self.offset(rhs);
self
}
}
impl Rect { impl Rect {
/// Creates a new `Rect` with the given position and size. /// Creates a new `Rect` with the given position and size.
/// ///
@ -80,7 +94,10 @@ impl Rect {
} }
/// Adds the given offset to this rectangle. /// Adds the given offset to this rectangle.
pub fn offset<V>(&mut self, offset: V) where V: Into<Vec2> { pub fn offset<V>(&mut self, offset: V)
where
V: Into<Vec2>,
{
let offset = offset.into(); let offset = offset.into();
self.top_left = self.top_left + offset; self.top_left = self.top_left + offset;
self.bottom_right = self.bottom_right + offset; self.bottom_right = self.bottom_right + offset;
@ -122,21 +139,29 @@ impl Rect {
} }
/// Returns the Y value of the top edge of the rectangle. /// Returns the Y value of the top edge of the rectangle.
///
/// This is inclusive.
pub fn top(self) -> usize { pub fn top(self) -> usize {
self.top_left.y self.top_left.y
} }
/// Returns the X value of the left edge of the rectangle. /// Returns the X value of the left edge of the rectangle.
///
/// This is inclusive.
pub fn left(self) -> usize { pub fn left(self) -> usize {
self.top_left.x self.top_left.x
} }
/// Returns the X value of the right edge of the rectangle. /// Returns the X value of the right edge of the rectangle.
///
/// This is inclusive.
pub fn right(self) -> usize { pub fn right(self) -> usize {
self.bottom_right.x self.bottom_right.x
} }
/// Returns the Y value of the botton edge of the rectangle. /// Returns the Y value of the botton edge of the rectangle.
///
/// This is inclusive.
pub fn bottom(self) -> usize { pub fn bottom(self) -> usize {
self.bottom_right.y self.bottom_right.y
} }
@ -145,4 +170,9 @@ impl Rect {
pub fn surface(self) -> usize { pub fn surface(self) -> usize {
self.width() * self.height() self.width() * self.height()
} }
/// Checks if a point is in `self`.
pub fn contains(self, point: Vec2) -> bool {
point.fits(self.top_left) && point.fits_in(self.bottom_right)
}
} }

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

@ -156,20 +156,20 @@
//! highlight = "#F00" //! highlight = "#F00"
//! highlight_inactive = "#5555FF" //! highlight_inactive = "#5555FF"
//! ``` //! ```
mod style; mod border_style;
mod effect;
mod color; mod color;
mod color_pair; mod color_pair;
mod color_style; mod color_style;
mod border_style; mod effect;
mod palette; mod palette;
mod style;
pub use self::border_style::BorderStyle; pub use self::border_style::BorderStyle;
pub use self::color::{BaseColor, Color}; 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

@ -8,7 +8,7 @@
//! use cursive::traits::*; //! use cursive::traits::*;
//! ``` //! ```
#[doc(no_inline)]
pub use With;
#[doc(no_inline)] #[doc(no_inline)]
pub use view::{Boxable, Finder, Identifiable, View}; pub use view::{Boxable, Finder, Identifiable, View};
#[doc(no_inline)]
pub use With;

View File

@ -1,5 +1,5 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
/// Atomic counter used by [`ProgressBar`]. /// Atomic counter used by [`ProgressBar`].
/// ///

View File

@ -70,10 +70,6 @@ impl<'a> Iterator for LinesIterator<'a> {
let width = row.width; let width = row.width;
Some(Row { Some(Row { start, end, width })
start,
end,
width,
})
} }
} }

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.
@ -82,7 +83,7 @@ where
debug_assert!(current_width <= available_width + delimiter_width); debug_assert!(current_width <= available_width + delimiter_width);
Span { Span {
length: length, length,
width: current_width, width: current_width,
} }
} }

View File

@ -47,10 +47,7 @@ where
} }
// Skip empty spans // Skip empty spans
if self.source.spans()[self.current_span] if self.source.spans()[self.current_span].as_ref().is_empty() {
.as_ref()
.is_empty()
{
self.current_span += 1; self.current_span += 1;
return self.next(); return self.next();
} }
@ -123,13 +120,9 @@ where
self.current_span += 1; self.current_span += 1;
// Skip empty spans // Skip empty spans
while let Some(true) = self.source while let Some(true) =
.spans() self.source.spans().get(self.current_span).map(|span| {
.get(self.current_span) span.as_ref().resolve(self.source.source()).is_empty()
.map(|span| {
span.as_ref()
.resolve(self.source.source())
.is_empty()
}) { }) {
self.current_span += 1; self.current_span += 1;
} }

View File

@ -76,11 +76,8 @@ where
self.width self.width
}; };
let mut chunks = prefix( let mut chunks =
&mut self.iter, prefix(&mut self.iter, allowed_width, &mut self.chunk_offset);
allowed_width,
&mut self.chunk_offset,
);
// println!("Chunks..: {:?}", chunks); // println!("Chunks..: {:?}", chunks);
@ -152,10 +149,7 @@ where
// We can know text was wrapped if the stop was optional, // We can know text was wrapped if the stop was optional,
// and there's more coming. // and there's more coming.
let text_wrap = !chunks let text_wrap = !chunks.last().map(|c| c.hard_stop).unwrap_or(true)
.last()
.map(|c| c.hard_stop)
.unwrap_or(true)
&& self.iter.peek().is_some(); && self.iter.peek().is_some();
// If we had to break a line in two, then at least pretent we're // If we had to break a line in two, then at least pretent we're
@ -178,9 +172,6 @@ where
// TODO: merge consecutive segments of the same span // TODO: merge consecutive segments of the same span
Some(Row { Some(Row { segments, width })
segments,
width,
})
} }
} }

View File

@ -3,7 +3,7 @@ use std::iter::Peekable;
/// Concatenates chunks as long as they fit in the given width. /// Concatenates chunks as long as they fit in the given width.
pub fn prefix<I>( pub fn prefix<I>(
tokens: &mut Peekable<I>, width: usize, offset: &mut ChunkPart tokens: &mut Peekable<I>, width: usize, offset: &mut ChunkPart,
) -> Vec<Chunk> ) -> Vec<Chunk>
where where
I: Iterator<Item = Chunk>, I: Iterator<Item = Chunk>,

View File

@ -8,9 +8,7 @@ fn input() -> StyledString {
text.append(StyledString::styled("didn't", Effect::Bold)); text.append(StyledString::styled("didn't", Effect::Bold));
text.append(StyledString::plain(" say ")); text.append(StyledString::plain(" say "));
text.append(StyledString::styled("half", Effect::Italic)); text.append(StyledString::styled("half", Effect::Italic));
text.append(StyledString::plain( text.append(StyledString::plain(" the things people say I did."));
" the things people say I did.",
));
text.append(StyledString::plain("\n")); text.append(StyledString::plain("\n"));
text.append(StyledString::plain("\n")); text.append(StyledString::plain("\n"));
text.append(StyledString::plain(" - A. Einstein")); text.append(StyledString::plain(" - A. Einstein"));
@ -49,25 +47,19 @@ fn test_line_breaks() {
attr: &Style::from(Effect::Italic), attr: &Style::from(Effect::Italic),
}, },
], ],
vec![ vec![Span {
Span { content: "the things people",
content: "the things people", attr: &Style::none(),
attr: &Style::none(), }],
}, vec![Span {
], content: "say I did.",
vec![ attr: &Style::none(),
Span { }],
content: "say I did.",
attr: &Style::none(),
},
],
vec![], vec![],
vec![ vec![Span {
Span { content: " - A. Einstein",
content: " - A. Einstein", attr: &Style::none(),
attr: &Style::none(), }],
},
],
] ]
); );
} }

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("> ")),
@ -139,10 +141,8 @@ Attention
==== ====
I *really* love __Cursive__!"; I *really* love __Cursive__!";
let spans = parse_spans(input); let spans = parse_spans(input);
let spans: Vec<_> = spans let spans: Vec<_> =
.iter() spans.iter().map(|span| span.resolve(input)).collect();
.map(|span| span.resolve(input))
.collect();
// println!("{:?}", spans); // println!("{:?}", spans);
assert_eq!( assert_eq!(

View File

@ -18,10 +18,7 @@ impl<R: Read> ProgressReader<R> {
/// You should make sure the progress bar knows how /// You should make sure the progress bar knows how
/// many bytes should be received. /// many bytes should be received.
pub fn new(counter: Counter, reader: R) -> Self { pub fn new(counter: Counter, reader: R) -> Self {
ProgressReader { ProgressReader { reader, counter }
reader: reader,
counter: counter,
}
} }
/// Unwraps this `ProgressReader`, returning the reader and counter. /// Unwraps this `ProgressReader`, returning the reader and counter.

View File

@ -195,15 +195,13 @@ impl<T> SpannedString<T> {
{ {
let source = source.into(); let source = source.into();
let spans = vec![ let spans = vec![IndexedSpan {
IndexedSpan { content: IndexedCow::Borrowed {
content: IndexedCow::Borrowed { start: 0,
start: 0, end: source.len(),
end: source.len(),
},
attr,
}, },
]; attr,
}];
Self::with_spans(source, spans) Self::with_spans(source, spans)
} }

View File

@ -1,9 +1,9 @@
//! Points on the 2D character grid. //! Points on the 2D character grid.
use XY;
use num::traits::Zero; use num::traits::Zero;
use std::cmp::{max, min, Ordering}; use std::cmp::{max, min, Ordering};
use std::ops::{Add, Div, Mul, Sub}; use std::ops::{Add, Div, Mul, Sub};
use XY;
/// Simple 2D size, in cells. /// Simple 2D size, in cells.
/// ///
@ -240,6 +240,27 @@ impl Mul<usize> for XY<usize> {
} }
} }
impl<T> Mul<XY<T>> for XY<T>
where
T: Mul<T>,
{
type Output = XY<T::Output>;
fn mul(self, other: XY<T>) -> Self::Output {
self.zip_map(other, |s, o| s * o)
}
}
impl<T> Div<XY<T>> for XY<T>
where
T: Div<T>,
{
type Output = XY<T::Output>;
fn div(self, other: XY<T>) -> Self::Output {
self.zip_map(other, |s, o| s / o)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Vec2; use super::Vec2;

View File

@ -8,7 +8,7 @@ use views::BoxView;
pub trait Boxable: View + Sized { pub trait Boxable: View + Sized {
/// Wraps `self` in a `BoxView` with the given size constraints. /// Wraps `self` in a `BoxView` with the given size constraints.
fn boxed( fn boxed(
self, width: SizeConstraint, height: SizeConstraint self, width: SizeConstraint, height: SizeConstraint,
) -> BoxView<Self> { ) -> BoxView<Self> {
BoxView::new(width, height, self) BoxView::new(width, height, self)
} }

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

@ -61,12 +61,7 @@ impl From<(usize, usize, usize, usize)> for Margins {
impl From<(i32, i32, i32, i32)> for Margins { impl From<(i32, i32, i32, i32)> for Margins {
fn from((left, right, top, bottom): (i32, i32, i32, i32)) -> Margins { fn from((left, right, top, bottom): (i32, i32, i32, i32)) -> Margins {
( (left as usize, right as usize, top as usize, bottom as usize).into()
left as usize,
right as usize,
top as usize,
bottom as usize,
).into()
} }
} }

View File

@ -41,17 +41,17 @@ mod view_wrapper;
// Essentials components // Essentials components
mod any; mod any;
mod finder; mod finder;
mod position;
mod margins; mod margins;
mod position;
mod size_cache; mod size_cache;
mod size_constraint; mod size_constraint;
mod view_path;
mod view; mod view;
mod view_path;
// Helper bases // Helper bases
mod scroll;
mod identifiable;
mod boxable; mod boxable;
mod identifiable;
mod scroll;
mod into_boxed_view; mod into_boxed_view;

View File

@ -1,6 +1,6 @@
use XY;
use std::cmp::min; use std::cmp::min;
use vec::Vec2; use vec::Vec2;
use XY;
/// Location of the view on screen /// Location of the view on screen
pub type Position = XY<Offset>; pub type Position = XY<Offset>;
@ -30,7 +30,7 @@ impl Position {
/// child with its top-left corner at the returned coordinates will /// child with its top-left corner at the returned coordinates will
/// position him appropriately. /// position him appropriately.
pub fn compute_offset<S, A, P>( pub fn compute_offset<S, A, P>(
&self, size: S, available: A, parent: P &self, size: S, available: A, parent: P,
) -> Vec2 ) -> Vec2
where where
S: Into<Vec2>, S: Into<Vec2>,
@ -65,7 +65,7 @@ pub enum Offset {
impl Offset { impl Offset {
/// Computes a single-dimension offset requred to draw a view. /// Computes a single-dimension offset requred to draw a view.
pub fn compute_offset( pub fn compute_offset(
&self, size: usize, available: usize, parent: usize &self, size: usize, available: usize, parent: usize,
) -> usize { ) -> usize {
if size > available { if size > available {
0 0

View File

@ -1,8 +1,8 @@
use Printer;
use div::div_up; use div::div_up;
use std::cmp::{max, min}; use std::cmp::{max, min};
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use Printer;
/// Provide scrolling functionalities to a view. /// Provide scrolling functionalities to a view.
/// ///
@ -262,7 +262,7 @@ impl ScrollBase {
// Y is the actual coordinate of the line. // Y is the actual coordinate of the line.
// The item ID is then Y + self.start_line // The item ID is then Y + self.start_line
line_drawer( line_drawer(
&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true), &printer.offset((0, y)).cropped((w, 1)),
y + self.start_line, y + self.start_line,
); );
} }

View File

@ -18,10 +18,7 @@ pub struct SizeCache {
impl SizeCache { impl SizeCache {
/// Creates a new sized cache /// Creates a new sized cache
pub fn new(value: usize, constrained: bool) -> Self { pub fn new(value: usize, constrained: bool) -> Self {
SizeCache { SizeCache { value, constrained }
value,
constrained,
}
} }
/// Returns `true` if `self` is still valid for the given `request`. /// Returns `true` if `self` is still valid for the given `request`.

View File

@ -1,15 +1,15 @@
use Printer;
use direction::Direction; use direction::Direction;
use event::{Event, EventResult}; use event::{AnyCb, Event, EventResult};
use rect::Rect;
use std::any::Any; use std::any::Any;
use vec::Vec2; use vec::Vec2;
use view::{AnyView, Selector}; use view::{AnyView, Selector};
use Printer;
/// Main trait defining a view behaviour. /// Main trait defining a view behaviour.
/// ///
/// This is what you should implement to define a custom View. /// This is what you should implement to define a custom View.
pub trait View: Any + AnyView { pub trait View: Any + AnyView {
/// Draws the view with the given printer (includes bounds) and focus. /// Draws the view with the given printer (includes bounds) and focus.
/// ///
/// This is the only *required* method to implement. /// This is the only *required* method to implement.
@ -63,7 +63,6 @@ pub trait View: Any + AnyView {
EventResult::Ignored EventResult::Ignored
} }
/// Runs a closure on the view identified by the given selector. /// Runs a closure on the view identified by the given selector.
/// ///
/// See [`Finder::call_on`] for a nicer interface, implemented for all /// See [`Finder::call_on`] for a nicer interface, implemented for all
@ -74,7 +73,7 @@ pub trait View: Any + AnyView {
/// If the selector doesn't find a match, the closure will not be run. /// If the selector doesn't find a match, the closure will not be run.
/// ///
/// Default implementation is a no-op. /// Default implementation is a no-op.
fn call_on_any<'a>(&mut self, _: &Selector, _: Box<FnMut(&mut Any) + 'a>) { fn call_on_any<'a>(&mut self, _: &Selector, _: AnyCb<'a>) {
// TODO: FnMut -> FnOnce once it works // TODO: FnMut -> FnOnce once it works
} }
@ -97,4 +96,16 @@ pub trait View: Any + AnyView {
let _ = source; let _ = source;
false false
} }
/// What part of the view is important and should be visible?
///
/// When only part of this view can be visible, this helps
/// determine which part.
///
/// It is given the view size (same size given to `layout`).
///
/// Default implementation return the entire view.
fn important_area(&self, view_size: Vec2) -> Rect {
Rect::from_corners((0, 0), view_size)
}
} }

View File

@ -1,9 +1,10 @@
use Printer;
use direction::Direction; use direction::Direction;
use event::{Event, EventResult}; use event::{AnyCb, Event, EventResult};
use rect::Rect;
use std::any::Any; use std::any::Any;
use vec::Vec2; use vec::Vec2;
use view::{Selector, View}; use view::{Selector, View};
use Printer;
/// Generic wrapper around a view. /// Generic wrapper around a view.
/// ///
@ -78,7 +79,7 @@ pub trait ViewWrapper: 'static {
/// Wraps the `find` method. /// Wraps the `find` method.
fn wrap_call_on_any<'a>( fn wrap_call_on_any<'a>(
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a> &mut self, selector: &Selector, callback: AnyCb<'a>,
) { ) {
self.with_view_mut(|v| v.call_on_any(selector, callback)); self.with_view_mut(|v| v.call_on_any(selector, callback));
} }
@ -93,6 +94,12 @@ pub trait ViewWrapper: 'static {
fn wrap_needs_relayout(&self) -> bool { fn wrap_needs_relayout(&self) -> bool {
self.with_view(|v| v.needs_relayout()).unwrap_or(true) self.with_view(|v| v.needs_relayout()).unwrap_or(true)
} }
/// Wraps the `important_area` method.
fn wrap_important_area(&self, size: Vec2) -> Rect {
self.with_view(|v| v.important_area(size))
.unwrap_or_else(|| Rect::from((0, 0)))
}
} }
// The main point of implementing ViewWrapper is to have View for free. // The main point of implementing ViewWrapper is to have View for free.
@ -118,7 +125,7 @@ impl<T: ViewWrapper> View for T {
} }
fn call_on_any<'a>( fn call_on_any<'a>(
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a> &mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>,
) { ) {
self.wrap_call_on_any(selector, callback) self.wrap_call_on_any(selector, callback)
} }
@ -130,6 +137,10 @@ impl<T: ViewWrapper> View for T {
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
self.wrap_focus_view(selector) self.wrap_focus_view(selector)
} }
fn important_area(&self, size: Vec2) -> Rect {
self.wrap_important_area(size)
}
} }
/// Convenient macro to implement the [`ViewWrapper`] trait. /// Convenient macro to implement the [`ViewWrapper`] trait.

View File

@ -1,6 +1,6 @@
use XY;
use vec::Vec2; use vec::Vec2;
use view::{SizeConstraint, View, ViewWrapper}; use view::{SizeConstraint, View, ViewWrapper};
use XY;
/// Wrapper around another view, with a controlled size. /// Wrapper around another view, with a controlled size.
/// ///
@ -39,7 +39,7 @@ impl<T: View> BoxView<T> {
/// ///
/// `None` values will use the wrapped view's preferences. /// `None` values will use the wrapped view's preferences.
pub fn new( pub fn new(
width: SizeConstraint, height: SizeConstraint, view: T width: SizeConstraint, height: SizeConstraint, view: T,
) -> Self { ) -> Self {
BoxView { BoxView {
size: (width, height).into(), size: (width, height).into(),
@ -50,7 +50,7 @@ impl<T: View> BoxView<T> {
/// Sets the size constraints for this view. /// Sets the size constraints for this view.
pub fn set_constraints( pub fn set_constraints(
&mut self, width: SizeConstraint, height: SizeConstraint &mut self, width: SizeConstraint, height: SizeConstraint,
) { ) {
self.set_width(width); self.set_width(width);
self.set_height(height); self.set_height(height);
@ -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

@ -1,11 +1,12 @@
use {Cursive, Printer, With};
use align::HAlign; use align::HAlign;
use direction::Direction; use direction::Direction;
use event::*; use event::*;
use rect::Rect;
use theme::ColorStyle; use theme::ColorStyle;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use {Cursive, Printer, With};
/// Simple text label with a callback when <Enter> is pressed. /// Simple text label with a callback when <Enter> is pressed.
/// ///
@ -21,6 +22,7 @@ pub struct Button {
label: String, label: String,
callback: Callback, callback: Callback,
enabled: bool, enabled: bool,
last_size: Vec2,
} }
impl Button { impl Button {
@ -43,6 +45,7 @@ impl Button {
label: label.into(), label: label.into(),
callback: Callback::from_fn(cb), callback: Callback::from_fn(cb),
enabled: true, enabled: true,
last_size: Vec2::zero(),
} }
} }
@ -140,13 +143,17 @@ impl View for Button {
}; };
let offset = let offset =
HAlign::Center.get_offset(self.label.len(), printer.size.x); HAlign::Center.get_offset(self.label.width(), printer.size.x);
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
printer.print((offset, 0), &self.label); printer.print((offset, 0), &self.label);
}); });
} }
fn layout(&mut self, size: Vec2) {
self.last_size = size;
}
fn required_size(&mut self, _: Vec2) -> Vec2 { fn required_size(&mut self, _: Vec2) -> Vec2 {
// Meh. Fixed size we are. // Meh. Fixed size we are.
self.req_size() self.req_size()
@ -155,6 +162,8 @@ impl View for Button {
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
// eprintln!("{:?}", event); // eprintln!("{:?}", event);
// eprintln!("{:?}", self.req_size()); // eprintln!("{:?}", self.req_size());
let width = self.label.width();
let self_offset = HAlign::Center.get_offset(width, self.last_size.x);
match event { match event {
// 10 is the ascii code for '\n', that is the return key // 10 is the ascii code for '\n', that is the return key
Event::Key(Key::Enter) => { Event::Key(Key::Enter) => {
@ -164,7 +173,8 @@ impl View for Button {
event: MouseEvent::Release(MouseButton::Left), event: MouseEvent::Release(MouseButton::Left),
position, position,
offset, offset,
} if position.fits_in_rect(offset, self.req_size()) => } if position
.fits_in_rect(offset + (self_offset, 0), self.req_size()) =>
{ {
EventResult::Consumed(Some(self.callback.clone())) EventResult::Consumed(Some(self.callback.clone()))
} }
@ -175,4 +185,11 @@ impl View for Button {
fn take_focus(&mut self, _: Direction) -> bool { fn take_focus(&mut self, _: Direction) -> bool {
self.enabled self.enabled
} }
fn important_area(&self, view_size: Vec2) -> Rect {
let width = self.label.width();
let offset = HAlign::Center.get_offset(width, view_size.x);
Rect::from_size((offset, 0), (width, 1))
}
} }

View File

@ -1,9 +1,10 @@
use direction::Direction;
use event::{AnyCb, Event, EventResult};
use rect::Rect;
use vec::Vec2;
use view::{Selector, View};
use Printer; use Printer;
use With; use With;
use direction::Direction;
use event::{Event, EventResult};
use vec::Vec2;
use view::View;
/// A blank view that forwards calls to closures. /// A blank view that forwards calls to closures.
/// ///
@ -17,6 +18,9 @@ pub struct Canvas<T> {
layout: Box<FnMut(&mut T, Vec2)>, layout: Box<FnMut(&mut T, Vec2)>,
take_focus: Box<FnMut(&mut T, Direction) -> bool>, take_focus: Box<FnMut(&mut T, Direction) -> bool>,
needs_relayout: Box<Fn(&T) -> bool>, needs_relayout: Box<Fn(&T) -> bool>,
focus_view: Box<FnMut(&mut T, &Selector) -> Result<(), ()>>,
call_on_any: Box<for<'a> FnMut(&mut T, &Selector, AnyCb<'a>)>,
important_area: Box<Fn(&T, Vec2) -> Rect>,
} }
impl<T: 'static + View> Canvas<T> { impl<T: 'static + View> Canvas<T> {
@ -31,6 +35,9 @@ impl<T: 'static + View> Canvas<T> {
.with_layout(T::layout) .with_layout(T::layout)
.with_take_focus(T::take_focus) .with_take_focus(T::take_focus)
.with_needs_relayout(T::needs_relayout) .with_needs_relayout(T::needs_relayout)
.with_focus_view(T::focus_view)
.with_call_on_any(T::call_on_any)
.with_important_area(T::important_area)
} }
} }
@ -55,6 +62,11 @@ impl<T> Canvas<T> {
layout: Box::new(|_, _| ()), layout: Box::new(|_, _| ()),
take_focus: Box::new(|_, _| false), take_focus: Box::new(|_, _| false),
needs_relayout: Box::new(|_| true), needs_relayout: Box::new(|_| true),
focus_view: Box::new(|_, _| Err(())),
call_on_any: Box::new(|_, _, _| ()),
important_area: Box::new(|_, size| {
Rect::from_corners((0, 0), size)
}),
} }
} }
@ -170,6 +182,60 @@ impl<T> Canvas<T> {
{ {
self.with(|s| s.set_needs_relayout(f)) self.with(|s| s.set_needs_relayout(f))
} }
/// Sets the closure for `call_on_any()`.
pub fn set_call_on_any<F>(&mut self, f: F)
where
F: 'static + for<'a> FnMut(&mut T, &Selector, AnyCb<'a>),
{
self.call_on_any = Box::new(f);
}
/// Sets the closure for `call_on_any()`.
///
/// Chainable variant.
pub fn with_call_on_any<F>(self, f: F) -> Self
where
F: 'static + for<'a> FnMut(&mut T, &Selector, AnyCb<'a>),
{
self.with(|s| s.set_call_on_any(f))
}
/// Sets the closure for `important_area()`.
pub fn set_important_area<F>(&mut self, f: F)
where
F: 'static + Fn(&T, Vec2) -> Rect,
{
self.important_area = Box::new(f);
}
/// Sets the closure for `important_area()`.
///
/// Chainable variant.
pub fn with_important_area<F>(self, f: F) -> Self
where
F: 'static + Fn(&T, Vec2) -> Rect,
{
self.with(|s| s.set_important_area(f))
}
/// Sets the closure for `focus_view()`.
pub fn set_focus_view<F>(&mut self, f: F)
where
F: 'static + FnMut(&mut T, &Selector) -> Result<(), ()>,
{
self.focus_view = Box::new(f);
}
/// Sets the closure for `focus_view()`.
///
/// Chainable variant.
pub fn with_focus_view<F>(self, f: F) -> Self
where
F: 'static + FnMut(&mut T, &Selector) -> Result<(), ()>,
{
self.with(|s| s.set_focus_view(f))
}
} }
impl<T: 'static> View for Canvas<T> { impl<T: 'static> View for Canvas<T> {
@ -192,4 +258,20 @@ impl<T: 'static> View for Canvas<T> {
fn take_focus(&mut self, source: Direction) -> bool { fn take_focus(&mut self, source: Direction) -> bool {
(self.take_focus)(&mut self.state, source) (self.take_focus)(&mut self.state, source)
} }
fn needs_relayout(&self) -> bool {
(self.needs_relayout)(&self.state)
}
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
(self.focus_view)(&mut self.state, selector)
}
fn important_area(&self, view_size: Vec2) -> Rect {
(self.important_area)(&self.state, view_size)
}
fn call_on_any<'a>(&mut self, selector: &Selector, cb: AnyCb<'a>) {
(self.call_on_any)(&mut self.state, selector, cb);
}
} }

View File

@ -1,12 +1,12 @@
use Cursive;
use Printer;
use With;
use direction::Direction; use direction::Direction;
use event::{Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Event, EventResult, Key, MouseButton, MouseEvent};
use std::rc::Rc; use std::rc::Rc;
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use Cursive;
use Printer;
use With;
/// Checkable box. /// Checkable box.
pub struct Checkbox { pub struct Checkbox {
@ -32,7 +32,7 @@ impl Checkbox {
/// Sets a callback to be used when the state changes. /// Sets a callback to be used when the state changes.
pub fn set_on_change<F: 'static + Fn(&mut Cursive, bool)>( pub fn set_on_change<F: 'static + Fn(&mut Cursive, bool)>(
&mut self, on_change: F &mut self, on_change: F,
) { ) {
self.on_change = Some(Rc::new(on_change)); self.on_change = Some(Rc::new(on_change));
} }
@ -41,7 +41,7 @@ impl Checkbox {
/// ///
/// Chainable variant. /// Chainable variant.
pub fn on_change<F: 'static + Fn(&mut Cursive, bool)>( pub fn on_change<F: 'static + Fn(&mut Cursive, bool)>(
self, on_change: F self, on_change: F,
) -> Self { ) -> Self {
self.with(|s| s.set_on_change(on_change)) self.with(|s| s.set_on_change(on_change))
} }

View File

@ -1,10 +1,7 @@
use Cursive;
use Printer;
use With;
use align::*; use align::*;
use direction::Direction; use direction::Direction;
use event::*; use event::{AnyCb, Event, EventResult, Key};
use std::any::Any; use rect::Rect;
use std::cell::Cell; use std::cell::Cell;
use std::cmp::max; use std::cmp::max;
use theme::ColorStyle; use theme::ColorStyle;
@ -12,6 +9,9 @@ use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{Margins, Selector, View}; use view::{Margins, Selector, View};
use views::{Button, DummyView, SizedView, TextView, ViewBox}; use views::{Button, DummyView, SizedView, TextView, ViewBox};
use Cursive;
use Printer;
use With;
/// Identifies currently focused element in [`Dialog`]. /// Identifies currently focused element in [`Dialog`].
/// ///
@ -246,7 +246,7 @@ impl Dialog {
/// Returns an iterator on this buttons for this dialog. /// Returns an iterator on this buttons for this dialog.
pub fn buttons_mut<'a>( pub fn buttons_mut<'a>(
&'a mut self &'a mut self,
) -> Box<'a + Iterator<Item = &'a mut Button>> { ) -> Box<'a + Iterator<Item = &'a mut Button>> {
Box::new(self.buttons.iter_mut().map(|b| &mut b.button.view)) Box::new(self.buttons.iter_mut().map(|b| &mut b.button.view))
} }
@ -281,7 +281,7 @@ impl Dialog {
// An event is received while a button is in focus // An event is received while a button is in focus
fn on_event_button( fn on_event_button(
&mut self, event: Event, button_id: usize &mut self, event: Event, button_id: usize,
) -> EventResult { ) -> EventResult {
let result = { let result = {
let button = &mut self.buttons[button_id]; let button = &mut self.buttons[button_id];
@ -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());
@ -366,11 +368,10 @@ impl Dialog {
// Add some special effect to the focused button // Add some special effect to the focused button
let position = Vec2::new(offset, y); let position = Vec2::new(offset, y);
button.offset.set(position); button.offset.set(position);
button.button.draw(&printer.sub_printer( button.button.draw(&printer
position, .offset(position)
size, .cropped(size)
self.focus == DialogFocus::Button(i), .focused(self.focus == DialogFocus::Button(i)));
));
// Keep 1 blank between two buttons // Keep 1 blank between two buttons
offset += size.x + 1; offset += size.x + 1;
// Also keep 1 blank above the buttons // Also keep 1 blank above the buttons
@ -382,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) {
@ -390,11 +392,10 @@ impl Dialog {
None => return, None => return,
}; };
self.content.draw(&printer.sub_printer( self.content.draw(&printer
self.borders.top_left() + self.padding.top_left(), .offset(self.borders.top_left() + self.padding.top_left())
inner_size, .cropped(inner_size)
self.focus == DialogFocus::Content, .focused(self.focus == DialogFocus::Content));
));
} }
fn draw_title(&self, printer: &Printer) { fn draw_title(&self, printer: &Printer) {
@ -405,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), "");
@ -563,13 +565,16 @@ impl View for Dialog {
} }
} }
fn call_on_any<'a>( fn call_on_any<'a>(&mut self, selector: &Selector, callback: AnyCb<'a>) {
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>
) {
self.content.call_on_any(selector, callback); self.content.call_on_any(selector, callback);
} }
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> { fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
self.content.focus_view(selector) self.content.focus_view(selector)
} }
fn important_area(&self, _: Vec2) -> Rect {
self.content.important_area(self.content.size)
+ self.borders.top_left() + self.padding.top_left()
}
} }

View File

@ -1,5 +1,5 @@
use Printer;
use view::View; use view::View;
use Printer;
/// Dummy view. /// Dummy view.
/// ///

View File

@ -1,5 +1,6 @@
use direction::Direction; use direction::Direction;
use event::{Callback, Event, EventResult, Key, MouseEvent}; use event::{Callback, Event, EventResult, Key, MouseEvent};
use rect::Rect;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
@ -367,8 +368,7 @@ impl EditView {
self.offset = 0; self.offset = 0;
self.set_cursor(len); self.set_cursor(len);
self.make_edit_cb() self.make_edit_cb().unwrap_or_else(Callback::dummy)
.unwrap_or_else(Callback::dummy)
} }
/// Get the current text. /// Get the current text.
@ -420,8 +420,7 @@ impl EditView {
self.keep_cursor_in_view(); self.keep_cursor_in_view();
self.make_edit_cb() self.make_edit_cb().unwrap_or_else(Callback::dummy)
.unwrap_or_else(Callback::dummy)
} }
/// Remove the character at the current cursor position. /// Remove the character at the current cursor position.
@ -436,8 +435,7 @@ impl EditView {
self.keep_cursor_in_view(); self.keep_cursor_in_view();
self.make_edit_cb() self.make_edit_cb().unwrap_or_else(Callback::dummy)
.unwrap_or_else(Callback::dummy)
} }
fn make_edit_cb(&self) -> Option<Callback> { fn make_edit_cb(&self) -> Option<Callback> {
@ -672,13 +670,13 @@ impl View for EditView {
offset, offset,
} if position.fits_in_rect(offset, (self.last_length, 1)) => } if position.fits_in_rect(offset, (self.last_length, 1)) =>
{ {
position.checked_sub(offset).map(|position| { if let Some(position) = position.checked_sub(offset) {
self.cursor = self.offset self.cursor = self.offset
+ simple_prefix( + simple_prefix(
&self.content[self.offset..], &self.content[self.offset..],
position.x, position.x,
).length; ).length;
}); }
} }
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }
@ -687,4 +685,22 @@ impl View for EditView {
EventResult::Consumed(self.make_edit_cb()) EventResult::Consumed(self.make_edit_cb())
} }
fn important_area(&self, _: Vec2) -> Rect {
let char_width = if self.cursor >= self.content.len() {
// Show a space if we're at the end of the content
1
} else {
// Otherwise look at the selected character.
self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap()
.width()
};
let x = self.content[..self.cursor].width();
Rect::from_size((x, 0), (char_width, 1))
}
} }

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

@ -77,15 +77,14 @@ impl<T: View + 'static> ViewWrapper for IdView<T> {
// Some for<'b> weirdness here to please the borrow checker gods... // Some for<'b> weirdness here to please the borrow checker gods...
fn wrap_call_on_any<'a>( fn wrap_call_on_any<'a>(
&mut self, selector: &Selector, mut callback: BoxedCallback<'a> &mut self, selector: &Selector, mut callback: BoxedCallback<'a>,
) { ) {
match selector { match selector {
&Selector::Id(id) if id == self.id => callback(self), &Selector::Id(id) if id == self.id => callback(self),
s => { s => {
self.view if let Ok(mut v) = self.view.try_borrow_mut() {
.try_borrow_mut() v.deref_mut().call_on_any(s, callback);
.ok() }
.map(|mut v| v.deref_mut().call_on_any(s, callback));
} }
} }
} }
@ -93,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

@ -1,5 +1,5 @@
use Printer;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use Printer;
/// Wrapper view that fills the background. /// Wrapper view that fills the background.
/// ///
@ -14,7 +14,7 @@ pub struct Layer<T: View> {
impl<T: View> Layer<T> { impl<T: View> Layer<T> {
/// Wraps the given view. /// Wraps the given view.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
Layer { view: view } Layer { view }
} }
inner_getters!(self.view: T); inner_getters!(self.view: T);

View File

@ -1,13 +1,13 @@
use Printer;
use With;
use XY;
use direction; use direction;
use event::{Event, EventResult, Key}; use event::{AnyCb, Event, EventResult, Key};
use std::any::Any; use rect::Rect;
use std::cmp::min; use std::cmp::min;
use std::ops::Deref; use std::ops::Deref;
use vec::Vec2; use vec::Vec2;
use view::{Selector, SizeCache, View}; use view::{Selector, SizeCache, View};
use Printer;
use With;
use XY;
/// Arranges its children linearly according to its orientation. /// Arranges its children linearly according to its orientation.
pub struct LinearLayout { pub struct LinearLayout {
@ -57,7 +57,7 @@ struct ChildItem<T> {
impl<T> ChildIterator<T> { impl<T> ChildIterator<T> {
fn new( fn new(
inner: T, orientation: direction::Orientation, available: usize inner: T, orientation: direction::Orientation, available: usize,
) -> Self { ) -> Self {
ChildIterator { ChildIterator {
inner, inner,
@ -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)
@ -203,7 +204,7 @@ impl LinearLayout {
/// Returns a cyclic mutable iterator starting with the child in focus /// Returns a cyclic mutable iterator starting with the child in focus
fn iter_mut<'a>( fn iter_mut<'a>(
&'a mut self, from_focus: bool, source: direction::Relative &'a mut self, from_focus: bool, source: direction::Relative,
) -> Box<Iterator<Item = (usize, &mut Child)> + 'a> { ) -> Box<Iterator<Item = (usize, &mut Child)> + 'a> {
match source { match source {
direction::Relative::Front => { direction::Relative::Front => {
@ -279,11 +280,11 @@ impl LinearLayout {
// this will give us the allowed window for a click. // this will give us the allowed window for a click.
let child_size = item.child.size.get(self.orientation); let child_size = item.child.size.get(self.orientation);
if (item.offset + child_size > position) if item.offset + child_size > position {
&& item.child.view.take_focus(direction::Direction::none()) if item.child.view.take_focus(direction::Direction::none())
{ {
// eprintln!("It's a match!"); self.focus = i;
self.focus = i; }
return; return;
} }
} }
@ -292,7 +293,7 @@ impl LinearLayout {
} }
fn try_focus( fn try_focus(
(i, child): (usize, &mut Child), source: direction::Direction (i, child): (usize, &mut Child), source: direction::Direction,
) -> Option<usize> { ) -> Option<usize> {
if child.view.take_focus(source) { if child.view.take_focus(source) {
Some(i) Some(i)
@ -314,11 +315,10 @@ impl View for LinearLayout {
// eprintln!("Printer size: {:?}", printer.size); // eprintln!("Printer size: {:?}", printer.size);
// eprintln!("Child size: {:?}", item.child.size); // eprintln!("Child size: {:?}", item.child.size);
// eprintln!("Offset: {:?}", item.offset); // eprintln!("Offset: {:?}", item.offset);
let printer = &printer.sub_printer( let printer = &printer
self.orientation.make_vec(item.offset, 0), .offset(self.orientation.make_vec(item.offset, 0))
item.child.size, .cropped(item.child.size)
i == self.focus, .focused(i == self.focus);
);
item.child.view.draw(printer); item.child.view.draw(printer);
} }
} }
@ -358,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();
@ -382,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();
@ -462,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]))
@ -482,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,
@ -554,8 +556,7 @@ impl View for LinearLayout {
} }
fn call_on_any<'a>( fn call_on_any<'a>(
&mut self, selector: &Selector, &mut self, selector: &Selector, mut callback: AnyCb<'a>,
mut callback: Box<FnMut(&mut Any) + 'a>,
) { ) {
for child in &mut self.children { for child in &mut self.children {
child child
@ -574,4 +575,30 @@ impl View for LinearLayout {
Err(()) Err(())
} }
fn important_area(&self, _: Vec2) -> Rect {
if self.children.is_empty() {
// Return dummy area if we are empty.
return Rect::from((0, 0));
}
// Pick the focused item, with its offset
let item = {
let mut iterator = ChildIterator::new(
self.children.iter(),
self.orientation,
usize::max_value(),
);
iterator.nth(self.focus).unwrap()
};
// Make a vector offset from the scalar value
let offset = self.orientation.make_vec(item.offset, 0);
// And ask the child its own area.
let rect = item.child.view.important_area(item.child.size);
// Add `offset` to the rect.
rect + offset
}
} }

View File

@ -1,13 +1,15 @@
use Cursive;
use Printer;
use With;
use direction; use direction;
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}; use event::{
use std::any::Any; AnyCb, Callback, Event, EventResult, Key, MouseButton, MouseEvent,
};
use rect::Rect;
use std::rc::Rc; use std::rc::Rc;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{ScrollBase, Selector, View}; use view::{ScrollBase, Selector, View};
use Cursive;
use Printer;
use With;
/// Represents a child from a [`ListView`]. /// Represents a child from a [`ListView`].
/// ///
@ -148,7 +150,7 @@ impl ListView {
} }
fn iter_mut<'a>( fn iter_mut<'a>(
&'a mut self, from_focus: bool, source: direction::Relative &'a mut self, from_focus: bool, source: direction::Relative,
) -> Box<Iterator<Item = (usize, &mut ListChild)> + 'a> { ) -> Box<Iterator<Item = (usize, &mut ListChild)> + 'a> {
match source { match source {
direction::Relative::Front => { direction::Relative::Front => {
@ -172,7 +174,7 @@ impl ListView {
} }
fn move_focus( fn move_focus(
&mut self, n: usize, source: direction::Direction &mut self, n: usize, source: direction::Direction,
) -> EventResult { ) -> EventResult {
let i = if let Some(i) = source let i = if let Some(i) = source
.relative(direction::Orientation::Vertical) .relative(direction::Orientation::Vertical)
@ -246,7 +248,7 @@ impl ListView {
} }
fn try_focus( fn try_focus(
(i, child): (usize, &mut ListChild), source: direction::Direction (i, child): (usize, &mut ListChild), source: direction::Direction,
) -> Option<usize> { ) -> Option<usize> {
match *child { match *child {
ListChild::Delimiter => None, ListChild::Delimiter => None,
@ -271,7 +273,9 @@ impl View for ListView {
.draw(printer, |printer, i| match self.children[i] { .draw(printer, |printer, i| match self.children[i] {
ListChild::Row(ref label, ref view) => { ListChild::Row(ref label, ref view) => {
printer.print((0, 0), label); printer.print((0, 0), label);
view.draw(&printer.offset((offset, 0), i == self.focus)); view.draw(&printer
.offset((offset, 0))
.focused(i == self.focus));
} }
ListChild::Delimiter => (), ListChild::Delimiter => (),
}); });
@ -279,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)
@ -306,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)
@ -316,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);
@ -399,12 +407,10 @@ impl View for ListView {
Event::Key(Key::PageDown) => { Event::Key(Key::PageDown) => {
self.move_focus(10, direction::Direction::up()) self.move_focus(10, direction::Direction::up())
} }
Event::Key(Key::Home) | Event::Ctrl(Key::Home) => { Event::Key(Key::Home) | Event::Ctrl(Key::Home) => self.move_focus(
self.move_focus( usize::max_value(),
usize::max_value(), direction::Direction::back(),
direction::Direction::back(), ),
)
}
Event::Key(Key::End) | Event::Ctrl(Key::End) => self.move_focus( Event::Key(Key::End) | Event::Ctrl(Key::End) => self.move_focus(
usize::max_value(), usize::max_value(),
direction::Direction::front(), direction::Direction::front(),
@ -437,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
@ -454,8 +459,7 @@ impl View for ListView {
} }
fn call_on_any<'a>( fn call_on_any<'a>(
&mut self, selector: &Selector, &mut self, selector: &Selector, mut callback: AnyCb<'a>,
mut callback: Box<FnMut(&mut Any) + 'a>,
) { ) {
for view in self.children.iter_mut().filter_map(ListChild::view) { for view in self.children.iter_mut().filter_map(ListChild::view) {
view.call_on_any(selector, Box::new(|any| callback(any))); view.call_on_any(selector, Box::new(|any| callback(any)));
@ -463,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)))
@ -476,4 +481,22 @@ impl View for ListView {
Err(()) Err(())
} }
} }
fn important_area(&self, size: Vec2) -> Rect {
if self.children.is_empty() {
return Rect::from((0, 0));
}
let labels_width = self.labels_width();
let area = match self.children[self.focus] {
ListChild::Row(_, ref view) => {
let available = Vec2::new(size.x - labels_width - 1, 1);
view.important_area(available) + (labels_width, 0)
}
ListChild::Delimiter => Rect::from_size((0, 0), (size.x, 1)),
};
area + (0, self.focus)
}
} }

View File

@ -1,15 +1,16 @@
use Cursive;
use Printer;
use With;
use align::Align; use align::Align;
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
use menu::{MenuItem, MenuTree}; use menu::{MenuItem, MenuTree};
use rect::Rect;
use std::cmp::min; use std::cmp::min;
use std::rc::Rc; use std::rc::Rc;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{Position, ScrollBase, View}; use view::{Position, ScrollBase, View};
use views::OnEventView; use views::OnEventView;
use Cursive;
use Printer;
use With;
/// Popup that shows a list of items. /// Popup that shows a list of items.
pub struct MenuPopup { pub struct MenuPopup {
@ -137,13 +138,13 @@ 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 let max_width = 4 + self
+ self.menu .menu
.children .children
.iter() .iter()
.map(Self::item_width) .map(Self::item_width)
.max() .max()
.unwrap_or(1); .unwrap_or(1);
let offset = Vec2::new(max_width, self.focus); let offset = Vec2::new(max_width, self.focus);
let action_cb = self.on_action.clone(); let action_cb = self.on_action.clone();
@ -209,7 +210,7 @@ impl View for MenuPopup {
let h = self.menu.len(); let h = self.menu.len();
// If we're too high, add a vertical offset // If we're too high, add a vertical offset
let offset = self.align.v.get_offset(h, printer.size.y); let offset = self.align.v.get_offset(h, printer.size.y);
let printer = &printer.offset((0, offset), true); let printer = &printer.offset((0, offset));
// Start with a box // Start with a box
printer.print_box(Vec2::new(0, 0), printer.size, false); printer.print_box(Vec2::new(0, 0), printer.size, false);
@ -217,8 +218,7 @@ impl View for MenuPopup {
// We're giving it a reduced size because of borders. // We're giving it a reduced size because of borders.
// But we're keeping the full width, // But we're keeping the full width,
// to integrate horizontal delimiters in the frame. // to integrate horizontal delimiters in the frame.
let size = printer.size - (0, 2); let printer = printer.offset((0, 1)).shrinked((0, 1));
let printer = printer.sub_printer((0, 1), size, true);
self.scrollbase.draw(&printer, |printer, i| { self.scrollbase.draw(&printer, |printer, i| {
printer.with_selection(i == self.focus, |printer| { printer.with_selection(i == self.focus, |printer| {
@ -250,13 +250,13 @@ 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 let w = 4 + self
+ self.menu .menu
.children .children
.iter() .iter()
.map(Self::item_width) .map(Self::item_width)
.max() .max()
.unwrap_or(1); .unwrap_or(1);
let h = 2 + self.menu.children.len(); let h = 2 + self.menu.children.len();
let scrolling = req.y < h; let scrolling = req.y < h;
@ -343,19 +343,16 @@ impl View for MenuPopup {
// eprintln!("Position: {:?} / {:?}", position, offset); // eprintln!("Position: {:?} / {:?}", position, offset);
// eprintln!("Last size: {:?}", self.last_size); // eprintln!("Last size: {:?}", self.last_size);
let inner_size = self.last_size.saturating_sub((2, 2)); let inner_size = self.last_size.saturating_sub((2, 2));
position.checked_sub(offset + (1, 1)).map( if let Some(position) = position.checked_sub(offset + (1, 1)) {
// `position` is not relative to the content // `position` is not relative to the content
// (It's inside the border) // (It's inside the border)
|position| { if position < inner_size {
if position < inner_size { let focus = position.y + self.scrollbase.start_line;
let focus = if !self.menu.children[focus].is_delimiter() {
position.y + self.scrollbase.start_line; self.focus = focus;
if !self.menu.children[focus].is_delimiter() {
self.focus = focus;
}
} }
}, }
); }
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::Release(MouseButton::Left), event: MouseEvent::Release(MouseButton::Left),
@ -400,4 +397,12 @@ impl View for MenuPopup {
self.scrollbase self.scrollbase
.set_heights(size.y.saturating_sub(2), self.menu.children.len()); .set_heights(size.y.saturating_sub(2), self.menu.children.len());
} }
fn important_area(&self, size: Vec2) -> Rect {
if self.menu.is_empty() {
return Rect::from((0, 0));
}
Rect::from_size((0, self.focus), (size.x, 1))
}
} }

View File

@ -1,14 +1,15 @@
use Cursive;
use Printer;
use direction; use direction;
use event::*; use event::*;
use menu::{MenuItem, MenuTree}; use menu::{MenuItem, MenuTree};
use rect::Rect;
use std::rc::Rc; use std::rc::Rc;
use theme::ColorStyle; use theme::ColorStyle;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{Position, View}; use view::{Position, View};
use views::{MenuPopup, OnEventView}; use views::{MenuPopup, OnEventView};
use Cursive;
use Printer;
/// Current state of the menubar /// Current state of the menubar
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -106,7 +107,7 @@ impl Menubar {
/// Insert a new item at the given position. /// Insert a new item at the given position.
pub fn insert_subtree<S>( pub fn insert_subtree<S>(
&mut self, i: usize, title: S, menu: MenuTree &mut self, i: usize, title: S, menu: MenuTree,
) -> &mut Self ) -> &mut Self
where where
S: Into<String>, S: Into<String>,
@ -341,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);
@ -371,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)
@ -379,4 +382,21 @@ impl View for Menubar {
Vec2::new(width, 1) Vec2::new(width, 1)
} }
fn important_area(&self, _: Vec2) -> Rect {
if self.root.is_empty() {
return Rect::from((0, 0));
}
// X position is 1 (margin before the first item) + sum of widths
// And each item has a 2 cells padding.
let x = 1 + self.root.children[..self.focus]
.iter()
.map(|child| child.label().width() + 2)
.sum::<usize>();
let width = self.root.children[self.focus].label().width();
Rect::from_size((x, 0), (width, 1))
}
} }

View File

@ -35,7 +35,6 @@ macro_rules! impl_enabled {
} }
} }
mod view_box;
mod box_view; mod box_view;
mod button; mod button;
mod canvas; mod canvas;
@ -43,24 +42,27 @@ 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 on_event_view;
mod layer; mod layer;
mod linear_layout; mod linear_layout;
mod list_view; mod list_view;
mod menubar;
mod menu_popup; mod menu_popup;
mod menubar;
mod on_event_view;
mod panel; mod panel;
mod progress_bar; mod progress_bar;
mod radio; mod radio;
mod scroll_view;
mod select_view; mod select_view;
mod slider_view;
mod shadow_view; mod shadow_view;
mod sized_view; mod sized_view;
mod slider_view;
mod stack_view; mod stack_view;
mod text_area; mod text_area;
mod text_view; mod text_view;
mod tracked_view; mod tracked_view;
mod view_box;
pub use self::box_view::BoxView; pub use self::box_view::BoxView;
pub use self::button::Button; pub use self::button::Button;
@ -69,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;
@ -79,6 +82,7 @@ pub use self::on_event_view::OnEventView;
pub use self::panel::Panel; pub use self::panel::Panel;
pub use self::progress_bar::ProgressBar; pub use self::progress_bar::ProgressBar;
pub use self::radio::{RadioButton, RadioGroup}; pub use self::radio::{RadioButton, RadioGroup};
pub use self::scroll_view::ScrollView;
pub use self::select_view::SelectView; pub use self::select_view::SelectView;
pub use self::shadow_view::ShadowView; pub use self::shadow_view::ShadowView;
pub use self::sized_view::SizedView; pub use self::sized_view::SizedView;

View File

@ -1,9 +1,9 @@
use Cursive;
use With;
use event::{Callback, Event, EventResult}; use event::{Callback, Event, EventResult};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use Cursive;
use With;
/// A wrapper view that can react to events. /// A wrapper view that can react to events.
/// ///

View File

@ -1,7 +1,8 @@
use Printer;
use event::{Event, EventResult}; use event::{Event, EventResult};
use rect::Rect;
use vec::Vec2; use vec::Vec2;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use Printer;
/// Draws a border around a wrapped view. /// Draws a border around a wrapped view.
#[derive(Debug)] #[derive(Debug)]
@ -34,14 +35,16 @@ impl<V: View> ViewWrapper for Panel<V> {
fn wrap_draw(&self, printer: &Printer) { fn wrap_draw(&self, printer: &Printer) {
printer.print_box((0, 0), printer.size, true); printer.print_box((0, 0), printer.size, true);
self.view.draw(&printer.sub_printer( let printer = printer.offset((1, 1)).shrinked((1, 1));
(1, 1), self.view.draw(&printer);
printer.size.saturating_sub((2, 2)),
true,
));
} }
fn wrap_layout(&mut self, size: Vec2) { fn wrap_layout(&mut self, size: Vec2) {
self.view.layout(size.saturating_sub((2, 2))); self.view.layout(size.saturating_sub((2, 2)));
} }
fn wrap_important_area(&self, size: Vec2) -> Rect {
let inner_size = size.saturating_sub((2, 2));
self.view.important_area(inner_size) + (1, 1)
}
} }

View File

@ -1,10 +1,10 @@
use Printer;
use align::HAlign; use align::HAlign;
use std::cmp; use std::cmp;
use std::thread; use std::thread;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
use utils::Counter; use utils::Counter;
use view::View; use view::View;
use Printer;
// pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>; // pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;
@ -96,7 +96,7 @@ impl ProgressBar {
/// ///
/// Chainable variant. /// Chainable variant.
pub fn with_task<F: FnOnce(Counter) + Send + 'static>( pub fn with_task<F: FnOnce(Counter) + Send + 'static>(
mut self, task: F mut self, task: F,
) -> Self { ) -> Self {
self.start(task); self.start(task);
self self
@ -116,7 +116,7 @@ impl ProgressBar {
/// } /// }
/// ``` /// ```
pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>( pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>(
mut self, label_maker: F mut self, label_maker: F,
) -> Self { ) -> Self {
self.label_maker = Box::new(label_maker); self.label_maker = Box::new(label_maker);
self self
@ -188,7 +188,7 @@ impl View for ProgressBar {
printer.with_effect(Effect::Reverse, |printer| { printer.with_effect(Effect::Reverse, |printer| {
printer.print((offset, 0), &label); printer.print((offset, 0), &label);
}); });
let printer = &printer.sub_printer((0, 0), (length, 1), true); let printer = &printer.cropped((length, 1));
printer.print_hline((0, 0), length, " "); printer.print_hline((0, 0), length, " ");
printer.print((offset, 0), &label); printer.print((offset, 0), &label);
}); });

View File

@ -1,4 +1,3 @@
use {Printer, With};
use direction::Direction; use direction::Direction;
use event::{Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Event, EventResult, Key, MouseButton, MouseEvent};
use std::cell::RefCell; use std::cell::RefCell;
@ -6,6 +5,7 @@ use std::rc::Rc;
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use {Printer, With};
struct SharedState<T> { struct SharedState<T> {
selection: usize, selection: usize,
@ -52,7 +52,7 @@ impl<T> RadioGroup<T> {
/// ///
/// The button will display `label` next to it, and will embed `value`. /// The button will display `label` next to it, and will embed `value`.
pub fn button<S: Into<String>>( pub fn button<S: Into<String>>(
&mut self, value: T, label: S &mut self, value: T, label: S,
) -> RadioButton<T> { ) -> RadioButton<T> {
let count = self.state.borrow().values.len(); let count = self.state.borrow().values.len();
self.state.borrow_mut().values.push(Rc::new(value)); self.state.borrow_mut().values.push(Rc::new(value));
@ -75,7 +75,7 @@ impl<T> RadioGroup<T> {
impl RadioGroup<String> { impl RadioGroup<String> {
/// Adds a button, using the label itself as value. /// Adds a button, using the label itself as value.
pub fn button_str<S: Into<String>>( pub fn button_str<S: Into<String>>(
&mut self, text: S &mut self, text: S,
) -> RadioButton<String> { ) -> RadioButton<String> {
let text = text.into(); let text = text.into();
self.button(text.clone(), text) self.button(text.clone(), text)
@ -104,7 +104,7 @@ impl<T> RadioButton<T> {
impl_enabled!(self.enabled); impl_enabled!(self.enabled);
fn new( fn new(
state: Rc<RefCell<SharedState<T>>>, id: usize, label: String state: Rc<RefCell<SharedState<T>>>, id: usize, label: String,
) -> Self { ) -> Self {
RadioButton { RadioButton {
state, state,

530
src/views/scroll_view.rs Normal file
View File

@ -0,0 +1,530 @@
use direction::{Direction, Orientation};
use event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent};
use rect::Rect;
use theme::ColorStyle;
use vec::Vec2;
use view::{Selector, View};
use xy::XY;
use Printer;
use With;
use std::cmp::min;
/// Wraps a view in a scrollable area.
pub struct ScrollView<V> {
// The wrapped view.
inner: V,
// This is the size the child thinks we're giving him.
inner_size: Vec2,
// Offset into the inner view.
//
// Our `(0,0)` will be inner's `offset`
offset: Vec2,
// What was our own size last time we checked.
//
// This includes scrollbars, if any.
last_size: Vec2,
// Are we scrollable in each direction?
enabled: XY<bool>,
// Should we show scrollbars?
//
// Even if this is true, no scrollbar will be printed if we don't need to
// scroll.
//
// TODO: have an option to always show the scrollbar.
// TODO: have an option to show scrollbar on top/left.
show_scrollbars: bool,
// How much padding should be between content and scrollbar?
scrollbar_padding: Vec2,
/// Initial position of the cursor when dragging.
thumb_grab: Option<(Orientation, usize)>,
}
impl<V> ScrollView<V> {
/// Creates a new ScrollView around `view`.
pub fn new(view: V) -> Self {
ScrollView {
inner: view,
inner_size: Vec2::zero(),
offset: Vec2::zero(),
last_size: Vec2::zero(),
enabled: XY::new(false, true),
show_scrollbars: true,
scrollbar_padding: Vec2::new(1, 0),
thumb_grab: None,
}
}
/// Returns the viewport in the inner content.
pub fn content_viewport(&self) -> Rect {
Rect::from_size(self.offset, self.available_size())
}
/// Sets the scroll offset to the given value
pub fn set_offset<S>(&mut self, offset: S)
where
S: Into<Vec2>,
{
let max_offset = self.inner_size.saturating_sub(self.available_size());
self.offset = offset.into().or_min(max_offset);
}
/// Controls whether this view can scroll vertically.
///
/// Defaults to `true`.
pub fn set_scroll_y(&mut self, enabled: bool) {
self.enabled.y = enabled;
}
/// Controls whether this view can scroll horizontally.
///
/// Defaults to `false`.
pub fn set_scroll_x(&mut self, enabled: bool) {
self.enabled.x = enabled;
}
/// Controls whether this view can scroll vertically.
///
/// Defaults to `true`.
///
/// Chainable variant.
pub fn scroll_y(self, enabled: bool) -> Self {
self.with(|s| s.set_scroll_y(enabled))
}
/// Controls whether this view can scroll horizontally.
///
/// Defaults to `false`.
///
/// Chainable variant.
pub fn scroll_x(self, enabled: bool) -> Self {
self.with(|s| s.set_scroll_x(enabled))
}
/// Programmatically scroll to the top of the view.
pub fn scroll_to_top(&mut self) {
let curr_x = self.offset.x;
self.set_offset((curr_x,0));
}
/// Programmatically scroll to the bottom of the view.
pub fn scroll_to_bottom(&mut self) {
let max_y = self.inner_size.saturating_sub(self.available_size()).y;
let curr_x = self.offset.x;
self.set_offset((curr_x, max_y));
}
/// Programmatically scroll to the leftmost side of the view.
pub fn scroll_to_left(&mut self) {
let curr_y = self.offset.y;
self.set_offset((0, curr_y));
}
/// Programmatically scroll to the rightmost side of the view.
pub fn scroll_to_right(&mut self) {
let max_x = self.inner_size.saturating_sub(self.available_size()).x;
let curr_y = self.offset.y;
self.set_offset((max_x, curr_y));
}
/// Returns for each axis if we are scrolling.
fn is_scrolling(&self) -> XY<bool> {
self.inner_size.zip_map(self.last_size, |i, s| i > s)
}
/// Stops grabbing the scrollbar.
fn release_grab(&mut self) {
self.thumb_grab = None;
}
/// Returns the size taken by the scrollbars.
///
/// Will be zero in axis where we're not scrolling.
fn scrollbar_size(&self) -> Vec2 {
self.is_scrolling()
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero())
}
/// Returns the size available for the child view.
fn available_size(&self) -> Vec2 {
self.last_size.saturating_sub(self.scrollbar_size())
}
}
impl<V> ScrollView<V>
where
V: View,
{
/// Compute the size we would need.
///
/// Given the constraints, and the axis that need scrollbars.
///
/// Returns `(inner_size, size, scrollable)`.
fn sizes_when_scrolling(
&mut self, constraint: Vec2, scrollable: XY<bool>,
) -> (Vec2, Vec2, XY<bool>) {
// This is the size taken by the scrollbars.
let scrollbar_size = scrollable
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero());
let available = constraint.saturating_sub(scrollbar_size);
// This the ideal size for the child. May not be what he gets.
let inner_size = self.inner.required_size(available);
// Where we're "enabled", accept the constraints.
// Where we're not, just forward inner_size.
let size = self.enabled.select_or(
Vec2::min(inner_size + scrollbar_size, constraint),
inner_size + scrollbar_size,
);
// On non-scrolling axis, give inner_size the available space instead.
let inner_size = self
.enabled
.select_or(inner_size, size.saturating_sub(scrollbar_size));
let new_scrollable = inner_size.zip_map(size, |i, s| i > s);
(inner_size, size, new_scrollable)
}
/// Starts scrolling from the cursor position.
///
/// Returns `true` if the event was consumed.
fn start_drag(&mut self, position: Vec2) -> bool {
let scrollbar_pos = self.last_size.saturating_sub((1, 1));
let grabbed = scrollbar_pos.zip_map(position, |s, p| s == p);
let lengths = self.scrollbar_thumb_lengths();
let offsets = self.scrollbar_thumb_offsets(lengths);
// See if we grabbed one of the scrollbars
for (orientation, pos, length, offset) in
XY::zip4(Orientation::pair(), position, lengths, offsets)
.zip(grabbed.swap())
.into_iter()
.filter(|&(_, grab)| grab)
.map(|(x, _)| x)
{
if pos >= offset && pos < offset + length {
// We grabbed the thumb! Now scroll from that position.
self.thumb_grab = Some((orientation, pos - offset));
} else {
// We hit the scrollbar, outside of the thumb.
// Let's move the middle there.
self.thumb_grab = Some((orientation, (length - 1) / 2));
self.drag(position);
}
return true;
}
false
}
fn drag(&mut self, position: Vec2) {
if let Some((orientation, grab)) = self.thumb_grab {
self.scroll_to_thumb(
orientation,
position.get(orientation).saturating_sub(grab),
);
}
}
fn scroll_to_thumb(&mut self, orientation: Orientation, thumb_pos: usize) {
let lengths = self.scrollbar_thumb_lengths();
let available = self.available_size();
// The new offset is:
// thumb_pos * (content + 1 - available) / (available + 1 - thumb size)
let new_offset = (self.inner_size + (1, 1)).saturating_sub(available)
* thumb_pos
/ (available + (1, 1)).saturating_sub(lengths);
let max_offset = self.inner_size.saturating_sub(self.available_size());
self.offset
.set_axis_from(orientation, &new_offset.or_min(max_offset));
}
/// Computes the size we would need given the constraints.
///
/// First be optimistic and try without scrollbars.
/// Then try with scrollbars if needed.
/// Then try again in case we now need to scroll both ways (!!!)
///
/// Returns `(inner_size, size)`
fn sizes(&mut self, constraint: Vec2) -> (Vec2, Vec2) {
let (inner_size, size, scrollable) =
self.sizes_when_scrolling(constraint, XY::new(false, false));
// If we need to add scrollbars, the available size will change.
if scrollable.any() && self.show_scrollbars {
// Attempt 2: he wants to scroll? Sure!
// Try again with some space for the scrollbar.
let (inner_size, size, new_scrollable) =
self.sizes_when_scrolling(constraint, scrollable);
if scrollable != new_scrollable {
// Again? We're now scrolling in a new direction?
// There is no end to this!
let (inner_size, size, _) =
self.sizes_when_scrolling(constraint, new_scrollable);
// That's enough. If the inner view changed again, ignore it!
// That'll teach it.
(inner_size, size)
} else {
// Yup, scrolling did it. We're goot to go now.
(inner_size, size)
}
} else {
// We're not showing any scrollbar, either because we don't scroll
// or because scrollbars are hidden.
(inner_size, size)
}
}
fn scrollbar_thumb_lengths(&self) -> Vec2 {
let available = self.available_size();
(available * available / self.inner_size).or_max((1, 1))
}
fn scrollbar_thumb_offsets(&self, lengths: Vec2) -> Vec2 {
let available = self.available_size();
// The number of steps is 1 + the "extra space"
let steps = (available + (1, 1)).saturating_sub(lengths);
steps * self.offset / (self.inner_size + (1, 1) - available)
}
}
impl<V> View for ScrollView<V>
where
V: View,
{
fn draw(&self, printer: &Printer) {
// Draw scrollbar?
let scrolling = self.is_scrolling();
let lengths = self.scrollbar_thumb_lengths();
let offsets = self.scrollbar_thumb_offsets(lengths);
let line_c = XY::new("-", "|");
let color = if printer.focused {
ColorStyle::highlight()
} else {
ColorStyle::highlight_inactive()
};
let size = self.available_size();
// TODO: use a more generic zip_all or something?
XY::zip5(lengths, offsets, size, line_c, Orientation::pair()).run_if(
scrolling,
|(length, offset, size, c, orientation)| {
let start = printer
.size
.saturating_sub((1, 1))
.with_axis(orientation, 0);
let offset = orientation.make_vec(offset, 0);
printer.print_line(orientation, start, size, c);
let thumb_c = if self
.thumb_grab
.map(|(o, _)| o == orientation)
.unwrap_or(false)
{
" "
} else {
""
};
printer.with_color(color, |printer| {
printer.print_line(
orientation,
start + offset,
length,
thumb_c,
);
});
},
);
if scrolling.both() {
printer.print(printer.size.saturating_sub((1, 1)), "");
}
// Draw content
let printer = printer
.cropped(size)
.content_offset(self.offset)
.inner_size(self.inner_size);
self.inner.draw(&printer);
}
fn on_event(&mut self, event: Event) -> EventResult {
// Relativize event accorging to the offset
let mut relative_event = event.clone();
// eprintln!("Mouse = {:?}", relative_event);
if let Some(pos) = relative_event.mouse_position_mut() {
*pos = *pos + self.offset;
}
match self.inner.on_event(relative_event) {
EventResult::Ignored => {
// If it's an arrow, try to scroll in the given direction.
// If it's a mouse scroll, try to scroll as well.
// Also allow Ctrl+arrow to move the view,
// but not the selection.
match event {
Event::Mouse {
event: MouseEvent::WheelUp,
..
} if self.enabled.y && self.offset.y > 0 =>
{
self.offset.y = self.offset.y.saturating_sub(3);
EventResult::Consumed(None)
}
Event::Mouse {
event: MouseEvent::WheelDown,
..
} if self.enabled.y
&& (self.offset.y + self.available_size().y
< self.inner_size.y) =>
{
self.offset.y = min(
self.inner_size
.y
.saturating_sub(self.available_size().y),
self.offset.y + 3,
);
EventResult::Consumed(None)
}
Event::Mouse {
event: MouseEvent::Press(MouseButton::Left),
position,
offset,
} if position
.checked_sub(offset)
.map(|position| self.start_drag(position))
.unwrap_or(false) =>
{
EventResult::Consumed(None)
}
Event::Mouse {
event: MouseEvent::Hold(MouseButton::Left),
position,
offset,
} => {
let position = position.saturating_sub(offset);
self.drag(position);
EventResult::Consumed(None)
}
Event::Mouse {
event: MouseEvent::Release(MouseButton::Left),
..
} => {
self.release_grab();
EventResult::Consumed(None)
}
Event::Ctrl(Key::Up) | Event::Key(Key::Up)
if self.enabled.y && self.offset.y > 0 =>
{
self.offset.y -= 1;
EventResult::Consumed(None)
}
Event::Ctrl(Key::Down) | Event::Key(Key::Down)
if self.enabled.y
&& (self.offset.y + self.available_size().y
< self.inner_size.y) =>
{
self.offset.y += 1;
EventResult::Consumed(None)
}
Event::Ctrl(Key::Left) | Event::Key(Key::Left)
if self.enabled.x && self.offset.x > 0 =>
{
self.offset.x -= 1;
EventResult::Consumed(None)
}
Event::Ctrl(Key::Right) | Event::Key(Key::Right)
if self.enabled.x
&& (self.offset.x + self.available_size().x
< self.inner_size.x) =>
{
self.offset.x += 1;
EventResult::Consumed(None)
}
_ => EventResult::Ignored,
}
}
other => {
// Fix offset?
let important = self.inner.important_area(self.inner_size);
// The furthest top-left we can go
let top_left = (important.bottom_right() + (1, 1))
.saturating_sub(self.available_size());
// The furthest bottom-right we can go
let bottom_right = important.top_left();
// "top_left < bottom_right" is NOT guaranteed
// if the child is larger than the view.
let offset_min = Vec2::min(top_left, bottom_right);
let offset_max = Vec2::max(top_left, bottom_right);
self.offset =
self.offset.or_max(offset_min).or_min(offset_max);
other
}
}
}
fn layout(&mut self, size: Vec2) {
// Size is final now
self.last_size = size;
let (inner_size, _) = self.sizes(size);
// Ask one more time
self.inner_size = inner_size;
self.inner.layout(self.inner_size);
// The offset cannot be more than content - available
self.offset = self
.offset
.or_min(inner_size.saturating_sub(self.available_size()));
}
fn needs_relayout(&self) -> bool {
self.inner.needs_relayout()
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
// Attempt 1: try without scrollbars
let (_, size) = self.sizes(constraint);
size
}
fn call_on_any<'a>(&mut self, selector: &Selector, cb: AnyCb<'a>) {
self.inner.call_on_any(selector, cb)
}
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
self.inner.focus_view(selector)
}
fn take_focus(&mut self, source: Direction) -> bool {
let is_scrollable = self.is_scrolling().any();
self.inner.take_focus(source) || is_scrollable
}
}

View File

@ -1,10 +1,8 @@
use Cursive;
use Printer;
use With;
use align::{Align, HAlign, VAlign}; use align::{Align, HAlign, VAlign};
use direction::Direction; use direction::Direction;
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
use menu::MenuTree; use menu::MenuTree;
use rect::Rect;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::cell::Cell; use std::cell::Cell;
use std::cmp::min; use std::cmp::min;
@ -14,6 +12,9 @@ use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::{Position, ScrollBase, View}; use view::{Position, ScrollBase, View};
use views::MenuPopup; use views::MenuPopup;
use Cursive;
use Printer;
use With;
/// View to select an item among a list. /// View to select an item among a list.
/// ///
@ -526,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))
{ {
@ -640,7 +642,10 @@ impl SelectView<String> {
} }
/// Convenient method to use the label as value. /// Convenient method to use the label as value.
pub fn insert_item_str<S>(&mut self, index: usize, label: S) where S: Into<String> { pub fn insert_item_str<S>(&mut self, index: usize, label: S)
where
S: Into<String>,
{
let label = label.into(); let label = label.into();
self.insert_item(index, label.clone(), label); self.insert_item(index, label.clone(), label);
} }
@ -710,8 +715,7 @@ impl<T: 'static> View for SelectView<T> {
} else { } else {
let h = self.items.len(); let h = self.items.len();
let offset = self.align.v.get_offset(h, printer.size.y); let offset = self.align.v.get_offset(h, printer.size.y);
let printer = let printer = &printer.offset((0, offset));
&printer.sub_printer(Vec2::new(0, offset), printer.size, true);
self.scrollbase.draw(printer, |printer, i| { self.scrollbase.draw(printer, |printer, i| {
printer.with_selection(i == self.focus(), |printer| { printer.with_selection(i == self.focus(), |printer| {
@ -732,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()
@ -772,6 +777,12 @@ impl<T: 'static> View for SelectView<T> {
self.scrollbase.set_heights(size.y, self.items.len()); self.scrollbase.set_heights(size.y, self.items.len());
} }
} }
fn important_area(&self, size: Vec2) -> Rect {
self.selected_id()
.map(|i| Rect::from_size((0, i), (size.x, 1)))
.unwrap_or_else(|| Rect::from((0, 0)))
}
} }
struct Item<T> { struct Item<T> {
@ -782,7 +793,7 @@ struct Item<T> {
impl<T> Item<T> { impl<T> Item<T> {
fn new(label: String, value: T) -> Self { fn new(label: String, value: T) -> Self {
Item { Item {
label: label, label,
value: Rc::new(value), value: Rc::new(value),
} }
} }

View File

@ -1,8 +1,8 @@
use Printer;
use event::{Event, EventResult}; use event::{Event, EventResult};
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use Printer;
/// Wrapper view that adds a shadow. /// Wrapper view that adds a shadow.
/// ///
@ -17,7 +17,7 @@ impl<T: View> ShadowView<T> {
/// Wraps the given view. /// Wraps the given view.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
ShadowView { ShadowView {
view: view, view,
top_padding: true, top_padding: true,
left_padding: true, left_padding: true,
} }
@ -80,7 +80,7 @@ impl<T: View> ViewWrapper for ShadowView<T> {
// Skip the first row/column // Skip the first row/column
let offset = let offset =
Vec2::new(self.left_padding as usize, self.top_padding as usize); Vec2::new(self.left_padding as usize, self.top_padding as usize);
let printer = &printer.offset(offset, true); let printer = &printer.offset(offset);
if printer.theme.shadow { if printer.theme.shadow {
let h = printer.size.y; let h = printer.size.y;
let w = printer.size.x; let w = printer.size.x;
@ -96,11 +96,7 @@ impl<T: View> ViewWrapper for ShadowView<T> {
} }
// Draw the view background // Draw the view background
let printer = printer.sub_printer( let printer = printer.shrinked((1, 1));
Vec2::zero(),
printer.size.saturating_sub((1, 1)),
true,
);
self.view.draw(&printer); self.view.draw(&printer);
} }
} }

View File

@ -3,18 +3,18 @@ use view::View;
use view::ViewWrapper; use view::ViewWrapper;
/// Wrapper around a view that remembers its size. /// Wrapper around a view that remembers its size.
pub struct SizedView<T: View> { pub struct SizedView<T> {
/// Wrapped view. /// Wrapped view.
pub view: T, pub view: T,
/// Cached size from the last layout() call. /// Cached size from the last layout() call.
pub size: Vec2, pub size: Vec2,
} }
impl<T: View> SizedView<T> { impl<T> SizedView<T> {
/// Wraps the given view. /// Wraps the given view.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
SizedView { SizedView {
view: view, view,
size: Vec2::zero(), size: Vec2::zero(),
} }
} }

View File

@ -1,11 +1,11 @@
use {Cursive, Printer};
use With;
use direction::{Direction, Orientation}; use direction::{Direction, Orientation};
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
use std::rc::Rc; use std::rc::Rc;
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use With;
use {Cursive, Printer};
/// A horizontal or vertical slider. /// A horizontal or vertical slider.
pub struct SliderView { pub struct SliderView {
@ -24,9 +24,9 @@ impl SliderView {
/// with one tick per block. /// with one tick per block.
pub fn new(orientation: Orientation, max_value: usize) -> Self { pub fn new(orientation: Orientation, max_value: usize) -> Self {
SliderView { SliderView {
orientation: orientation, orientation,
value: 0, value: 0,
max_value: max_value, max_value,
on_change: None, on_change: None,
on_enter: None, on_enter: None,
dragging: false, dragging: false,
@ -186,10 +186,10 @@ impl View for SliderView {
offset, offset,
} if position.fits_in_rect(offset, self.req_size()) => } if position.fits_in_rect(offset, self.req_size()) =>
{ {
position.checked_sub(offset).map(|position| { if let Some(position) = position.checked_sub(offset) {
self.dragging = true; self.dragging = true;
self.value = self.orientation.get(&position); self.value = self.orientation.get(&position);
}); }
self.get_change_result() self.get_change_result()
} }
Event::Mouse { Event::Mouse {

View File

@ -1,14 +1,13 @@
use Printer;
use With;
use direction::Direction; use direction::Direction;
use event::{Event, EventResult}; use event::{AnyCb, Event, EventResult};
use std::any::Any;
use std::cell; use std::cell;
use std::ops::Deref; use std::ops::Deref;
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use vec::Vec2;
use view::{IntoBoxedView, Offset, Position, Selector, View, ViewWrapper}; use view::{IntoBoxedView, Offset, Position, Selector, View, ViewWrapper};
use views::{Layer, ShadowView, ViewBox}; use views::{Layer, ShadowView, ViewBox};
use Printer;
use With;
/// Simple stack of views. /// Simple stack of views.
/// Only the top-most view is active and can receive input. /// Only the top-most view is active and can receive input.
@ -37,7 +36,7 @@ pub enum LayerPosition {
impl Placement { impl Placement {
pub fn compute_offset<S, A, P>( pub fn compute_offset<S, A, P>(
&self, size: S, available: A, parent: P &self, size: S, available: A, parent: P,
) -> Vec2 ) -> Vec2
where where
S: Into<Vec2>, S: Into<Vec2>,
@ -57,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> {
@ -68,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,
} }
} }
} }
@ -78,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,
} }
} }
@ -88,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,
} }
} }
} }
@ -98,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),
} }
} }
@ -105,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),
} }
} }
@ -112,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),
} }
} }
@ -119,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),
} }
} }
@ -126,17 +139,19 @@ 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),
} }
} }
fn call_on_any<'a>( fn call_on_any<'a>(&mut self, selector: &Selector, callback: AnyCb<'a>) {
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>
) {
match *self { match *self {
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)
} }
@ -146,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),
} }
} }
@ -184,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,
@ -270,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
@ -289,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.
@ -364,7 +412,7 @@ impl StackView {
/// Has no effect on fullscreen layers /// Has no effect on fullscreen layers
/// Has no effect if layer is not found /// Has no effect if layer is not found
pub fn reposition_layer( pub fn reposition_layer(
&mut self, layer: LayerPosition, position: Position &mut self, layer: LayerPosition, position: Position,
) { ) {
let i = self.get_index(layer); let i = self.get_index(layer);
let child = match self.layers.get_mut(i) { let child = match self.layers.get_mut(i) {
@ -411,11 +459,10 @@ impl StackView {
StackPositionIterator::new(self.layers.iter(), printer.size) StackPositionIterator::new(self.layers.iter(), printer.size)
.enumerate() .enumerate()
{ {
v.view.draw(&printer.sub_printer( v.view.draw(&printer
offset, .offset(offset)
v.size, .cropped(v.size)
i + 1 == last, .focused(i + 1 == last));
));
} }
}); });
} }
@ -527,8 +574,7 @@ impl View for StackView {
} }
fn call_on_any<'a>( fn call_on_any<'a>(
&mut self, selector: &Selector, &mut self, selector: &Selector, mut callback: AnyCb<'a>,
mut callback: Box<FnMut(&mut Any) + 'a>,
) { ) {
for layer in &mut self.layers { for layer in &mut self.layers {
layer layer
@ -617,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

@ -1,6 +1,6 @@
use {Printer, With, XY};
use direction::Direction; use direction::Direction;
use event::{Event, EventResult, Key, MouseButton, MouseEvent}; use event::{Event, EventResult, Key, MouseButton, MouseEvent};
use rect::Rect;
use std::cmp::min; use std::cmp::min;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -8,6 +8,7 @@ use unicode_width::UnicodeWidthStr;
use utils::lines::simple::{prefix, simple_prefix, LinesIterator, Row}; use utils::lines::simple::{prefix, simple_prefix, LinesIterator, Row};
use vec::Vec2; use vec::Vec2;
use view::{ScrollBase, SizeCache, View}; use view::{ScrollBase, SizeCache, View};
use {Printer, With, XY};
/// Multi-lines text editor. /// Multi-lines text editor.
/// ///
@ -162,6 +163,10 @@ impl TextArea {
self.row_at(self.cursor) self.row_at(self.cursor)
} }
fn selected_col(&self) -> usize {
self.col_at(self.cursor)
}
fn page_up(&mut self) { fn page_up(&mut self) {
for _ in 0..5 { for _ in 0..5 {
self.move_up(); self.move_up();
@ -437,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(),
) )
@ -568,13 +574,10 @@ impl View for TextArea {
event: MouseEvent::Press(_), event: MouseEvent::Press(_),
position, position,
offset, offset,
} if position.fits_in_rect(offset, self.last_size) => } if !self.rows.is_empty()
&& position.fits_in_rect(offset, self.last_size) =>
{ {
position.checked_sub(offset).map(|position| { if let Some(position) = position.checked_sub(offset) {
if self.rows.is_empty() {
return;
}
let y = position.y + self.scrollbase.start_line; let y = position.y + self.scrollbase.start_line;
let y = min(y, self.rows.len() - 1); let y = min(y, self.rows.len() - 1);
let x = position.x; let x = position.x;
@ -582,7 +585,7 @@ impl View for TextArea {
let content = &self.content[row.start..row.end]; let content = &self.content[row.start..row.end];
self.cursor = row.start + simple_prefix(content, x).length; self.cursor = row.start + simple_prefix(content, x).length;
}); }
} }
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }
@ -604,4 +607,24 @@ impl View for TextArea {
self.last_size = size; self.last_size = size;
self.compute_rows(size); self.compute_rows(size);
} }
fn important_area(&self, _: Vec2) -> Rect {
// The important area is a single character
let char_width = if self.cursor >= self.content.len() {
// If we're are the end of the content, it'll be a space
1
} else {
// Otherwise it's the selected grapheme
self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap()
.width()
};
Rect::from_size(
(self.selected_col(), self.selected_row()),
(char_width, 1),
)
}
} }

View File

@ -1,19 +1,19 @@
use Printer;
use With;
use XY;
use align::*; use align::*;
use direction::Direction; use direction::Direction;
use event::*; use event::*;
use owning_ref::{ArcRef, OwningHandle}; use owning_ref::{ArcRef, OwningHandle};
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Mutex, MutexGuard};
use std::sync::Arc; use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};
use theme::Effect; use theme::Effect;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use utils::lines::spans::{LinesIterator, Row}; use utils::lines::spans::{LinesIterator, Row};
use utils::markup::StyledString; use utils::markup::StyledString;
use vec::Vec2; use vec::Vec2;
use view::{ScrollBase, ScrollStrategy, SizeCache, View}; use view::{ScrollBase, ScrollStrategy, SizeCache, View};
use Printer;
use With;
use XY;
/// Provides access to the content of a [`TextView`]. /// Provides access to the content of a [`TextView`].
/// ///
@ -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()
@ -451,7 +452,7 @@ impl View for TextView {
let h = self.rows.len(); let h = self.rows.len();
// If the content is smaller than the view, align it somewhere. // If the content is smaller than the view, align it somewhere.
let offset = self.align.v.get_offset(h, printer.size.y); let offset = self.align.v.get_offset(h, printer.size.y);
let printer = &printer.offset((0, offset), true); let printer = &printer.offset((0, offset));
let content = self.content.lock().unwrap(); let content = self.content.lock().unwrap();

View File

@ -1,8 +1,8 @@
use Printer;
use std::cell::Cell; use std::cell::Cell;
use vec::Vec2; use vec::Vec2;
use view::{View, ViewWrapper}; use view::{View, ViewWrapper};
use views::IdView; use views::IdView;
use Printer;
/// Wrapper around a view that remembers its position. /// Wrapper around a view that remembers its position.
pub struct TrackedView<T: View> { pub struct TrackedView<T: View> {
@ -21,7 +21,7 @@ impl<T: View> TrackedView<T> {
/// Creates a new `TrackedView` around `view`. /// Creates a new `TrackedView` around `view`.
pub fn new(view: T) -> Self { pub fn new(view: T) -> Self {
TrackedView { TrackedView {
view: view, view,
offset: Cell::new(Vec2::zero()), offset: Cell::new(Vec2::zero()),
} }
} }

101
src/xy.rs
View File

@ -16,18 +16,60 @@ impl<T> XY<T> {
XY { x, y } XY { x, y }
} }
/// Swaps the x and y values.
pub fn swap(self) -> Self {
XY::new(self.y, self.x)
}
/// Returns `f(self.x, self.y)`
pub fn fold<U, F>(self, f: F) -> U
where
F: FnOnce(T, T) -> U,
{
f(self.x, self.y)
}
/// Creates a new `XY` by applying `f` to `x` and `y`. /// Creates a new `XY` by applying `f` to `x` and `y`.
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> XY<U> { pub fn map<U, F>(self, f: F) -> XY<U>
where
F: Fn(T) -> U,
{
XY::new(f(self.x), f(self.y)) XY::new(f(self.x), f(self.y))
} }
/// Applies `f` on axis where `condition` is true.
///
/// Carries over `self` otherwise.
pub fn map_if<F>(self, condition: XY<bool>, f: F) -> Self
where
F: Fn(T) -> T,
{
self.zip_map(condition, |v, c| if c { f(v) } else { v })
}
/// Applies `f` on axis where `condition` is true.
///
/// Returns `None` otherwise.
pub fn run_if<F, U>(self, condition: XY<bool>, f: F) -> XY<Option<U>>
where
F: Fn(T) -> U,
{
self.zip_map(condition, |v, c| if c { Some(f(v)) } else { None })
}
/// Creates a new `XY` by applying `f` to `x`, and carrying `y` over. /// Creates a new `XY` by applying `f` to `x`, and carrying `y` over.
pub fn map_x<F: Fn(T) -> T>(self, f: F) -> Self { pub fn map_x<F>(self, f: F) -> Self
where
F: FnOnce(T) -> T,
{
XY::new(f(self.x), self.y) XY::new(f(self.x), self.y)
} }
/// Creates a new `XY` by applying `f` to `y`, and carrying `x` over. /// Creates a new `XY` by applying `f` to `y`, and carrying `x` over.
pub fn map_y<F: Fn(T) -> T>(self, f: F) -> Self { pub fn map_y<F>(self, f: F) -> Self
where
F: FnOnce(T) -> T,
{
XY::new(self.x, f(self.y)) XY::new(self.x, f(self.y))
} }
@ -46,6 +88,11 @@ impl<T> XY<T> {
iter::once(&self.x).chain(iter::once(&self.y)) iter::once(&self.x).chain(iter::once(&self.y))
} }
/// Creates an iterator that returns `x`, then `y`.
pub fn into_iter(self) -> iter::Chain<iter::Once<T>, iter::Once<T>> {
iter::once(self.x).chain(iter::once(self.y))
}
/// Returns a reference to the value on the given axis. /// Returns a reference to the value on the given axis.
pub fn get(&self, o: Orientation) -> &T { pub fn get(&self, o: Orientation) -> &T {
match o { match o {
@ -67,8 +114,30 @@ impl<T> XY<T> {
XY::new((self.x, other.x), (self.y, other.y)) XY::new((self.x, other.x), (self.y, other.y))
} }
/// Returns a new `XY` of tuples made by zipping `self`, `a` and `b`.
pub fn zip3<U, V>(self, a: XY<U>, b: XY<V>) -> XY<(T, U, V)> {
XY::new((self.x, a.x, b.x), (self.y, a.y, b.y))
}
/// Returns a new `XY` of tuples made by zipping `self`, `a`, `b` and `c`.
pub fn zip4<U, V, W>(
self, a: XY<U>, b: XY<V>, c: XY<W>,
) -> XY<(T, U, V, W)> {
XY::new((self.x, a.x, b.x, c.x), (self.y, a.y, b.y, c.y))
}
/// Returns a new `XY` of tuples made by zipping `self`, `a`, `b`, `c` and `d`.
pub fn zip5<U, V, W, Z>(
self, a: XY<U>, b: XY<V>, c: XY<W>, d: XY<Z>,
) -> XY<(T, U, V, W, Z)> {
XY::new((self.x, a.x, b.x, c.x, d.x), (self.y, a.y, b.y, c.y, d.y))
}
/// Returns a new `XY` by calling `f` on `self` and `other` for each axis. /// Returns a new `XY` by calling `f` on `self` and `other` for each axis.
pub fn zip_map<U, V, F: Fn(T, U) -> V>(self, other: XY<U>, f: F) -> XY<V> { pub fn zip_map<U, V, F>(self, other: XY<U>, f: F) -> XY<V>
where
F: Fn(T, U) -> V,
{
XY::new(f(self.x, other.x), f(self.y, other.y)) XY::new(f(self.x, other.x), f(self.y, other.y))
} }
} }
@ -97,19 +166,31 @@ impl<T: Clone> XY<T> {
impl<T> XY<Option<T>> { impl<T> XY<Option<T>> {
/// Returns a new `XY` by calling `unwrap_or` on each axis. /// Returns a new `XY` by calling `unwrap_or` on each axis.
pub fn unwrap_or(self, other: XY<T>) -> XY<T> { pub fn unwrap_or(self, other: XY<T>) -> XY<T> {
self.zip_map(other, |s, o| s.unwrap_or(o)) self.zip_map(other, Option::unwrap_or)
} }
} }
impl XY<bool> { impl XY<bool> {
/// Returns `true` if any of `x` or `y` is `true`. /// Returns `true` if any of `x` or `y` is `true`.
pub fn any(&self) -> bool { pub fn any(&self) -> bool {
self.x || self.y use std::ops::BitOr;
self.fold(BitOr::bitor)
} }
/// Returns `true` if both `x` and `y` are `true`. /// Returns `true` if both `x` and `y` are `true`.
pub fn both(&self) -> bool { pub fn both(&self) -> bool {
self.x && self.y use std::ops::BitAnd;
self.fold(BitAnd::bitand)
}
/// For each axis, keeps elements from `other` if `self` is `true`.
pub fn select<T>(&self, other: XY<T>) -> XY<Option<T>> {
self.zip_map(other, |keep, o| if keep { Some(o) } else { None })
}
/// For each axis, selects `if_true` if `self` is true, else `if_false`.
pub fn select_or<T>(&self, if_true: XY<T>, if_false: XY<T>) -> XY<T> {
self.select(if_true).unwrap_or(if_false)
} }
} }
@ -125,3 +206,9 @@ impl<T> From<(T, T)> for XY<T> {
XY::new(x, y) XY::new(x, y)
} }
} }
impl<T, U> From<(XY<T>, XY<U>)> for XY<(T, U)> {
fn from((t, u): (XY<T>, XY<U>)) -> Self {
t.zip(u)
}
}