Add size cache to ScrollView

This commit is contained in:
Alexandre Bury 2018-06-20 11:44:09 -07:00
parent 5be79740ad
commit 9bd1eb320d
5 changed files with 65 additions and 48 deletions

View File

@ -24,7 +24,7 @@ pub mod pan;
/// threads, ncurses' signal handler is confused and he can't keep track of /// threads, ncurses' signal handler is confused and he can't keep track of
/// the terminal size. Poor ncurses. /// the terminal size. Poor ncurses.
fn terminal_size() -> Vec2 { fn terminal_size() -> Vec2 {
term_size::dimensions().unwrap_or((0,0)).into() term_size::dimensions().unwrap_or((0, 0)).into()
} }
fn split_i32(code: i32) -> Vec<u8> { fn split_i32(code: i32) -> Vec<u8> {

View File

@ -126,5 +126,6 @@ pub mod backend;
pub use cursive::{CbFunc, Cursive, ScreenId}; pub use cursive::{CbFunc, Cursive, ScreenId};
pub use printer::Printer; pub use printer::Printer;
pub use vec::Vec2;
pub use with::With; pub use with::With;
pub use xy::XY; pub use xy::XY;

View File

@ -52,9 +52,6 @@ impl SizeCache {
/// * `size` must fit inside `req`. /// * `size` must fit inside `req`.
/// * for each dimension, `constrained = (size == req)` /// * for each dimension, `constrained = (size == req)`
pub fn build(size: Vec2, req: Vec2) -> XY<Self> { pub fn build(size: Vec2, req: Vec2) -> XY<Self> {
XY::new( size.zip_map(req, |size, req| SizeCache::new(size, size >= req))
SizeCache::new(size.x, size.x >= req.x),
SizeCache::new(size.y, size.y >= req.y),
)
} }
} }

View File

@ -1,57 +1,57 @@
use std::cmp::min;
use direction::{Direction, Orientation}; use direction::{Direction, Orientation};
use event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent}; use event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent};
use rect::Rect; use rect::Rect;
use theme::ColorStyle; use theme::ColorStyle;
use vec::Vec2; use view::{Selector, SizeCache, View};
use view::{Selector, View}; use {Printer, Vec2, With, XY};
use xy::XY;
use Printer;
use With;
use std::cmp::min;
/// Wraps a view in a scrollable area. /// Wraps a view in a scrollable area.
pub struct ScrollView<V> { pub struct ScrollView<V> {
// The wrapped view. /// The wrapped view.
inner: V, inner: V,
// This is the size the child thinks we're giving him. /// This is the size the child thinks we're giving him.
inner_size: Vec2, inner_size: Vec2,
// Offset into the inner view. /// Offset into the inner view.
// ///
// Our `(0,0)` will be inner's `offset` /// Our `(0,0)` will be inner's `offset`
offset: Vec2, offset: Vec2,
// What was our own size last time we checked. /// What was our own size last time we checked.
// ///
// This includes scrollbars, if any. /// This includes scrollbars, if any.
last_size: Vec2, last_size: Vec2,
// Are we scrollable in each direction? /// Are we scrollable in each direction?
enabled: XY<bool>, enabled: XY<bool>,
// Should we show scrollbars? /// Should we show scrollbars?
// ///
// Even if this is true, no scrollbar will be printed if we don't need to /// Even if this is true, no scrollbar will be printed if we don't need to
// scroll. /// scroll.
// ///
// TODO: have an option to always show the scrollbar. /// TODO: have an option to always show the scrollbar.
// TODO: have an option to show scrollbar on top/left. /// TODO: have an option to show scrollbar on top/left.
show_scrollbars: bool, show_scrollbars: bool,
// How much padding should be between content and scrollbar? /// How much padding should be between content and scrollbar?
scrollbar_padding: Vec2, scrollbar_padding: Vec2,
/// Initial position of the cursor when dragging. /// Initial position of the cursor when dragging.
thumb_grab: Option<(Orientation, usize)>, thumb_grab: Option<(Orientation, usize)>,
/// We keep the cache here so it can be busted when we change the content.
size_cache: Option<XY<SizeCache>>,
} }
impl<V> ScrollView<V> { impl<V> ScrollView<V> {
/// Creates a new ScrollView around `view`. /// Creates a new ScrollView around `view`.
pub fn new(view: V) -> Self { pub fn new(inner: V) -> Self {
ScrollView { ScrollView {
inner: view, inner,
inner_size: Vec2::zero(), inner_size: Vec2::zero(),
offset: Vec2::zero(), offset: Vec2::zero(),
last_size: Vec2::zero(), last_size: Vec2::zero(),
@ -59,6 +59,7 @@ impl<V> ScrollView<V> {
show_scrollbars: true, show_scrollbars: true,
scrollbar_padding: Vec2::new(1, 0), scrollbar_padding: Vec2::new(1, 0),
thumb_grab: None, thumb_grab: None,
size_cache: None,
} }
} }
@ -81,6 +82,7 @@ impl<V> ScrollView<V> {
/// Defaults to `true`. /// Defaults to `true`.
pub fn set_scroll_y(&mut self, enabled: bool) { pub fn set_scroll_y(&mut self, enabled: bool) {
self.enabled.y = enabled; self.enabled.y = enabled;
self.invalidate_cache();
} }
/// Controls whether this view can scroll horizontally. /// Controls whether this view can scroll horizontally.
@ -88,6 +90,7 @@ impl<V> ScrollView<V> {
/// Defaults to `false`. /// Defaults to `false`.
pub fn set_scroll_x(&mut self, enabled: bool) { pub fn set_scroll_x(&mut self, enabled: bool) {
self.enabled.x = enabled; self.enabled.x = enabled;
self.invalidate_cache();
} }
/// Controls whether this view can scroll vertically. /// Controls whether this view can scroll vertically.
@ -134,6 +137,11 @@ impl<V> ScrollView<V> {
self.set_offset((max_x, curr_y)); self.set_offset((max_x, curr_y));
} }
/// Clears the cache.
fn invalidate_cache(&mut self) {
self.size_cache = None;
}
/// Returns for each axis if we are scrolling. /// Returns for each axis if we are scrolling.
fn is_scrolling(&self) -> XY<bool> { fn is_scrolling(&self) -> XY<bool> {
self.inner_size.zip_map(self.last_size, |i, s| i > s) self.inner_size.zip_map(self.last_size, |i, s| i > s)
@ -268,6 +276,7 @@ where
/// ///
/// Returns `(inner_size, size)` /// Returns `(inner_size, size)`
fn sizes(&mut self, constraint: Vec2) -> (Vec2, Vec2) { fn sizes(&mut self, constraint: Vec2) -> (Vec2, Vec2) {
// Attempt 1: try without scrollbars
let (inner_size, size, scrollable) = let (inner_size, size, scrollable) =
self.sizes_when_scrolling(constraint, XY::new(false, false)); self.sizes_when_scrolling(constraint, XY::new(false, false));
@ -308,6 +317,19 @@ where
let steps = (available + (1, 1)).saturating_sub(lengths); let steps = (available + (1, 1)).saturating_sub(lengths);
steps * self.offset / (self.inner_size + (1, 1) - available) steps * self.offset / (self.inner_size + (1, 1) - available)
} }
fn compute_inner_size(&mut self, constraint: Vec2) {
if self
.size_cache
.map(|cache| cache.zip_map(constraint, SizeCache::accept).both())
.unwrap_or(false)
{
return;
}
let (inner_size, _) = self.sizes(constraint);
self.inner_size = inner_size;
self.size_cache = Some(SizeCache::build(inner_size, constraint));
}
} }
impl<V> View for ScrollView<V> impl<V> View for ScrollView<V>
@ -524,25 +546,21 @@ where
// Size is final now // Size is final now
self.last_size = size; self.last_size = size;
let (inner_size, _) = self.sizes(size); self.compute_inner_size(size);
// Ask one more time
self.inner_size = inner_size;
self.inner.layout(self.inner_size); self.inner.layout(self.inner_size);
// The offset cannot be more than content - available // The offset cannot be more than (content - available)
self.offset = self self.offset = self
.offset .offset
.or_min(inner_size.saturating_sub(self.available_size())); .or_min(self.inner_size.saturating_sub(self.available_size()));
} }
fn needs_relayout(&self) -> bool { fn needs_relayout(&self) -> bool {
self.inner.needs_relayout() self.inner.needs_relayout() || self.size_cache.is_none()
} }
fn required_size(&mut self, constraint: Vec2) -> Vec2 { fn required_size(&mut self, constraint: Vec2) -> Vec2 {
// Attempt 1: try without scrollbars
let (_, size) = self.sizes(constraint); let (_, size) = self.sizes(constraint);
size size

View File

@ -1,19 +1,18 @@
use align::*;
use direction::Direction;
use event::*;
use owning_ref::{ArcRef, OwningHandle};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
use theme::Effect;
use owning_ref::{ArcRef, OwningHandle};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use align::*;
use direction::Direction;
use event::*;
use theme::Effect;
use utils::lines::spans::{LinesIterator, Row}; use utils::lines::spans::{LinesIterator, Row};
use utils::markup::StyledString; use utils::markup::StyledString;
use vec::Vec2;
use view::{ScrollBase, ScrollStrategy, SizeCache, View}; use view::{ScrollBase, ScrollStrategy, SizeCache, View};
use Printer; use {Printer, Vec2, With, XY};
use With;
use XY;
/// Provides access to the content of a [`TextView`]. /// Provides access to the content of a [`TextView`].
/// ///
@ -382,6 +381,8 @@ impl TextView {
return; return;
} }
eprintln!("RECOMPUTE");
// Completely bust the cache // Completely bust the cache
// Just in case we fail, we don't want to leave a bad cache. // Just in case we fail, we don't want to leave a bad cache.
content.size_cache = None; content.size_cache = None;