mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add mouse scroll support to TextView
This commit is contained in:
parent
a6fb0e71cd
commit
5931ab17c8
@ -1,6 +1,5 @@
|
||||
use Printer;
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use theme::ColorStyle;
|
||||
use vec::Vec2;
|
||||
|
||||
@ -12,10 +11,13 @@ use vec::Vec2;
|
||||
pub struct ScrollBase {
|
||||
/// First line visible
|
||||
pub start_line: usize,
|
||||
|
||||
/// Content height
|
||||
pub content_height: usize,
|
||||
|
||||
/// Number of lines displayed
|
||||
pub view_height: usize,
|
||||
|
||||
/// Padding for the scrollbar
|
||||
///
|
||||
/// If present, the scrollbar will be shifted
|
||||
@ -27,6 +29,9 @@ pub struct ScrollBase {
|
||||
|
||||
/// Blank between the text and the scrollbar.
|
||||
pub right_padding: usize,
|
||||
|
||||
/// Initial position of the cursor when dragging.
|
||||
pub thumb_grab: usize,
|
||||
}
|
||||
|
||||
/// Defines the scrolling behaviour on content or size change
|
||||
@ -54,6 +59,7 @@ impl ScrollBase {
|
||||
view_height: 0,
|
||||
scrollbar_offset: 0,
|
||||
right_padding: 1,
|
||||
thumb_grab: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +87,8 @@ impl ScrollBase {
|
||||
self.content_height = content_height;
|
||||
|
||||
if self.scrollable() {
|
||||
self.start_line = min(self.start_line,
|
||||
self.content_height - self.view_height);
|
||||
self.start_line =
|
||||
min(self.start_line, self.content_height - self.view_height);
|
||||
} else {
|
||||
self.start_line = 0;
|
||||
}
|
||||
@ -129,11 +135,24 @@ impl ScrollBase {
|
||||
/// Never further than the bottom of the view.
|
||||
pub fn scroll_down(&mut self, n: usize) {
|
||||
if self.scrollable() {
|
||||
self.start_line = min(self.start_line + n,
|
||||
self.content_height - self.view_height);
|
||||
self.start_line = min(
|
||||
self.start_line + n,
|
||||
self.content_height - self.view_height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrolls down until the scrollbar thumb is at the given location.
|
||||
pub fn scroll_to_thumb(&mut self, thumb_y: usize, thumb_height: usize) {
|
||||
// The min() is there to stop at the bottom of the content.
|
||||
// The saturating_sub is there to stop at the bottom of the content.
|
||||
self.start_line = min(
|
||||
(1 + self.content_height - self.view_height) * thumb_y
|
||||
/ (self.view_height - thumb_height + 1),
|
||||
self.content_height - self.view_height,
|
||||
);
|
||||
}
|
||||
|
||||
/// Scroll up by the given number of lines.
|
||||
///
|
||||
/// Never above the top of the view.
|
||||
@ -143,6 +162,42 @@ impl ScrollBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts scrolling from the given cursor position.
|
||||
pub fn start_drag(&mut self, position: Vec2, width: usize) -> bool {
|
||||
// First: are we on the correct column?
|
||||
if position.x != self.scrollbar_x(width) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Now, did we hit the thumb? Or should we direct-jump?
|
||||
let height = self.scrollbar_thumb_height();
|
||||
let thumb_y = self.scrollbar_thumb_y(height);
|
||||
|
||||
if position.y >= thumb_y && position.y < thumb_y + height {
|
||||
// Grabbed!
|
||||
self.thumb_grab = position.y - thumb_y;
|
||||
} else {
|
||||
// Just jump a bit...
|
||||
self.thumb_grab = height / 2;
|
||||
}
|
||||
|
||||
self.drag(position);
|
||||
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Keeps scrolling by dragging the cursor.
|
||||
pub fn drag(&mut self, position: Vec2) {
|
||||
// Our goal is self.scrollbar_thumb_y()+thumb_grab == position.y
|
||||
// Which means that position.y is the middle of the scrollbar.
|
||||
let height = self.scrollbar_thumb_height();
|
||||
let grab = self.thumb_grab;
|
||||
self.scroll_to_thumb(position.y.saturating_sub(grab), height);
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
@ -168,14 +223,15 @@ impl ScrollBase {
|
||||
/// });
|
||||
/// ```
|
||||
pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
|
||||
where F: Fn(&Printer, usize)
|
||||
where
|
||||
F: Fn(&Printer, usize),
|
||||
{
|
||||
if self.view_height == 0 {
|
||||
return;
|
||||
}
|
||||
// Print the content in a sub_printer
|
||||
let max_y = min(self.view_height,
|
||||
self.content_height - self.start_line);
|
||||
let max_y =
|
||||
min(self.view_height, self.content_height - self.start_line);
|
||||
let w = if self.scrollable() {
|
||||
// We have to remove the bar width and the padding.
|
||||
printer.size.x.saturating_sub(1 + self.right_padding)
|
||||
@ -186,10 +242,10 @@ impl ScrollBase {
|
||||
for y in 0..max_y {
|
||||
// Y is the actual coordinate of the line.
|
||||
// The item ID is then Y + self.start_line
|
||||
line_drawer(&printer.sub_printer(Vec2::new(0, y),
|
||||
Vec2::new(w, 1),
|
||||
true),
|
||||
y + self.start_line);
|
||||
line_drawer(
|
||||
&printer.sub_printer(Vec2::new(0, y), Vec2::new(w, 1), true),
|
||||
y + self.start_line,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -199,15 +255,8 @@ impl ScrollBase {
|
||||
// (that way we avoid using floats).
|
||||
// (ratio) * max_height
|
||||
// Where ratio is ({start or end} / content.height)
|
||||
let height = max(1,
|
||||
self.view_height * self.view_height /
|
||||
self.content_height);
|
||||
// Number of different possible positions
|
||||
let steps = self.view_height - height + 1;
|
||||
|
||||
// Now
|
||||
let start = steps * self.start_line /
|
||||
(1 + self.content_height - self.view_height);
|
||||
let height = self.scrollbar_thumb_height();
|
||||
let start = self.scrollbar_thumb_y(height);
|
||||
|
||||
let color = if printer.focused {
|
||||
ColorStyle::Highlight
|
||||
@ -215,12 +264,34 @@ impl ScrollBase {
|
||||
ColorStyle::HighlightInactive
|
||||
};
|
||||
|
||||
// TODO: use 1 instead of 2
|
||||
let scrollbar_x = printer.size.x.saturating_sub(1 + self.scrollbar_offset);
|
||||
let scrollbar_x = self.scrollbar_x(printer.size.x);
|
||||
|
||||
// The background
|
||||
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
|
||||
|
||||
// The scrollbar thumb
|
||||
printer.with_color(color, |printer| {
|
||||
printer.print_vline((scrollbar_x, start), height, "▒");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the X position of the scrollbar, given the size available.
|
||||
///
|
||||
/// Note that this does not depend whether or
|
||||
/// not a scrollbar will actually be present.
|
||||
pub fn scrollbar_x(&self, total_size: usize) -> usize {
|
||||
total_size.saturating_sub(1 + self.scrollbar_offset)
|
||||
}
|
||||
|
||||
/// Returns the height of the scrollbar thumb.
|
||||
pub fn scrollbar_thumb_height(&self) -> usize {
|
||||
max(1, self.view_height * self.view_height / self.content_height)
|
||||
}
|
||||
|
||||
/// Returns the y position of the scrollbar thumb.
|
||||
pub fn scrollbar_thumb_y(&self, scrollbar_thumb_height: usize) -> usize {
|
||||
let steps = self.view_height - scrollbar_thumb_height + 1;
|
||||
steps * self.start_line / (1 + self.content_height - self.view_height)
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,10 @@ use XY;
|
||||
use align::*;
|
||||
use direction::Direction;
|
||||
use event::*;
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use utils::{LinesIterator, Row};
|
||||
use vec::Vec2;
|
||||
use view::{SizeCache, View, ScrollBase, ScrollStrategy};
|
||||
use view::{ScrollBase, ScrollStrategy, SizeCache, View};
|
||||
|
||||
/// A simple view showing a fixed text
|
||||
pub struct TextView {
|
||||
@ -181,8 +179,8 @@ impl TextView {
|
||||
|
||||
// First attempt: naively hope that we won't need a scrollbar_width
|
||||
// (This means we try to use the entire available width for text).
|
||||
self.rows = LinesIterator::new(strip_last_newline(&self.content),
|
||||
size.x)
|
||||
self.rows =
|
||||
LinesIterator::new(strip_last_newline(&self.content), size.x)
|
||||
.collect();
|
||||
|
||||
// Width taken by the scrollbar. Without a scrollbar, it's 0.
|
||||
@ -241,11 +239,10 @@ impl TextView {
|
||||
|
||||
impl View for TextView {
|
||||
fn draw(&self, printer: &Printer) {
|
||||
|
||||
let h = self.rows.len();
|
||||
// If the content is smaller than the view, align it somewhere.
|
||||
let offset = self.align.v.get_offset(h, printer.size.y);
|
||||
let printer =
|
||||
&printer.sub_printer(Vec2::new(0, offset), printer.size, true);
|
||||
let printer = &printer.offset((0, offset), true);
|
||||
|
||||
self.scrollbase.draw(printer, |printer, i| {
|
||||
let row = &self.rows[i];
|
||||
@ -261,14 +258,56 @@ impl View for TextView {
|
||||
return EventResult::Ignored;
|
||||
}
|
||||
|
||||
// We know we are scrollable, otherwise the event would just be ignored.
|
||||
match event {
|
||||
Event::Key(Key::Home) => self.scrollbase.scroll_top(),
|
||||
Event::Key(Key::End) => self.scrollbase.scroll_bottom(),
|
||||
Event::Key(Key::Up) if self.scrollbase.can_scroll_up() => {
|
||||
self.scrollbase.scroll_up(1)
|
||||
}
|
||||
Event::Key(Key::Down) if self.scrollbase
|
||||
.can_scroll_down() => self.scrollbase.scroll_down(1),
|
||||
Event::Key(Key::Down) if self.scrollbase.can_scroll_down() => {
|
||||
self.scrollbase.scroll_down(1)
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelDown,
|
||||
position: _,
|
||||
offset: _,
|
||||
} if self.scrollbase.can_scroll_down() =>
|
||||
{
|
||||
self.scrollbase.scroll_down(5)
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::WheelUp,
|
||||
position: _,
|
||||
offset: _,
|
||||
} if self.scrollbase.can_scroll_up() =>
|
||||
{
|
||||
self.scrollbase.scroll_up(5)
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Press(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} if position
|
||||
.checked_sub(offset)
|
||||
.and_then(|position| {
|
||||
self.width.map(
|
||||
|width| self.scrollbase.start_drag(position, width),
|
||||
)
|
||||
})
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
// Start scroll drag at the given position
|
||||
}
|
||||
Event::Mouse {
|
||||
event: MouseEvent::Hold(MouseButton::Left),
|
||||
position,
|
||||
offset,
|
||||
} => {
|
||||
position
|
||||
.checked_sub(offset)
|
||||
.map(|position| self.scrollbase.drag(position));
|
||||
}
|
||||
Event::Key(Key::PageDown) => self.scrollbase.scroll_down(10),
|
||||
Event::Key(Key::PageUp) => self.scrollbase.scroll_up(10),
|
||||
_ => return EventResult::Ignored,
|
||||
|
Loading…
Reference in New Issue
Block a user