More checks against small viewports

Prevents panics when the terminal is resized.
This commit is contained in:
Alexandre Bury 2017-03-05 11:34:45 -08:00
parent 8dc420c3ed
commit 7d9cb03ffb
5 changed files with 75 additions and 31 deletions

View File

@ -343,6 +343,7 @@ impl View for EditView {
fn layout(&mut self, size: Vec2) {
self.last_length = size.x;
// println_stderr!("Promised: {}", size.x);
}
fn take_focus(&mut self, _: Direction) -> bool {

View File

@ -26,6 +26,7 @@ struct Child {
}
impl Child {
// Compute and caches the required size.
fn required_size(&mut self, req: Vec2) -> Vec2 {
self.size = self.view.required_size(req);
self.size
@ -36,6 +37,17 @@ impl Child {
}
}
fn cap<'a, I: Iterator<Item = &'a mut usize>>(iter: I, max: usize) {
let mut available = max;
for item in iter {
if *item > available {
*item = available;
}
available -= *item;
}
}
impl LinearLayout {
/// Creates a new layout with the given orientation.
pub fn new(orientation: direction::Orientation) -> Self {
@ -197,6 +209,7 @@ impl View for LinearLayout {
let o = self.orientation;
for child in &mut self.children {
// Every item has the same size orthogonal to the layout
child.size.set_axis_from(o.swap(), &size);
child.view.layout(size.with_axis_from(o, &child.size));
}
@ -225,8 +238,8 @@ impl View for LinearLayout {
return ideal;
}
// Ok, so maybe it didn't.
// Budget cuts, everyone.
// Ok, so maybe it didn't. Budget cuts, everyone.
// Let's pretend we have almost no space in this direction.
let budget_req = req.with_axis(self.orientation, 1);
// println_stderr!("Budget req: {:?}", budget_req);
@ -240,8 +253,15 @@ impl View for LinearLayout {
// println_stderr!("Desperate: {:?}", desperate);
// This is the lowest we'll ever go. It better fit at least.
let orientation = self.orientation;
if !desperate.fits_in(req) {
// Just give up...
// TODO: hard-cut
cap(self.children
.iter_mut()
.map(|c| c.size.get_mut(orientation)),
*req.get(self.orientation));
// TODO: print some error message or something
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
// self.cache = Some(SizeCache::build(desperate, req));

View File

@ -6,6 +6,8 @@ use event::{Callback, Event, EventResult, Key};
use std::any::Any;
use std::rc::Rc;
use unicode_width::UnicodeWidthStr;
use vec::Vec2;
use view::ScrollBase;
use view::Selector;
@ -132,16 +134,16 @@ impl ListView {
fn move_focus(&mut self, n: usize, source: direction::Direction)
-> EventResult {
let i = if let Some(i) =
source.relative(direction::Orientation::Vertical)
.and_then(|rel| {
// The iterator starts at the focused element.
// We don't want that one.
self.iter_mut(true, rel)
.skip(1)
.filter_map(|p| try_focus(p, source))
.take(n)
.last()
}) {
source.relative(direction::Orientation::Vertical)
.and_then(|rel| {
// The iterator starts at the focused element.
// We don't want that one.
self.iter_mut(true, rel)
.skip(1)
.filter_map(|p| try_focus(p, source))
.take(n)
.last()
}) {
i
} else {
return EventResult::Ignored;
@ -181,28 +183,30 @@ impl View for ListView {
let offset = self.children
.iter()
.map(Child::label)
.map(str::len)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0) + 1;
self.scrollbase.draw(printer, |printer, i| {
match self.children[i] {
Child::Row(ref label, ref view) => {
printer.print((0, 0), label);
view.draw(&printer.offset((offset, 0), i == self.focus));
}
Child::Delimiter => (),
// println_stderr!("Offset: {}", offset);
self.scrollbase.draw(printer, |printer, i| match self.children[i] {
Child::Row(ref label, ref view) => {
printer.print((0, 0), label);
view.draw(&printer.offset((offset, 0), i == self.focus));
}
Child::Delimiter => (),
});
}
fn required_size(&mut self, req: Vec2) -> Vec2 {
let label_size = self.children
// We'll show 2 columns: the labels, and the views.
let label_width = self.children
.iter()
.map(Child::label)
.map(str::len)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0);
let view_size = self.children
.iter_mut()
.filter_map(Child::view)
@ -211,26 +215,34 @@ impl View for ListView {
.unwrap_or(0);
if self.children.len() > req.y {
Vec2::new(label_size + 1 + view_size + 2, req.y)
Vec2::new(label_width + 1 + view_size + 2, req.y)
} else {
Vec2::new(label_size + 1 + view_size, self.children.len())
Vec2::new(label_width + 1 + view_size, self.children.len())
}
}
fn layout(&mut self, size: Vec2) {
self.scrollbase.set_heights(size.y, self.children.len());
let label_size = self.children
// We'll show 2 columns: the labels, and the views.
let label_width = self.children
.iter()
.map(Child::label)
.map(str::len)
.map(UnicodeWidthStr::width)
.max()
.unwrap_or(0);
let mut available = size.x - label_size - 1;
if self.children.len() > size.y {
available -= 2;
}
let spacing = 1;
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 };
let available = if label_width + spacing + scrollbar_width > size.x {
// We have no space for the kids! :(
0
} else {
size.x - label_width - spacing - scrollbar_width
};
// println_stderr!("Available: {}", available);
for child in self.children.iter_mut().filter_map(Child::view) {
child.layout(Vec2::new(available, 1));

View File

@ -340,6 +340,9 @@ impl<T: 'static> View for SelectView<T> {
ColorStyle::Highlight
};
let x = printer.size.x;
if x == 0 {
return;
}
printer.with_color(style, |printer| {

View File

@ -45,6 +45,14 @@ impl<T> XY<T> {
}
}
/// Returns a mutable reference to the value on the given axis.
pub fn get_mut(&mut self, o: Orientation) -> &mut T {
match o {
Orientation::Horizontal => &mut self.x,
Orientation::Vertical => &mut self.y,
}
}
/// Returns a new `XY` of tuples made by zipping `self` and `other`.
pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
XY::new((self.x, other.x), (self.y, other.y))
@ -56,7 +64,7 @@ impl<T> XY<T> {
}
}
impl <T: Clone> XY<T> {
impl<T: Clone> XY<T> {
/// Returns a new `XY` with the axis `o` set to `value`.
pub fn with_axis(&self, o: Orientation, value: T) -> Self {
let mut new = self.clone();