Cache layout in TextView

Calling either get_min_size or layout will cache the results, so the
next call with the same constraints will not re-compute everything.
This commit is contained in:
Alexandre Bury 2016-07-09 18:23:58 -07:00
parent 30e9316df1
commit 2860467f29
14 changed files with 79 additions and 43 deletions

View File

@ -25,6 +25,15 @@ extern crate toml;
extern crate unicode_segmentation; extern crate unicode_segmentation;
extern crate unicode_width; extern crate unicode_width;
macro_rules! println_stderr(
($($arg:tt)*) => { {
use ::std::io::Write;
let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
r.expect("failed printing to stderr");
} }
);
pub mod event; pub mod event;
pub mod view; pub mod view;
pub mod printer; pub mod printer;

View File

@ -30,7 +30,7 @@ impl<T: View> BoxView<T> {
impl<T: View> ViewWrapper for BoxView<T> { impl<T: View> ViewWrapper for BoxView<T> {
wrap_impl!(&self.view); wrap_impl!(&self.view);
fn wrap_get_min_size(&self, mut req: Vec2) -> Vec2 { fn wrap_get_min_size(&mut self, mut req: Vec2) -> Vec2 {
if self.size.x > 0 { if self.size.x > 0 {
req.x = cmp::min(self.size.x, req.x); req.x = cmp::min(self.size.x, req.x);
} }

View File

@ -43,7 +43,7 @@ impl View for Button {
}); });
} }
fn get_min_size(&self, _: Vec2) -> Vec2 { fn get_min_size(&mut self, _: Vec2) -> Vec2 {
// Meh. Fixed size we are. // Meh. Fixed size we are.
Vec2::new(2 + self.label.width(), 1) Vec2::new(2 + self.label.width(), 1)
} }

View File

@ -159,7 +159,7 @@ impl View for Dialog {
} }
fn get_min_size(&self, req: Vec2) -> Vec2 { fn get_min_size(&mut self, req: Vec2) -> Vec2 {
// Padding and borders are not available for kids. // Padding and borders are not available for kids.
let content_req = req - (self.padding.combined() + self.borders.combined()); let content_req = req - (self.padding.combined() + self.borders.combined());
let content_size = self.content.get_min_size(content_req); let content_size = self.content.get_min_size(content_req);
@ -168,7 +168,7 @@ impl View for Dialog {
if !self.buttons.is_empty() { if !self.buttons.is_empty() {
buttons_size.x += self.buttons.len() - 1; buttons_size.x += self.buttons.len() - 1;
} }
for button in &self.buttons { for button in &mut self.buttons {
let s = button.view.get_min_size(req); let s = button.view.get_min_size(req);
buttons_size.x += s.x; buttons_size.x += s.x;
buttons_size.y = max(buttons_size.y, s.y + 1); buttons_size.y = max(buttons_size.y, s.y + 1);

View File

@ -139,7 +139,7 @@ impl View for EditView {
self.last_length = size.x; self.last_length = size.x;
} }
fn get_min_size(&self, _: Vec2) -> Vec2 { fn get_min_size(&mut self, _: Vec2) -> Vec2 {
Vec2::new(self.min_length, 1) Vec2::new(self.min_length, 1)
} }

View File

@ -16,7 +16,7 @@ impl<T: View> FullView<T> {
impl<T: View> ViewWrapper for FullView<T> { impl<T: View> ViewWrapper for FullView<T> {
wrap_impl!(&self.view); wrap_impl!(&self.view);
fn wrap_get_min_size(&self, req: Vec2) -> Vec2 { fn wrap_get_min_size(&mut self, req: Vec2) -> Vec2 {
req req
} }
} }

View File

@ -127,7 +127,7 @@ impl View for LinearLayout {
// Look how mean we are: we offer the whole size to every child. // Look how mean we are: we offer the whole size to every child.
// As if they could get it all. // As if they could get it all.
let min_sizes: Vec<Vec2> = self.children let min_sizes: Vec<Vec2> = self.children
.iter() .iter_mut()
.map(|child| Vec2::min(size, child.view.get_min_size(size))) .map(|child| Vec2::min(size, child.view.get_min_size(size)))
.collect(); .collect();
let min_size = self.orientation.stack(min_sizes.iter()); let min_size = self.orientation.stack(min_sizes.iter());
@ -160,10 +160,10 @@ impl View for LinearLayout {
} }
} }
fn get_min_size(&self, req: Vec2) -> Vec2 { fn get_min_size(&mut self, req: Vec2) -> Vec2 {
// First, make a naive scenario: everything will work fine. // First, make a naive scenario: everything will work fine.
let sizes: Vec<Vec2> = self.children let sizes: Vec<Vec2> = self.children
.iter() .iter_mut()
.map(|view| view.view.get_min_size(req)) .map(|view| view.view.get_min_size(req))
.collect(); .collect();
self.orientation.stack(sizes.iter()) self.orientation.stack(sizes.iter())

View File

@ -160,7 +160,7 @@ impl View for MenuPopup {
}); });
} }
fn get_min_size(&self, req: Vec2) -> Vec2 { fn get_min_size(&mut self, req: Vec2) -> Vec2 {
// We can't really shrink our items here, so it's not flexible. // We can't really shrink our items here, so it's not flexible.
let w = 4 + let w = 4 +
self.menu self.menu

View File

@ -65,7 +65,7 @@ pub trait View {
} }
/// Returns the minimum size the view requires under the given restrictions. /// Returns the minimum size the view requires under the given restrictions.
fn get_min_size(&self, Vec2) -> Vec2 { fn get_min_size(&mut self, Vec2) -> Vec2 {
Vec2::new(1, 1) Vec2::new(1, 1)
} }

View File

@ -145,7 +145,7 @@ impl<T: 'static> View for SelectView<T> {
}); });
} }
fn get_min_size(&self, req: Vec2) -> Vec2 { fn get_min_size(&mut self, req: Vec2) -> Vec2 {
// Items here are not compressible. // Items here are not compressible.
// So no matter what the horizontal requirements are, // So no matter what the horizontal requirements are,
// we'll still return our longest item. // we'll still return our longest item.

View File

@ -41,7 +41,7 @@ impl<T: View> ShadowView<T> {
impl<T: View> ViewWrapper for ShadowView<T> { impl<T: View> ViewWrapper for ShadowView<T> {
wrap_impl!(&self.view); wrap_impl!(&self.view);
fn wrap_get_min_size(&self, req: Vec2) -> Vec2 { fn wrap_get_min_size(&mut self, req: Vec2) -> Vec2 {
let offset = self.padding(); let offset = self.padding();
self.view.get_min_size(req - offset) + offset self.view.get_min_size(req - offset) + offset
} }

View File

@ -100,11 +100,11 @@ impl View for StackView {
} }
} }
fn get_min_size(&self, size: Vec2) -> Vec2 { fn get_min_size(&mut self, size: Vec2) -> Vec2 {
// The min size is the max of all children's // The min size is the max of all children's
self.layers self.layers
.iter() .iter_mut()
.map(|layer| layer.view.get_min_size(size)) .map(|layer| layer.view.get_min_size(size))
.fold(Vec2::new(1, 1), Vec2::max) .fold(Vec2::new(1, 1), Vec2::max)
} }

View File

@ -19,12 +19,15 @@ pub struct TextView {
// ScrollBase make many scrolling-related things easier // ScrollBase make many scrolling-related things easier
scrollbase: ScrollBase, scrollbase: ScrollBase,
last_size: Option<Vec2>,
width: Option<usize>,
} }
// Subset of the main content representing a row on the display. // Subset of the main content representing a row on the display.
struct Row { struct Row {
start: usize, start: usize,
end: usize, end: usize,
width: usize,
} }
// If the last character is a newline, strip it. // If the last character is a newline, strip it.
@ -63,6 +66,8 @@ impl TextView {
rows: Vec::new(), rows: Vec::new(),
scrollbase: ScrollBase::new(), scrollbase: ScrollBase::new(),
align: Align::top_left(), align: Align::top_left(),
last_size: None,
width: None,
} }
} }
@ -119,6 +124,35 @@ impl TextView {
Vec2::new(max_width, height) Vec2::new(max_width, height)
} }
fn is_cache_valid(&self, size: Vec2) -> bool {
match self.last_size {
None => false,
Some(last) => if last.x != size.x {
false
} else {
(last.y < self.rows.len()) == (size.y < self.rows.len())
},
}
}
fn compute_rows(&mut self, size: Vec2) {
if !self.is_cache_valid(size) {
// Recompute
self.rows = LinesIterator::new(&self.content, size.x).collect();
let mut scrollbar = 0;
if self.rows.len() > size.y {
scrollbar = 2;
// If we're too high, include a scrollbar
self.rows = LinesIterator::new(&self.content, size.x - scrollbar)
.collect();
}
self.width = self.rows.iter().map(|row| row.width).max().map(|w| w + scrollbar);
self.last_size = Some(size);
}
}
} }
// Given a multiline string, and a given maximum width, // Given a multiline string, and a given maximum width,
@ -152,17 +186,20 @@ impl<'a> Iterator for LinesIterator<'a> {
let start = self.start; let start = self.start;
let content = &self.content[self.start..]; let content = &self.content[self.start..];
if let Some(next) = content.find('\n') { let next = content.find('\n').unwrap_or(content.len());
if content[..next].width() <= self.width { let content = &content[..next];
let line_width = content.width();
if line_width <= self.width {
// We found a newline before the allowed limit. // We found a newline before the allowed limit.
// Break early. // Break early.
self.start += next + 1; self.start += next + 1;
return Some(Row { return Some(Row {
start: start, start: start,
end: next + start, end: next + start,
width: line_width,
}); });
} }
}
// Keep adding indivisible tokens // Keep adding indivisible tokens
let head_bytes = match head_bytes(content.split(' '), self.width, " ") { let head_bytes = match head_bytes(content.split(' '), self.width, " ") {
@ -175,6 +212,7 @@ impl<'a> Iterator for LinesIterator<'a> {
Some(Row { Some(Row {
start: start, start: start,
end: start + head_bytes, end: start + head_bytes,
width: self.width,
}) })
} }
} }
@ -220,20 +258,13 @@ impl View for TextView {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn get_min_size(&self, size: Vec2) -> Vec2 { fn get_min_size(&mut self, size: Vec2) -> Vec2 {
// If we have no directive, ask for a single big line. // If we have no directive, ask for a single big line.
// TODO: what if the text has newlines?? // TODO: what if the text has newlines??
// Don't _force_ the max width, but take it if we have to. // Don't _force_ the max width, but take it if we have to.
let ideal = self.get_ideal_size();
if size.x >= ideal.x { self.compute_rows(size);
ideal Vec2::new(self.width.unwrap_or(0), self.rows.len())
} else {
// Ok, se we have less width than we'd like.
// Take everything we can, and plan our height accordingly.
let h = self.get_num_lines(size.x);
Vec2::new(size.x, h)
}
} }
fn take_focus(&mut self) -> bool { fn take_focus(&mut self) -> bool {
@ -242,11 +273,7 @@ impl View for TextView {
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
// Compute the text rows. // Compute the text rows.
self.rows = LinesIterator::new(&self.content, size.x).collect(); self.compute_rows(size);
if self.rows.len() > size.y {
self.rows = LinesIterator::new(&self.content, size.x - 2)
.collect();
}
self.scrollbase.set_heights(size.y, self.rows.len()); self.scrollbase.set_heights(size.y, self.rows.len());
} }
} }

View File

@ -21,8 +21,8 @@ pub trait ViewWrapper {
} }
/// Wraps the get_min_size method. /// Wraps the get_min_size method.
fn wrap_get_min_size(&self, req: Vec2) -> Vec2 { fn wrap_get_min_size(&mut self, req: Vec2) -> Vec2 {
self.get_view().get_min_size(req) self.get_view_mut().get_min_size(req)
} }
/// Wraps the on_event method. /// Wraps the on_event method.
@ -50,7 +50,7 @@ impl<T: ViewWrapper> View for T {
self.wrap_draw(printer); self.wrap_draw(printer);
} }
fn get_min_size(&self, req: Vec2) -> Vec2 { fn get_min_size(&mut self, req: Vec2) -> Vec2 {
self.wrap_get_min_size(req) self.wrap_get_min_size(req)
} }