mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-10 03:10:41 +00:00
Move event handling outside of scroll::Core
This commit is contained in:
parent
31b8e1f515
commit
0a66978d7f
@ -17,6 +17,11 @@ use crate::XY;
|
||||
/// [`XY`]: crate::XY
|
||||
pub type Vec2 = XY<usize>;
|
||||
|
||||
/// A signed 2D quantity, in cells.
|
||||
///
|
||||
/// Usually represents an offset.
|
||||
pub type Vec2i = XY<isize>;
|
||||
|
||||
impl<T: PartialOrd> PartialOrd for XY<T> {
|
||||
/// `a < b` <=> `a.x < b.x && a.y < b.y`
|
||||
fn partial_cmp(&self, other: &XY<T>) -> Option<Ordering> {
|
||||
|
@ -1,14 +1,15 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use crate::direction::Orientation;
|
||||
use crate::event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent};
|
||||
use crate::printer::Printer;
|
||||
use crate::rect::Rect;
|
||||
use crate::theme::ColorStyle;
|
||||
use crate::view::{ScrollStrategy, Selector, SizeCache};
|
||||
use crate::with::With;
|
||||
use crate::Vec2;
|
||||
use crate::XY;
|
||||
use crate::{
|
||||
direction::Orientation,
|
||||
event::{AnyCb, Event, EventResult, Key, MouseButton, MouseEvent},
|
||||
printer::Printer,
|
||||
rect::Rect,
|
||||
theme::ColorStyle,
|
||||
view::{ScrollStrategy, Selector, SizeCache},
|
||||
with::With,
|
||||
Vec2, XY,
|
||||
};
|
||||
|
||||
/// Describes an item with a scroll core.
|
||||
///
|
||||
@ -217,137 +218,6 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle an event after processing by the content.
|
||||
pub fn on_inner_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
inner_result: EventResult,
|
||||
important_area: Rect,
|
||||
) -> EventResult {
|
||||
match inner_result {
|
||||
EventResult::Ignored => {
|
||||
// The view ignored the event, so we're free to use it.
|
||||
|
||||
// If it's an arrow, try to scroll in the given direction.
|
||||
// If it's a mouse scroll, try to scroll as well.
|
||||
// Also allow Ctrl+arrow to move the view,
|
||||
// without affecting the selection.
|
||||
match event {
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelUp,
|
||||
..
|
||||
} if self.enabled.y && self.offset.y > 0 => {
|
||||
self.offset.y = self.offset.y.saturating_sub(3);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelDown,
|
||||
..
|
||||
} if self.enabled.y
|
||||
&& (self.offset.y + self.last_available_size().y
|
||||
< self.inner_size.y) =>
|
||||
{
|
||||
self.offset.y = min(
|
||||
self.inner_size
|
||||
.y
|
||||
.saturating_sub(self.last_available_size().y),
|
||||
self.offset.y + 3,
|
||||
);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Press(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} if self.show_scrollbars
|
||||
&& position
|
||||
.checked_sub(offset)
|
||||
.map(|position| self.start_drag(position))
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
// Just consume the event.
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Hold(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} if self.show_scrollbars => {
|
||||
let position = position.saturating_sub(offset);
|
||||
self.drag(position);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Release(MouseButton::Left),
|
||||
..
|
||||
} => {
|
||||
self.release_grab();
|
||||
}
|
||||
Event::Key(Key::Home) if self.enabled.any() => {
|
||||
self.offset =
|
||||
self.enabled.select_or(Vec2::zero(), self.offset);
|
||||
}
|
||||
Event::Key(Key::End) if self.enabled.any() => {
|
||||
let max_offset = self
|
||||
.inner_size
|
||||
.saturating_sub(self.last_available_size());
|
||||
self.offset =
|
||||
self.enabled.select_or(max_offset, self.offset);
|
||||
}
|
||||
Event::Ctrl(Key::Up) | Event::Key(Key::Up)
|
||||
if self.enabled.y && self.offset.y > 0 =>
|
||||
{
|
||||
self.offset.y -= 1;
|
||||
}
|
||||
Event::Key(Key::PageUp)
|
||||
if self.enabled.y && self.offset.y > 0 =>
|
||||
{
|
||||
self.offset.y = self.offset.y.saturating_sub(5);
|
||||
}
|
||||
Event::Key(Key::PageDown)
|
||||
if self.enabled.y
|
||||
&& (self.offset.y
|
||||
+ self.last_available_size().y
|
||||
< self.inner_size.y) =>
|
||||
{
|
||||
// No `min` check here - we allow going over the edge.
|
||||
self.offset.y += 5;
|
||||
}
|
||||
Event::Ctrl(Key::Down) | Event::Key(Key::Down)
|
||||
if self.enabled.y
|
||||
&& (self.offset.y
|
||||
+ self.last_available_size().y
|
||||
< self.inner_size.y) =>
|
||||
{
|
||||
self.offset.y += 1;
|
||||
}
|
||||
Event::Ctrl(Key::Left) | Event::Key(Key::Left)
|
||||
if self.enabled.x && self.offset.x > 0 =>
|
||||
{
|
||||
self.offset.x -= 1;
|
||||
}
|
||||
Event::Ctrl(Key::Right) | Event::Key(Key::Right)
|
||||
if self.enabled.x
|
||||
&& (self.offset.x
|
||||
+ self.last_available_size().x
|
||||
< self.inner_size.x) =>
|
||||
{
|
||||
self.offset.x += 1;
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
};
|
||||
|
||||
// We just scrolled manually, so reset the scroll strategy.
|
||||
self.scroll_strategy = ScrollStrategy::KeepRow;
|
||||
// TODO: return callback on_scroll?
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
other => {
|
||||
// The view consumed the event. Maybe something changed?
|
||||
|
||||
self.scroll_to_rect(important_area);
|
||||
|
||||
other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the size given in a layout phase.
|
||||
pub(crate) fn set_last_size(
|
||||
&mut self,
|
||||
@ -507,6 +377,7 @@ impl Core {
|
||||
{
|
||||
let max_offset =
|
||||
self.inner_size.saturating_sub(self.last_available_size());
|
||||
|
||||
self.offset = offset.into().or_min(max_offset);
|
||||
}
|
||||
|
||||
@ -599,32 +470,56 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
/// Scroll by `n` cells to the left.
|
||||
pub fn scroll_left(&mut self, n: usize) {
|
||||
// Goal: never repeat .x (to prevent typo/confusion)
|
||||
// TODO: further reduce code duplication.
|
||||
let offset = self.offset.as_ref_mut().x;
|
||||
*offset = offset.saturating_sub(n);
|
||||
}
|
||||
|
||||
/// Scroll by `n` cells to the top.
|
||||
pub fn scroll_up(&mut self, n: usize) {
|
||||
let offset = self.offset.as_ref_mut().y;
|
||||
*offset = offset.saturating_sub(n);
|
||||
}
|
||||
|
||||
/// Scroll by `n` cells to the bottom.
|
||||
pub fn scroll_down(&mut self, n: usize) {
|
||||
let max_offset = self.max_offset();
|
||||
let (offset, max) = XY::zip(self.offset.as_ref_mut(), max_offset).y;
|
||||
*offset = min(max, *offset + n);
|
||||
}
|
||||
|
||||
/// Scroll by `n` cells to the right.
|
||||
pub fn scroll_right(&mut self, n: usize) {
|
||||
let max_offset = self.max_offset();
|
||||
let (offset, max) = XY::zip(self.offset.as_ref_mut(), max_offset).x;
|
||||
*offset = min(max, *offset + n);
|
||||
}
|
||||
|
||||
/// Programmatically scroll to the top of the view.
|
||||
pub fn scroll_to_top(&mut self) {
|
||||
let curr_x = self.offset.x;
|
||||
self.set_offset((curr_x, 0));
|
||||
self.offset.y = 0;
|
||||
}
|
||||
|
||||
/// Programmatically scroll to the bottom of the view.
|
||||
pub fn scroll_to_bottom(&mut self) {
|
||||
let max_y =
|
||||
self.inner_size.saturating_sub(self.last_available_size()).y;
|
||||
let curr_x = self.offset.x;
|
||||
self.set_offset((curr_x, max_y));
|
||||
self.offset.y = self.max_offset().y;
|
||||
}
|
||||
|
||||
/// Programmatically scroll to the leftmost side of the view.
|
||||
pub fn scroll_to_left(&mut self) {
|
||||
let curr_y = self.offset.y;
|
||||
self.set_offset((0, curr_y));
|
||||
self.offset.x = 0;
|
||||
}
|
||||
|
||||
/// Programmatically scroll to the rightmost side of the view.
|
||||
pub fn scroll_to_right(&mut self) {
|
||||
let max_x =
|
||||
self.inner_size.saturating_sub(self.last_available_size()).x;
|
||||
let curr_y = self.offset.y;
|
||||
self.set_offset((max_x, curr_y));
|
||||
self.offset.x = self.max_offset().x;
|
||||
}
|
||||
|
||||
fn max_offset(&self) -> Vec2 {
|
||||
self.inner_size.saturating_sub(self.last_available_size())
|
||||
}
|
||||
|
||||
/// Clears the cache.
|
||||
@ -639,7 +534,7 @@ impl Core {
|
||||
}
|
||||
|
||||
/// Stops grabbing the scrollbar.
|
||||
fn release_grab(&mut self) {
|
||||
pub fn release_grab(&mut self) {
|
||||
self.thumb_grab = None;
|
||||
}
|
||||
|
||||
@ -664,10 +559,45 @@ impl Core {
|
||||
self.last_available_size() + self.scrollbar_size()
|
||||
}
|
||||
|
||||
/// Checks if we can scroll up.
|
||||
///
|
||||
/// Returns `true` if vertical scrolling is enabled, and if we are not at
|
||||
/// the top already.
|
||||
pub fn can_scroll_up(&self) -> bool {
|
||||
self.enabled.y && self.offset.y > 0
|
||||
}
|
||||
|
||||
/// Checks if we can scroll to the left.
|
||||
///
|
||||
/// Returns `true` if horizontal scrolling is enabled, and if we are not at
|
||||
/// the left edge already.
|
||||
pub fn can_scroll_left(&self) -> bool {
|
||||
self.enabled.x && self.offset.x > 0
|
||||
}
|
||||
|
||||
/// Checks if we can scroll down.
|
||||
///
|
||||
/// Returns `true` if vertical scrolling is enabled, and if we are not at
|
||||
/// the bottom already.
|
||||
pub fn can_scroll_down(&self) -> bool {
|
||||
self.enabled.y
|
||||
&& (self.offset.y + self.last_available_size().y
|
||||
< self.inner_size.y)
|
||||
}
|
||||
|
||||
/// Checks if we can scroll to the right.
|
||||
///
|
||||
/// Returns `true` if horizontal scrolling is enabled, and if we are not at
|
||||
/// the right edge already.
|
||||
pub fn can_scroll_right(&self) -> bool {
|
||||
self.enabled.x
|
||||
&& (self.offset.x + self.last_available_size().x
|
||||
< self.inner_size.x)
|
||||
}
|
||||
/// Starts scrolling from the cursor position.
|
||||
///
|
||||
/// Returns `true` if the event was consumed.
|
||||
fn start_drag(&mut self, position: Vec2) -> bool {
|
||||
pub fn start_drag(&mut self, position: Vec2) -> bool {
|
||||
// For each scrollbar, how far it is.
|
||||
let scrollbar_pos = self.last_outer_size().saturating_sub((1, 1));
|
||||
let lengths = self.scrollbar_thumb_lengths();
|
||||
@ -708,7 +638,7 @@ impl Core {
|
||||
}
|
||||
|
||||
/// Called when a mouse drag is detected.
|
||||
fn drag(&mut self, position: Vec2) {
|
||||
pub fn drag(&mut self, position: Vec2) {
|
||||
// Only do something if we grabbed something before.
|
||||
if let Some((orientation, grab)) = self.thumb_grab {
|
||||
self.scroll_to_thumb(
|
||||
|
@ -2,12 +2,14 @@
|
||||
//!
|
||||
//! Most functions take a generic `Model` class, and various closures to get
|
||||
//! the required things from this model.
|
||||
use crate::event::{Event, EventResult};
|
||||
use crate::rect::Rect;
|
||||
use crate::view::scroll;
|
||||
use crate::xy::XY;
|
||||
use crate::Printer;
|
||||
use crate::Vec2;
|
||||
//!
|
||||
use crate::{
|
||||
event::{Event, EventResult, Key, MouseButton, MouseEvent},
|
||||
rect::Rect,
|
||||
view::scroll,
|
||||
xy::XY,
|
||||
Printer, Vec2,
|
||||
};
|
||||
|
||||
/// Implements `View::draw` over the `model`.
|
||||
pub fn draw<Model, GetScroller, Draw>(
|
||||
@ -224,7 +226,124 @@ pub fn on_event<Model: ?Sized>(
|
||||
} 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)
|
||||
|
||||
match result {
|
||||
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 a mouse scroll, try to scroll as well.
|
||||
// Also allow Ctrl+arrow to move the view,
|
||||
// without affecting the selection.
|
||||
match event {
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelUp,
|
||||
..
|
||||
} if get_scroller(model).can_scroll_up() => {
|
||||
get_scroller(model).scroll_up(3);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelDown,
|
||||
..
|
||||
} if get_scroller(model).can_scroll_down() => {
|
||||
get_scroller(model).scroll_down(3);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Press(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} if get_scroller(model).get_show_scrollbars()
|
||||
&& position
|
||||
.checked_sub(offset)
|
||||
.map(|position| {
|
||||
get_scroller(model).start_drag(position)
|
||||
})
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
// Just consume the event.
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Hold(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} if get_scroller(model).get_show_scrollbars() => {
|
||||
let position = position.saturating_sub(offset);
|
||||
get_scroller(model).drag(position);
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Release(MouseButton::Left),
|
||||
..
|
||||
} => {
|
||||
get_scroller(model).release_grab();
|
||||
}
|
||||
Event::Key(Key::Home)
|
||||
if get_scroller(model).is_enabled().any() =>
|
||||
{
|
||||
let actions: XY<fn(&mut scroll::Core)> = XY::new(
|
||||
scroll::Core::scroll_to_left,
|
||||
scroll::Core::scroll_to_top,
|
||||
);
|
||||
let scroller = get_scroller(model);
|
||||
actions.run_if(scroller.is_enabled(), |a| a(scroller));
|
||||
}
|
||||
Event::Key(Key::End)
|
||||
if get_scroller(model).is_enabled().any() =>
|
||||
{
|
||||
let actions: XY<fn(&mut scroll::Core)> = XY::new(
|
||||
scroll::Core::scroll_to_right,
|
||||
scroll::Core::scroll_to_bottom,
|
||||
);
|
||||
let scroller = get_scroller(model);
|
||||
actions.run_if(scroller.is_enabled(), |a| a(scroller));
|
||||
}
|
||||
Event::Ctrl(Key::Up) | Event::Key(Key::Up)
|
||||
if get_scroller(model).can_scroll_up() =>
|
||||
{
|
||||
get_scroller(model).scroll_up(1);
|
||||
}
|
||||
Event::Key(Key::PageUp)
|
||||
if get_scroller(model).can_scroll_up() =>
|
||||
{
|
||||
get_scroller(model).scroll_up(5);
|
||||
}
|
||||
Event::Key(Key::PageDown)
|
||||
if get_scroller(model).can_scroll_down() =>
|
||||
{
|
||||
// No `min` check here - we allow going over the edge.
|
||||
get_scroller(model).scroll_down(5);
|
||||
}
|
||||
Event::Ctrl(Key::Down) | Event::Key(Key::Down)
|
||||
if get_scroller(model).can_scroll_down() =>
|
||||
{
|
||||
get_scroller(model).scroll_down(1);
|
||||
}
|
||||
Event::Ctrl(Key::Left) | Event::Key(Key::Left)
|
||||
if get_scroller(model).can_scroll_left() =>
|
||||
{
|
||||
get_scroller(model).scroll_left(1);
|
||||
}
|
||||
Event::Ctrl(Key::Right) | Event::Key(Key::Right)
|
||||
if get_scroller(model).can_scroll_right() =>
|
||||
{
|
||||
get_scroller(model).scroll_right(1);
|
||||
}
|
||||
_ => return EventResult::Ignored,
|
||||
};
|
||||
|
||||
// We just scrolled manually, so reset the scroll strategy.
|
||||
get_scroller(model)
|
||||
.set_scroll_strategy(scroll::ScrollStrategy::KeepRow);
|
||||
|
||||
// TODO: return callback on_scroll?
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
other => {
|
||||
// The view consumed the event. Maybe something changed?
|
||||
let inner_size = get_scroller(model).inner_size();
|
||||
let important = important_area(model, inner_size);
|
||||
get_scroller(model).scroll_to_rect(important);
|
||||
|
||||
other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,12 @@
|
||||
/// Generic trait to enable chainable API
|
||||
pub trait With: Sized {
|
||||
/// Calls the given closure and return the result.
|
||||
///
|
||||
/// Used to chainify wrapper constructors.
|
||||
fn wrap_with<U, F: FnOnce(Self) -> U>(self, f: F) -> U {
|
||||
f(self)
|
||||
}
|
||||
|
||||
/// Calls the given closure on `self`.
|
||||
fn with<F: FnOnce(&mut Self)>(mut self, f: F) -> Self {
|
||||
f(&mut self);
|
||||
|
@ -110,9 +110,9 @@ impl<T> XY<T> {
|
||||
///
|
||||
/// assert_eq!(xy.run_if(cond, |v| v * 3), XY::new(Some(3), None));
|
||||
/// ```
|
||||
pub fn run_if<F, U>(self, condition: XY<bool>, f: F) -> XY<Option<U>>
|
||||
pub fn run_if<F, U>(self, condition: XY<bool>, mut f: F) -> XY<Option<U>>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
self.zip_map(condition, |v, c| if c { Some(f(v)) } else { None })
|
||||
}
|
||||
@ -163,7 +163,7 @@ impl<T> XY<T> {
|
||||
(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Return a `XY` with references to this one's values.
|
||||
/// Returns a `XY` with references to this one's values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -184,6 +184,11 @@ impl<T> XY<T> {
|
||||
XY::new(&self.x, &self.y)
|
||||
}
|
||||
|
||||
/// Returns a `XY` with mutable references to this one's values.
|
||||
pub fn as_ref_mut(&mut self) -> XY<&mut T> {
|
||||
XY::new(&mut self.x, &mut self.y)
|
||||
}
|
||||
|
||||
/// Creates an iterator that returns references to `x`, then `y`.
|
||||
///
|
||||
/// # Examples
|
||||
@ -331,9 +336,9 @@ impl<T> XY<T> {
|
||||
/// let xy = a.zip_map(b, |(a1, a2), b| if b { a1 } else { a2 });
|
||||
/// assert_eq!(xy, XY::new(1, 20));
|
||||
/// ```
|
||||
pub fn zip_map<U, V, F>(self, other: XY<U>, f: F) -> XY<V>
|
||||
pub fn zip_map<U, V, F>(self, other: XY<U>, mut f: F) -> XY<V>
|
||||
where
|
||||
F: Fn(T, U) -> V,
|
||||
F: FnMut(T, U) -> V,
|
||||
{
|
||||
XY::new(f(self.x, other.x), f(self.y, other.y))
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use cursive::align::HAlign;
|
||||
use cursive::view::Scrollable;
|
||||
use cursive::event::{EventResult, Key};
|
||||
use cursive::traits::With;
|
||||
use cursive::view::{scroll::Scroller, Scrollable};
|
||||
use cursive::views::{Dialog, Panel, TextView};
|
||||
use cursive::{align::HAlign, views::OnEventView};
|
||||
|
||||
fn main() {
|
||||
// Read some long text from a file.
|
||||
@ -14,11 +16,33 @@ fn main() {
|
||||
// The text is too long to fit on a line, so the view will wrap lines,
|
||||
// and will adapt to the terminal size.
|
||||
siv.add_fullscreen_layer(
|
||||
Dialog::around(Panel::new(TextView::new(content).scrollable()))
|
||||
.title("Unicode and wide-character support")
|
||||
// This is the alignment for the button
|
||||
.h_align(HAlign::Center)
|
||||
.button("Quit", |s| s.quit()),
|
||||
Dialog::around(Panel::new(
|
||||
TextView::new(content)
|
||||
.scrollable()
|
||||
.wrap_with(OnEventView::new)
|
||||
.on_pre_event_inner(Key::PageUp, |v, _| {
|
||||
let scroller = v.get_scroller_mut();
|
||||
if scroller.can_scroll_up() {
|
||||
scroller.scroll_up(
|
||||
scroller.last_outer_size().y.saturating_sub(1),
|
||||
);
|
||||
}
|
||||
Some(EventResult::Consumed(None))
|
||||
})
|
||||
.on_pre_event_inner(Key::PageDown, |v, _| {
|
||||
let scroller = v.get_scroller_mut();
|
||||
if scroller.can_scroll_down() {
|
||||
scroller.scroll_down(
|
||||
scroller.last_outer_size().y.saturating_sub(1),
|
||||
);
|
||||
}
|
||||
Some(EventResult::Consumed(None))
|
||||
}),
|
||||
))
|
||||
.title("Unicode and wide-character support")
|
||||
// This is the alignment for the button
|
||||
.h_align(HAlign::Center)
|
||||
.button("Quit", |s| s.quit()),
|
||||
);
|
||||
// Show a popup on top of the view.
|
||||
siv.add_layer(Dialog::info(
|
||||
|
Loading…
Reference in New Issue
Block a user