Fix size cache in ScrollView

This commit is contained in:
Alexandre Bury 2018-08-20 13:30:42 -07:00
parent 682fadeb1c
commit acc1405c2a
7 changed files with 110 additions and 27 deletions

View File

@ -1,3 +1,4 @@
use With;
use vec::Vec2; use vec::Vec2;
use view::{SizeConstraint, View, ViewWrapper}; use view::{SizeConstraint, View, ViewWrapper};
use XY; use XY;
@ -27,8 +28,11 @@ pub struct BoxView<T: View> {
/// ///
/// This means if the required size is less than the computed size, /// This means if the required size is less than the computed size,
/// consider returning a smaller size. /// consider returning a smaller size.
/// For instance, try to return the child's desires size. /// For instance, try to return the child's desired size.
squishable: bool, squishable: bool, // TODO: remove?
/// Set to `true` whenever we change some settings. Means we should re-layout just in case.
invalidated: bool,
/// The actual view we're wrapping. /// The actual view we're wrapping.
view: T, view: T,
@ -44,6 +48,7 @@ impl<T: View> BoxView<T> {
BoxView { BoxView {
size: (width, height).into(), size: (width, height).into(),
squishable: false, squishable: false,
invalidated: true,
view, view,
} }
} }
@ -61,6 +66,7 @@ impl<T: View> BoxView<T> {
/// Leaves the height unchanged. /// Leaves the height unchanged.
pub fn set_width(&mut self, width: SizeConstraint) { pub fn set_width(&mut self, width: SizeConstraint) {
self.size.x = width; self.size.x = width;
self.invalidate();
} }
/// Sets the height constraint for this view. /// Sets the height constraint for this view.
@ -68,6 +74,7 @@ impl<T: View> BoxView<T> {
/// Leaves the width unchanged. /// Leaves the width unchanged.
pub fn set_height(&mut self, height: SizeConstraint) { pub fn set_height(&mut self, height: SizeConstraint) {
self.size.y = height; self.size.y = height;
self.invalidate();
} }
/// Sets `self` to be squishable. /// Sets `self` to be squishable.
@ -78,9 +85,14 @@ impl<T: View> BoxView<T> {
/// ///
/// More specifically, if the available space is less than the size we /// More specifically, if the available space is less than the size we
/// would normally ask for, return the child size. /// would normally ask for, return the child size.
pub fn squishable(mut self) -> Self { pub fn squishable(self) -> Self {
self.squishable = true; self.with(|s| s.set_squishable(true))
self }
/// Controls the "squishability" of `self`.
pub fn set_squishable(&mut self, squishable: bool) {
self.squishable = squishable;
self.invalidate();
} }
/// Wraps `view` in a new `BoxView` with the given size. /// Wraps `view` in a new `BoxView` with the given size.
@ -185,6 +197,12 @@ impl<T: View> BoxView<T> {
) )
} }
/// Should be called anytime something changes.
fn invalidate(&mut self) {
self.invalidated = true;
}
inner_getters!(self.view: T); inner_getters!(self.view: T);
} }
@ -192,34 +210,42 @@ impl<T: View> ViewWrapper for BoxView<T> {
wrap_impl!(self.view: T); wrap_impl!(self.view: T);
fn wrap_required_size(&mut self, req: Vec2) -> Vec2 { fn wrap_required_size(&mut self, req: Vec2) -> Vec2 {
// This is what the child will see as request.
let req = self.size.zip_map(req, SizeConstraint::available); let req = self.size.zip_map(req, SizeConstraint::available);
// This is the size the child would like to have.
let child_size = self.view.required_size(req); let child_size = self.view.required_size(req);
// Some of this request will be granted, but maybe not all.
let result = self let result = self
.size .size
.zip_map(child_size.zip(req), SizeConstraint::result); .zip_map(child_size.zip(req), SizeConstraint::result);
debug!("{:?}", result); debug!("{:?}", result);
if self.squishable { if !self.squishable {
result
} else {
// When we're squishable, special behaviour:
//
// We respect the request if we're less or equal. // We respect the request if we're less or equal.
let respect_req = result.zip_map(req, |res, req| res <= req); let respect_req = result.zip_map(req, |res, req| res <= req);
result.zip_map(
respect_req.zip(child_size), // If we respect the request, keep the result
|res, (respect, child)| { // Otherwise, take the child as squish attempt.
if respect { respect_req.select_or(result, child_size)
// If we respect the request, keep the result
res
} else {
// Otherwise, take the child as squish attempt.
child
}
},
)
} else {
result
} }
} }
fn wrap_layout(&mut self, size: Vec2) {
self.invalidated = false;
self.view.layout(size);
}
fn wrap_needs_relayout(&self) -> bool {
self.invalidated || self.view.needs_relayout()
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -23,6 +23,8 @@ pub struct Button {
callback: Callback, callback: Callback,
enabled: bool, enabled: bool,
last_size: Vec2, last_size: Vec2,
invalidated: bool,
} }
impl Button { impl Button {
@ -46,6 +48,7 @@ impl Button {
callback: Callback::from_fn(cb), callback: Callback::from_fn(cb),
enabled: true, enabled: true,
last_size: Vec2::zero(), last_size: Vec2::zero(),
invalidated: true,
} }
} }
@ -121,11 +124,16 @@ impl Button {
S: Into<String>, S: Into<String>,
{ {
self.label = label.into(); self.label = label.into();
self.invalidate();
} }
fn req_size(&self) -> Vec2 { fn req_size(&self) -> Vec2 {
Vec2::new(self.label.width(), 1) Vec2::new(self.label.width(), 1)
} }
fn invalidate(&mut self) {
self.invalidated = true;
}
} }
impl View for Button { impl View for Button {
@ -152,6 +160,7 @@ impl View for Button {
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
self.last_size = size; self.last_size = size;
self.invalidated = false;
} }
fn required_size(&mut self, _: Vec2) -> Vec2 { fn required_size(&mut self, _: Vec2) -> Vec2 {
@ -195,4 +204,8 @@ impl View for Button {
Rect::from_size((offset, 0), (width, 1)) Rect::from_size((offset, 0), (width, 1))
} }
fn needs_relayout(&self) -> bool {
self.invalidated
}
} }

View File

@ -75,6 +75,9 @@ pub struct Dialog {
// How to align the buttons under the view. // How to align the buttons under the view.
align: Align, align: Align,
// `true` when we needs to relayout
invalidated: bool,
} }
new_default!(Dialog); new_default!(Dialog);
@ -98,6 +101,7 @@ impl Dialog {
padding: Margins::new(1, 1, 0, 0), padding: Margins::new(1, 1, 0, 0),
borders: Margins::new(1, 1, 1, 1), borders: Margins::new(1, 1, 1, 1),
align: Align::top_right(), align: Align::top_right(),
invalidated: true,
} }
} }
@ -126,6 +130,7 @@ impl Dialog {
/// Gets mutable access to the content. /// Gets mutable access to the content.
pub fn get_content_mut(&mut self) -> &mut View { pub fn get_content_mut(&mut self) -> &mut View {
self.invalidate();
&mut *self.content.view &mut *self.content.view
} }
@ -134,6 +139,7 @@ impl Dialog {
/// Previous content will be dropped. /// Previous content will be dropped.
pub fn set_content<V: View + 'static>(&mut self, view: V) { pub fn set_content<V: View + 'static>(&mut self, view: V) {
self.content = SizedView::new(ViewBox::boxed(view)); self.content = SizedView::new(ViewBox::boxed(view));
self.invalidate();
} }
/// Convenient method to create a dialog with a simple text content. /// Convenient method to create a dialog with a simple text content.
@ -164,6 +170,7 @@ impl Dialog {
F: 'static + Fn(&mut Cursive), F: 'static + Fn(&mut Cursive),
{ {
self.buttons.push(ChildButton::new(label, cb)); self.buttons.push(ChildButton::new(label, cb));
self.invalidate();
} }
/// Returns the number of buttons on this dialog. /// Returns the number of buttons on this dialog.
@ -174,6 +181,7 @@ impl Dialog {
/// Removes any button from `self`. /// Removes any button from `self`.
pub fn clear_buttons(&mut self) { pub fn clear_buttons(&mut self) {
self.buttons.clear(); self.buttons.clear();
self.invalidate();
} }
/// Removes a button from this dialog. /// Removes a button from this dialog.
@ -183,6 +191,7 @@ impl Dialog {
/// Panics if `i >= self.buttons_len()`. /// Panics if `i >= self.buttons_len()`.
pub fn remove_button(&mut self, i: usize) { pub fn remove_button(&mut self, i: usize) {
self.buttons.remove(i); self.buttons.remove(i);
self.invalidate();
} }
/// Sets the horizontal alignment for the buttons, if any. /// Sets the horizontal alignment for the buttons, if any.
@ -224,6 +233,7 @@ impl Dialog {
/// Sets the title of the dialog. /// Sets the title of the dialog.
pub fn set_title<S: Into<String>>(&mut self, label: S) { pub fn set_title<S: Into<String>>(&mut self, label: S) {
self.title = label.into(); self.title = label.into();
self.invalidate();
} }
/// Sets the horizontal position of the title in the dialog. /// Sets the horizontal position of the title in the dialog.
@ -271,6 +281,7 @@ impl Dialog {
/// Returns an iterator on this buttons for this dialog. /// Returns an iterator on this buttons for this dialog.
pub fn buttons_mut(&mut self) -> impl Iterator<Item = &mut Button> { pub fn buttons_mut(&mut self) -> impl Iterator<Item = &mut Button> {
self.invalidate();
self.buttons.iter_mut().map(|b| &mut b.button.view) self.buttons.iter_mut().map(|b| &mut b.button.view)
} }
@ -504,6 +515,10 @@ impl Dialog {
} }
} }
} }
fn invalidate(&mut self) {
self.invalidated = true;
}
} }
impl View for Dialog { impl View for Dialog {
@ -583,8 +598,11 @@ impl View for Dialog {
if buttons_height > size.y { if buttons_height > size.y {
buttons_height = size.y; buttons_height = size.y;
} }
self.content self.content
.layout(size.saturating_sub((0, buttons_height))); .layout(size.saturating_sub((0, buttons_height)));
self.invalidated = false;
} }
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
@ -627,4 +645,8 @@ impl View for Dialog {
+ self.borders.top_left() + self.borders.top_left()
+ self.padding.top_left() + self.padding.top_left()
} }
fn needs_relayout(&self) -> bool {
self.invalidated || self.content.needs_relayout()
}
} }

View File

@ -8,4 +8,6 @@ pub struct DummyView;
impl View for DummyView { impl View for DummyView {
fn draw(&self, _: &Printer) {} fn draw(&self, _: &Printer) {}
fn needs_relayout(&self) -> bool { false }
} }

View File

@ -1,4 +1,5 @@
use view::{Selector, View, ViewWrapper}; use view::{Selector, View, ViewWrapper};
use vec::Vec2;
use With; use With;
use std::any::Any; use std::any::Any;
@ -14,6 +15,7 @@ use std::any::Any;
pub struct HideableView<V> { pub struct HideableView<V> {
view: V, view: V,
visible: bool, visible: bool,
invalidated: bool,
} }
impl<V> HideableView<V> { impl<V> HideableView<V> {
@ -24,12 +26,14 @@ impl<V> HideableView<V> {
HideableView { HideableView {
view, view,
visible: true, visible: true,
invalidated: true,
} }
} }
/// Sets the visibility for this view. /// Sets the visibility for this view.
pub fn set_visible(&mut self, visible: bool) { pub fn set_visible(&mut self, visible: bool) {
self.visible = visible; self.visible = visible;
self.invalidate();
} }
/// Sets the visibility for this view to `false`. /// Sets the visibility for this view to `false`.
@ -49,6 +53,10 @@ impl<V> HideableView<V> {
self.with(Self::hide) self.with(Self::hide)
} }
fn invalidate(&mut self) {
self.invalidated = true;
}
inner_getters!(self.view: V); inner_getters!(self.view: V);
} }
@ -91,4 +99,13 @@ impl<V: View> ViewWrapper for HideableView<V> {
{ {
Ok(self.view) Ok(self.view)
} }
fn wrap_layout(&mut self, size: Vec2) {
self.invalidated = false;
self.with_view_mut(|v| v.layout(size));
}
fn wrap_needs_relayout(&self) -> bool {
self.invalidated || (self.visible && self.view.needs_relayout())
}
} }

View File

@ -323,11 +323,13 @@ where
/// Returns `(inner_size, desired_size)` /// Returns `(inner_size, desired_size)`
fn sizes(&mut self, constraint: Vec2, strict: bool) -> (Vec2, Vec2) { fn sizes(&mut self, constraint: Vec2, strict: bool) -> (Vec2, Vec2) {
// First: try the cache // First: try the cache
if self if !self.inner.needs_relayout() && self
.size_cache .size_cache
.map(|cache| cache.zip_map(constraint, SizeCache::accept).both()) .map(|cache| cache.zip_map(constraint, SizeCache::accept).both())
.unwrap_or(false) .unwrap_or(false)
{ {
// eprintln!("Cache: {:?}; constraint: {:?}", self.size_cache, constraint);
// The new constraint shouldn't change much, // The new constraint shouldn't change much,
// so we can re-use previous values // so we can re-use previous values
return ( return (

View File

@ -336,12 +336,6 @@ impl TextView {
// Desired width // Desired width
self.width = self.rows.iter().map(|row| row.width).max(); self.width = self.rows.iter().map(|row| row.width).max();
// The entire "virtual" size (includes all rows)
let my_size = Vec2::new(self.width.unwrap_or(0), self.rows.len());
// Build a fresh cache.
content.size_cache = Some(SizeCache::build(my_size, size));
} }
// Invalidates the cache, so next call will recompute everything. // Invalidates the cache, so next call will recompute everything.
@ -390,5 +384,12 @@ impl View for TextView {
// Compute the text rows. // Compute the text rows.
self.last_size = size; self.last_size = size;
self.compute_rows(size); self.compute_rows(size);
// The entire "virtual" size (includes all rows)
let my_size = Vec2::new(self.width.unwrap_or(0), self.rows.len());
// Build a fresh cache.
let mut content = self.content.lock().unwrap();
content.size_cache = Some(SizeCache::build(my_size, size));
} }
} }