mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
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:
parent
e5ef01c90f
commit
4b5a7867e3
13
src/lib.rs
13
src/lib.rs
@ -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;
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>(
|
|
||||||
&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)
|
|
||||||
} else {
|
} else {
|
||||||
// Again? We're now scrolling in a new direction?
|
None
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
198
src/view/scroll/raw.rs
Normal 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)
|
||||||
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
fn important_area(&self, size: Vec2) -> Rect {
|
||||||
if self.menu.is_empty() {
|
scroll::important_area(
|
||||||
return Rect::from((0, 0));
|
self,
|
||||||
}
|
size.saturating_sub((2, 2)),
|
||||||
|
Self::inner_important_area,
|
||||||
Rect::from_size((0, self.focus), (size.x, 1))
|
)
|
||||||
}
|
.with(|area| area.offset((1, 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 {
|
|
||||||
Rect::from_size((0, *self.focus), (size.x, 1))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user