Fix scroll::core size handling

This commit is contained in:
Alexandre Bury 2020-07-06 10:23:49 -07:00
parent 2e308d8d26
commit db9a3c32b1
5 changed files with 103 additions and 60 deletions

View File

@ -71,10 +71,10 @@ pub struct Core {
/// 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 the size available to print the child last time?
/// ///
/// This includes scrollbars, if any. /// Excludes any potential scrollbar.
last_size: Vec2, last_available: Vec2,
/// Are we scrollable in each direction? /// Are we scrollable in each direction?
enabled: XY<bool>, enabled: XY<bool>,
@ -97,7 +97,7 @@ pub struct Core {
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. /// We keep the cache here so it can be busted when we change the content.
size_cache: Option<XY<SizeCache>>, size_cache: Option<XY<SizeCache<bool>>>,
/// Defines how to update the offset when the view size changes. /// Defines how to update the offset when the view size changes.
scroll_strategy: ScrollStrategy, scroll_strategy: ScrollStrategy,
@ -115,7 +115,7 @@ impl Core {
Core { Core {
inner_size: Vec2::zero(), inner_size: Vec2::zero(),
offset: Vec2::zero(), offset: Vec2::zero(),
last_size: Vec2::zero(), last_available: Vec2::zero(),
enabled: XY::new(false, true), enabled: XY::new(false, true),
show_scrollbars: true, show_scrollbars: true,
scrollbar_padding: Vec2::new(1, 0), scrollbar_padding: Vec2::new(1, 0),
@ -345,13 +345,16 @@ impl Core {
} }
/// Specifies the size given in a layout phase. /// Specifies the size given in a layout phase.
pub(crate) fn set_last_size(&mut self, last_size: Vec2) { pub(crate) fn set_last_size(
self.last_size = last_size; &mut self,
} last_size: Vec2,
scrolling: XY<bool>,
/// Returns the size last given in `set_last_size()`. ) {
pub fn last_size(&self) -> Vec2 { self.last_available = last_size.saturating_sub(
self.last_size scrolling
.swap()
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero()),
);
} }
/// Specifies the size allocated to the content. /// Specifies the size allocated to the content.
@ -360,8 +363,14 @@ impl Core {
} }
/// Rebuild the cache with the given parameters. /// Rebuild the cache with the given parameters.
pub(crate) fn build_cache(&mut self, self_size: Vec2, last_size: Vec2) { pub(crate) fn build_cache(
self.size_cache = Some(SizeCache::build(self_size, last_size)); &mut self,
self_size: Vec2,
last_size: Vec2,
scrolling: XY<bool>,
) {
self.size_cache =
Some(SizeCache::build_extra(self_size, last_size, scrolling));
} }
/// Makes sure the viewport is within the content. /// Makes sure the viewport is within the content.
@ -532,7 +541,7 @@ impl Core {
/// Try to keep the given `rect` in view. /// Try to keep the given `rect` in view.
pub fn keep_in_view(&mut self, rect: Rect) { pub fn keep_in_view(&mut self, rect: Rect) {
let min = rect.bottom_right().saturating_sub(self.last_size); let min = rect.bottom_right().saturating_sub(self.available_size());
let max = rect.top_left(); let max = rect.top_left();
let (min, max) = (Vec2::min(min, max), Vec2::max(min, max)); let (min, max) = (Vec2::min(min, max), Vec2::max(min, max));
@ -558,7 +567,7 @@ impl Core {
/// Scroll until the given point is visible. /// Scroll until the given point is visible.
pub fn scroll_to(&mut self, pos: Vec2) { pub fn scroll_to(&mut self, pos: Vec2) {
// The furthest top-left we can go // The furthest top-left we can go
let min = pos.saturating_sub(self.last_size); let min = pos.saturating_sub(self.available_size());
// How far to the bottom-right we can go // How far to the bottom-right we can go
let max = pos; let max = pos;
@ -567,8 +576,8 @@ impl Core {
/// Scroll until the given column is visible. /// Scroll until the given column is visible.
pub fn scroll_to_x(&mut self, x: usize) { pub fn scroll_to_x(&mut self, x: usize) {
if x >= self.offset.x + self.last_size.x { if x >= self.offset.x + self.available_size().x {
self.offset.x = 1 + x - self.last_size.x; self.offset.x = 1 + x - self.available_size().x;
} else if x < self.offset.x { } else if x < self.offset.x {
self.offset.x = x; self.offset.x = x;
} }
@ -576,8 +585,8 @@ impl Core {
/// Scroll until the given row is visible. /// Scroll until the given row is visible.
pub fn scroll_to_y(&mut self, y: usize) { pub fn scroll_to_y(&mut self, y: usize) {
if y >= self.offset.y + self.last_size.y { if y >= self.offset.y + self.available_size().y {
self.offset.y = 1 + y - self.last_size.y; self.offset.y = 1 + y - self.available_size().y;
} else if y < self.offset.y { } else if y < self.offset.y {
self.offset.y = y; self.offset.y = y;
} }
@ -616,7 +625,7 @@ impl Core {
/// Returns for each axis if we are scrolling. /// Returns for each axis if we are scrolling.
pub fn is_scrolling(&self) -> XY<bool> { pub fn is_scrolling(&self) -> XY<bool> {
self.inner_size.zip_map(self.last_size, |i, s| i > s) self.inner_size.zip_map(self.available_size(), |i, s| i > s)
} }
/// Stops grabbing the scrollbar. /// Stops grabbing the scrollbar.
@ -637,11 +646,12 @@ impl Core {
/// Returns the size available for the child view. /// Returns the size available for the child view.
fn available_size(&self) -> Vec2 { fn available_size(&self) -> Vec2 {
if self.show_scrollbars { self.last_available
self.last_size.saturating_sub(self.scrollbar_size()) }
} else {
self.last_size /// Returns the last size given by `layout`.
} pub fn last_outer_size(&self) -> Vec2 {
self.available_size() + self.scrollbar_size()
} }
/// Starts scrolling from the cursor position. /// Starts scrolling from the cursor position.
@ -649,7 +659,7 @@ impl Core {
/// Returns `true` if the event was consumed. /// Returns `true` if the event was consumed.
fn start_drag(&mut self, position: Vec2) -> bool { fn start_drag(&mut self, position: Vec2) -> bool {
// For each scrollbar, how far it is. // For each scrollbar, how far it is.
let scrollbar_pos = self.last_size.saturating_sub((1, 1)); let scrollbar_pos = self.last_outer_size().saturating_sub((1, 1));
let lengths = self.scrollbar_thumb_lengths(); let lengths = self.scrollbar_thumb_lengths();
let offsets = self.scrollbar_thumb_offsets(lengths); let offsets = self.scrollbar_thumb_offsets(lengths);
let available = self.available_size(); let available = self.available_size();
@ -725,10 +735,17 @@ impl Core {
/// Tries to apply the cache to the current constraint. /// Tries to apply the cache to the current constraint.
/// ///
/// Returns the cached value if it works, or `None`. /// Returns the cached value if it works, or `None`.
pub(crate) fn try_cache(&self, constraint: Vec2) -> Option<(Vec2, Vec2)> { pub(crate) fn try_cache(
&self,
constraint: Vec2,
) -> Option<(Vec2, Vec2, XY<bool>)> {
self.size_cache.and_then(|cache| { self.size_cache.and_then(|cache| {
if cache.zip_map(constraint, SizeCache::accept).both() { if cache.zip_map(constraint, SizeCache::accept).both() {
Some((self.inner_size, cache.map(|c| c.value))) Some((
self.inner_size,
cache.map(|c| c.value),
cache.map(|c| c.extra),
))
} else { } else {
None None
} }

View File

@ -92,7 +92,7 @@ fn sizes<Model, GetScroller, RequiredSize>(
model: &mut Model, model: &mut Model,
get_scroller: &mut GetScroller, get_scroller: &mut GetScroller,
required_size: &mut RequiredSize, required_size: &mut RequiredSize,
) -> (Vec2, Vec2) ) -> (Vec2, Vec2, XY<bool>)
where where
Model: ?Sized, Model: ?Sized,
GetScroller: FnMut(&mut Model) -> &mut scroll::Core, GetScroller: FnMut(&mut Model) -> &mut scroll::Core,
@ -128,7 +128,7 @@ where
); );
if scrolling == new_scrolling { if scrolling == new_scrolling {
// Yup, scrolling did it. We're good to go now. // Yup, scrolling did it. We're good to go now.
(inner_size, size) (inner_size, size, scrolling)
} else { } else {
// Again? We're now scrolling in a new direction? // Again? We're now scrolling in a new direction?
// There is no end to this! // There is no end to this!
@ -143,12 +143,12 @@ where
// That's enough. If the inner view changed again, ignore it! // That's enough. If the inner view changed again, ignore it!
// That'll teach it. // That'll teach it.
(inner_size, size) (inner_size, size, new_scrolling)
} }
} else { } else {
// We're not showing any scrollbar, either because we don't scroll // We're not showing any scrollbar, either because we don't scroll
// or because scrollbars are hidden. // or because scrollbars are hidden.
(inner_size, size) (inner_size, size, scrolling)
} }
} }
@ -166,10 +166,8 @@ pub fn layout<Model, GetScroller, RequiredSize, Layout>(
RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, RequiredSize: FnMut(&mut Model, Vec2) -> Vec2,
Layout: FnMut(&mut Model, Vec2), Layout: FnMut(&mut Model, Vec2),
{ {
get_scroller(model).set_last_size(size);
// This is what we'd like // This is what we'd like
let (inner_size, self_size) = sizes( let (inner_size, self_size, scrolling) = sizes(
size, size,
true, true,
needs_relayout, needs_relayout,
@ -177,9 +175,9 @@ pub fn layout<Model, GetScroller, RequiredSize, Layout>(
&mut get_scroller, &mut get_scroller,
&mut required_size, &mut required_size,
); );
get_scroller(model).set_last_size(self_size, scrolling);
get_scroller(model).set_inner_size(inner_size); get_scroller(model).set_inner_size(inner_size);
get_scroller(model).build_cache(self_size, size); get_scroller(model).build_cache(self_size, size, scrolling);
layout(model, inner_size); layout(model, inner_size);
@ -199,7 +197,7 @@ where
GetScroller: FnMut(&mut Model) -> &mut scroll::Core, GetScroller: FnMut(&mut Model) -> &mut scroll::Core,
RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, RequiredSize: FnMut(&mut Model, Vec2) -> Vec2,
{ {
let (_, size) = sizes( let (_, size, _) = sizes(
constraint, constraint,
false, false,
needs_relayout, needs_relayout,

View File

@ -5,7 +5,7 @@ use crate::XY;
/// ///
/// This is not a View, but something to help you if you create your own Views. /// This is not a View, but something to help you if you create your own Views.
#[derive(PartialEq, Debug, Clone, Copy)] #[derive(PartialEq, Debug, Clone, Copy)]
pub struct SizeCache { pub struct SizeCache<T = ()> {
/// Cached value /// Cached value
pub value: usize, pub value: usize,
/// `true` if the last size was constrained. /// `true` if the last size was constrained.
@ -13,31 +13,21 @@ pub struct SizeCache {
/// If unconstrained, any request larger than this value /// If unconstrained, any request larger than this value
/// would return the same size. /// would return the same size.
pub constrained: bool, pub constrained: bool,
/// Extra field.
pub extra: T,
} }
impl SizeCache { impl SizeCache<()> {
/// Creates a new sized cache /// Creates a new sized cache
pub fn new(value: usize, constrained: bool) -> Self { pub fn new(value: usize, constrained: bool) -> Self {
SizeCache { value, constrained } SizeCache {
} value,
constrained,
/// Returns `true` if `self` is still valid for the given `request`. extra: (),
pub fn accept(self, request: usize) -> bool {
match (request, self.value) {
// Request a smaller size than last time? Hell no!
(r, v) if r < v => false,
// Request exactly what we had last time? Sure!
(r, v) if r == v => true,
// Request more than we had last time? Maybe?
_ => !self.constrained,
} }
} }
/// Returns the value in the cache.
pub fn value(self) -> usize {
self.value
}
/// Creates a new bi-dimensional cache. /// Creates a new bi-dimensional cache.
/// ///
/// It will stay valid for the same request, and compatible ones. /// It will stay valid for the same request, and compatible ones.
@ -56,3 +46,40 @@ impl SizeCache {
size.zip_map(req, |size, req| SizeCache::new(size, size >= req)) size.zip_map(req, |size, req| SizeCache::new(size, size >= req))
} }
} }
impl<T> SizeCache<T> {
/// Creates a new sized cache
pub fn new_extra(value: usize, constrained: bool, extra: T) -> Self {
Self {
value,
constrained,
extra,
}
}
/// Creates a new bi-dimensional cache.
///
/// Similar to `build()`, but includes the extra field.
pub fn build_extra(size: Vec2, req: Vec2, extra: XY<T>) -> XY<Self> {
XY::zip3(size, req, extra).map(|(size, req, extra)| {
SizeCache::new_extra(size, size >= req, extra)
})
}
/// Returns `true` if `self` is still valid for the given `request`.
pub fn accept(self, request: usize) -> bool {
match (request, self.value) {
// Request a smaller size than last time? Hell no!
(r, v) if r < v => false,
// Request exactly what we had last time? Sure!
(r, v) if r == v => true,
// Request more than we had last time? Maybe?
_ => !self.constrained,
}
}
/// Returns the value in the cache.
pub fn value(self) -> usize {
self.value
}
}

View File

@ -397,7 +397,7 @@ impl View for MenuPopup {
// Mouse clicks outside of the popup should dismiss it. // Mouse clicks outside of the popup should dismiss it.
if !position.fits_in_rect( if !position.fits_in_rect(
offset, offset,
self.scroll_core.last_size() + (2, 2), self.scroll_core.last_outer_size() + (2, 2),
) { ) {
let dismiss_cb = self.on_dismiss.clone(); let dismiss_cb = self.on_dismiss.clone();
return EventResult::with_cb(move |s| { return EventResult::with_cb(move |s| {

View File

@ -192,7 +192,8 @@ impl<V> ScrollView<V> {
where where
V: View, V: View,
{ {
let important_area = self.inner.important_area(self.core.last_size()); let important_area =
self.inner.important_area(self.core.last_outer_size());
self.core.scroll_to_rect(important_area); self.core.scroll_to_rect(important_area);
self.on_scroll_callback() self.on_scroll_callback()