Add new ScrollBase

This commit is contained in:
Alexandre Bury 2019-03-07 22:33:09 -08:00
parent bc7972d539
commit ae0e9216ce
5 changed files with 190 additions and 21 deletions

View File

@ -29,6 +29,9 @@ pub struct Printer<'a, 'b> {
/// Size of the area we are allowed to draw on. /// Size of the area we are allowed to draw on.
/// ///
/// Anything outside of this should be discarded. /// Anything outside of this should be discarded.
///
/// The view being drawn can ingore this, but anything further than that
/// will be ignored.
pub output_size: Vec2, pub output_size: Vec2,
/// Size allocated to the view. /// Size allocated to the view.
@ -39,6 +42,9 @@ pub struct Printer<'a, 'b> {
/// Offset into the view for this printer. /// Offset into the view for this printer.
/// ///
/// The view being drawn can ignore this, but anything to the top-left of
/// this will actually be ignored, so it can be used to skip this part.
///
/// A print request `x`, will really print at `x - content_offset`. /// A print request `x`, will really print at `x - content_offset`.
pub content_offset: Vec2, pub content_offset: Vec2,

144
src/view/scroll/base.rs Normal file
View File

@ -0,0 +1,144 @@
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,
/// Number of lines displayed
pub view_height: usize,
/// Blank between the text and the scrollbar.
pub right_padding: usize,
/// Initial position of the cursor when dragging.
pub thumb_grab: Option<usize>,
}
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(),
view_height: 0,
right_padding: 1,
thumb_grab: None,
}
}
/// 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((y, 0)).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

@ -10,13 +10,12 @@ use crate::view::{ScrollStrategy, Selector, SizeCache};
use crate::with::With; use crate::with::With;
use crate::XY; use crate::XY;
use crate::view::scroll::{ use crate::view::scroll::{InnerLayout, InnerOnEvent, InnerRequiredSize};
InnerDraw, InnerLayout, InnerOnEvent, InnerRequiredSize,
};
/// Core system for scrolling views. /// Core system for scrolling views.
/// ///
/// See also [`ScrollView`](crate::views::ScrollView). /// See also [`ScrollView`](crate::views::ScrollView).
#[derive(Debug)]
pub struct ScrollCore { pub struct ScrollCore {
/// 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,
@ -81,7 +80,10 @@ impl ScrollCore {
} }
/// Performs the `View::draw()` operation. /// Performs the `View::draw()` operation.
pub fn draw<I: InnerDraw>(&self, printer: &Printer<'_, '_>, inner: I) { pub fn draw<F>(&self, printer: &Printer<'_, '_>, inner_draw: F)
where
F: FnOnce(&Printer<'_, '_>),
{
// Draw scrollbar? // Draw scrollbar?
let scrolling = self.is_scrolling(); let scrolling = self.is_scrolling();
@ -141,7 +143,7 @@ impl ScrollCore {
.content_offset(self.offset) .content_offset(self.offset)
.inner_size(self.inner_size); .inner_size(self.inner_size);
inner.draw(&printer); inner_draw(&printer);
} }
/// Performs `View::on_event()` /// Performs `View::on_event()`
@ -180,10 +182,12 @@ impl ScrollCore {
match result { match result {
EventResult::Ignored => { EventResult::Ignored => {
// The view ignored the event, so we're free to use it.
// If it's an arrow, try to scroll in the given direction. // If it's an arrow, try to scroll in the given direction.
// If it's a mouse scroll, try to scroll as well. // If it's a mouse scroll, try to scroll as well.
// Also allow Ctrl+arrow to move the view, // Also allow Ctrl+arrow to move the view,
// but not the selection. // without affecting the selection.
match event { match event {
Event::Mouse { Event::Mouse {
event: MouseEvent::WheelUp, event: MouseEvent::WheelUp,
@ -287,6 +291,8 @@ impl ScrollCore {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
other => { other => {
// The view consumed the event. Maybe something changed?
// Fix offset? // Fix offset?
let important = inner.important_area(self.inner_size); let important = inner.important_area(self.inner_size);
@ -409,6 +415,22 @@ impl ScrollCore {
self.with(|s| s.set_scroll_strategy(strategy)) self.with(|s| s.set_scroll_strategy(strategy))
} }
/// Sets the padding between content and scrollbar.
pub fn set_scrollbar_padding<V: Into<Vec2>>(
&mut self, scrollbar_padding: V,
) {
self.scrollbar_padding = scrollbar_padding.into();
}
/// Sets the padding between content and scrollbar.
///
/// Chainable variant.
pub fn scrollbar_padding<V: Into<Vec2>>(
self, scrollbar_padding: V,
) -> Self {
self.with(|s| s.set_scrollbar_padding(scrollbar_padding))
}
/// Control whether scroll bars are visibile. /// Control whether scroll bars are visibile.
/// ///
/// Defaults to `true`. /// Defaults to `true`.
@ -466,6 +488,15 @@ impl ScrollCore {
self.with(|s| s.set_scroll_x(enabled)) self.with(|s| s.set_scroll_x(enabled))
} }
/// Scroll until the given column is visible.
pub fn scroll_to_x(&mut self, x: usize) {
if x > self.offset.x + self.last_size.x {
self.offset.x = 1 + x - self.last_size.x;
} else if x < self.offset.x {
self.offset.x = x;
}
}
/// Programmatically scroll to the top of the view. /// Programmatically scroll to the top of the view.
pub fn scroll_to_top(&mut self) { pub fn scroll_to_top(&mut self) {
let curr_x = self.offset.x; let curr_x = self.offset.x;

View File

@ -4,13 +4,13 @@
//! //!
//! [`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 traits;
pub use self::base::ScrollBase;
pub use self::core::ScrollCore; pub use self::core::ScrollCore;
pub use self::traits::{ pub use self::traits::{InnerLayout, InnerOnEvent, InnerRequiredSize};
InnerDraw, InnerLayout, InnerOnEvent, InnerRequiredSize,
};
/// Defines the scrolling behaviour on content or size change /// Defines the scrolling behaviour on content or size change
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,5 +1,4 @@
use crate::event::{Event, EventResult}; use crate::event::{Event, EventResult};
use crate::printer::Printer;
use crate::rect::Rect; use crate::rect::Rect;
use crate::vec::Vec2; use crate::vec::Vec2;
@ -24,17 +23,6 @@ impl<'a, V: View> InnerOnEvent for &'a mut V {
} }
/// Inner implementation for `ScrollCore::draw()` /// Inner implementation for `ScrollCore::draw()`
pub trait InnerDraw {
/// Performs `View::draw()`
fn draw(&self, printer: &Printer<'_, '_>);
}
impl<'a, V: View> InnerDraw for &'a V {
fn draw(&self, printer: &Printer<'_, '_>) {
<V as View>::draw(self, printer);
}
}
/// Inner implementation for `ScrollCore::InnerLayout()` /// Inner implementation for `ScrollCore::InnerLayout()`
pub trait InnerLayout { pub trait InnerLayout {
/// Performs `View::layout()` /// Performs `View::layout()`