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_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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user