Refactor scroll::Core mechanism.

Moved most View-trait-performing-functions outside of `scroll::Core`
into `scroll::raw`.
Added a `Scroller` trait and convenience methods like `scroll::layout` to
weave the borrows appropriately.
This commit is contained in:
Alexandre Bury 2019-03-12 14:28:35 -07:00
parent e5ef01c90f
commit 4b5a7867e3
8 changed files with 805 additions and 671 deletions

View File

@ -87,7 +87,6 @@ pub mod align;
pub mod direction; pub mod direction;
pub mod logger; pub mod logger;
pub mod menu; pub mod menu;
pub mod rect;
pub mod theme; pub mod theme;
pub mod vec; pub mod vec;
pub mod views; pub mod views;
@ -95,6 +94,7 @@ pub mod views;
// This probably doesn't need to be public? // This probably doesn't need to be public?
mod cursive; mod cursive;
mod printer; mod printer;
mod rect;
mod with; mod with;
mod xy; mod xy;
@ -103,8 +103,9 @@ mod utf8;
pub mod backend; pub mod backend;
pub use crate::cursive::{CbFunc, CbSink, Cursive, ScreenId}; pub use self::cursive::{CbFunc, CbSink, Cursive, ScreenId};
pub use crate::printer::Printer; pub use self::printer::Printer;
pub use crate::vec::Vec2; pub use self::rect::Rect;
pub use crate::with::With; pub use self::vec::Vec2;
pub use crate::xy::XY; pub use self::with::With;
pub use self::xy::XY;

View File

@ -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>(F);
impl<F> InnerRequiredSize for RequiredSize<F>
where
F: FnMut(Vec2) -> Vec2,
{
fn needs_relayout(&self) -> bool {
true
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
self.0(constraint)
}
}
impl<F> InnerLayout for RequiredSize<F>
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<F>(&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<F>(
&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<F>(&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<F>(
&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<I>(&mut self, event: Event, inner: I) -> EventResult
where
I: InnerOnEvent,
{
self.core.on_event(event, inner)
}
}

View File

@ -1,6 +1,6 @@
use std::cmp::min; use std::cmp::min;
use crate::direction::{Direction, Orientation}; use crate::direction::Orientation;
use crate::event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent}; use crate::event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent};
use crate::printer::Printer; use crate::printer::Printer;
use crate::rect::Rect; use crate::rect::Rect;
@ -10,13 +10,25 @@ use crate::view::{ScrollStrategy, Selector, SizeCache};
use crate::with::With; use crate::with::With;
use crate::XY; 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. /// Core system for scrolling views.
/// ///
/// See also [`ScrollView`](crate::views::ScrollView). /// See also [`ScrollView`](crate::views::ScrollView).
#[derive(Debug)] #[derive(Debug)]
pub struct ScrollCore { pub struct Core {
/// This is the size the child thinks we're giving him. /// This is the size the child thinks we're giving him.
inner_size: Vec2, inner_size: Vec2,
@ -57,16 +69,16 @@ pub struct ScrollCore {
scroll_strategy: ScrollStrategy, scroll_strategy: ScrollStrategy,
} }
impl Default for ScrollCore { impl Default for Core {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl ScrollCore { impl Core {
/// Creates a new `ScrollCore`. /// Creates a new `Core`.
pub fn new() -> Self { pub fn new() -> Self {
ScrollCore { Core {
inner_size: Vec2::zero(), inner_size: Vec2::zero(),
offset: Vec2::zero(), offset: Vec2::zero(),
last_size: Vec2::zero(), last_size: Vec2::zero(),
@ -79,11 +91,10 @@ impl ScrollCore {
} }
} }
/// Performs the `View::draw()` operation. /// Returns a sub-printer ready to draw the content.
pub fn draw<F>(&self, printer: &Printer<'_, '_>, inner_draw: F) pub fn sub_printer<'a, 'b>(
where &self, printer: &Printer<'a, 'b>,
F: FnOnce(&Printer<'_, '_>), ) -> Printer<'a, 'b> {
{
// Draw scrollbar? // Draw scrollbar?
let scrolling = self.is_scrolling(); let scrolling = self.is_scrolling();
@ -138,27 +149,21 @@ impl ScrollCore {
} }
// Draw content // Draw content
let printer = printer printer
.cropped(size) .cropped(size)
.content_offset(self.offset) .content_offset(self.offset)
.inner_size(self.inner_size); .inner_size(self.inner_size)
inner_draw(&printer);
} }
/// Performs `View::on_event()` /// Returns `true` if `event` should be processed by the content.
pub fn on_event<I: InnerOnEvent>( ///
&mut self, event: Event, mut inner: I, /// This also updates `event` so that it is relative to the content.
) -> EventResult { pub fn is_event_inside(&self, event: &mut Event) -> bool {
// Relativize event accorging to the offset if let Event::Mouse {
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 mut position,
ref offset, ref offset,
.. ..
} = relative_event } = event
{ {
// For mouse events, check if it falls inside the available area // For mouse events, check if it falls inside the available area
let inside = position let inside = position
@ -170,17 +175,15 @@ impl ScrollCore {
} else { } else {
// For key events, assume it's inside by default. // For key events, assume it's inside by default.
true true
}; }
}
let result = if inside { /// Handle an event after processing by the content.
// If the event is inside, give it to the child. pub fn on_inner_event(
inner.on_event(relative_event) &mut self, event: Event, inner_result: EventResult,
} else { important_area: Rect,
// Otherwise, pretend it wasn't there. ) -> EventResult {
EventResult::Ignored match inner_result {
};
match result {
EventResult::Ignored => { EventResult::Ignored => {
// The view ignored the event, so we're free to use it. // 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? // The view consumed the event. Maybe something changed?
// Fix offset? // Fix offset?
let important = inner.important_area(self.inner_size); let important = important_area;
// The furthest top-left we can go // The furthest top-left we can go
let top_left = (important.bottom_right() + (1, 1)) let top_left = (important.bottom_right() + (1, 1))
@ -315,21 +318,28 @@ impl ScrollCore {
} }
} }
/// Performs `View::layout()` /// Specifies the size given in a layout phase.
pub fn layout<I: InnerLayout>(&mut self, size: Vec2, mut inner: I) { pub(crate) fn set_last_size(&mut self, last_size: Vec2) {
// Size is final now, negociations are over. self.last_size = last_size;
self.last_size = size; }
// This is what we'd like /// Returns the size last given in `set_last_size()`.
let (inner_size, self_size) = pub fn last_size(&self) -> Vec2 {
self.sizes(size, true, Layout2Sizes { inner: &mut inner }); 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.inner_size = inner_size;
}
self.size_cache = Some(SizeCache::build(self_size, size)); /// Rebuild the cache with the given parameters.
pub(crate) fn build_cache(&mut self, self_size: Vec2, last_size: Vec2) {
inner.layout(self.inner_size); 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. // Keep the offset in the valid range.
self.offset = self self.offset = self
.offset .offset
@ -339,25 +349,11 @@ impl ScrollCore {
self.adjust_scroll(); self.adjust_scroll();
} }
/// Performs `View::needs_relayout()` /// Returns `true` if we should relayout, no matter the content.
pub fn needs_relayout<F>(&self, inner_needs_relayout: F) -> bool ///
where /// Even if this returns `false`, the content itself might still needs to relayout.
F: FnOnce() -> bool, pub fn needs_relayout(&self) -> bool {
{ self.size_cache.is_none()
self.size_cache.is_none() || inner_needs_relayout()
}
/// Performs `View::required_size()`
pub fn required_size<I: InnerRequiredSize>(
&mut self, constraint: Vec2, mut inner: I,
) -> Vec2 {
let (_, size) = self.sizes(
constraint,
false,
Required2Sizes { inner: &mut inner },
);
size
} }
/// Performs `View::call_on_any()` /// Performs `View::call_on_any()`
@ -380,17 +376,6 @@ impl ScrollCore {
inner_focus_view(selector) inner_focus_view(selector)
} }
/// Performs `View::take_focus()`
pub fn take_focus<F>(
&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. /// 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()) Rect::from_size(self.offset, self.available_size())
@ -431,6 +416,19 @@ impl ScrollCore {
self.with(|s| s.set_scrollbar_padding(scrollbar_padding)) 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<bool> {
self.enabled
}
/// Control whether scroll bars are visibile. /// Control whether scroll bars are visibile.
/// ///
/// Defaults to `true`. /// Defaults to `true`.
@ -445,6 +443,18 @@ impl ScrollCore {
self.with(|s| s.set_show_scrollbars(show_scrollbars)) 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 /// 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
@ -529,7 +539,7 @@ impl ScrollCore {
} }
/// Returns for each axis if we are scrolling. /// Returns for each axis if we are scrolling.
fn is_scrolling(&self) -> XY<bool> { pub fn is_scrolling(&self) -> XY<bool> {
self.inner_size.zip_map(self.last_size, |i, s| i > s) self.inner_size.zip_map(self.last_size, |i, s| i > s)
} }
@ -543,7 +553,7 @@ impl ScrollCore {
/// Will be zero in axis where we're not scrolling. /// Will be zero in axis where we're not scrolling.
/// ///
/// The scrollbar_size().x will be the horizontal space taken by the vertical scrollbar. /// 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() self.is_scrolling()
.swap() .swap()
.select_or(self.scrollbar_padding + (1, 1), Vec2::zero()) .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<I: InnerSizes>(
&mut self, constraint: Vec2, scrollable: XY<bool>, strict: bool,
inner: &mut I,
) -> (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 = 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. /// Starts scrolling from the cursor position.
/// ///
/// Returns `true` if the event was consumed. /// Returns `true` if the event was consumed.
@ -679,74 +646,17 @@ impl ScrollCore {
.set_axis_from(orientation, &new_offset.or_min(max_offset)); .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. /// Returns the cached value if it works, or `None`.
/// Then try with scrollbars if needed. pub(crate) fn try_cache(&self, constraint: Vec2) -> Option<(Vec2, Vec2)> {
/// Then try again in case we now need to scroll both ways (!!!) self.size_cache.and_then(|cache| {
/// if cache.zip_map(constraint, SizeCache::accept).both() {
/// Returns `(inner_size, desired_size)` Some((self.inner_size, cache.map(|c| c.value)))
fn sizes<I: InnerSizes>( } else {
&mut self, constraint: Vec2, strict: bool, mut inner: I, None
) -> (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)
} 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)
}
} 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 { 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)
}
}

View File

@ -2,17 +2,17 @@
//! //!
//! *This module is still unstable and may go through breaking changes.* //! *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. //! [`ScrollView`](crate::views::ScrollView) may be an easier way to add scrolling to an existing view.
mod base;
mod core; mod core;
mod traits; mod raw;
pub use self::base::ScrollBase; pub use self::core::{Core, Scroller};
pub use self::core::ScrollCore;
pub use self::traits::{InnerLayout, InnerOnEvent, InnerRequiredSize}; use crate::event::{Event, EventResult};
use crate::{Printer, Rect, Vec2};
/// Defines the scrolling behaviour on content or size change /// Defines the scrolling behaviour on content or size change
#[derive(Debug)] #[derive(Debug)]
@ -30,3 +30,210 @@ impl Default for ScrollStrategy {
ScrollStrategy::KeepRow 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<T, OnEvent, ImportantArea>(
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<T, ImportantArea>(
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<T, Layout, RequiredSize>(
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<T, RequiredSize>(
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<T, 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<T, LineDrawer>(
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<T, LeftBorder, TopBorder, RightBorder, BottomBorder>(
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<T, IsHDelim, IsVDelim>(
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), "");
}
},
);
}

198
src/view/scroll/raw.rs Normal file
View File

@ -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<Model, GetScroller, 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<Model, GetScroller, RequiredSize>(
constraint: Vec2, scrollable: XY<bool>, strict: bool, model: &mut Model,
get_scroller: &mut GetScroller, required_size: &mut RequiredSize,
) -> (Vec2, Vec2, XY<bool>)
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<Model, GetScroller, RequiredSize>(
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<Model, GetScroller, RequiredSize, 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<Model, GetScroller, RequiredSize>(
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<Model, GetScroller, OnEvent, ImportantArea>(
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)
}

View File

@ -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 {
<V as View>::on_event(self, event)
}
fn important_area(&self, size: Vec2) -> Rect {
<V as View>::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) {
<V as View>::layout(self, size);
}
fn needs_relayout(&self) -> bool {
<V as View>::needs_relayout(self)
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
<V as View>::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<V: View> InnerRequiredSize for &mut V {
fn needs_relayout(&self) -> bool {
<V as View>::needs_relayout(self)
}
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
<V as View>::required_size(self, constraint)
}
}

View File

@ -5,7 +5,7 @@ use crate::event::{
use crate::menu::{MenuItem, MenuTree}; use crate::menu::{MenuItem, MenuTree};
use crate::rect::Rect; use crate::rect::Rect;
use crate::vec::Vec2; use crate::vec::Vec2;
use crate::view::scroll::{InnerOnEvent, ScrollBase}; use crate::view::scroll;
use crate::view::{Position, View}; use crate::view::{Position, View};
use crate::views::OnEventView; use crate::views::OnEventView;
use crate::Cursive; use crate::Cursive;
@ -19,11 +19,23 @@ use unicode_width::UnicodeWidthStr;
pub struct MenuPopup { pub struct MenuPopup {
menu: Rc<MenuTree>, menu: Rc<MenuTree>,
focus: usize, focus: usize,
scrollbase: ScrollBase, scroll_core: scroll::Core,
align: Align, align: Align,
on_dismiss: Option<Callback>, on_dismiss: Option<Callback>,
on_action: Option<Callback>, on_action: Option<Callback>,
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 { impl MenuPopup {
@ -32,11 +44,10 @@ impl MenuPopup {
MenuPopup { MenuPopup {
menu, menu,
focus: 0, focus: 0,
scrollbase: ScrollBase::new(), scroll_core: scroll::Core::new(),
align: Align::top_left(), align: Align::top_left(),
on_dismiss: None, on_dismiss: None,
on_action: None, on_action: None,
last_size: Vec2::zero(),
} }
} }
@ -52,6 +63,11 @@ impl MenuPopup {
self.with(|s| s.set_focus(focus)) 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 { fn item_width(item: &MenuItem) -> usize {
match *item { match *item {
MenuItem::Delimiter => 1, MenuItem::Delimiter => 1,
@ -107,6 +123,193 @@ impl MenuPopup {
pub fn set_on_action<F: 'static + Fn(&mut Cursive)>(&mut self, f: F) { pub fn set_on_action<F: 'static + Fn(&mut Cursive)>(&mut self, f: F) {
self.on_action = Some(Callback::from_fn(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<MenuTree>) -> 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 { impl View for MenuPopup {
@ -121,14 +324,19 @@ impl View for MenuPopup {
let printer = &printer.offset((0, offset)); let printer = &printer.offset((0, offset));
// Start with a box // 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. // We're giving it a reduced size because of borders.
let printer = printer.shrinked_centered((2, 2)); let printer = printer.shrinked_centered((2, 2));
self.scrollbase.draw(&printer, |printer, i| { scroll::draw_lines(self, &printer, |s, printer, i| {
printer.with_selection(i == self.focus, |printer| { printer.with_selection(i == s.focus, |printer| {
let item = &self.menu.children[i]; let item = &s.menu.children[i];
match *item { match *item {
MenuItem::Delimiter => { MenuItem::Delimiter => {
// printer.print_hdelim((0, 0), printer.size.x) // 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. // We can't really shrink our items here, so it's not flexible.
// 2 is the padding // 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(); scroll::required_size(
self,
let res = self req.saturating_sub((2, 2)),
.scrollbase true,
.required_size(req.saturating_sub((2, 2)), |_| Vec2::new(w, h)) Self::inner_required_size,
+ (2, 2); ) + (2, 2)
res
} }
fn on_event(&mut self, event: Event) -> EventResult { fn on_event(&mut self, event: Event) -> EventResult {
match self.scrollbase.on_event( match scroll::on_event(
self,
event.relativized((1, 1)), event.relativized((1, 1)),
OnEvent { Self::inner_on_event,
focus: &mut self.focus, Self::inner_important_area,
menu: &self.menu,
on_dismiss: &self.on_dismiss,
on_action: &self.on_action,
last_size: &self.last_size,
},
) { ) {
EventResult::Ignored => { EventResult::Ignored => {
// Check back the non-relativized event now
if let Event::Mouse { if let Event::Mouse {
event: MouseEvent::Press(_), event: MouseEvent::Press(_),
position, position,
offset, offset,
} = event } = 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(); let dismiss_cb = self.on_dismiss.clone();
return EventResult::with_cb(move |s| { return EventResult::with_cb(move |s| {
if let Some(ref cb) = dismiss_cb { if let Some(ref cb) = dismiss_cb {
@ -213,210 +416,21 @@ impl View for MenuPopup {
} }
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
self.last_size = size; scroll::layout(
self,
let children = &self.menu.children; size.saturating_sub((2, 2)),
true,
self.scrollbase.layout(size.saturating_sub((2, 2)), |size| { |_s, _size| (),
Vec2::new(size.x, children.len()) 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<MenuTree>,
on_dismiss: &'a Option<Callback>,
on_action: &'a Option<Callback>,
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<MenuTree>) -> 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 { 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)))
} }
} }

View File

@ -1,15 +1,26 @@
use crate::direction::Direction; use crate::direction::Direction;
use crate::event::{AnyCb, Event, EventResult}; use crate::event::{AnyCb, Event, EventResult};
use crate::rect::Rect;
use crate::view::{scroll, ScrollStrategy, Selector, View}; 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. /// 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,
core: scroll::ScrollCore, core: scroll::Core,
}
impl<V> scroll::Scroller for ScrollView<V>
where
V: View,
{
fn get_scroller(&self) -> &scroll::Core {
&self.core
}
fn get_scroller_mut(&mut self) -> &mut scroll::Core {
&mut self.core
}
} }
impl<V> ScrollView<V> impl<V> ScrollView<V>
@ -20,7 +31,7 @@ where
pub fn new(inner: V) -> Self { pub fn new(inner: V) -> Self {
ScrollView { ScrollView {
inner, inner,
core: scroll::ScrollCore::new(), core: scroll::Core::new(),
} }
} }
@ -134,23 +145,39 @@ where
V: View, V: View,
{ {
fn draw(&self, printer: &Printer<'_, '_>) { 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 { 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) { 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 { 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 { 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>) { 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 { fn take_focus(&mut self, source: Direction) -> bool {
let inner = &mut self.inner; self.inner.take_focus(source) || self.core.is_scrolling().any()
self.core }
.take_focus(source, |source| inner.take_focus(source))
fn important_area(&self, size: Vec2) -> Rect {
scroll::important_area(self, size, |s, si| s.inner.important_area(si))
} }
} }