diff --git a/src/lib.rs b/src/lib.rs index c0a43a4..be0e0b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,6 @@ pub mod align; pub mod direction; pub mod logger; pub mod menu; -pub mod rect; pub mod theme; pub mod vec; pub mod views; @@ -95,6 +94,7 @@ pub mod views; // This probably doesn't need to be public? mod cursive; mod printer; +mod rect; mod with; mod xy; @@ -103,8 +103,9 @@ mod utf8; pub mod backend; -pub use crate::cursive::{CbFunc, CbSink, Cursive, ScreenId}; -pub use crate::printer::Printer; -pub use crate::vec::Vec2; -pub use crate::with::With; -pub use crate::xy::XY; +pub use self::cursive::{CbFunc, CbSink, Cursive, ScreenId}; +pub use self::printer::Printer; +pub use self::rect::Rect; +pub use self::vec::Vec2; +pub use self::with::With; +pub use self::xy::XY; diff --git a/src/view/scroll/base.rs b/src/view/scroll/base.rs deleted file mode 100644 index 3420c2b..0000000 --- a/src/view/scroll/base.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::vec::Vec2; -use crate::Printer; - -use crate::direction::Direction; -use crate::event::{Event, EventResult}; -use crate::view::scroll::ScrollCore; -use crate::view::scroll::{InnerLayout, InnerOnEvent, InnerRequiredSize}; - -/// 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 { - core: ScrollCore, -} - -struct RequiredSize(F); - -impl InnerRequiredSize for RequiredSize -where - F: FnMut(Vec2) -> Vec2, -{ - fn needs_relayout(&self) -> bool { - true - } - - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - self.0(constraint) - } -} - -impl InnerLayout for RequiredSize -where - F: FnMut(Vec2) -> Vec2, -{ - fn layout(&mut self, _size: Vec2) {} - - fn needs_relayout(&self) -> bool { - true - } - - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - self.0(constraint) - } -} - -impl ScrollBase { - /// Creates a new, uninitialized scrollbar. - pub fn new() -> Self { - ScrollBase { - core: ScrollCore::new(), - } - } - - /// Performs `View::layout()`. - pub fn layout(&mut self, size: Vec2, required_size: F) - where - F: FnMut(Vec2) -> Vec2, - { - self.core.layout(size, RequiredSize(required_size)); - } - - /// Performs `View::required_size()`. - pub fn required_size( - &mut self, constraint: Vec2, required_size: F, - ) -> Vec2 - where - F: FnMut(Vec2) -> Vec2, - { - self.core - .required_size(constraint, RequiredSize(required_size)) - } - - /// 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(&self, printer: &Printer<'_, '_>, mut line_drawer: F) - where - F: FnMut(&Printer<'_, '_>, usize), - { - self.core.draw(printer, |printer| { - let start = printer.content_offset.y; - let end = start + printer.output_size.y; - for y in start..end { - let printer = - printer.offset((0, y)).cropped((printer.size.x, 1)); - line_drawer(&printer, y); - } - }); - } - - /// Performs `View::take_focus()`. - pub fn take_focus( - &mut self, source: Direction, inner_take_focus: F, - ) -> bool - where - F: FnOnce(Direction) -> bool, - { - self.core.take_focus(source, inner_take_focus) - } - - /// Performs `View::on_event()`. - pub fn on_event(&mut self, event: Event, inner: I) -> EventResult - where - I: InnerOnEvent, - { - self.core.on_event(event, inner) - } -} diff --git a/src/view/scroll/core.rs b/src/view/scroll/core.rs index 1dcafa4..7b1d766 100644 --- a/src/view/scroll/core.rs +++ b/src/view/scroll/core.rs @@ -1,6 +1,6 @@ use std::cmp::min; -use crate::direction::{Direction, Orientation}; +use crate::direction::Orientation; use crate::event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent}; use crate::printer::Printer; use crate::rect::Rect; @@ -10,13 +10,25 @@ use crate::view::{ScrollStrategy, Selector, SizeCache}; use crate::with::With; use crate::XY; -use crate::view::scroll::{InnerLayout, InnerOnEvent, InnerRequiredSize}; +/// Describes an item with a scroll core. +/// +/// This trait is used to represent "something that can scroll". +/// All it needs is an accessible core. +/// +/// See the various methods in the [`scroll`](crate::view::scroll) module. +pub trait Scroller { + /// Returns a mutable access to the scroll core. + fn get_scroller_mut(&mut self) -> &mut Core; + + /// Returns an immutable access to the scroll core. + fn get_scroller(&self) -> &Core; +} /// Core system for scrolling views. /// /// See also [`ScrollView`](crate::views::ScrollView). #[derive(Debug)] -pub struct ScrollCore { +pub struct Core { /// This is the size the child thinks we're giving him. inner_size: Vec2, @@ -57,16 +69,16 @@ pub struct ScrollCore { scroll_strategy: ScrollStrategy, } -impl Default for ScrollCore { +impl Default for Core { fn default() -> Self { Self::new() } } -impl ScrollCore { - /// Creates a new `ScrollCore`. +impl Core { + /// Creates a new `Core`. pub fn new() -> Self { - ScrollCore { + Core { inner_size: Vec2::zero(), offset: Vec2::zero(), last_size: Vec2::zero(), @@ -79,11 +91,10 @@ impl ScrollCore { } } - /// Performs the `View::draw()` operation. - pub fn draw(&self, printer: &Printer<'_, '_>, inner_draw: F) - where - F: FnOnce(&Printer<'_, '_>), - { + /// Returns a sub-printer ready to draw the content. + pub fn sub_printer<'a, 'b>( + &self, printer: &Printer<'a, 'b>, + ) -> Printer<'a, 'b> { // Draw scrollbar? let scrolling = self.is_scrolling(); @@ -138,27 +149,21 @@ impl ScrollCore { } // Draw content - let printer = printer + printer .cropped(size) .content_offset(self.offset) - .inner_size(self.inner_size); - - inner_draw(&printer); + .inner_size(self.inner_size) } - /// Performs `View::on_event()` - pub fn on_event( - &mut self, event: Event, mut inner: I, - ) -> EventResult { - // Relativize event accorging to the offset - let mut relative_event = event.clone(); - - // Should the event be treated inside, by the inner view? - let inside = if let Event::Mouse { + /// Returns `true` if `event` should be processed by the content. + /// + /// This also updates `event` so that it is relative to the content. + pub fn is_event_inside(&self, event: &mut Event) -> bool { + if let Event::Mouse { ref mut position, ref offset, .. - } = relative_event + } = event { // For mouse events, check if it falls inside the available area let inside = position @@ -170,17 +175,15 @@ impl ScrollCore { } 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. - inner.on_event(relative_event) - } else { - // Otherwise, pretend it wasn't there. - EventResult::Ignored - }; - - match result { + /// Handle an event after processing by the content. + pub fn on_inner_event( + &mut self, event: Event, inner_result: EventResult, + important_area: Rect, + ) -> EventResult { + match inner_result { EventResult::Ignored => { // The view ignored the event, so we're free to use it. @@ -294,7 +297,7 @@ impl ScrollCore { // The view consumed the event. Maybe something changed? // Fix offset? - let important = inner.important_area(self.inner_size); + let important = important_area; // The furthest top-left we can go let top_left = (important.bottom_right() + (1, 1)) @@ -315,21 +318,28 @@ impl ScrollCore { } } - /// Performs `View::layout()` - pub fn layout(&mut self, size: Vec2, mut inner: I) { - // Size is final now, negociations are over. - self.last_size = size; + /// Specifies the size given in a layout phase. + pub(crate) fn set_last_size(&mut self, last_size: Vec2) { + self.last_size = last_size; + } - // This is what we'd like - let (inner_size, self_size) = - self.sizes(size, true, Layout2Sizes { inner: &mut inner }); + /// Returns the size last given in `set_last_size()`. + pub fn last_size(&self) -> Vec2 { + self.last_size + } + /// Specifies the size allocated to the content. + pub(crate) fn set_inner_size(&mut self, inner_size: Vec2) { self.inner_size = inner_size; + } - self.size_cache = Some(SizeCache::build(self_size, size)); - - inner.layout(self.inner_size); + /// Rebuild the cache with the given parameters. + pub(crate) fn build_cache(&mut self, self_size: Vec2, last_size: Vec2) { + self.size_cache = Some(SizeCache::build(self_size, last_size)); + } + /// Makes sure the viewport is within the content. + pub(crate) fn update_offset(&mut self) { // Keep the offset in the valid range. self.offset = self .offset @@ -339,25 +349,11 @@ impl ScrollCore { self.adjust_scroll(); } - /// Performs `View::needs_relayout()` - pub fn needs_relayout(&self, inner_needs_relayout: F) -> bool - where - F: FnOnce() -> bool, - { - self.size_cache.is_none() || inner_needs_relayout() - } - - /// Performs `View::required_size()` - pub fn required_size( - &mut self, constraint: Vec2, mut inner: I, - ) -> Vec2 { - let (_, size) = self.sizes( - constraint, - false, - Required2Sizes { inner: &mut inner }, - ); - - size + /// Returns `true` if we should relayout, no matter the content. + /// + /// Even if this returns `false`, the content itself might still needs to relayout. + pub fn needs_relayout(&self) -> bool { + self.size_cache.is_none() } /// Performs `View::call_on_any()` @@ -380,17 +376,6 @@ impl ScrollCore { inner_focus_view(selector) } - /// Performs `View::take_focus()` - pub fn take_focus( - &mut self, source: Direction, inner_take_focus: F, - ) -> bool - where - F: FnOnce(Direction) -> bool, - { - let is_scrollable = self.is_scrolling().any(); - inner_take_focus(source) || is_scrollable - } - /// Returns the viewport in the inner content. pub fn content_viewport(&self) -> Rect { Rect::from_size(self.offset, self.available_size()) @@ -431,6 +416,19 @@ impl ScrollCore { self.with(|s| s.set_scrollbar_padding(scrollbar_padding)) } + /// Returns the padding between content and scrollbar. + pub fn get_scrollbar_padding(&self) -> Vec2 { + self.scrollbar_padding + } + + /// For each axis, returns `true` if this view can scroll. + /// + /// For example, a vertically-scrolling view will return + /// `XY { x: false, y: true }`. + pub fn is_enabled(&self) -> XY { + self.enabled + } + /// Control whether scroll bars are visibile. /// /// Defaults to `true`. @@ -445,6 +443,18 @@ impl ScrollCore { self.with(|s| s.set_show_scrollbars(show_scrollbars)) } + /// Returns `true` if we will show scrollbars when needed. + /// + /// Scrollbars are always hidden when not needed. + pub fn get_show_scrollbars(&self) -> bool { + self.show_scrollbars + } + + /// Returns the size given to the content on the last layout phase. + pub fn inner_size(&self) -> Vec2 { + self.inner_size + } + /// Sets the scroll offset to the given value pub fn set_offset(&mut self, offset: S) where @@ -529,7 +539,7 @@ impl ScrollCore { } /// Returns for each axis if we are scrolling. - fn is_scrolling(&self) -> XY { + pub fn is_scrolling(&self) -> XY { self.inner_size.zip_map(self.last_size, |i, s| i > s) } @@ -543,7 +553,7 @@ impl ScrollCore { /// 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 { + pub fn scrollbar_size(&self) -> Vec2 { self.is_scrolling() .swap() .select_or(self.scrollbar_padding + (1, 1), Vec2::zero()) @@ -558,49 +568,6 @@ impl ScrollCore { } } - /// 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, strict: bool, - inner: &mut I, - ) -> (Vec2, Vec2, XY) { - // 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 = 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. @@ -679,74 +646,17 @@ impl ScrollCore { .set_axis_from(orientation, &new_offset.or_min(max_offset)); } - /// Computes the size we would need given the constraints. + /// Tries to apply the cache to the current constraint. /// - /// 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, mut inner: I, - ) -> (Vec2, Vec2) { - // First: try the cache - let valid_cache = !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, - &mut inner, - ); - - // 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, &mut inner, - ); - if scrollable == new_scrollable { - // Yup, scrolling did it. We're good to go now. - (inner_size, size) + /// Returns the cached value if it works, or `None`. + pub(crate) fn try_cache(&self, constraint: Vec2) -> Option<(Vec2, Vec2)> { + self.size_cache.and_then(|cache| { + if cache.zip_map(constraint, SizeCache::accept).both() { + Some((self.inner_size, cache.map(|c| c.value))) } 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, - &mut inner, - ); - - // That's enough. If the inner view changed again, ignore it! - // That'll teach it. - (inner_size, size) + None } - } 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 { @@ -774,34 +684,3 @@ impl ScrollCore { } } } - -struct Layout2Sizes<'a, I> { - inner: &'a mut I, -} - -trait InnerSizes { - fn needs_relayout(&self) -> bool; - fn required_size(&mut self, constraint: Vec2) -> Vec2; -} - -impl<'a, I: InnerLayout> InnerSizes for Layout2Sizes<'a, I> { - fn needs_relayout(&self) -> bool { - self.inner.needs_relayout() - } - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - self.inner.required_size(constraint) - } -} - -struct Required2Sizes<'a, I> { - inner: &'a mut I, -} - -impl<'a, I: InnerRequiredSize> InnerSizes for Required2Sizes<'a, I> { - fn needs_relayout(&self) -> bool { - self.inner.needs_relayout() - } - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - self.inner.required_size(constraint) - } -} diff --git a/src/view/scroll/mod.rs b/src/view/scroll/mod.rs index d88e94f..d0e2170 100644 --- a/src/view/scroll/mod.rs +++ b/src/view/scroll/mod.rs @@ -2,17 +2,17 @@ //! //! *This module is still unstable and may go through breaking changes.* //! -//! This module defines [`ScrollCore`](crate::view::scroll::ScrollCore) and related traits. +//! This module defines [`Core`](crate::view::scroll::Core) and related traits. //! //! [`ScrollView`](crate::views::ScrollView) may be an easier way to add scrolling to an existing view. -mod base; mod core; -mod traits; +mod raw; -pub use self::base::ScrollBase; -pub use self::core::ScrollCore; -pub use self::traits::{InnerLayout, InnerOnEvent, InnerRequiredSize}; +pub use self::core::{Core, Scroller}; + +use crate::event::{Event, EventResult}; +use crate::{Printer, Rect, Vec2}; /// Defines the scrolling behaviour on content or size change #[derive(Debug)] @@ -30,3 +30,210 @@ impl Default for ScrollStrategy { ScrollStrategy::KeepRow } } + +/// Performs `View::on_event` on a `scroll::Scroller`. +/// +/// Example: +/// +/// ```rust,ignore +/// fn on_event(&mut self, event: Event) -> EventResult { +/// scroll::on_event(self, event, Self::inner_on_event, Self::inner_important_area) +/// } +/// ``` +pub fn on_event( + scroller: &mut T, event: Event, on_event: OnEvent, + important_area: ImportantArea, +) -> EventResult +where + T: Scroller, + OnEvent: FnMut(&mut T, Event) -> EventResult, + ImportantArea: FnMut(&T, Vec2) -> Rect, +{ + raw::on_event( + event, + scroller, + Scroller::get_scroller_mut, + on_event, + important_area, + ) +} + +/// Performs `View::important_area` on a `scroll::Scroller`. +pub fn important_area( + scroller: &T, size: Vec2, mut important_area: ImportantArea, +) -> Rect +where + T: Scroller, + ImportantArea: FnMut(&T, Vec2) -> Rect, +{ + let viewport = scroller.get_scroller().content_viewport(); + let area = important_area(scroller, size); + let top_left = area.top_left().saturating_sub(viewport.top_left()); + let bot_right = area + .bottom_right() + .saturating_sub(viewport.top_left()) + .or_min(viewport.bottom_right()); + + Rect::from_corners(top_left, bot_right) +} + +/// Performs `View::layout` on a `scroll::Scroller`. +pub fn layout( + scroller: &mut T, size: Vec2, needs_relayout: bool, layout: Layout, + required_size: RequiredSize, +) where + T: Scroller, + Layout: FnMut(&mut T, Vec2), + RequiredSize: FnMut(&mut T, Vec2) -> Vec2, +{ + raw::layout( + size, + needs_relayout, + scroller, + Scroller::get_scroller_mut, + required_size, + layout, + ); +} + +/// Performs `View::required_size` on a `scroll::Scroller`. +pub fn required_size( + scroller: &mut T, size: Vec2, needs_relayout: bool, + required_size: RequiredSize, +) -> Vec2 +where + T: Scroller, + RequiredSize: FnMut(&mut T, Vec2) -> Vec2, +{ + raw::required_size( + size, + needs_relayout, + scroller, + Scroller::get_scroller_mut, + required_size, + ) +} + +/// Performs `View::draw` on a `scroll::Scroller`. +pub fn draw(scroller: &T, printer: &Printer, draw: Draw) +where + T: Scroller, + Draw: FnOnce(&T, &Printer), +{ + raw::draw(printer, scroller, Scroller::get_scroller, draw); +} + +/// Performs a line-based `View::draw` on a `scroll::Scroller`. +/// +/// This is an alternative to `scroll::draw()` when you just need to print individual lines. +pub fn draw_lines( + scroller: &T, printer: &Printer, mut line_drawer: LineDrawer, +) where + T: Scroller, + LineDrawer: FnMut(&T, &Printer, usize), +{ + draw(scroller, printer, |s, printer| { + let start = printer.content_offset.y; + let end = start + printer.output_size.y; + for y in start..end { + let printer = printer.offset((0, y)).cropped((printer.size.x, 1)); + line_drawer(s, &printer, y); + } + }); +} + +/// Draws a frame around the scrollable content. +/// +/// `left_border` will be called for each row to draw the left border for the given line number. +pub fn draw_frame( + scroller: &T, printer: &Printer, mut left_border: LeftBorder, + mut top_border: TopBorder, mut right_border: RightBorder, + mut bottom_border: BottomBorder, +) where + T: Scroller, + LeftBorder: FnMut(&T, &Printer, usize), + TopBorder: FnMut(&T, &Printer, usize), + RightBorder: FnMut(&T, &Printer, usize), + BottomBorder: FnMut(&T, &Printer, usize), +{ + let viewport = scroller.get_scroller().content_viewport(); + let size = printer.size.saturating_sub((1, 1)); + + for (i, x) in (viewport.left()..=viewport.right()).enumerate() { + top_border(scroller, &printer.offset((i + 1, 0)), x); + bottom_border(scroller, &printer.offset((i + 1, size.y)), x); + } + + // Also draw padding + let scrollbar_size = scroller.get_scroller().scrollbar_size(); + printer.print_hline((viewport.right() + 2, 0), scrollbar_size.x, "─"); + printer.print_hline( + (viewport.right() + 2, size.y), + scrollbar_size.x, + "─", + ); + printer.print_vline((0, viewport.bottom() + 2), scrollbar_size.y, "│"); + printer.print_vline( + (size.x, viewport.bottom() + 2), + scrollbar_size.y, + "│", + ); + + for (i, y) in (viewport.top()..=viewport.bottom()).enumerate() { + left_border(scroller, &printer.offset((0, i + 1)), y); + right_border(scroller, &printer.offset((size.x, i + 1)), y); + } + + printer.print((0, 0), "┌"); + printer.print(size.keep_y(), "└"); + printer.print(size.keep_x(), "┐"); + printer.print(size, "┘"); +} + +/// Draws a box-style frame around a scrollable content. +/// +/// Assumes horizontal lines are present in the content whenever `is_h_delim` +/// returns `true` (and vertical lines when `is_v_delim` returns `true`). +/// +/// It will print a box with the appropriate `├`, `┤` and so on. +pub fn draw_box_frame( + scroller: &T, printer: &Printer, is_h_delim: IsHDelim, + is_v_delim: IsVDelim, +) where + T: Scroller, + IsHDelim: Fn(&T, usize) -> bool, + IsVDelim: Fn(&T, usize) -> bool, +{ + draw_frame( + scroller, + printer, + |s, printer, y| { + if is_h_delim(s, y) { + printer.print((0, 0), "├"); + } else { + printer.print((0, 0), "│"); + } + }, + |s, printer, x| { + if is_v_delim(s, x) { + printer.print((0, 0), "┬"); + } else { + printer.print((0, 0), "─"); + } + }, + |s, printer, y| { + if is_h_delim(s, y) { + printer.print((0, 0), "┤"); + } else { + printer.print((0, 0), "│"); + } + }, + |s, printer, x| { + if is_v_delim(s, x) { + printer.print((0, 0), "┴"); + } else { + printer.print((0, 0), "─"); + } + }, + ); +} diff --git a/src/view/scroll/raw.rs b/src/view/scroll/raw.rs new file mode 100644 index 0000000..f1696e6 --- /dev/null +++ b/src/view/scroll/raw.rs @@ -0,0 +1,198 @@ +use crate::event::{Event, EventResult}; +use crate::rect::Rect; +use crate::view::scroll; +use crate::xy::XY; +use crate::Printer; +use crate::Vec2; + +pub fn draw( + printer: &Printer, model: &Model, mut get_scroller: GetScroller, + inner_draw: Draw, +) where + Model: ?Sized, + GetScroller: FnMut(&Model) -> &scroll::Core, + Draw: FnOnce(&Model, &Printer), +{ + let printer = get_scroller(model).sub_printer(printer); + inner_draw(model, &printer); +} + +fn sizes_when_scrolling( + constraint: Vec2, scrollable: XY, strict: bool, model: &mut Model, + get_scroller: &mut GetScroller, required_size: &mut RequiredSize, +) -> (Vec2, Vec2, XY) +where + Model: ?Sized, + GetScroller: FnMut(&mut Model) -> &mut scroll::Core, + RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, +{ + // This is the size taken by the scrollbars. + let scrollbar_size = scrollable.swap().select_or( + get_scroller(model).get_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 = required_size(model, available); + + // Where we're "enabled", accept the constraints. + // Where we're not, just forward inner_size. + let size = get_scroller(model).is_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 = get_scroller(model) + .is_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) +} + +fn sizes( + constraint: Vec2, strict: bool, needs_relayout: bool, model: &mut Model, + get_scroller: &mut GetScroller, required_size: &mut RequiredSize, +) -> (Vec2, Vec2) +where + Model: ?Sized, + GetScroller: FnMut(&mut Model) -> &mut scroll::Core, + RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, +{ + if !needs_relayout { + if let Some(cached) = get_scroller(model).try_cache(constraint) { + return cached; + } + } + + // Attempt 1: try without scrollbars + let (inner_size, size, scrollable) = sizes_when_scrolling( + constraint, + XY::new(false, false), + strict, + model, + get_scroller, + required_size, + ); + + // If we need to add scrollbars, the available size will change. + if scrollable.any() && get_scroller(model).get_show_scrollbars() { + // Attempt 2: he wants to scroll? Sure! + // Try again with some space for the scrollbar. + let (inner_size, size, new_scrollable) = sizes_when_scrolling( + constraint, + scrollable, + strict, + model, + get_scroller, + required_size, + ); + 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, _) = sizes_when_scrolling( + constraint, + new_scrollable, + strict, + model, + get_scroller, + required_size, + ); + + // 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) + } +} + +pub fn layout( + size: Vec2, needs_relayout: bool, model: &mut Model, + mut get_scroller: GetScroller, mut required_size: RequiredSize, + mut layout: Layout, +) where + Model: ?Sized, + GetScroller: FnMut(&mut Model) -> &mut scroll::Core, + RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, + Layout: FnMut(&mut Model, Vec2), +{ + get_scroller(model).set_last_size(size); + + // This is what we'd like + let (inner_size, self_size) = sizes( + size, + true, + needs_relayout, + model, + &mut get_scroller, + &mut required_size, + ); + + get_scroller(model).set_inner_size(inner_size); + get_scroller(model).build_cache(self_size, size); + + layout(model, inner_size); + + get_scroller(model).update_offset(); +} + +pub fn required_size( + constraint: Vec2, needs_relayout: bool, model: &mut Model, + mut get_scroller: GetScroller, mut required_size: RequiredSize, +) -> Vec2 +where + Model: ?Sized, + GetScroller: FnMut(&mut Model) -> &mut scroll::Core, + RequiredSize: FnMut(&mut Model, Vec2) -> Vec2, +{ + let (_, size) = sizes( + constraint, + false, + needs_relayout, + model, + &mut get_scroller, + &mut required_size, + ); + + size +} + +pub fn on_event( + event: Event, model: &mut Model, mut get_scroller: GetScroller, + mut on_event: OnEvent, mut important_area: ImportantArea, +) -> EventResult +where + Model: ?Sized, + GetScroller: FnMut(&mut Model) -> &mut scroll::Core, + OnEvent: FnMut(&mut Model, Event) -> EventResult, + ImportantArea: FnMut(&Model, Vec2) -> Rect, +{ + let mut relative_event = event.clone(); + let inside = get_scroller(model).is_event_inside(&mut relative_event); + let result = if inside { + on_event(model, relative_event) + } else { + EventResult::Ignored + }; + let inner_size = get_scroller(model).inner_size(); + let important = important_area(model, inner_size); + get_scroller(model).on_inner_event(event, result, important) +} diff --git a/src/view/scroll/traits.rs b/src/view/scroll/traits.rs deleted file mode 100644 index 5f3ec2d..0000000 --- a/src/view/scroll/traits.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::event::{Event, EventResult}; -use crate::rect::Rect; - -use crate::vec::Vec2; -use crate::view::View; - -/// Inner implementation for `ScrollCore::on_event` -pub trait InnerOnEvent { - /// Performs `View::on_event()` - fn on_event(&mut self, event: Event) -> EventResult; - - /// Performs `View::important_area()` - fn important_area(&self, size: Vec2) -> Rect; -} - -impl<'a, V: View> InnerOnEvent for &'a mut V { - fn on_event(&mut self, event: Event) -> EventResult { - ::on_event(self, event) - } - fn important_area(&self, size: Vec2) -> Rect { - ::important_area(self, size) - } -} - -/// Inner implementation for `ScrollCore::draw()` -/// Inner implementation for `ScrollCore::InnerLayout()` -pub trait InnerLayout { - /// Performs `View::layout()` - fn layout(&mut self, size: Vec2); - /// Performs `View::needs_relayout()` - fn needs_relayout(&self) -> bool; - /// Performs `View::required_size()` - fn required_size(&mut self, constraint: Vec2) -> Vec2; -} - -impl<'a, V: View> InnerLayout for &'a mut V { - fn layout(&mut self, size: Vec2) { - ::layout(self, size); - } - fn needs_relayout(&self) -> bool { - ::needs_relayout(self) - } - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - ::required_size(self, constraint) - } -} - -/// Inner implementation for `ScrollCore::required_size()` -pub trait InnerRequiredSize { - /// Performs `View::needs_relayout()` - fn needs_relayout(&self) -> bool; - /// Performs `View::required_size()` - fn required_size(&mut self, constraint: Vec2) -> Vec2; -} - -impl InnerRequiredSize for &mut V { - fn needs_relayout(&self) -> bool { - ::needs_relayout(self) - } - fn required_size(&mut self, constraint: Vec2) -> Vec2 { - ::required_size(self, constraint) - } -} diff --git a/src/views/menu_popup.rs b/src/views/menu_popup.rs index 06331b7..cece4b6 100644 --- a/src/views/menu_popup.rs +++ b/src/views/menu_popup.rs @@ -5,7 +5,7 @@ use crate::event::{ use crate::menu::{MenuItem, MenuTree}; use crate::rect::Rect; use crate::vec::Vec2; -use crate::view::scroll::{InnerOnEvent, ScrollBase}; +use crate::view::scroll; use crate::view::{Position, View}; use crate::views::OnEventView; use crate::Cursive; @@ -19,11 +19,23 @@ use unicode_width::UnicodeWidthStr; pub struct MenuPopup { menu: Rc, focus: usize, - scrollbase: ScrollBase, + scroll_core: scroll::Core, align: Align, on_dismiss: Option, on_action: Option, - last_size: Vec2, +} + +// The `scroll::Scroller` trait is used to weave the borrow phases. +// +// TODO: use some macro to auto-generate this. +impl scroll::Scroller for MenuPopup { + fn get_scroller(&self) -> &scroll::Core { + &self.scroll_core + } + + fn get_scroller_mut(&mut self) -> &mut scroll::Core { + &mut self.scroll_core + } } impl MenuPopup { @@ -32,11 +44,10 @@ impl MenuPopup { MenuPopup { menu, focus: 0, - scrollbase: ScrollBase::new(), + scroll_core: scroll::Core::new(), align: Align::top_left(), on_dismiss: None, on_action: None, - last_size: Vec2::zero(), } } @@ -52,6 +63,11 @@ impl MenuPopup { self.with(|s| s.set_focus(focus)) } + /// Returns the position of the currently focused child. + pub fn get_focus(&self) -> usize { + self.focus + } + fn item_width(item: &MenuItem) -> usize { match *item { MenuItem::Delimiter => 1, @@ -107,6 +123,193 @@ impl MenuPopup { pub fn set_on_action(&mut self, f: F) { self.on_action = Some(Callback::from_fn(f)); } + + fn scroll_up(&mut self, mut n: usize, cycle: bool) { + while n > 0 { + if self.focus > 0 { + self.focus -= 1; + } else if cycle { + self.focus = self.menu.children.len() - 1; + } else { + break; + } + + if !self.menu.children[self.focus].is_delimiter() { + n -= 1; + } + } + } + + fn scroll_down(&mut self, mut n: usize, cycle: bool) { + while n > 0 { + if self.focus + 1 < self.menu.children.len() { + self.focus += 1; + } else if cycle { + self.focus = 0; + } else { + // Stop if we're at the bottom. + break; + } + + if !self.menu.children[self.focus].is_delimiter() { + n -= 1; + } + } + } + + fn submit(&mut self) -> EventResult { + match self.menu.children[self.focus] { + MenuItem::Leaf(_, ref cb) => { + let cb = cb.clone(); + let action_cb = self.on_action.clone(); + EventResult::with_cb(move |s| { + // Remove ourselves from the face of the earth + s.pop_layer(); + // If we had prior orders, do it now. + if let Some(ref action_cb) = action_cb { + action_cb.clone()(s); + } + // And transmit his last words. + cb.clone()(s); + }) + } + MenuItem::Subtree(_, ref tree) => self.make_subtree_cb(tree), + _ => unreachable!("Delimiters cannot be submitted."), + } + } + + fn dismiss(&mut self) -> EventResult { + let dismiss_cb = self.on_dismiss.clone(); + EventResult::with_cb(move |s| { + if let Some(ref cb) = dismiss_cb { + cb.clone()(s); + } + s.pop_layer(); + }) + } + + fn make_subtree_cb(&self, tree: &Rc) -> EventResult { + let tree = Rc::clone(tree); + let max_width = 4 + self + .menu + .children + .iter() + .map(MenuPopup::item_width) + .max() + .unwrap_or(1); + let offset = Vec2::new(max_width, self.focus); + let action_cb = self.on_action.clone(); + + EventResult::with_cb(move |s| { + let action_cb = action_cb.clone(); + s.screen_mut().add_layer_at( + Position::parent(offset), + OnEventView::new(MenuPopup::new(Rc::clone(&tree)).on_action( + move |s| { + // This will happen when the subtree popup + // activates something; + // First, remove ourselve. + s.pop_layer(); + if let Some(ref action_cb) = action_cb { + action_cb.clone()(s); + } + }, + )) + .on_event(Key::Left, |s| { + s.pop_layer(); + }), + ); + }) + } + + /// Handle an event for the content. + /// + /// Here the event has already been relativized. This means `y=0` points to the first item. + fn inner_on_event(&mut self, event: Event) -> EventResult { + match event { + Event::Key(Key::Up) => self.scroll_up(1, true), + Event::Key(Key::PageUp) => self.scroll_up(5, false), + Event::Key(Key::Down) => self.scroll_down(1, true), + Event::Key(Key::PageDown) => self.scroll_down(5, false), + + Event::Key(Key::Home) => self.focus = 0, + Event::Key(Key::End) => { + self.focus = self.menu.children.len().saturating_sub(1) + } + + Event::Key(Key::Right) + if self.menu.children[self.focus].is_subtree() => + { + return match self.menu.children[self.focus] { + MenuItem::Subtree(_, ref tree) => { + self.make_subtree_cb(tree) + } + _ => unreachable!("Child is a subtree"), + }; + } + Event::Key(Key::Enter) + if !self.menu.children[self.focus].is_delimiter() => + { + return self.submit(); + } + Event::Mouse { + event: MouseEvent::Press(_), + position, + offset, + } => { + // eprintln!("Position: {:?} / {:?}", position, offset); + if let Some(position) = position.checked_sub(offset) { + // Now `position` is relative to the top-left of the content. + let focus = position.y; + if !self.menu.children[focus].is_delimiter() { + self.focus = focus; + } + } + } + Event::Mouse { + event: MouseEvent::Release(MouseButton::Left), + position, + offset, + } if !self.menu.children[self.focus].is_delimiter() + && position + .checked_sub(offset) + .map(|position| position.y == self.focus) + .unwrap_or(false) => + { + return self.submit(); + } + Event::Key(Key::Esc) => { + return self.dismiss(); + } + + _ => return EventResult::Ignored, + } + + EventResult::Consumed(None) + } + + /// Compute the required size for the content. + fn inner_required_size(&mut self, _req: Vec2) -> Vec2 { + let w = 2 + self + .menu + .children + .iter() + .map(Self::item_width) + .max() + .unwrap_or(1); + + let h = self.menu.children.len(); + + Vec2::new(w, h) + } + + fn inner_important_area(&self, size: Vec2) -> Rect { + if self.menu.is_empty() { + return Rect::from((0, 0)); + } + + Rect::from_size((0, self.focus), (size.x, 1)) + } } impl View for MenuPopup { @@ -121,14 +324,19 @@ impl View for MenuPopup { let printer = &printer.offset((0, offset)); // Start with a box - printer.print_box(Vec2::new(0, 0), printer.size, false); + scroll::draw_box_frame( + self, + &printer, + |s, y| s.menu.children[y].is_delimiter(), + |_s, _x| false, + ); // We're giving it a reduced size because of borders. let printer = printer.shrinked_centered((2, 2)); - self.scrollbase.draw(&printer, |printer, i| { - printer.with_selection(i == self.focus, |printer| { - let item = &self.menu.children[i]; + scroll::draw_lines(self, &printer, |s, printer, i| { + printer.with_selection(i == s.focus, |printer| { + let item = &s.menu.children[i]; match *item { MenuItem::Delimiter => { // printer.print_hdelim((0, 0), printer.size.x) @@ -159,43 +367,38 @@ impl View for MenuPopup { // We can't really shrink our items here, so it's not flexible. // 2 is the padding - let w = 2 + self - .menu - .children - .iter() - .map(Self::item_width) - .max() - .unwrap_or(1); - let h = self.menu.children.len(); - - let res = self - .scrollbase - .required_size(req.saturating_sub((2, 2)), |_| Vec2::new(w, h)) - + (2, 2); - - res + scroll::required_size( + self, + req.saturating_sub((2, 2)), + true, + Self::inner_required_size, + ) + (2, 2) } fn on_event(&mut self, event: Event) -> EventResult { - match self.scrollbase.on_event( + match scroll::on_event( + self, event.relativized((1, 1)), - OnEvent { - focus: &mut self.focus, - menu: &self.menu, - on_dismiss: &self.on_dismiss, - on_action: &self.on_action, - last_size: &self.last_size, - }, + Self::inner_on_event, + Self::inner_important_area, ) { EventResult::Ignored => { + // Check back the non-relativized event now if let Event::Mouse { event: MouseEvent::Press(_), position, offset, } = event { - if !position.fits_in_rect(offset, self.last_size) { + // Mouse press will be ignored if they are outside of the content. + // They can be on the border, or entirely outside of the popup. + + // Mouse clicks outside of the popup should dismiss it. + if !position.fits_in_rect( + offset, + self.scroll_core.last_size() + (2, 2), + ) { let dismiss_cb = self.on_dismiss.clone(); return EventResult::with_cb(move |s| { if let Some(ref cb) = dismiss_cb { @@ -213,210 +416,21 @@ impl View for MenuPopup { } fn layout(&mut self, size: Vec2) { - self.last_size = size; - - let children = &self.menu.children; - - self.scrollbase.layout(size.saturating_sub((2, 2)), |size| { - Vec2::new(size.x, children.len()) - }); + scroll::layout( + self, + size.saturating_sub((2, 2)), + true, + |_s, _size| (), + Self::inner_required_size, + ); } fn important_area(&self, size: Vec2) -> Rect { - if self.menu.is_empty() { - return Rect::from((0, 0)); - } - - Rect::from_size((0, self.focus), (size.x, 1)) - } -} - -struct OnEvent<'a> { - focus: &'a mut usize, - menu: &'a Rc, - on_dismiss: &'a Option, - on_action: &'a Option, - last_size: &'a Vec2, -} - -impl<'a> OnEvent<'a> { - fn scroll_up(&mut self, mut n: usize, cycle: bool) { - while n > 0 { - if *self.focus > 0 { - *self.focus -= 1; - } else if cycle { - *self.focus = self.menu.children.len() - 1; - } else { - break; - } - - if !self.menu.children[*self.focus].is_delimiter() { - n -= 1; - } - } - } - - fn scroll_down(&mut self, mut n: usize, cycle: bool) { - while n > 0 { - if *self.focus + 1 < self.menu.children.len() { - *self.focus += 1; - } else if cycle { - *self.focus = 0; - } else { - break; - } - if !self.menu.children[*self.focus].is_delimiter() { - n -= 1; - } - } - } - fn submit(&mut self) -> EventResult { - match self.menu.children[*self.focus] { - MenuItem::Leaf(_, ref cb) => { - let cb = cb.clone(); - let action_cb = self.on_action.clone(); - EventResult::with_cb(move |s| { - // Remove ourselves from the face of the earth - s.pop_layer(); - // If we had prior orders, do it now. - if let Some(ref action_cb) = action_cb { - action_cb.clone()(s); - } - // And transmit his last words. - cb.clone()(s); - }) - } - MenuItem::Subtree(_, ref tree) => self.make_subtree_cb(tree), - _ => panic!("No delimiter here"), - } - } - - fn dismiss(&mut self) -> EventResult { - let dismiss_cb = self.on_dismiss.clone(); - EventResult::with_cb(move |s| { - if let Some(ref cb) = dismiss_cb { - cb.clone()(s); - } - s.pop_layer(); - }) - } - - fn make_subtree_cb(&self, tree: &Rc) -> EventResult { - let tree = Rc::clone(tree); - let max_width = 4 + self - .menu - .children - .iter() - .map(MenuPopup::item_width) - .max() - .unwrap_or(1); - let offset = Vec2::new(max_width, *self.focus); - let action_cb = self.on_action.clone(); - - EventResult::with_cb(move |s| { - let action_cb = action_cb.clone(); - s.screen_mut().add_layer_at( - Position::parent(offset), - OnEventView::new(MenuPopup::new(Rc::clone(&tree)).on_action( - move |s| { - // This will happen when the subtree popup - // activates something; - // First, remove ourselve. - s.pop_layer(); - if let Some(ref action_cb) = action_cb { - action_cb.clone()(s); - } - }, - )) - .on_event(Key::Left, |s| { - s.pop_layer(); - }), - ); - }) - } -} - -impl<'a> InnerOnEvent for OnEvent<'a> { - fn on_event(&mut self, event: Event) -> EventResult { - match event { - Event::Key(Key::Up) => self.scroll_up(1, true), - Event::Key(Key::PageUp) => self.scroll_up(5, false), - Event::Key(Key::Down) => self.scroll_down(1, true), - Event::Key(Key::PageDown) => self.scroll_down(5, false), - - Event::Key(Key::Home) => *self.focus = 0, - Event::Key(Key::End) => { - *self.focus = self.menu.children.len().saturating_sub(1) - } - - Event::Key(Key::Right) - if self.menu.children[*self.focus].is_subtree() => - { - return match self.menu.children[*self.focus] { - MenuItem::Subtree(_, ref tree) => { - self.make_subtree_cb(tree) - } - _ => panic!("Not a subtree???"), - }; - } - Event::Key(Key::Enter) - if !self.menu.children[*self.focus].is_delimiter() => - { - return self.submit(); - } - Event::Mouse { - event: MouseEvent::Press(_), - position, - offset, - } if position.fits_in_rect( - offset, - self.last_size.saturating_sub((2, 2)), - ) => - { - // eprintln!("Position: {:?} / {:?}", position, offset); - // eprintln!("Last size: {:?}", self.last_size); - let inner_size = self.last_size.saturating_sub((2, 2)); - if let Some(position) = position.checked_sub(offset) { - // `position` is not relative to the content - // (It's inside the border) - if position < inner_size { - let focus = position.y; - if !self.menu.children[focus].is_delimiter() { - *self.focus = focus; - } - } - } - } - Event::Mouse { - event: MouseEvent::Release(MouseButton::Left), - position, - offset, - } if !self.menu.children[*self.focus].is_delimiter() - && position - .checked_sub(offset) - .map(|position| { - position < self.last_size.saturating_sub((2, 2)) - && (position.y == *self.focus) - }) - .unwrap_or(false) => - { - return self.submit(); - } - Event::Key(Key::Esc) - | Event::Mouse { - event: MouseEvent::Press(_), - .. - } => { - return self.dismiss(); - } - - _ => return EventResult::Ignored, - } - - EventResult::Consumed(None) - } - - fn important_area(&self, size: Vec2) -> Rect { - Rect::from_size((0, *self.focus), (size.x, 1)) + scroll::important_area( + self, + size.saturating_sub((2, 2)), + Self::inner_important_area, + ) + .with(|area| area.offset((1, 1))) } } diff --git a/src/views/scroll_view.rs b/src/views/scroll_view.rs index e1492d0..a654508 100644 --- a/src/views/scroll_view.rs +++ b/src/views/scroll_view.rs @@ -1,15 +1,26 @@ use crate::direction::Direction; use crate::event::{AnyCb, Event, EventResult}; -use crate::rect::Rect; use crate::view::{scroll, ScrollStrategy, Selector, View}; -use crate::{Printer, Vec2, With}; +use crate::{Printer, Rect, Vec2, With}; /// Wraps a view in a scrollable area. pub struct ScrollView { /// The wrapped view. inner: V, - core: scroll::ScrollCore, + core: scroll::Core, +} + +impl scroll::Scroller for ScrollView +where + V: View, +{ + fn get_scroller(&self) -> &scroll::Core { + &self.core + } + fn get_scroller_mut(&mut self) -> &mut scroll::Core { + &mut self.core + } } impl ScrollView @@ -20,7 +31,7 @@ where pub fn new(inner: V) -> Self { ScrollView { inner, - core: scroll::ScrollCore::new(), + core: scroll::Core::new(), } } @@ -134,23 +145,39 @@ where V: View, { fn draw(&self, printer: &Printer<'_, '_>) { - self.core.draw(printer, |printer| self.inner.draw(printer)); + scroll::draw(self, printer, |s, p| s.inner.draw(p)); } fn on_event(&mut self, event: Event) -> EventResult { - self.core.on_event(event, &mut self.inner) + scroll::on_event( + self, + event, + |s, e| s.inner.on_event(e), + |s, si| s.inner.important_area(si), + ) } fn layout(&mut self, size: Vec2) { - self.core.layout(size, &mut self.inner); + scroll::layout( + self, + size, + self.inner.needs_relayout(), + |s, si| s.inner.layout(si), + |s, c| s.inner.required_size(c), + ); } fn needs_relayout(&self) -> bool { - self.core.needs_relayout(|| self.inner.needs_relayout()) + self.core.needs_relayout() || self.inner.needs_relayout() } fn required_size(&mut self, constraint: Vec2) -> Vec2 { - self.core.required_size(constraint, &mut self.inner) + scroll::required_size( + self, + constraint, + self.inner.needs_relayout(), + |s, c| s.inner.required_size(c), + ) } fn call_on_any<'a>(&mut self, selector: &Selector<'_>, cb: AnyCb<'a>) { @@ -162,8 +189,10 @@ where } fn take_focus(&mut self, source: Direction) -> bool { - let inner = &mut self.inner; - self.core - .take_focus(source, |source| inner.take_focus(source)) + self.inner.take_focus(source) || self.core.is_scrolling().any() + } + + fn important_area(&self, size: Vec2) -> Rect { + scroll::important_area(self, size, |s, si| s.inner.important_area(si)) } }