mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-08 18:30:40 +00:00
Merge branch 'scroll'
This commit is contained in:
commit
0ed8eabc5e
@ -19,7 +19,11 @@ use cursive::{Cursive, Printer};
|
||||
fn main() {
|
||||
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());
|
||||
|
||||
|
@ -9,7 +9,11 @@ use cursive::{Cursive, Printer};
|
||||
|
||||
fn main() {
|
||||
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();
|
||||
}
|
||||
|
@ -87,8 +87,11 @@ impl View for BufferView {
|
||||
|
||||
fn draw(&self, printer: &Printer) {
|
||||
// Print the end of the buffer
|
||||
for (i, line) in
|
||||
self.buffer.iter().rev().take(printer.size.y).enumerate()
|
||||
for (i, line) in self.buffer
|
||||
.iter()
|
||||
.rev()
|
||||
.take(printer.size.y)
|
||||
.enumerate()
|
||||
{
|
||||
printer.print((0, printer.size.y - 1 - i), line);
|
||||
}
|
||||
|
@ -12,7 +12,10 @@ fn main() {
|
||||
let mut siv = Cursive::default();
|
||||
|
||||
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(
|
||||
"cool?",
|
||||
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
|
||||
|
@ -64,7 +64,8 @@ impl Board {
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -189,7 +189,9 @@ impl cursive::view::View for BoardView {
|
||||
Cell::Unknown => "[]",
|
||||
Cell::Flag => "()",
|
||||
Cell::Visible(n) => {
|
||||
[" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"][n]
|
||||
[
|
||||
" ", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8"
|
||||
][n]
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -87,7 +87,11 @@ fn phase_2(s: &mut Cursive) {
|
||||
// Let's prepare the progress bars...
|
||||
let mut linear = LinearLayout::vertical();
|
||||
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();
|
||||
|
@ -38,6 +38,10 @@ fn on_edit(siv: &mut Cursive, _content: &str, _cursor: usize) {
|
||||
let matches = edit_1.get_content() == edit_2.get_content();
|
||||
|
||||
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
39
examples/scroll.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,9 @@ fn main() {
|
||||
);
|
||||
|
||||
// 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| {
|
||||
// When Ctrl-F is pressed, show the Find popup.
|
||||
|
@ -6,7 +6,8 @@ use cursive::Cursive;
|
||||
fn main() {
|
||||
let mut siv = Cursive::default();
|
||||
// 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.
|
||||
// siv.load_theme(include_str!("../assets/style.toml")).unwrap();
|
||||
|
@ -8,7 +8,9 @@ fn main() {
|
||||
let mut siv = Cursive::default();
|
||||
|
||||
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(
|
||||
ColorStyle::new(
|
||||
Color::Rgb(200, 150, 150),
|
||||
|
@ -12,7 +12,7 @@ pub struct Align {
|
||||
impl Align {
|
||||
/// Creates a new Align object from the given alignments.
|
||||
pub fn new(h: HAlign, v: VAlign) -> Self {
|
||||
Align { h: h, v: v }
|
||||
Align { h, v }
|
||||
}
|
||||
|
||||
/// Creates a top-left alignment.
|
||||
|
@ -95,7 +95,7 @@ fn find_closest(color: &Color, max_colors: i16) -> i16 {
|
||||
// (r - 8) / 10 = n
|
||||
//
|
||||
let n = (r - 8) / 10;
|
||||
(232 + n) as i16
|
||||
i16::from(232 + n)
|
||||
} else {
|
||||
// Generic RGB
|
||||
let r = 6 * u16::from(r) / 256;
|
||||
|
@ -110,7 +110,7 @@ impl Cursive {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
Cursive {
|
||||
theme: theme,
|
||||
theme,
|
||||
screens: vec![views::StackView::new()],
|
||||
last_sizes: Vec::new(),
|
||||
global_callbacks: HashMap::new(),
|
||||
@ -119,7 +119,7 @@ impl Cursive {
|
||||
running: true,
|
||||
cb_source: rx,
|
||||
cb_sink: tx,
|
||||
backend: backend,
|
||||
backend,
|
||||
}
|
||||
}
|
||||
|
||||
@ -589,7 +589,7 @@ impl Cursive {
|
||||
// Print the stackview background before the menubar
|
||||
let offset = if self.menubar.autohide { 0 } else { 1 };
|
||||
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);
|
||||
|
||||
@ -597,11 +597,7 @@ impl Cursive {
|
||||
// If the menubar is active, nothing else can be.
|
||||
// Draw the menubar?
|
||||
if self.menubar.visible() {
|
||||
let printer = printer.sub_printer(
|
||||
Vec2::zero(),
|
||||
printer.size,
|
||||
self.menubar.receive_events(),
|
||||
);
|
||||
let printer = printer.focused(self.menubar.receive_events());
|
||||
self.menubar.draw(&printer);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,11 @@ pub enum 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.
|
||||
///
|
||||
/// (`Horizontal` will return the x value,
|
||||
|
18
src/event.rs
18
src/event.rs
@ -13,6 +13,7 @@
|
||||
//! [global callback](../struct.Cursive.html#method.add_global_callback)
|
||||
//! table is checked.
|
||||
|
||||
use std::any::Any;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use vec::Vec2;
|
||||
@ -24,6 +25,9 @@ use Cursive;
|
||||
pub struct Callback(Rc<Box<Fn(&mut Cursive)>>);
|
||||
// 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 {
|
||||
/// Wraps the given function into a `Callback` object.
|
||||
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.
|
||||
///
|
||||
/// If `self` is a mouse event, adds `top_left` to its offset.
|
||||
|
317
src/printer.rs
317
src/printer.rs
@ -1,32 +1,68 @@
|
||||
//! Makes drawing on ncurses windows easier.
|
||||
//! Provide higher-level abstraction to draw things on backends.
|
||||
|
||||
use backend::Backend;
|
||||
use direction::Orientation;
|
||||
use enumset::EnumSet;
|
||||
use std::cell::Cell;
|
||||
use std::cmp::min;
|
||||
use std::rc::Rc;
|
||||
use theme::{BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use utils::lines::simple::prefix;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use utils::lines::simple::{prefix, suffix};
|
||||
use vec::Vec2;
|
||||
use with::With;
|
||||
|
||||
/// Convenient interface to draw on a subset of the screen.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// A print request at `x` will really print at `x + offset`.
|
||||
pub offset: Vec2,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// 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.
|
||||
pub focused: bool,
|
||||
|
||||
/// Currently used theme
|
||||
pub theme: &'a Theme,
|
||||
|
||||
/// `true` if nothing has been drawn yet.
|
||||
new: Rc<Cell<bool>>,
|
||||
/// Backend used to actually draw things
|
||||
backend: &'b Backend,
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
@ -35,12 +71,14 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
pub fn new<T: Into<Vec2>>(
|
||||
size: T, theme: &'a Theme, backend: &'b Backend,
|
||||
) -> Self {
|
||||
let size = size.into();
|
||||
Printer {
|
||||
offset: Vec2::zero(),
|
||||
size: size.into(),
|
||||
content_offset: Vec2::zero(),
|
||||
output_size: size,
|
||||
size,
|
||||
focused: true,
|
||||
theme,
|
||||
new: Rc::new(Cell::new(true)),
|
||||
backend,
|
||||
}
|
||||
}
|
||||
@ -55,61 +93,152 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
.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.
|
||||
// We don't want people to start calling prints in parallel?
|
||||
/// Prints some text at the given position relative to the window.
|
||||
pub fn print<S: Into<Vec2>>(&self, pos: S, text: &str) {
|
||||
self.new.set(false);
|
||||
pub fn print<S: Into<Vec2>>(&self, start: S, text: &str) {
|
||||
// Where we are asked to start printing. Oh boy.
|
||||
let start = start.into();
|
||||
|
||||
let p = pos.into();
|
||||
if p.y >= self.size.y || p.x >= self.size.x {
|
||||
// We accept requests between `content_offset` and
|
||||
// `content_offset + output_size`.
|
||||
if !(start < (self.output_size + self.content_offset)) {
|
||||
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?
|
||||
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.
|
||||
// (Actually we want the "width" of the string, see unicode-width)
|
||||
let prefix_len = prefix(text.graphemes(true), room, "").length;
|
||||
let text = &text[..prefix_len];
|
||||
assert!(text.width() <= room);
|
||||
|
||||
let p = p + self.offset;
|
||||
self.backend.print_at(p, text);
|
||||
let start = start + self.offset;
|
||||
self.backend.print_at(start, text);
|
||||
}
|
||||
|
||||
/// Prints a vertical line using the given character.
|
||||
pub fn print_vline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) {
|
||||
self.new.set(false);
|
||||
pub fn print_vline<T: Into<Vec2>>(
|
||||
&self, start: T, height: usize, c: &str,
|
||||
) {
|
||||
let start = start.into();
|
||||
|
||||
let p = start.into();
|
||||
if p.y > self.size.y || p.x > self.size.x {
|
||||
// Here again, we can abort if we're trying to print too far right or
|
||||
// too low.
|
||||
if !start.fits_in(self.output_size + self.content_offset) {
|
||||
return;
|
||||
}
|
||||
let len = min(len, self.size.y - p.y);
|
||||
|
||||
let p = p + self.offset;
|
||||
for y in 0..len {
|
||||
self.backend.print_at(p + (0, y), c);
|
||||
// hidden_part describes how far to the top left of the viewport we are.
|
||||
let hidden_part = self.content_offset.saturating_sub(start);
|
||||
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.
|
||||
pub fn print_hline<T: Into<Vec2>>(&self, start: T, len: usize, c: &str) {
|
||||
self.new.set(false);
|
||||
pub fn print_hline<T: Into<Vec2>>(&self, start: T, width: usize, c: &str) {
|
||||
let start = start.into();
|
||||
|
||||
let p = start.into();
|
||||
if p.y > self.size.y || p.x > self.size.x {
|
||||
// Nothing to be done if the start if too far to the bottom/right
|
||||
if !start.fits_in(self.output_size + self.content_offset) {
|
||||
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;
|
||||
self.backend.print_at(p, &text);
|
||||
let hidden_part = self.content_offset.saturating_sub(start);
|
||||
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,
|
||||
@ -149,8 +278,6 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
let color = style.color;
|
||||
let effects = style.effects;
|
||||
|
||||
// eprintln!("{:?}", effects);
|
||||
|
||||
if let Some(color) = color {
|
||||
self.with_color(color, |printer| {
|
||||
printer.with_effects(effects, f);
|
||||
@ -182,8 +309,9 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
size: self.size,
|
||||
focused: self.focused,
|
||||
theme: theme,
|
||||
new: self.new.clone(),
|
||||
backend: self.backend,
|
||||
output_size: self.output_size,
|
||||
content_offset: self.content_offset,
|
||||
};
|
||||
f(&new_printer);
|
||||
}
|
||||
@ -224,8 +352,6 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
pub fn print_box<T: Into<Vec2>, S: Into<Vec2>>(
|
||||
&self, start: T, size: S, invert: bool,
|
||||
) {
|
||||
self.new.set(false);
|
||||
|
||||
let start = start.into();
|
||||
let size = size.into();
|
||||
|
||||
@ -310,37 +436,98 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
}
|
||||
|
||||
/// 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();
|
||||
self.print(start, "├");
|
||||
self.print_hline(start + (1, 0), len.saturating_sub(2), "─");
|
||||
self.print(start + (len.saturating_sub(1), 0), "┤");
|
||||
}
|
||||
|
||||
/// Returns a printer on a subset of this one's area.
|
||||
pub fn sub_printer<S: Into<Vec2>, T: Into<Vec2>>(
|
||||
&self, offset: S, size: T, focused: bool,
|
||||
) -> Printer<'a, 'b> {
|
||||
let size = size.into();
|
||||
let offset = offset.into().or_min(self.size);
|
||||
let available = if !offset.fits_in(self.size) {
|
||||
Vec2::zero()
|
||||
} else {
|
||||
Vec2::min(self.size - offset, size)
|
||||
};
|
||||
Printer {
|
||||
offset: self.offset + offset,
|
||||
// We can't be larger than what remains
|
||||
size: available,
|
||||
focused: self.focused && focused,
|
||||
theme: self.theme,
|
||||
backend: self.backend,
|
||||
new: Rc::clone(&self.new),
|
||||
}
|
||||
/// Returns a sub-printer with the given offset.
|
||||
///
|
||||
/// It will print in an area slightly to the bottom/right.
|
||||
pub fn offset<S>(&self, offset: S) -> Printer
|
||||
where
|
||||
S: Into<Vec2>,
|
||||
{
|
||||
let offset = offset.into();
|
||||
self.clone().with(|s| {
|
||||
// If we are drawing a part of the content,
|
||||
// let's reduce this first.
|
||||
let consumed = Vec2::min(s.content_offset, offset);
|
||||
|
||||
let offset = offset - consumed;
|
||||
s.content_offset = s.content_offset - consumed;
|
||||
|
||||
s.offset = s.offset + offset;
|
||||
|
||||
s.output_size = s.output_size.saturating_sub(offset);
|
||||
s.size = s.size.saturating_sub(offset);
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a sub-printer with the given offset.
|
||||
pub fn offset<S: Into<Vec2>>(&self, offset: S, focused: bool) -> Printer {
|
||||
self.sub_printer(offset, self.size, focused)
|
||||
/// Returns a new sub-printer inheriting the given focus.
|
||||
///
|
||||
/// 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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
29
src/rect.rs
29
src/rect.rs
@ -1,5 +1,5 @@
|
||||
//! Rectangles on the 2D character grid.
|
||||
|
||||
use std::ops::Add;
|
||||
use vec::Vec2;
|
||||
|
||||
/// A non-empty rectangle on the 2D grid.
|
||||
@ -7,7 +7,9 @@ use vec::Vec2;
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Rect {
|
||||
/// Top-left corner, inclusive
|
||||
top_left: Vec2,
|
||||
/// Bottom-right corner, inclusive
|
||||
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 {
|
||||
/// Creates a new `Rect` with the given position and size.
|
||||
///
|
||||
@ -125,21 +139,29 @@ impl Rect {
|
||||
}
|
||||
|
||||
/// Returns the Y value of the top edge of the rectangle.
|
||||
///
|
||||
/// This is inclusive.
|
||||
pub fn top(self) -> usize {
|
||||
self.top_left.y
|
||||
}
|
||||
|
||||
/// Returns the X value of the left edge of the rectangle.
|
||||
///
|
||||
/// This is inclusive.
|
||||
pub fn left(self) -> usize {
|
||||
self.top_left.x
|
||||
}
|
||||
|
||||
/// Returns the X value of the right edge of the rectangle.
|
||||
///
|
||||
/// This is inclusive.
|
||||
pub fn right(self) -> usize {
|
||||
self.bottom_right.x
|
||||
}
|
||||
|
||||
/// Returns the Y value of the botton edge of the rectangle.
|
||||
///
|
||||
/// This is inclusive.
|
||||
pub fn bottom(self) -> usize {
|
||||
self.bottom_right.y
|
||||
}
|
||||
@ -148,4 +170,9 @@ impl Rect {
|
||||
pub fn surface(self) -> usize {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ where
|
||||
debug_assert!(current_width <= available_width + delimiter_width);
|
||||
|
||||
Span {
|
||||
length: length,
|
||||
length,
|
||||
width: current_width,
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,7 @@ impl<R: Read> ProgressReader<R> {
|
||||
/// You should make sure the progress bar knows how
|
||||
/// many bytes should be received.
|
||||
pub fn new(counter: Counter, reader: R) -> Self {
|
||||
ProgressReader {
|
||||
reader: reader,
|
||||
counter: counter,
|
||||
}
|
||||
ProgressReader { reader, counter }
|
||||
}
|
||||
|
||||
/// Unwraps this `ProgressReader`, returning the reader and counter.
|
||||
|
21
src/vec.rs
21
src/vec.rs
@ -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)]
|
||||
mod tests {
|
||||
use super::Vec2;
|
||||
|
@ -262,7 +262,7 @@ impl ScrollBase {
|
||||
// Y is the actual coordinate of the line.
|
||||
// The item ID is then Y + self.start_line
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult};
|
||||
use event::{AnyCb, Event, EventResult};
|
||||
use rect::Rect;
|
||||
use std::any::Any;
|
||||
use vec::Vec2;
|
||||
use view::{AnyView, Selector};
|
||||
@ -72,7 +73,7 @@ pub trait View: Any + AnyView {
|
||||
/// If the selector doesn't find a match, the closure will not be run.
|
||||
///
|
||||
/// 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
|
||||
}
|
||||
|
||||
@ -95,4 +96,16 @@ pub trait View: Any + AnyView {
|
||||
let _ = source;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult};
|
||||
use event::{AnyCb, Event, EventResult};
|
||||
use rect::Rect;
|
||||
use std::any::Any;
|
||||
use vec::Vec2;
|
||||
use view::{Selector, View};
|
||||
@ -78,7 +79,7 @@ pub trait ViewWrapper: 'static {
|
||||
|
||||
/// Wraps the `find` method.
|
||||
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));
|
||||
}
|
||||
@ -93,6 +94,12 @@ pub trait ViewWrapper: 'static {
|
||||
fn wrap_needs_relayout(&self) -> bool {
|
||||
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.
|
||||
@ -130,6 +137,10 @@ impl<T: ViewWrapper> View for T {
|
||||
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||
self.wrap_focus_view(selector)
|
||||
}
|
||||
|
||||
fn important_area(&self, size: Vec2) -> Rect {
|
||||
self.wrap_important_area(size)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenient macro to implement the [`ViewWrapper`] trait.
|
||||
|
@ -1,6 +1,7 @@
|
||||
use align::HAlign;
|
||||
use direction::Direction;
|
||||
use event::*;
|
||||
use rect::Rect;
|
||||
use theme::ColorStyle;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use vec::Vec2;
|
||||
@ -21,6 +22,7 @@ pub struct Button {
|
||||
label: String,
|
||||
callback: Callback,
|
||||
enabled: bool,
|
||||
last_size: Vec2,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
@ -43,6 +45,7 @@ impl Button {
|
||||
label: label.into(),
|
||||
callback: Callback::from_fn(cb),
|
||||
enabled: true,
|
||||
last_size: Vec2::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,13 +143,17 @@ impl View for Button {
|
||||
};
|
||||
|
||||
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.print((offset, 0), &self.label);
|
||||
});
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
self.last_size = size;
|
||||
}
|
||||
|
||||
fn required_size(&mut self, _: Vec2) -> Vec2 {
|
||||
// Meh. Fixed size we are.
|
||||
self.req_size()
|
||||
@ -155,6 +162,8 @@ impl View for Button {
|
||||
fn on_event(&mut self, event: Event) -> EventResult {
|
||||
// eprintln!("{:?}", event);
|
||||
// eprintln!("{:?}", self.req_size());
|
||||
let width = self.label.width();
|
||||
let self_offset = HAlign::Center.get_offset(width, self.last_size.x);
|
||||
match event {
|
||||
// 10 is the ascii code for '\n', that is the return key
|
||||
Event::Key(Key::Enter) => {
|
||||
@ -164,7 +173,8 @@ impl View for Button {
|
||||
event: MouseEvent::Release(MouseButton::Left),
|
||||
position,
|
||||
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()))
|
||||
}
|
||||
@ -175,4 +185,11 @@ impl View for Button {
|
||||
fn take_focus(&mut self, _: Direction) -> bool {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult};
|
||||
use event::{AnyCb, Event, EventResult};
|
||||
use rect::Rect;
|
||||
use vec::Vec2;
|
||||
use view::View;
|
||||
use view::{Selector, View};
|
||||
use Printer;
|
||||
use With;
|
||||
|
||||
@ -17,6 +18,9 @@ pub struct Canvas<T> {
|
||||
layout: Box<FnMut(&mut T, Vec2)>,
|
||||
take_focus: Box<FnMut(&mut T, Direction) -> 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> {
|
||||
@ -31,6 +35,9 @@ impl<T: 'static + View> Canvas<T> {
|
||||
.with_layout(T::layout)
|
||||
.with_take_focus(T::take_focus)
|
||||
.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(|_, _| ()),
|
||||
take_focus: Box::new(|_, _| false),
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
@ -192,4 +258,20 @@ impl<T: 'static> View for Canvas<T> {
|
||||
fn take_focus(&mut self, source: Direction) -> bool {
|
||||
(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);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use align::*;
|
||||
use direction::Direction;
|
||||
use event::*;
|
||||
use std::any::Any;
|
||||
use event::{AnyCb, Event, EventResult, Key};
|
||||
use rect::Rect;
|
||||
use std::cell::Cell;
|
||||
use std::cmp::max;
|
||||
use theme::ColorStyle;
|
||||
@ -368,11 +368,10 @@ impl Dialog {
|
||||
// Add some special effect to the focused button
|
||||
let position = Vec2::new(offset, y);
|
||||
button.offset.set(position);
|
||||
button.button.draw(&printer.sub_printer(
|
||||
position,
|
||||
size,
|
||||
self.focus == DialogFocus::Button(i),
|
||||
));
|
||||
button.button.draw(&printer
|
||||
.offset(position)
|
||||
.cropped(size)
|
||||
.focused(self.focus == DialogFocus::Button(i)));
|
||||
// Keep 1 blank between two buttons
|
||||
offset += size.x + 1;
|
||||
// Also keep 1 blank above the buttons
|
||||
@ -393,11 +392,10 @@ impl Dialog {
|
||||
None => return,
|
||||
};
|
||||
|
||||
self.content.draw(&printer.sub_printer(
|
||||
self.borders.top_left() + self.padding.top_left(),
|
||||
inner_size,
|
||||
self.focus == DialogFocus::Content,
|
||||
));
|
||||
self.content.draw(&printer
|
||||
.offset(self.borders.top_left() + self.padding.top_left())
|
||||
.cropped(inner_size)
|
||||
.focused(self.focus == DialogFocus::Content));
|
||||
}
|
||||
|
||||
fn draw_title(&self, printer: &Printer) {
|
||||
@ -567,13 +565,16 @@ impl View for Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(
|
||||
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>,
|
||||
) {
|
||||
fn call_on_any<'a>(&mut self, selector: &Selector, callback: AnyCb<'a>) {
|
||||
self.content.call_on_any(selector, callback);
|
||||
}
|
||||
|
||||
fn focus_view(&mut self, selector: &Selector) -> Result<(), ()> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use direction::Direction;
|
||||
use event::{Callback, Event, EventResult, Key, MouseEvent};
|
||||
use rect::Rect;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use theme::{ColorStyle, Effect};
|
||||
@ -669,13 +670,13 @@ impl View for EditView {
|
||||
offset,
|
||||
} 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
|
||||
+ simple_prefix(
|
||||
&self.content[self.offset..],
|
||||
position.x,
|
||||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
}
|
||||
@ -684,4 +685,22 @@ impl View for EditView {
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -82,10 +82,9 @@ impl<T: View + 'static> ViewWrapper for IdView<T> {
|
||||
match selector {
|
||||
&Selector::Id(id) if id == self.id => callback(self),
|
||||
s => {
|
||||
self.view
|
||||
.try_borrow_mut()
|
||||
.ok()
|
||||
.map(|mut v| v.deref_mut().call_on_any(s, callback));
|
||||
if let Ok(mut v) = self.view.try_borrow_mut() {
|
||||
v.deref_mut().call_on_any(s, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pub struct Layer<T: View> {
|
||||
impl<T: View> Layer<T> {
|
||||
/// Wraps the given view.
|
||||
pub fn new(view: T) -> Self {
|
||||
Layer { view: view }
|
||||
Layer { view }
|
||||
}
|
||||
|
||||
inner_getters!(self.view: T);
|
||||
|
@ -1,6 +1,6 @@
|
||||
use direction;
|
||||
use event::{Event, EventResult, Key};
|
||||
use std::any::Any;
|
||||
use event::{AnyCb, Event, EventResult, Key};
|
||||
use rect::Rect;
|
||||
use std::cmp::min;
|
||||
use std::ops::Deref;
|
||||
use vec::Vec2;
|
||||
@ -280,11 +280,11 @@ impl LinearLayout {
|
||||
// this will give us the allowed window for a click.
|
||||
let child_size = item.child.size.get(self.orientation);
|
||||
|
||||
if (item.offset + child_size > position)
|
||||
&& item.child.view.take_focus(direction::Direction::none())
|
||||
{
|
||||
// eprintln!("It's a match!");
|
||||
self.focus = i;
|
||||
if item.offset + child_size > position {
|
||||
if item.child.view.take_focus(direction::Direction::none())
|
||||
{
|
||||
self.focus = i;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -315,11 +315,10 @@ impl View for LinearLayout {
|
||||
// eprintln!("Printer size: {:?}", printer.size);
|
||||
// eprintln!("Child size: {:?}", item.child.size);
|
||||
// eprintln!("Offset: {:?}", item.offset);
|
||||
let printer = &printer.sub_printer(
|
||||
self.orientation.make_vec(item.offset, 0),
|
||||
item.child.size,
|
||||
i == self.focus,
|
||||
);
|
||||
let printer = &printer
|
||||
.offset(self.orientation.make_vec(item.offset, 0))
|
||||
.cropped(item.child.size)
|
||||
.focused(i == self.focus);
|
||||
item.child.view.draw(printer);
|
||||
}
|
||||
}
|
||||
@ -557,8 +556,7 @@ impl View for LinearLayout {
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(
|
||||
&mut self, selector: &Selector,
|
||||
mut callback: Box<FnMut(&mut Any) + 'a>,
|
||||
&mut self, selector: &Selector, mut callback: AnyCb<'a>,
|
||||
) {
|
||||
for child in &mut self.children {
|
||||
child
|
||||
@ -577,4 +575,30 @@ impl View for LinearLayout {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use direction;
|
||||
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
|
||||
use std::any::Any;
|
||||
use event::{
|
||||
AnyCb, Callback, Event, EventResult, Key, MouseButton, MouseEvent,
|
||||
};
|
||||
use rect::Rect;
|
||||
use std::rc::Rc;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use vec::Vec2;
|
||||
@ -271,7 +273,9 @@ impl View for ListView {
|
||||
.draw(printer, |printer, i| match self.children[i] {
|
||||
ListChild::Row(ref label, ref view) => {
|
||||
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 => (),
|
||||
});
|
||||
@ -403,10 +407,14 @@ impl View for ListView {
|
||||
Event::Key(Key::PageDown) => {
|
||||
self.move_focus(10, direction::Direction::up())
|
||||
}
|
||||
Event::Key(Key::Home) | Event::Ctrl(Key::Home) => self
|
||||
.move_focus(usize::max_value(), direction::Direction::back()),
|
||||
Event::Key(Key::End) | Event::Ctrl(Key::End) => self
|
||||
.move_focus(usize::max_value(), direction::Direction::front()),
|
||||
Event::Key(Key::Home) | Event::Ctrl(Key::Home) => self.move_focus(
|
||||
usize::max_value(),
|
||||
direction::Direction::back(),
|
||||
),
|
||||
Event::Key(Key::End) | Event::Ctrl(Key::End) => self.move_focus(
|
||||
usize::max_value(),
|
||||
direction::Direction::front(),
|
||||
),
|
||||
Event::Key(Key::Tab) => {
|
||||
self.move_focus(1, direction::Direction::front())
|
||||
}
|
||||
@ -451,8 +459,7 @@ impl View for ListView {
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(
|
||||
&mut self, selector: &Selector,
|
||||
mut callback: Box<FnMut(&mut Any) + 'a>,
|
||||
&mut self, selector: &Selector, mut callback: AnyCb<'a>,
|
||||
) {
|
||||
for view in self.children.iter_mut().filter_map(ListChild::view) {
|
||||
view.call_on_any(selector, Box::new(|any| callback(any)));
|
||||
@ -474,4 +481,22 @@ impl View for ListView {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use align::Align;
|
||||
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
|
||||
use menu::{MenuItem, MenuTree};
|
||||
use rect::Rect;
|
||||
use std::cmp::min;
|
||||
use std::rc::Rc;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@ -209,7 +210,7 @@ impl View for MenuPopup {
|
||||
let h = self.menu.len();
|
||||
// If we're too high, add a vertical offset
|
||||
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
|
||||
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.
|
||||
// But we're keeping the full width,
|
||||
// to integrate horizontal delimiters in the frame.
|
||||
let size = printer.size - (0, 2);
|
||||
let printer = printer.sub_printer((0, 1), size, true);
|
||||
let printer = printer.offset((0, 1)).shrinked((0, 1));
|
||||
|
||||
self.scrollbase.draw(&printer, |printer, i| {
|
||||
printer.with_selection(i == self.focus, |printer| {
|
||||
@ -343,19 +343,16 @@ impl View for MenuPopup {
|
||||
// eprintln!("Position: {:?} / {:?}", position, offset);
|
||||
// eprintln!("Last size: {:?}", self.last_size);
|
||||
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
|
||||
// (It's inside the border)
|
||||
|position| {
|
||||
if position < inner_size {
|
||||
let focus =
|
||||
position.y + self.scrollbase.start_line;
|
||||
if !self.menu.children[focus].is_delimiter() {
|
||||
self.focus = focus;
|
||||
}
|
||||
if position < inner_size {
|
||||
let focus = position.y + self.scrollbase.start_line;
|
||||
if !self.menu.children[focus].is_delimiter() {
|
||||
self.focus = focus;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Release(MouseButton::Left),
|
||||
@ -400,4 +397,12 @@ impl View for MenuPopup {
|
||||
self.scrollbase
|
||||
.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))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use direction;
|
||||
use event::*;
|
||||
use menu::{MenuItem, MenuTree};
|
||||
use rect::Rect;
|
||||
use std::rc::Rc;
|
||||
use theme::ColorStyle;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@ -381,4 +382,21 @@ impl View for Menubar {
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ mod on_event_view;
|
||||
mod panel;
|
||||
mod progress_bar;
|
||||
mod radio;
|
||||
mod scroll_view;
|
||||
mod select_view;
|
||||
mod shadow_view;
|
||||
mod sized_view;
|
||||
@ -81,6 +82,7 @@ pub use self::on_event_view::OnEventView;
|
||||
pub use self::panel::Panel;
|
||||
pub use self::progress_bar::ProgressBar;
|
||||
pub use self::radio::{RadioButton, RadioGroup};
|
||||
pub use self::scroll_view::ScrollView;
|
||||
pub use self::select_view::SelectView;
|
||||
pub use self::shadow_view::ShadowView;
|
||||
pub use self::sized_view::SizedView;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use event::{Event, EventResult};
|
||||
use rect::Rect;
|
||||
use vec::Vec2;
|
||||
use view::{View, ViewWrapper};
|
||||
use Printer;
|
||||
@ -34,14 +35,16 @@ impl<V: View> ViewWrapper for Panel<V> {
|
||||
|
||||
fn wrap_draw(&self, printer: &Printer) {
|
||||
printer.print_box((0, 0), printer.size, true);
|
||||
self.view.draw(&printer.sub_printer(
|
||||
(1, 1),
|
||||
printer.size.saturating_sub((2, 2)),
|
||||
true,
|
||||
));
|
||||
let printer = printer.offset((1, 1)).shrinked((1, 1));
|
||||
self.view.draw(&printer);
|
||||
}
|
||||
|
||||
fn wrap_layout(&mut self, size: Vec2) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +188,7 @@ impl View for ProgressBar {
|
||||
printer.with_effect(Effect::Reverse, |printer| {
|
||||
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((offset, 0), &label);
|
||||
});
|
||||
|
504
src/views/scroll_view.rs
Normal file
504
src/views/scroll_view.rs
Normal file
@ -0,0 +1,504 @@
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ use align::{Align, HAlign, VAlign};
|
||||
use direction::Direction;
|
||||
use event::{Callback, Event, EventResult, Key, MouseButton, MouseEvent};
|
||||
use menu::MenuTree;
|
||||
use rect::Rect;
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::Cell;
|
||||
use std::cmp::min;
|
||||
@ -714,8 +715,7 @@ impl<T: 'static> View for SelectView<T> {
|
||||
} else {
|
||||
let h = self.items.len();
|
||||
let offset = self.align.v.get_offset(h, printer.size.y);
|
||||
let printer =
|
||||
&printer.sub_printer(Vec2::new(0, offset), printer.size, true);
|
||||
let printer = &printer.offset((0, offset));
|
||||
|
||||
self.scrollbase.draw(printer, |printer, i| {
|
||||
printer.with_selection(i == self.focus(), |printer| {
|
||||
@ -777,6 +777,12 @@ impl<T: 'static> View for SelectView<T> {
|
||||
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> {
|
||||
@ -787,7 +793,7 @@ struct Item<T> {
|
||||
impl<T> Item<T> {
|
||||
fn new(label: String, value: T) -> Self {
|
||||
Item {
|
||||
label: label,
|
||||
label,
|
||||
value: Rc::new(value),
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ impl<T: View> ShadowView<T> {
|
||||
/// Wraps the given view.
|
||||
pub fn new(view: T) -> Self {
|
||||
ShadowView {
|
||||
view: view,
|
||||
view,
|
||||
top_padding: true,
|
||||
left_padding: true,
|
||||
}
|
||||
@ -80,7 +80,7 @@ impl<T: View> ViewWrapper for ShadowView<T> {
|
||||
// Skip the first row/column
|
||||
let offset =
|
||||
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 {
|
||||
let h = printer.size.y;
|
||||
let w = printer.size.x;
|
||||
@ -96,11 +96,7 @@ impl<T: View> ViewWrapper for ShadowView<T> {
|
||||
}
|
||||
|
||||
// Draw the view background
|
||||
let printer = printer.sub_printer(
|
||||
Vec2::zero(),
|
||||
printer.size.saturating_sub((1, 1)),
|
||||
true,
|
||||
);
|
||||
let printer = printer.shrinked((1, 1));
|
||||
self.view.draw(&printer);
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,18 @@ use view::View;
|
||||
use view::ViewWrapper;
|
||||
|
||||
/// Wrapper around a view that remembers its size.
|
||||
pub struct SizedView<T: View> {
|
||||
pub struct SizedView<T> {
|
||||
/// Wrapped view.
|
||||
pub view: T,
|
||||
/// Cached size from the last layout() call.
|
||||
pub size: Vec2,
|
||||
}
|
||||
|
||||
impl<T: View> SizedView<T> {
|
||||
impl<T> SizedView<T> {
|
||||
/// Wraps the given view.
|
||||
pub fn new(view: T) -> Self {
|
||||
SizedView {
|
||||
view: view,
|
||||
view,
|
||||
size: Vec2::zero(),
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ impl SliderView {
|
||||
/// with one tick per block.
|
||||
pub fn new(orientation: Orientation, max_value: usize) -> Self {
|
||||
SliderView {
|
||||
orientation: orientation,
|
||||
orientation,
|
||||
value: 0,
|
||||
max_value: max_value,
|
||||
max_value,
|
||||
on_change: None,
|
||||
on_enter: None,
|
||||
dragging: false,
|
||||
@ -186,10 +186,10 @@ impl View for SliderView {
|
||||
offset,
|
||||
} 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.value = self.orientation.get(&position);
|
||||
});
|
||||
}
|
||||
self.get_change_result()
|
||||
}
|
||||
Event::Mouse {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult};
|
||||
use std::any::Any;
|
||||
use event::{AnyCb, Event, EventResult};
|
||||
use std::cell;
|
||||
use std::ops::Deref;
|
||||
use theme::ColorStyle;
|
||||
@ -145,9 +144,7 @@ impl<T: View> View for ChildWrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(
|
||||
&mut self, selector: &Selector, callback: Box<FnMut(&mut Any) + 'a>,
|
||||
) {
|
||||
fn call_on_any<'a>(&mut self, selector: &Selector, callback: AnyCb<'a>) {
|
||||
match *self {
|
||||
ChildWrapper::Shadow(ref mut v) => {
|
||||
v.call_on_any(selector, callback)
|
||||
@ -462,11 +459,10 @@ impl StackView {
|
||||
StackPositionIterator::new(self.layers.iter(), printer.size)
|
||||
.enumerate()
|
||||
{
|
||||
v.view.draw(&printer.sub_printer(
|
||||
offset,
|
||||
v.size,
|
||||
i + 1 == last,
|
||||
));
|
||||
v.view.draw(&printer
|
||||
.offset(offset)
|
||||
.cropped(v.size)
|
||||
.focused(i + 1 == last));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -578,8 +574,7 @@ impl View for StackView {
|
||||
}
|
||||
|
||||
fn call_on_any<'a>(
|
||||
&mut self, selector: &Selector,
|
||||
mut callback: Box<FnMut(&mut Any) + 'a>,
|
||||
&mut self, selector: &Selector, mut callback: AnyCb<'a>,
|
||||
) {
|
||||
for layer in &mut self.layers {
|
||||
layer
|
||||
|
@ -1,5 +1,6 @@
|
||||
use direction::Direction;
|
||||
use event::{Event, EventResult, Key, MouseButton, MouseEvent};
|
||||
use rect::Rect;
|
||||
use std::cmp::min;
|
||||
use theme::{ColorStyle, Effect};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
@ -162,6 +163,10 @@ impl TextArea {
|
||||
self.row_at(self.cursor)
|
||||
}
|
||||
|
||||
fn selected_col(&self) -> usize {
|
||||
self.col_at(self.cursor)
|
||||
}
|
||||
|
||||
fn page_up(&mut self) {
|
||||
for _ in 0..5 {
|
||||
self.move_up();
|
||||
@ -569,13 +574,10 @@ impl View for TextArea {
|
||||
event: MouseEvent::Press(_),
|
||||
position,
|
||||
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 self.rows.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(position) = position.checked_sub(offset) {
|
||||
let y = position.y + self.scrollbase.start_line;
|
||||
let y = min(y, self.rows.len() - 1);
|
||||
let x = position.x;
|
||||
@ -583,7 +585,7 @@ impl View for TextArea {
|
||||
let content = &self.content[row.start..row.end];
|
||||
|
||||
self.cursor = row.start + simple_prefix(content, x).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
}
|
||||
@ -605,4 +607,24 @@ impl View for TextArea {
|
||||
self.last_size = 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ impl View for TextView {
|
||||
let h = self.rows.len();
|
||||
// If the content is smaller than the view, align it somewhere.
|
||||
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();
|
||||
|
||||
|
@ -21,7 +21,7 @@ impl<T: View> TrackedView<T> {
|
||||
/// Creates a new `TrackedView` around `view`.
|
||||
pub fn new(view: T) -> Self {
|
||||
TrackedView {
|
||||
view: view,
|
||||
view,
|
||||
offset: Cell::new(Vec2::zero()),
|
||||
}
|
||||
}
|
||||
|
101
src/xy.rs
101
src/xy.rs
@ -16,18 +16,60 @@ impl<T> XY<T> {
|
||||
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`.
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
@ -46,6 +88,11 @@ impl<T> XY<T> {
|
||||
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.
|
||||
pub fn get(&self, o: Orientation) -> &T {
|
||||
match o {
|
||||
@ -67,8 +114,30 @@ impl<T> XY<T> {
|
||||
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.
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -97,19 +166,31 @@ impl<T: Clone> XY<T> {
|
||||
impl<T> XY<Option<T>> {
|
||||
/// Returns a new `XY` by calling `unwrap_or` on each axis.
|
||||
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> {
|
||||
/// Returns `true` if any of `x` or `y` is `true`.
|
||||
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`.
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> From<(XY<T>, XY<U>)> for XY<(T, U)> {
|
||||
fn from((t, u): (XY<T>, XY<U>)) -> Self {
|
||||
t.zip(u)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user