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_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 view;
pub mod printer;

View File

@ -30,7 +30,7 @@ impl<T: View> BoxView<T> {
impl<T: View> ViewWrapper for BoxView<T> {
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 {
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.
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.
let content_req = req - (self.padding.combined() + self.borders.combined());
let content_size = self.content.get_min_size(content_req);
@ -168,7 +168,7 @@ impl View for Dialog {
if !self.buttons.is_empty() {
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);
buttons_size.x += s.x;
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;
}
fn get_min_size(&self, _: Vec2) -> Vec2 {
fn get_min_size(&mut self, _: Vec2) -> Vec2 {
Vec2::new(self.min_length, 1)
}

View File

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

View File

@ -127,7 +127,7 @@ impl View for LinearLayout {
// Look how mean we are: we offer the whole size to every child.
// As if they could get it all.
let min_sizes: Vec<Vec2> = self.children
.iter()
.iter_mut()
.map(|child| Vec2::min(size, child.view.get_min_size(size)))
.collect();
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.
let sizes: Vec<Vec2> = self.children
.iter()
.iter_mut()
.map(|view| view.view.get_min_size(req))
.collect();
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.
let w = 4 +
self.menu

View File

@ -65,7 +65,7 @@ pub trait View {
}
/// 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)
}

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.
// So no matter what the horizontal requirements are,
// 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> {
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();
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
self.layers
.iter()
.iter_mut()
.map(|layer| layer.view.get_min_size(size))
.fold(Vec2::new(1, 1), Vec2::max)
}

View File

@ -19,12 +19,15 @@ pub struct TextView {
// ScrollBase make many scrolling-related things easier
scrollbase: ScrollBase,
last_size: Option<Vec2>,
width: Option<usize>,
}
// Subset of the main content representing a row on the display.
struct Row {
start: usize,
end: usize,
width: usize,
}
// If the last character is a newline, strip it.
@ -63,6 +66,8 @@ impl TextView {
rows: Vec::new(),
scrollbase: ScrollBase::new(),
align: Align::top_left(),
last_size: None,
width: None,
}
}
@ -119,6 +124,35 @@ impl TextView {
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,
@ -152,16 +186,19 @@ impl<'a> Iterator for LinesIterator<'a> {
let start = self.start;
let content = &self.content[self.start..];
if let Some(next) = content.find('\n') {
if content[..next].width() <= self.width {
// We found a newline before the allowed limit.
// Break early.
self.start += next + 1;
return Some(Row {
start: start,
end: next + start,
});
}
let next = content.find('\n').unwrap_or(content.len());
let content = &content[..next];
let line_width = content.width();
if line_width <= self.width {
// We found a newline before the allowed limit.
// Break early.
self.start += next + 1;
return Some(Row {
start: start,
end: next + start,
width: line_width,
});
}
// Keep adding indivisible tokens
@ -175,6 +212,7 @@ impl<'a> Iterator for LinesIterator<'a> {
Some(Row {
start: start,
end: start + head_bytes,
width: self.width,
})
}
}
@ -220,20 +258,13 @@ impl View for TextView {
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.
// TODO: what if the text has newlines??
// Don't _force_ the max width, but take it if we have to.
let ideal = self.get_ideal_size();
if size.x >= ideal.x {
ideal
} 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)
}
self.compute_rows(size);
Vec2::new(self.width.unwrap_or(0), self.rows.len())
}
fn take_focus(&mut self) -> bool {
@ -242,11 +273,7 @@ impl View for TextView {
fn layout(&mut self, size: Vec2) {
// Compute the text rows.
self.rows = LinesIterator::new(&self.content, size.x).collect();
if self.rows.len() > size.y {
self.rows = LinesIterator::new(&self.content, size.x - 2)
.collect();
}
self.compute_rows(size);
self.scrollbase.set_heights(size.y, self.rows.len());
}
}

View File

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