From 7d9cb03ffbfeeeac2e02adce2b4139a62fc5d31c Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 5 Mar 2017 11:34:45 -0800 Subject: [PATCH] More checks against small viewports Prevents panics when the terminal is resized. --- src/views/edit_view.rs | 1 + src/views/linear_layout.rs | 24 ++++++++++++-- src/views/list_view.rs | 68 ++++++++++++++++++++++---------------- src/views/select_view.rs | 3 ++ src/xy.rs | 10 +++++- 5 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 734ef28..e6626ea 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -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 { diff --git a/src/views/linear_layout.rs b/src/views/linear_layout.rs index 9c6a750..0ceb621 100644 --- a/src/views/linear_layout.rs +++ b/src/views/linear_layout.rs @@ -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>(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)); diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 77a84df..453ec30 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -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)); diff --git a/src/views/select_view.rs b/src/views/select_view.rs index d7181ee..b0301cd 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -340,6 +340,9 @@ impl View for SelectView { ColorStyle::Highlight }; let x = printer.size.x; + if x == 0 { + return; + } printer.with_color(style, |printer| { diff --git a/src/xy.rs b/src/xy.rs index 4f30323..3e22b46 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -45,6 +45,14 @@ impl XY { } } + /// 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(self, other: XY) -> XY<(T, U)> { XY::new((self.x, other.x), (self.y, other.y)) @@ -56,7 +64,7 @@ impl XY { } } -impl XY { +impl XY { /// 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();