mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
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:
parent
30e9316df1
commit
2860467f29
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user