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) { fn layout(&mut self, size: Vec2) {
self.last_length = size.x; self.last_length = size.x;
// println_stderr!("Promised: {}", size.x);
} }
fn take_focus(&mut self, _: Direction) -> bool { fn take_focus(&mut self, _: Direction) -> bool {

View File

@ -26,6 +26,7 @@ struct Child {
} }
impl Child { impl Child {
// Compute and caches the required size.
fn required_size(&mut self, req: Vec2) -> Vec2 { fn required_size(&mut self, req: Vec2) -> Vec2 {
self.size = self.view.required_size(req); self.size = self.view.required_size(req);
self.size 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 { impl LinearLayout {
/// Creates a new layout with the given orientation. /// Creates a new layout with the given orientation.
pub fn new(orientation: direction::Orientation) -> Self { pub fn new(orientation: direction::Orientation) -> Self {
@ -197,6 +209,7 @@ impl View for LinearLayout {
let o = self.orientation; let o = self.orientation;
for child in &mut self.children { 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.size.set_axis_from(o.swap(), &size);
child.view.layout(size.with_axis_from(o, &child.size)); child.view.layout(size.with_axis_from(o, &child.size));
} }
@ -225,8 +238,8 @@ impl View for LinearLayout {
return ideal; return ideal;
} }
// Ok, so maybe it didn't. // Ok, so maybe it didn't. Budget cuts, everyone.
// Budget cuts, everyone. // Let's pretend we have almost no space in this direction.
let budget_req = req.with_axis(self.orientation, 1); let budget_req = req.with_axis(self.orientation, 1);
// println_stderr!("Budget req: {:?}", budget_req); // println_stderr!("Budget req: {:?}", budget_req);
@ -240,8 +253,15 @@ impl View for LinearLayout {
// println_stderr!("Desperate: {:?}", desperate); // println_stderr!("Desperate: {:?}", desperate);
// This is the lowest we'll ever go. It better fit at least. // This is the lowest we'll ever go. It better fit at least.
let orientation = self.orientation;
if !desperate.fits_in(req) { if !desperate.fits_in(req) {
// Just give up... // 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 // TODO: print some error message or something
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req); // println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
// self.cache = Some(SizeCache::build(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::any::Any;
use std::rc::Rc; use std::rc::Rc;
use unicode_width::UnicodeWidthStr;
use vec::Vec2; use vec::Vec2;
use view::ScrollBase; use view::ScrollBase;
use view::Selector; use view::Selector;
@ -132,16 +134,16 @@ impl ListView {
fn move_focus(&mut self, n: usize, source: direction::Direction) fn move_focus(&mut self, n: usize, source: direction::Direction)
-> EventResult { -> EventResult {
let i = if let Some(i) = let i = if let Some(i) =
source.relative(direction::Orientation::Vertical) source.relative(direction::Orientation::Vertical)
.and_then(|rel| { .and_then(|rel| {
// The iterator starts at the focused element. // The iterator starts at the focused element.
// We don't want that one. // We don't want that one.
self.iter_mut(true, rel) self.iter_mut(true, rel)
.skip(1) .skip(1)
.filter_map(|p| try_focus(p, source)) .filter_map(|p| try_focus(p, source))
.take(n) .take(n)
.last() .last()
}) { }) {
i i
} else { } else {
return EventResult::Ignored; return EventResult::Ignored;
@ -181,28 +183,30 @@ impl View for ListView {
let offset = self.children let offset = self.children
.iter() .iter()
.map(Child::label) .map(Child::label)
.map(str::len) .map(UnicodeWidthStr::width)
.max() .max()
.unwrap_or(0) + 1; .unwrap_or(0) + 1;
self.scrollbase.draw(printer, |printer, i| { // println_stderr!("Offset: {}", offset);
match self.children[i] {
Child::Row(ref label, ref view) => { self.scrollbase.draw(printer, |printer, i| match self.children[i] {
printer.print((0, 0), label); Child::Row(ref label, ref view) => {
view.draw(&printer.offset((offset, 0), i == self.focus)); printer.print((0, 0), label);
} view.draw(&printer.offset((offset, 0), i == self.focus));
Child::Delimiter => (),
} }
Child::Delimiter => (),
}); });
} }
fn required_size(&mut self, req: Vec2) -> Vec2 { 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() .iter()
.map(Child::label) .map(Child::label)
.map(str::len) .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(Child::view) .filter_map(Child::view)
@ -211,26 +215,34 @@ impl View for ListView {
.unwrap_or(0); .unwrap_or(0);
if self.children.len() > req.y { 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 { } 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) { fn layout(&mut self, size: Vec2) {
self.scrollbase.set_heights(size.y, self.children.len()); 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() .iter()
.map(Child::label) .map(Child::label)
.map(str::len) .map(UnicodeWidthStr::width)
.max() .max()
.unwrap_or(0); .unwrap_or(0);
let mut available = size.x - label_size - 1;
if self.children.len() > size.y { let spacing = 1;
available -= 2; 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) { for child in self.children.iter_mut().filter_map(Child::view) {
child.layout(Vec2::new(available, 1)); child.layout(Vec2::new(available, 1));

View File

@ -340,6 +340,9 @@ impl<T: 'static> View for SelectView<T> {
ColorStyle::Highlight ColorStyle::Highlight
}; };
let x = printer.size.x; let x = printer.size.x;
if x == 0 {
return;
}
printer.with_color(style, |printer| { 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`. /// Returns a new `XY` of tuples made by zipping `self` and `other`.
pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> { pub fn zip<U>(self, other: XY<U>) -> XY<(T, U)> {
XY::new((self.x, other.x), (self.y, other.y)) 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`. /// Returns a new `XY` with the axis `o` set to `value`.
pub fn with_axis(&self, o: Orientation, value: T) -> Self { pub fn with_axis(&self, o: Orientation, value: T) -> Self {
let mut new = self.clone(); let mut new = self.clone();