Add scroll strategy to ScrollView

This commit is contained in:
Alexandre Bury 2018-06-20 11:56:22 -07:00
parent 9bd1eb320d
commit 2935f0f569
2 changed files with 49 additions and 25 deletions

View File

@ -4,7 +4,7 @@ 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 view::{Selector, SizeCache, View}; use view::{ScrollStrategy, Selector, SizeCache, View};
use {Printer, Vec2, With, XY}; use {Printer, Vec2, With, XY};
/// Wraps a view in a scrollable area. /// Wraps a view in a scrollable area.
@ -45,9 +45,15 @@ pub struct ScrollView<V> {
/// 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>>,
/// Defines how to update the offset when the view size changes.
scroll_strategy: ScrollStrategy,
} }
impl<V> ScrollView<V> { impl<V> ScrollView<V>
where
V: View,
{
/// Creates a new ScrollView around `view`. /// Creates a new ScrollView around `view`.
pub fn new(inner: V) -> Self { pub fn new(inner: V) -> Self {
ScrollView { ScrollView {
@ -60,6 +66,7 @@ impl<V> ScrollView<V> {
scrollbar_padding: Vec2::new(1, 0), scrollbar_padding: Vec2::new(1, 0),
thumb_grab: None, thumb_grab: None,
size_cache: None, size_cache: None,
scroll_strategy: ScrollStrategy::KeepRow,
} }
} }
@ -68,6 +75,25 @@ impl<V> ScrollView<V> {
Rect::from_size(self.offset, self.available_size()) Rect::from_size(self.offset, self.available_size())
} }
/// Defines the way scrolling is adjusted on content or size change.
///
/// The scroll strategy defines how the scrolling position is adjusted
/// when the size of the view or the content change.
///
/// It is reset to `ScrollStrategy::KeepRow` whenever the user scrolls
/// manually.
pub fn set_scroll_strategy(&mut self, strategy: ScrollStrategy) {
self.scroll_strategy = strategy;
self.adjust_scroll();
}
/// Defines the way scrolling is adjusted on content or size change.
///
/// Chainable variant.
pub fn scroll_strategy(self, strategy: ScrollStrategy) -> Self {
self.with(|s| s.set_scroll_strategy(strategy))
}
/// Sets the scroll offset to the given value /// Sets the scroll offset to the given value
pub fn set_offset<S>(&mut self, offset: S) pub fn set_offset<S>(&mut self, offset: S)
where where
@ -165,12 +191,7 @@ impl<V> ScrollView<V> {
fn available_size(&self) -> Vec2 { fn available_size(&self) -> Vec2 {
self.last_size.saturating_sub(self.scrollbar_size()) self.last_size.saturating_sub(self.scrollbar_size())
} }
}
impl<V> ScrollView<V>
where
V: View,
{
/// Compute the size we would need. /// Compute the size we would need.
/// ///
/// Given the constraints, and the axis that need scrollbars. /// Given the constraints, and the axis that need scrollbars.
@ -330,6 +351,15 @@ where
self.inner_size = inner_size; self.inner_size = inner_size;
self.size_cache = Some(SizeCache::build(inner_size, constraint)); self.size_cache = Some(SizeCache::build(inner_size, constraint));
} }
/// Apply the scrolling strategy to the current scroll position.
fn adjust_scroll(&mut self) {
match self.scroll_strategy {
ScrollStrategy::StickToTop => self.scroll_to_top(),
ScrollStrategy::StickToBottom => self.scroll_to_bottom(),
ScrollStrategy::KeepRow => (),
}
}
} }
impl<V> View for ScrollView<V> impl<V> View for ScrollView<V>
@ -417,7 +447,6 @@ where
} if self.enabled.y && self.offset.y > 0 => } if self.enabled.y && self.offset.y > 0 =>
{ {
self.offset.y = self.offset.y.saturating_sub(3); self.offset.y = self.offset.y.saturating_sub(3);
EventResult::Consumed(None)
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::WheelDown, event: MouseEvent::WheelDown,
@ -432,7 +461,6 @@ where
.saturating_sub(self.available_size().y), .saturating_sub(self.available_size().y),
self.offset.y + 3, self.offset.y + 3,
); );
EventResult::Consumed(None)
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::Press(MouseButton::Left), event: MouseEvent::Press(MouseButton::Left),
@ -443,7 +471,7 @@ where
.map(|position| self.start_drag(position)) .map(|position| self.start_drag(position))
.unwrap_or(false) => .unwrap_or(false) =>
{ {
EventResult::Consumed(None) // Just consume the event.
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::Hold(MouseButton::Left), event: MouseEvent::Hold(MouseButton::Left),
@ -452,19 +480,16 @@ where
} => { } => {
let position = position.saturating_sub(offset); let position = position.saturating_sub(offset);
self.drag(position); self.drag(position);
EventResult::Consumed(None)
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::Release(MouseButton::Left), event: MouseEvent::Release(MouseButton::Left),
.. ..
} => { } => {
self.release_grab(); self.release_grab();
EventResult::Consumed(None)
} }
Event::Key(Key::Home) if self.enabled.any() => { Event::Key(Key::Home) if self.enabled.any() => {
self.offset = self.offset =
self.enabled.select_or(Vec2::zero(), self.offset); self.enabled.select_or(Vec2::zero(), self.offset);
EventResult::Consumed(None)
} }
Event::Key(Key::End) if self.enabled.any() => { Event::Key(Key::End) if self.enabled.any() => {
let max_offset = self let max_offset = self
@ -472,19 +497,16 @@ where
.saturating_sub(self.available_size()); .saturating_sub(self.available_size());
self.offset = self.offset =
self.enabled.select_or(max_offset, self.offset); self.enabled.select_or(max_offset, self.offset);
EventResult::Consumed(None)
} }
Event::Ctrl(Key::Up) | Event::Key(Key::Up) Event::Ctrl(Key::Up) | Event::Key(Key::Up)
if self.enabled.y && self.offset.y > 0 => if self.enabled.y && self.offset.y > 0 =>
{ {
self.offset.y -= 1; self.offset.y -= 1;
EventResult::Consumed(None)
} }
Event::Key(Key::PageUp) Event::Key(Key::PageUp)
if self.enabled.y && self.offset.y > 0 => if self.enabled.y && self.offset.y > 0 =>
{ {
self.offset.y = self.offset.y.saturating_sub(5); self.offset.y = self.offset.y.saturating_sub(5);
EventResult::Consumed(None)
} }
Event::Key(Key::PageDown) Event::Key(Key::PageDown)
if self.enabled.y if self.enabled.y
@ -492,7 +514,6 @@ where
< self.inner_size.y) => < self.inner_size.y) =>
{ {
self.offset.y += 5; self.offset.y += 5;
EventResult::Consumed(None)
} }
Event::Ctrl(Key::Down) | Event::Key(Key::Down) Event::Ctrl(Key::Down) | Event::Key(Key::Down)
if self.enabled.y if self.enabled.y
@ -500,13 +521,11 @@ where
< self.inner_size.y) => < self.inner_size.y) =>
{ {
self.offset.y += 1; self.offset.y += 1;
EventResult::Consumed(None)
} }
Event::Ctrl(Key::Left) | Event::Key(Key::Left) Event::Ctrl(Key::Left) | Event::Key(Key::Left)
if self.enabled.x && self.offset.x > 0 => if self.enabled.x && self.offset.x > 0 =>
{ {
self.offset.x -= 1; self.offset.x -= 1;
EventResult::Consumed(None)
} }
Event::Ctrl(Key::Right) | Event::Key(Key::Right) Event::Ctrl(Key::Right) | Event::Key(Key::Right)
if self.enabled.x if self.enabled.x
@ -514,11 +533,15 @@ where
< self.inner_size.x) => < self.inner_size.x) =>
{ {
self.offset.x += 1; self.offset.x += 1;
}
_ => return EventResult::Ignored,
};
// We just scrolled manually, so reset the scroll strategy.
self.scroll_strategy = ScrollStrategy::KeepRow;
// TODO: return callback on_scroll?
EventResult::Consumed(None) EventResult::Consumed(None)
} }
_ => EventResult::Ignored,
}
}
other => { other => {
// Fix offset? // Fix offset?
let important = self.inner.important_area(self.inner_size); let important = self.inner.important_area(self.inner_size);
@ -550,10 +573,13 @@ where
self.inner.layout(self.inner_size); self.inner.layout(self.inner_size);
// The offset cannot be more than (content - available) // Keep the offset in the valid range.
self.offset = self self.offset = self
.offset .offset
.or_min(self.inner_size.saturating_sub(self.available_size())); .or_min(self.inner_size.saturating_sub(self.available_size()));
// Possibly update the offset if we're following a specific strategy.
self.adjust_scroll();
} }
fn needs_relayout(&self) -> bool { fn needs_relayout(&self) -> bool {

View File

@ -381,8 +381,6 @@ 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;