mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Move logic from ScrollView
to ScrollCore
This commit is contained in:
parent
ef4448efe2
commit
6f28569dae
@ -224,7 +224,7 @@ impl Cursive {
|
|||||||
|
|
||||||
/// Show the debug console.
|
/// Show the debug console.
|
||||||
///
|
///
|
||||||
/// Currently, this will show logs if [`::logger::init()`] was called.
|
/// Currently, this will show logs if [`logger::init()`](crate::logger::init()) was called.
|
||||||
pub fn show_debug_console(&mut self) {
|
pub fn show_debug_console(&mut self) {
|
||||||
self.add_layer(
|
self.add_layer(
|
||||||
views::Dialog::around(views::ScrollView::new(views::IdView::new(
|
views::Dialog::around(views::ScrollView::new(views::IdView::new(
|
||||||
|
@ -52,7 +52,8 @@ impl log::Log for CursiveLogger {
|
|||||||
///
|
///
|
||||||
/// Make sure this is the only logger your are using.
|
/// Make sure this is the only logger your are using.
|
||||||
///
|
///
|
||||||
/// Use a [`::views::DebugView`] to see the logs, or use [`::Cursive::toggle_debug_console()`].
|
/// Use a [`DebugView`](crate::views::DebugView) to see the logs, or use
|
||||||
|
/// [`Cursive::toggle_debug_console()`](crate::Cursive::toggle_debug_console()).
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
// TODO: Configure the deque size?
|
// TODO: Configure the deque size?
|
||||||
LOGS.lock().unwrap().reserve(1_000);
|
LOGS.lock().unwrap().reserve(1_000);
|
||||||
|
@ -51,7 +51,8 @@ mod view_trait;
|
|||||||
// Helper bases
|
// Helper bases
|
||||||
mod boxable;
|
mod boxable;
|
||||||
mod identifiable;
|
mod identifiable;
|
||||||
mod scroll;
|
pub mod scroll;
|
||||||
|
mod scroll_base;
|
||||||
mod scrollable;
|
mod scrollable;
|
||||||
|
|
||||||
mod into_boxed_view;
|
mod into_boxed_view;
|
||||||
@ -63,7 +64,7 @@ pub use self::identifiable::Identifiable;
|
|||||||
pub use self::into_boxed_view::IntoBoxedView;
|
pub use self::into_boxed_view::IntoBoxedView;
|
||||||
pub use self::margins::Margins;
|
pub use self::margins::Margins;
|
||||||
pub use self::position::{Offset, Position};
|
pub use self::position::{Offset, Position};
|
||||||
pub use self::scroll::{ScrollBase, ScrollStrategy};
|
pub use self::scroll_base::{ScrollBase, ScrollStrategy};
|
||||||
pub use self::scrollable::Scrollable;
|
pub use self::scrollable::Scrollable;
|
||||||
pub use self::size_cache::SizeCache;
|
pub use self::size_cache::SizeCache;
|
||||||
pub use self::size_constraint::SizeConstraint;
|
pub use self::size_constraint::SizeConstraint;
|
||||||
|
1041
src/view/scroll.rs
1041
src/view/scroll.rs
File diff suppressed because it is too large
Load Diff
316
src/view/scroll_base.rs
Normal file
316
src/view/scroll_base.rs
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
use crate::div::div_up;
|
||||||
|
use crate::theme::ColorStyle;
|
||||||
|
use crate::vec::Vec2;
|
||||||
|
use crate::Printer;
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
/// Provide scrolling functionalities to a view.
|
||||||
|
///
|
||||||
|
/// You're not supposed to use this directly,
|
||||||
|
/// but it can be helpful if you create your own Views.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ScrollBase {
|
||||||
|
/// First line visible
|
||||||
|
pub start_line: usize,
|
||||||
|
|
||||||
|
/// Content height
|
||||||
|
pub content_height: usize,
|
||||||
|
|
||||||
|
/// Number of lines displayed
|
||||||
|
pub view_height: usize,
|
||||||
|
|
||||||
|
/// Padding for the scrollbar
|
||||||
|
///
|
||||||
|
/// If present, the scrollbar will be shifted
|
||||||
|
/// `scrollbar_offset` columns to the left.
|
||||||
|
///
|
||||||
|
/// (Useful when each item includes its own side borders,
|
||||||
|
/// to draw the scrollbar inside.)
|
||||||
|
pub scrollbar_offset: usize,
|
||||||
|
|
||||||
|
/// Blank between the text and the scrollbar.
|
||||||
|
pub right_padding: usize,
|
||||||
|
|
||||||
|
/// Initial position of the cursor when dragging.
|
||||||
|
pub thumb_grab: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the scrolling behaviour on content or size change
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ScrollStrategy {
|
||||||
|
/// Keeps the same row number
|
||||||
|
KeepRow,
|
||||||
|
/// Sticks to the top.
|
||||||
|
StickToTop,
|
||||||
|
/// Sticks to the bottom of the view.
|
||||||
|
StickToBottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ScrollStrategy {
|
||||||
|
fn default() -> Self {
|
||||||
|
ScrollStrategy::KeepRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollBase {
|
||||||
|
/// Creates a new, uninitialized scrollbar.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ScrollBase {
|
||||||
|
start_line: 0,
|
||||||
|
content_height: 0,
|
||||||
|
view_height: 0,
|
||||||
|
scrollbar_offset: 0,
|
||||||
|
right_padding: 1,
|
||||||
|
thumb_grab: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shifts the scrollbar toward the inside of the view.
|
||||||
|
///
|
||||||
|
/// Used by views that draw their side borders in the children.
|
||||||
|
/// Pushing the scrollbar to the left allows it to stay inside
|
||||||
|
/// the borders.
|
||||||
|
pub fn scrollbar_offset(mut self, offset: usize) -> Self {
|
||||||
|
self.scrollbar_offset = offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the number of blank cells between the text and the scrollbar.
|
||||||
|
///
|
||||||
|
/// Defaults to 1.
|
||||||
|
pub fn right_padding(mut self, padding: usize) -> Self {
|
||||||
|
self.right_padding = padding;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this method whem the content or the view changes.
|
||||||
|
pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
|
||||||
|
self.view_height = view_height;
|
||||||
|
self.content_height = content_height;
|
||||||
|
|
||||||
|
// eprintln!("Setting heights: {} in {}", content_height, view_height);
|
||||||
|
|
||||||
|
if self.scrollable() {
|
||||||
|
self.start_line =
|
||||||
|
min(self.start_line, self.content_height - self.view_height);
|
||||||
|
} else {
|
||||||
|
self.start_line = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `TRUE` if the view needs to scroll.
|
||||||
|
pub fn scrollable(&self) -> bool {
|
||||||
|
self.view_height < self.content_height
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `TRUE` unless we are at the top.
|
||||||
|
pub fn can_scroll_up(&self) -> bool {
|
||||||
|
self.start_line > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `TRUE` unless we are at the bottom.
|
||||||
|
pub fn can_scroll_down(&self) -> bool {
|
||||||
|
self.start_line + self.view_height < self.content_height
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll to the top of the view.
|
||||||
|
pub fn scroll_top(&mut self) {
|
||||||
|
self.start_line = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes sure that the given line is visible, scrolling if needed.
|
||||||
|
pub fn scroll_to(&mut self, y: usize) {
|
||||||
|
if y >= self.start_line + self.view_height {
|
||||||
|
self.start_line = 1 + y - self.view_height;
|
||||||
|
} else if y < self.start_line {
|
||||||
|
self.start_line = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll to the bottom of the view.
|
||||||
|
pub fn scroll_bottom(&mut self) {
|
||||||
|
if self.scrollable() {
|
||||||
|
self.start_line = self.content_height - self.view_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll down by the given number of line.
|
||||||
|
///
|
||||||
|
/// Never further than the bottom of the view.
|
||||||
|
pub fn scroll_down(&mut self, n: usize) {
|
||||||
|
if self.scrollable() {
|
||||||
|
self.start_line = min(
|
||||||
|
self.start_line + n,
|
||||||
|
self.content_height - self.view_height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scrolls down until the scrollbar thumb is at the given location.
|
||||||
|
pub fn scroll_to_thumb(&mut self, thumb_y: usize, thumb_height: usize) {
|
||||||
|
// The min() is there to stop at the bottom of the content.
|
||||||
|
// The saturating_sub is there to stop at the bottom of the content.
|
||||||
|
// eprintln!("Scrolling to {}", thumb_y);
|
||||||
|
self.start_line = min(
|
||||||
|
div_up(
|
||||||
|
(1 + self.content_height - self.view_height) * thumb_y,
|
||||||
|
self.view_height - thumb_height + 1,
|
||||||
|
),
|
||||||
|
self.content_height - self.view_height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll up by the given number of lines.
|
||||||
|
///
|
||||||
|
/// Never above the top of the view.
|
||||||
|
pub fn scroll_up(&mut self, n: usize) {
|
||||||
|
if self.scrollable() {
|
||||||
|
self.start_line -= min(self.start_line, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts scrolling from the given cursor position.
|
||||||
|
pub fn start_drag(&mut self, position: Vec2, width: usize) -> bool {
|
||||||
|
// First: are we on the correct column?
|
||||||
|
let scrollbar_x = self.scrollbar_x(width);
|
||||||
|
// eprintln!("Grabbed {} for {}", position.x, scrollbar_x);
|
||||||
|
if position.x != scrollbar_x {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, did we hit the thumb? Or should we direct-jump?
|
||||||
|
let height = self.scrollbar_thumb_height();
|
||||||
|
let thumb_y = self.scrollbar_thumb_y(height);
|
||||||
|
|
||||||
|
if position.y >= thumb_y && position.y < thumb_y + height {
|
||||||
|
// Grabbed!
|
||||||
|
self.thumb_grab = Some(position.y - thumb_y);
|
||||||
|
} else {
|
||||||
|
// Just jump a bit...
|
||||||
|
self.thumb_grab = Some((height - 1) / 2);
|
||||||
|
// eprintln!("Grabbed at {}", self.thumb_grab);
|
||||||
|
self.drag(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps scrolling by dragging the cursor.
|
||||||
|
pub fn drag(&mut self, position: Vec2) {
|
||||||
|
// Our goal is self.scrollbar_thumb_y()+thumb_grab == position.y
|
||||||
|
// Which means that position.y is the middle of the scrollbar.
|
||||||
|
// eprintln!("Dragged: {:?}", position);
|
||||||
|
// eprintln!("thumb: {:?}", self.thumb_grab);
|
||||||
|
if let Some(grab) = self.thumb_grab {
|
||||||
|
let height = self.scrollbar_thumb_height();
|
||||||
|
self.scroll_to_thumb(position.y.saturating_sub(grab), height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if we are in the process of dragging the scroll thumb.
|
||||||
|
pub fn is_dragging(&self) -> bool {
|
||||||
|
self.thumb_grab.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops grabbing the scrollbar.
|
||||||
|
pub fn release_grab(&mut self) {
|
||||||
|
self.thumb_grab = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws the scroll bar and the content using the given drawer.
|
||||||
|
///
|
||||||
|
/// `line_drawer` will be called once for each line that needs to be drawn.
|
||||||
|
/// It will be given the absolute ID of the item to draw..
|
||||||
|
/// It will also be given a printer with the correct offset,
|
||||||
|
/// so it should only print on the first line.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use cursive::view::ScrollBase;
|
||||||
|
/// # use cursive::Printer;
|
||||||
|
/// # use cursive::theme;
|
||||||
|
/// # use cursive::backend;
|
||||||
|
/// # let scrollbase = ScrollBase::new();
|
||||||
|
/// # let b = backend::dummy::Backend::init();
|
||||||
|
/// # let t = theme::load_default();
|
||||||
|
/// # let printer = Printer::new((5,1), &t, &*b);
|
||||||
|
/// # let printer = &printer;
|
||||||
|
/// let lines = ["Line 1", "Line number 2"];
|
||||||
|
/// scrollbase.draw(printer, |printer, i| {
|
||||||
|
/// printer.print((0,0), lines[i]);
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn draw<F>(&self, printer: &Printer<'_, '_>, line_drawer: F)
|
||||||
|
where
|
||||||
|
F: Fn(&Printer<'_, '_>, usize),
|
||||||
|
{
|
||||||
|
if self.view_height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Print the content in a sub_printer
|
||||||
|
let max_y =
|
||||||
|
min(self.view_height, self.content_height - self.start_line);
|
||||||
|
let w = if self.scrollable() {
|
||||||
|
// We have to remove the bar width and the padding.
|
||||||
|
printer.size.x.saturating_sub(1 + self.right_padding)
|
||||||
|
} else {
|
||||||
|
printer.size.x
|
||||||
|
};
|
||||||
|
|
||||||
|
for y in 0..max_y {
|
||||||
|
// Y is the actual coordinate of the line.
|
||||||
|
// The item ID is then Y + self.start_line
|
||||||
|
line_drawer(
|
||||||
|
&printer.offset((0, y)).cropped((w, 1)),
|
||||||
|
y + self.start_line,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And draw the scrollbar if needed
|
||||||
|
if self.view_height < self.content_height {
|
||||||
|
// We directly compute the size of the scrollbar
|
||||||
|
// (that way we avoid using floats).
|
||||||
|
// (ratio) * max_height
|
||||||
|
// Where ratio is ({start or end} / content.height)
|
||||||
|
let height = self.scrollbar_thumb_height();
|
||||||
|
let start = self.scrollbar_thumb_y(height);
|
||||||
|
|
||||||
|
let color = if printer.focused {
|
||||||
|
ColorStyle::highlight()
|
||||||
|
} else {
|
||||||
|
ColorStyle::highlight_inactive()
|
||||||
|
};
|
||||||
|
|
||||||
|
let scrollbar_x = self.scrollbar_x(printer.size.x);
|
||||||
|
// eprintln!("Drawing bar at x={}", scrollbar_x);
|
||||||
|
|
||||||
|
// The background
|
||||||
|
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
|
||||||
|
|
||||||
|
// The scrollbar thumb
|
||||||
|
printer.with_color(color, |printer| {
|
||||||
|
printer.print_vline((scrollbar_x, start), height, "▒");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the X position of the scrollbar, given the size available.
|
||||||
|
///
|
||||||
|
/// Note that this does not depend whether or
|
||||||
|
/// not a scrollbar will actually be present.
|
||||||
|
pub fn scrollbar_x(&self, total_size: usize) -> usize {
|
||||||
|
total_size.saturating_sub(1 + self.scrollbar_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the height of the scrollbar thumb.
|
||||||
|
pub fn scrollbar_thumb_height(&self) -> usize {
|
||||||
|
max(1, self.view_height * self.view_height / self.content_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the y position of the scrollbar thumb.
|
||||||
|
pub fn scrollbar_thumb_y(&self, scrollbar_thumb_height: usize) -> usize {
|
||||||
|
let steps = self.view_height - scrollbar_thumb_height + 1;
|
||||||
|
steps * self.start_line / (1 + self.content_height - self.view_height)
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ use crate::views::ScrollView;
|
|||||||
|
|
||||||
/// Makes a view wrappable in a [`ScrollView`].
|
/// Makes a view wrappable in a [`ScrollView`].
|
||||||
///
|
///
|
||||||
/// [`ScrollView`]: ::views::ScrollView
|
/// [`ScrollView`]: crate::views::ScrollView
|
||||||
pub trait Scrollable: View + Sized {
|
pub trait Scrollable: View + Sized {
|
||||||
/// Wraps `self` in a `ScrollView`.
|
/// Wraps `self` in a `ScrollView`.
|
||||||
fn scrollable(self) -> ScrollView<Self> {
|
fn scrollable(self) -> ScrollView<Self> {
|
||||||
|
@ -1,55 +1,15 @@
|
|||||||
use std::cmp::min;
|
use crate::direction::Direction;
|
||||||
|
use crate::event::{AnyCb, Event, EventResult};
|
||||||
use crate::direction::{Direction, Orientation};
|
|
||||||
use crate::event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent};
|
|
||||||
use crate::rect::Rect;
|
use crate::rect::Rect;
|
||||||
use crate::theme::ColorStyle;
|
use crate::view::{scroll, ScrollStrategy, Selector, View};
|
||||||
use crate::view::{ScrollStrategy, Selector, SizeCache, View};
|
use crate::{Printer, Vec2, With};
|
||||||
use crate::{Printer, Vec2, With, XY};
|
|
||||||
|
|
||||||
/// 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.
|
core: scroll::ScrollCore,
|
||||||
inner_size: Vec2,
|
|
||||||
|
|
||||||
/// Offset into the inner view.
|
|
||||||
///
|
|
||||||
/// Our `(0,0)` will be inner's `offset`
|
|
||||||
offset: Vec2,
|
|
||||||
|
|
||||||
/// What was our own size last time we checked.
|
|
||||||
///
|
|
||||||
/// This includes scrollbars, if any.
|
|
||||||
last_size: Vec2,
|
|
||||||
|
|
||||||
/// Are we scrollable in each direction?
|
|
||||||
enabled: XY<bool>,
|
|
||||||
|
|
||||||
/// Should we show scrollbars?
|
|
||||||
///
|
|
||||||
/// Even if this is true, no scrollbar will be printed if we don't need to
|
|
||||||
/// scroll.
|
|
||||||
///
|
|
||||||
/// TODO: have an option to always show the scrollbar.
|
|
||||||
/// TODO: have an option to show scrollbar on top/left.
|
|
||||||
show_scrollbars: bool,
|
|
||||||
|
|
||||||
/// How much padding should be between content and scrollbar?
|
|
||||||
///
|
|
||||||
/// scrollbar_padding.x is the horizontal padding before the vertical scrollbar.
|
|
||||||
scrollbar_padding: Vec2,
|
|
||||||
|
|
||||||
/// Initial position of the cursor when dragging.
|
|
||||||
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>>,
|
|
||||||
|
|
||||||
/// Defines how to update the offset when the view size changes.
|
|
||||||
scroll_strategy: ScrollStrategy,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> ScrollView<V>
|
impl<V> ScrollView<V>
|
||||||
@ -60,21 +20,13 @@ where
|
|||||||
pub fn new(inner: V) -> Self {
|
pub fn new(inner: V) -> Self {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
inner,
|
inner,
|
||||||
inner_size: Vec2::zero(),
|
core: scroll::ScrollCore::new(),
|
||||||
offset: Vec2::zero(),
|
|
||||||
last_size: Vec2::zero(),
|
|
||||||
enabled: XY::new(false, true),
|
|
||||||
show_scrollbars: true,
|
|
||||||
scrollbar_padding: Vec2::new(1, 0),
|
|
||||||
thumb_grab: None,
|
|
||||||
size_cache: None,
|
|
||||||
scroll_strategy: ScrollStrategy::KeepRow,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the viewport in the inner content.
|
/// Returns the viewport in the inner content.
|
||||||
pub fn content_viewport(&self) -> Rect {
|
pub fn content_viewport(&self) -> Rect {
|
||||||
Rect::from_size(self.offset, self.available_size())
|
self.core.content_viewport()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the way scrolling is adjusted on content or size change.
|
/// Defines the way scrolling is adjusted on content or size change.
|
||||||
@ -85,8 +37,7 @@ where
|
|||||||
/// It is reset to `ScrollStrategy::KeepRow` whenever the user scrolls
|
/// It is reset to `ScrollStrategy::KeepRow` whenever the user scrolls
|
||||||
/// manually.
|
/// manually.
|
||||||
pub fn set_scroll_strategy(&mut self, strategy: ScrollStrategy) {
|
pub fn set_scroll_strategy(&mut self, strategy: ScrollStrategy) {
|
||||||
self.scroll_strategy = strategy;
|
self.core.set_scroll_strategy(strategy);
|
||||||
self.adjust_scroll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines the way scrolling is adjusted on content or size change.
|
/// Defines the way scrolling is adjusted on content or size change.
|
||||||
@ -100,7 +51,7 @@ where
|
|||||||
///
|
///
|
||||||
/// Defaults to `true`.
|
/// Defaults to `true`.
|
||||||
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool) {
|
pub fn set_show_scrollbars(&mut self, show_scrollbars: bool) {
|
||||||
self.show_scrollbars = show_scrollbars;
|
self.core.set_show_scrollbars(show_scrollbars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control whether scroll bars are visibile.
|
/// Control whether scroll bars are visibile.
|
||||||
@ -115,24 +66,21 @@ where
|
|||||||
where
|
where
|
||||||
S: Into<Vec2>,
|
S: Into<Vec2>,
|
||||||
{
|
{
|
||||||
let max_offset = self.inner_size.saturating_sub(self.available_size());
|
self.core.set_offset(offset);
|
||||||
self.offset = offset.into().or_min(max_offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls whether this view can scroll vertically.
|
/// Controls whether this view can scroll vertically.
|
||||||
///
|
///
|
||||||
/// 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.core.set_scroll_y(enabled);
|
||||||
self.invalidate_cache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls whether this view can scroll horizontally.
|
/// Controls whether this view can scroll horizontally.
|
||||||
///
|
///
|
||||||
/// 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.core.set_scroll_x(enabled);
|
||||||
self.invalidate_cache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls whether this view can scroll vertically.
|
/// Controls whether this view can scroll vertically.
|
||||||
@ -155,272 +103,22 @@ where
|
|||||||
|
|
||||||
/// Programmatically scroll to the top of the view.
|
/// Programmatically scroll to the top of the view.
|
||||||
pub fn scroll_to_top(&mut self) {
|
pub fn scroll_to_top(&mut self) {
|
||||||
let curr_x = self.offset.x;
|
self.core.scroll_to_top();
|
||||||
self.set_offset((curr_x, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Programmatically scroll to the bottom of the view.
|
/// Programmatically scroll to the bottom of the view.
|
||||||
pub fn scroll_to_bottom(&mut self) {
|
pub fn scroll_to_bottom(&mut self) {
|
||||||
let max_y = self.inner_size.saturating_sub(self.available_size()).y;
|
self.core.scroll_to_bottom();
|
||||||
let curr_x = self.offset.x;
|
|
||||||
self.set_offset((curr_x, max_y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Programmatically scroll to the leftmost side of the view.
|
/// Programmatically scroll to the leftmost side of the view.
|
||||||
pub fn scroll_to_left(&mut self) {
|
pub fn scroll_to_left(&mut self) {
|
||||||
let curr_y = self.offset.y;
|
self.core.scroll_to_left();
|
||||||
self.set_offset((0, curr_y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Programmatically scroll to the rightmost side of the view.
|
/// Programmatically scroll to the rightmost side of the view.
|
||||||
pub fn scroll_to_right(&mut self) {
|
pub fn scroll_to_right(&mut self) {
|
||||||
let max_x = self.inner_size.saturating_sub(self.available_size()).x;
|
self.core.scroll_to_right();
|
||||||
let curr_y = self.offset.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.
|
|
||||||
fn is_scrolling(&self) -> XY<bool> {
|
|
||||||
self.inner_size.zip_map(self.last_size, |i, s| i > s)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stops grabbing the scrollbar.
|
|
||||||
fn release_grab(&mut self) {
|
|
||||||
self.thumb_grab = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size taken by the scrollbars.
|
|
||||||
///
|
|
||||||
/// Will be zero in axis where we're not scrolling.
|
|
||||||
///
|
|
||||||
/// The scrollbar_size().x will be the horizontal space taken by the vertical scrollbar.
|
|
||||||
fn scrollbar_size(&self) -> Vec2 {
|
|
||||||
self.is_scrolling()
|
|
||||||
.swap()
|
|
||||||
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size available for the child view.
|
|
||||||
fn available_size(&self) -> Vec2 {
|
|
||||||
if self.show_scrollbars {
|
|
||||||
self.last_size.saturating_sub(self.scrollbar_size())
|
|
||||||
} else {
|
|
||||||
self.last_size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the size we would need.
|
|
||||||
///
|
|
||||||
/// Given the constraints, and the axis that need scrollbars.
|
|
||||||
///
|
|
||||||
/// Returns `(inner_size, size, scrollable)`.
|
|
||||||
fn sizes_when_scrolling(
|
|
||||||
&mut self, constraint: Vec2, scrollable: XY<bool>, strict: bool,
|
|
||||||
) -> (Vec2, Vec2, XY<bool>) {
|
|
||||||
// This is the size taken by the scrollbars.
|
|
||||||
let scrollbar_size = scrollable
|
|
||||||
.swap()
|
|
||||||
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero());
|
|
||||||
|
|
||||||
let available = constraint.saturating_sub(scrollbar_size);
|
|
||||||
|
|
||||||
// This the ideal size for the child. May not be what he gets.
|
|
||||||
let inner_size = self.inner.required_size(available);
|
|
||||||
|
|
||||||
// Where we're "enabled", accept the constraints.
|
|
||||||
// Where we're not, just forward inner_size.
|
|
||||||
let size = self.enabled.select_or(
|
|
||||||
Vec2::min(inner_size + scrollbar_size, constraint),
|
|
||||||
inner_size + scrollbar_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
// In strict mode, there's no way our size is over constraints.
|
|
||||||
let size = if strict {
|
|
||||||
size.or_min(constraint)
|
|
||||||
} else {
|
|
||||||
size
|
|
||||||
};
|
|
||||||
|
|
||||||
// On non-scrolling axis, give inner_size the available space instead.
|
|
||||||
let inner_size = self
|
|
||||||
.enabled
|
|
||||||
.select_or(inner_size, size.saturating_sub(scrollbar_size));
|
|
||||||
|
|
||||||
let new_scrollable = inner_size.zip_map(size, |i, s| i > s);
|
|
||||||
|
|
||||||
(inner_size, size, new_scrollable)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts scrolling from the cursor position.
|
|
||||||
///
|
|
||||||
/// Returns `true` if the event was consumed.
|
|
||||||
fn start_drag(&mut self, position: Vec2) -> bool {
|
|
||||||
// For each scrollbar, how far it is.
|
|
||||||
let scrollbar_pos = self.last_size.saturating_sub((1, 1));
|
|
||||||
let lengths = self.scrollbar_thumb_lengths();
|
|
||||||
let offsets = self.scrollbar_thumb_offsets(lengths);
|
|
||||||
let available = self.available_size();
|
|
||||||
|
|
||||||
// This is true for Y if we grabbed the vertical scrollbar
|
|
||||||
// More specifically, we need both (for instance for the vertical bar):
|
|
||||||
// * To be in the right column: X == scrollbar_pos
|
|
||||||
// * To be in the right range: Y < available
|
|
||||||
let grabbed = position
|
|
||||||
.zip_map(scrollbar_pos, |p, s| p == s)
|
|
||||||
.swap()
|
|
||||||
.and(position.zip_map(available, |p, a| p < a));
|
|
||||||
|
|
||||||
// Iterate on axises, and keep the one we grabbed.
|
|
||||||
if let Some((orientation, pos, length, offset)) =
|
|
||||||
XY::zip4(Orientation::pair(), position, lengths, offsets)
|
|
||||||
.keep(grabbed.and(self.enabled))
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x)
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
if pos >= offset && pos < offset + length {
|
|
||||||
// We grabbed the thumb! Now scroll from that position.
|
|
||||||
self.thumb_grab = Some((orientation, pos - offset));
|
|
||||||
} else {
|
|
||||||
// We hit the scrollbar, outside of the thumb.
|
|
||||||
// Let's move the middle there.
|
|
||||||
self.thumb_grab = Some((orientation, (length - 1) / 2));
|
|
||||||
self.drag(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a mouse drag is detected.
|
|
||||||
fn drag(&mut self, position: Vec2) {
|
|
||||||
// Only do something if we grabbed something before.
|
|
||||||
if let Some((orientation, grab)) = self.thumb_grab {
|
|
||||||
self.scroll_to_thumb(
|
|
||||||
orientation,
|
|
||||||
position.get(orientation).saturating_sub(grab),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scroll_to_thumb(&mut self, orientation: Orientation, thumb_pos: usize) {
|
|
||||||
let lengths = self.scrollbar_thumb_lengths();
|
|
||||||
let available = self.available_size();
|
|
||||||
|
|
||||||
// We want self.scrollbar_thumb_offsets() to be thumb_pos
|
|
||||||
// steps * self.o / (self.inner + 1 - available) = thumb_pos
|
|
||||||
// self.o = thumb_pos * (self.inner + 1 - available) / (available + 1 - lengths)
|
|
||||||
|
|
||||||
// The new offset is:
|
|
||||||
// thumb_pos * (content + 1 - available) / (available + 1 - thumb size)
|
|
||||||
let extra =
|
|
||||||
(available + (1, 1)).saturating_sub(lengths).or_max((1, 1));
|
|
||||||
|
|
||||||
// We're dividing by this value, so make sure it's positive!
|
|
||||||
assert!(extra > Vec2::zero());
|
|
||||||
|
|
||||||
let new_offset =
|
|
||||||
((self.inner_size + (1, 1)).saturating_sub(available) * thumb_pos)
|
|
||||||
.div_up(extra);
|
|
||||||
let max_offset = self.inner_size.saturating_sub(self.available_size());
|
|
||||||
self.offset
|
|
||||||
.set_axis_from(orientation, &new_offset.or_min(max_offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes the size we would need given the constraints.
|
|
||||||
///
|
|
||||||
/// First be optimistic and try without scrollbars.
|
|
||||||
/// Then try with scrollbars if needed.
|
|
||||||
/// Then try again in case we now need to scroll both ways (!!!)
|
|
||||||
///
|
|
||||||
/// Returns `(inner_size, desired_size)`
|
|
||||||
fn sizes(&mut self, constraint: Vec2, strict: bool) -> (Vec2, Vec2) {
|
|
||||||
// First: try the cache
|
|
||||||
let valid_cache = !self.inner.needs_relayout()
|
|
||||||
&& self
|
|
||||||
.size_cache
|
|
||||||
.map(|cache| {
|
|
||||||
cache.zip_map(constraint, SizeCache::accept).both()
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if valid_cache {
|
|
||||||
// eprintln!("Cache: {:?}; constraint: {:?}", self.size_cache, constraint);
|
|
||||||
|
|
||||||
// The new constraint shouldn't change much,
|
|
||||||
// so we can re-use previous values
|
|
||||||
return (
|
|
||||||
self.inner_size,
|
|
||||||
self.size_cache.unwrap().map(|c| c.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt 1: try without scrollbars
|
|
||||||
let (inner_size, size, scrollable) = self.sizes_when_scrolling(
|
|
||||||
constraint,
|
|
||||||
XY::new(false, false),
|
|
||||||
strict,
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we need to add scrollbars, the available size will change.
|
|
||||||
if scrollable.any() && self.show_scrollbars {
|
|
||||||
// Attempt 2: he wants to scroll? Sure!
|
|
||||||
// Try again with some space for the scrollbar.
|
|
||||||
let (inner_size, size, new_scrollable) =
|
|
||||||
self.sizes_when_scrolling(constraint, scrollable, strict);
|
|
||||||
if scrollable == new_scrollable {
|
|
||||||
// Yup, scrolling did it. We're good to go now.
|
|
||||||
(inner_size, size)
|
|
||||||
} else {
|
|
||||||
// Again? We're now scrolling in a new direction?
|
|
||||||
// There is no end to this!
|
|
||||||
let (inner_size, size, _) = self.sizes_when_scrolling(
|
|
||||||
constraint,
|
|
||||||
new_scrollable,
|
|
||||||
strict,
|
|
||||||
);
|
|
||||||
|
|
||||||
// That's enough. If the inner view changed again, ignore it!
|
|
||||||
// That'll teach it.
|
|
||||||
(inner_size, size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We're not showing any scrollbar, either because we don't scroll
|
|
||||||
// or because scrollbars are hidden.
|
|
||||||
(inner_size, size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scrollbar_thumb_lengths(&self) -> Vec2 {
|
|
||||||
let available = self.available_size();
|
|
||||||
// The length should be (visible / total) * visible
|
|
||||||
|
|
||||||
(available * available / self.inner_size.or_max((1, 1))).or_max((1, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scrollbar_thumb_offsets(&self, lengths: Vec2) -> Vec2 {
|
|
||||||
let available = self.available_size();
|
|
||||||
// The number of steps is 1 + the "extra space"
|
|
||||||
let steps = (available + (1, 1)).saturating_sub(lengths);
|
|
||||||
let max_offset = self.inner_size.saturating_sub(available) + (1, 1);
|
|
||||||
|
|
||||||
steps * self.offset / max_offset
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the wrapped view.
|
/// Returns the wrapped view.
|
||||||
@ -436,259 +134,23 @@ where
|
|||||||
V: View,
|
V: View,
|
||||||
{
|
{
|
||||||
fn draw(&self, printer: &Printer<'_, '_>) {
|
fn draw(&self, printer: &Printer<'_, '_>) {
|
||||||
// Draw scrollbar?
|
self.core.draw(printer, &self.inner);
|
||||||
let scrolling = self.is_scrolling();
|
|
||||||
|
|
||||||
let lengths = self.scrollbar_thumb_lengths();
|
|
||||||
let offsets = self.scrollbar_thumb_offsets(lengths);
|
|
||||||
|
|
||||||
let line_c = XY::new("-", "|");
|
|
||||||
|
|
||||||
let color = if printer.focused {
|
|
||||||
ColorStyle::highlight()
|
|
||||||
} else {
|
|
||||||
ColorStyle::highlight_inactive()
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = self.available_size();
|
|
||||||
|
|
||||||
// Draw the scrollbars
|
|
||||||
XY::zip5(lengths, offsets, size, line_c, Orientation::pair()).run_if(
|
|
||||||
scrolling,
|
|
||||||
|(length, offset, size, c, orientation)| {
|
|
||||||
let start = printer
|
|
||||||
.size
|
|
||||||
.saturating_sub((1, 1))
|
|
||||||
.with_axis(orientation, 0);
|
|
||||||
let offset = orientation.make_vec(offset, 0);
|
|
||||||
|
|
||||||
printer.print_line(orientation, start, size, c);
|
|
||||||
|
|
||||||
let thumb_c = if self
|
|
||||||
.thumb_grab
|
|
||||||
.map(|(o, _)| o == orientation)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
" "
|
|
||||||
} else {
|
|
||||||
"▒"
|
|
||||||
};
|
|
||||||
printer.with_color(color, |printer| {
|
|
||||||
printer.print_line(
|
|
||||||
orientation,
|
|
||||||
start + offset,
|
|
||||||
length,
|
|
||||||
thumb_c,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Draw the X between the two scrollbars.
|
|
||||||
if scrolling.both() {
|
|
||||||
printer.print(printer.size.saturating_sub((1, 1)), "╳");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw content
|
|
||||||
let printer = printer
|
|
||||||
.cropped(size)
|
|
||||||
.content_offset(self.offset)
|
|
||||||
.inner_size(self.inner_size);
|
|
||||||
self.inner.draw(&printer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
fn on_event(&mut self, event: Event) -> EventResult {
|
||||||
// Relativize event accorging to the offset
|
self.core.on_event(event, &mut self.inner)
|
||||||
let mut relative_event = event.clone();
|
|
||||||
|
|
||||||
// Should the event be treated inside, by the inner view?
|
|
||||||
let inside = if let Event::Mouse {
|
|
||||||
ref mut position,
|
|
||||||
ref offset,
|
|
||||||
..
|
|
||||||
} = relative_event
|
|
||||||
{
|
|
||||||
// For mouse events, check if it falls inside the available area
|
|
||||||
let inside = position
|
|
||||||
.checked_sub(offset)
|
|
||||||
.map(|p| p.fits_in(self.available_size()))
|
|
||||||
.unwrap_or(false);
|
|
||||||
*position = *position + self.offset;
|
|
||||||
inside
|
|
||||||
} else {
|
|
||||||
// For key events, assume it's inside by default.
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = if inside {
|
|
||||||
// If the event is inside, give it to the child.
|
|
||||||
self.inner.on_event(relative_event)
|
|
||||||
} else {
|
|
||||||
// Otherwise, pretend it wasn't there.
|
|
||||||
EventResult::Ignored
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
EventResult::Ignored => {
|
|
||||||
// If it's an arrow, try to scroll in the given direction.
|
|
||||||
// If it's a mouse scroll, try to scroll as well.
|
|
||||||
// Also allow Ctrl+arrow to move the view,
|
|
||||||
// but not the selection.
|
|
||||||
match event {
|
|
||||||
Event::Mouse {
|
|
||||||
event: MouseEvent::WheelUp,
|
|
||||||
..
|
|
||||||
} if self.enabled.y && self.offset.y > 0 => {
|
|
||||||
self.offset.y = self.offset.y.saturating_sub(3);
|
|
||||||
}
|
|
||||||
Event::Mouse {
|
|
||||||
event: MouseEvent::WheelDown,
|
|
||||||
..
|
|
||||||
} if self.enabled.y
|
|
||||||
&& (self.offset.y + self.available_size().y
|
|
||||||
< self.inner_size.y) =>
|
|
||||||
{
|
|
||||||
self.offset.y = min(
|
|
||||||
self.inner_size
|
|
||||||
.y
|
|
||||||
.saturating_sub(self.available_size().y),
|
|
||||||
self.offset.y + 3,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::Mouse {
|
|
||||||
event: MouseEvent::Press(MouseButton::Left),
|
|
||||||
position,
|
|
||||||
offset,
|
|
||||||
} if self.show_scrollbars
|
|
||||||
&& position
|
|
||||||
.checked_sub(offset)
|
|
||||||
.map(|position| self.start_drag(position))
|
|
||||||
.unwrap_or(false) =>
|
|
||||||
{
|
|
||||||
// Just consume the event.
|
|
||||||
}
|
|
||||||
Event::Mouse {
|
|
||||||
event: MouseEvent::Hold(MouseButton::Left),
|
|
||||||
position,
|
|
||||||
offset,
|
|
||||||
} if self.show_scrollbars => {
|
|
||||||
let position = position.saturating_sub(offset);
|
|
||||||
self.drag(position);
|
|
||||||
}
|
|
||||||
Event::Mouse {
|
|
||||||
event: MouseEvent::Release(MouseButton::Left),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
self.release_grab();
|
|
||||||
}
|
|
||||||
Event::Key(Key::Home) if self.enabled.any() => {
|
|
||||||
self.offset =
|
|
||||||
self.enabled.select_or(Vec2::zero(), self.offset);
|
|
||||||
}
|
|
||||||
Event::Key(Key::End) if self.enabled.any() => {
|
|
||||||
let max_offset = self
|
|
||||||
.inner_size
|
|
||||||
.saturating_sub(self.available_size());
|
|
||||||
self.offset =
|
|
||||||
self.enabled.select_or(max_offset, self.offset);
|
|
||||||
}
|
|
||||||
Event::Ctrl(Key::Up) | Event::Key(Key::Up)
|
|
||||||
if self.enabled.y && self.offset.y > 0 =>
|
|
||||||
{
|
|
||||||
self.offset.y -= 1;
|
|
||||||
}
|
|
||||||
Event::Key(Key::PageUp)
|
|
||||||
if self.enabled.y && self.offset.y > 0 =>
|
|
||||||
{
|
|
||||||
self.offset.y = self.offset.y.saturating_sub(5);
|
|
||||||
}
|
|
||||||
Event::Key(Key::PageDown)
|
|
||||||
if self.enabled.y
|
|
||||||
&& (self.offset.y + self.available_size().y
|
|
||||||
< self.inner_size.y) =>
|
|
||||||
{
|
|
||||||
self.offset.y += 5;
|
|
||||||
}
|
|
||||||
Event::Ctrl(Key::Down) | Event::Key(Key::Down)
|
|
||||||
if self.enabled.y
|
|
||||||
&& (self.offset.y + self.available_size().y
|
|
||||||
< self.inner_size.y) =>
|
|
||||||
{
|
|
||||||
self.offset.y += 1;
|
|
||||||
}
|
|
||||||
Event::Ctrl(Key::Left) | Event::Key(Key::Left)
|
|
||||||
if self.enabled.x && self.offset.x > 0 =>
|
|
||||||
{
|
|
||||||
self.offset.x -= 1;
|
|
||||||
}
|
|
||||||
Event::Ctrl(Key::Right) | Event::Key(Key::Right)
|
|
||||||
if self.enabled.x
|
|
||||||
&& (self.offset.x + self.available_size().x
|
|
||||||
< self.inner_size.x) =>
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
// Fix offset?
|
|
||||||
let important = self.inner.important_area(self.inner_size);
|
|
||||||
|
|
||||||
// The furthest top-left we can go
|
|
||||||
let top_left = (important.bottom_right() + (1, 1))
|
|
||||||
.saturating_sub(self.available_size());
|
|
||||||
// The furthest bottom-right we can go
|
|
||||||
let bottom_right = important.top_left();
|
|
||||||
|
|
||||||
// "top_left < bottom_right" is NOT guaranteed
|
|
||||||
// if the child is larger than the view.
|
|
||||||
let offset_min = Vec2::min(top_left, bottom_right);
|
|
||||||
let offset_max = Vec2::max(top_left, bottom_right);
|
|
||||||
|
|
||||||
self.offset =
|
|
||||||
self.offset.or_max(offset_min).or_min(offset_max);
|
|
||||||
|
|
||||||
other
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
fn layout(&mut self, size: Vec2) {
|
||||||
// Size is final now, negociations are over.
|
self.core.layout(size, &mut self.inner);
|
||||||
self.last_size = size;
|
|
||||||
|
|
||||||
// This is what we'd like
|
|
||||||
let (inner_size, self_size) = self.sizes(size, true);
|
|
||||||
|
|
||||||
self.inner_size = inner_size;
|
|
||||||
|
|
||||||
self.size_cache = Some(SizeCache::build(self_size, size));
|
|
||||||
|
|
||||||
self.inner.layout(self.inner_size);
|
|
||||||
|
|
||||||
// Keep the offset in the valid range.
|
|
||||||
self.offset = self
|
|
||||||
.offset
|
|
||||||
.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 {
|
||||||
self.inner.needs_relayout() || self.size_cache.is_none()
|
self.core.needs_relayout(|| self.inner.needs_relayout())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
|
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
|
||||||
let (_, size) = self.sizes(constraint, false);
|
self.core.required_size(constraint, &mut self.inner)
|
||||||
|
|
||||||
size
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_on_any<'a>(&mut self, selector: &Selector<'_>, cb: AnyCb<'a>) {
|
fn call_on_any<'a>(&mut self, selector: &Selector<'_>, cb: AnyCb<'a>) {
|
||||||
@ -700,7 +162,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn take_focus(&mut self, source: Direction) -> bool {
|
fn take_focus(&mut self, source: Direction) -> bool {
|
||||||
let is_scrollable = self.is_scrolling().any();
|
let inner = &mut self.inner;
|
||||||
self.inner.take_focus(source) || is_scrollable
|
self.core
|
||||||
|
.take_focus(source, |source| inner.take_focus(source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user