cursive/src/view/scroll.rs

191 lines
6.1 KiB
Rust
Raw Normal View History

2016-06-28 05:10:59 +00:00
use std::cmp::{max, min};
use theme::ColorStyle;
use vec::Vec2;
2016-07-14 06:25:54 +00:00
use Printer;
2015-05-31 23:58:55 +00:00
/// Provide scrolling functionalities to a view.
2016-07-21 05:08:06 +00:00
///
/// You're not supposed to use this directly,
/// but it can be helpful if you create your own Views.
2016-06-28 05:40:11 +00:00
#[derive(Default)]
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
/// `scrollbar_padding` columns to the left.
///
/// (Useful when each item includes its own side borders,
/// to draw the scrollbar inside.)
pub scrollbar_padding: usize,
}
impl ScrollBase {
/// Creates a new, uninitialized scrollbar.
pub fn new() -> Self {
ScrollBase {
start_line: 0,
content_height: 0,
view_height: 0,
scrollbar_padding: 0,
}
}
/// Shifts the scrollbar toward the inside of the view.
///
/// Used by views that draw their side borders in the children.
/// Pushing the scrollbar to the left allows it to stay inside
/// the borders.
pub fn bar_padding(mut self, padding: usize) -> Self {
self.scrollbar_padding = padding;
self
}
2015-05-31 23:58:55 +00:00
/// Call this method whem the content or the view changes.
pub fn set_heights(&mut self, view_height: usize, content_height: usize) {
self.view_height = view_height;
self.content_height = content_height;
if self.scrollable() {
2016-07-10 02:05:51 +00:00
self.start_line = min(self.start_line,
self.content_height - self.view_height);
} else {
self.start_line = 0;
}
}
2015-05-31 23:58:55 +00:00
/// Returns `TRUE` if the view needs to scroll.
pub fn scrollable(&self) -> bool {
self.view_height < self.content_height
}
2015-05-31 23:58:55 +00:00
/// Returns `TRUE` unless we are at the top.
pub fn can_scroll_up(&self) -> bool {
self.start_line > 0
}
2015-05-31 23:58:55 +00:00
/// Returns `TRUE` unless we are at the bottom.
pub fn can_scroll_down(&self) -> bool {
self.start_line + self.view_height < self.content_height
}
2015-05-31 23:58:55 +00:00
/// Scroll to the top of the view.
pub fn scroll_top(&mut self) {
self.start_line = 0;
}
2015-05-31 23:58:55 +00:00
/// Makes sure that the given line is visible, scrolling if needed.
pub fn scroll_to(&mut self, y: usize) {
if y >= self.start_line + self.view_height {
self.start_line = 1 + y - self.view_height;
} else if y < self.start_line {
self.start_line = y;
}
}
2015-05-31 23:58:55 +00:00
/// Scroll to the bottom of the view.
pub fn scroll_bottom(&mut self) {
self.start_line = self.content_height - self.view_height;
}
2016-07-11 02:11:21 +00:00
/// Scroll down by the given number of line.
///
/// Never further than the bottom of the view.
pub fn scroll_down(&mut self, n: usize) {
2016-07-10 02:05:51 +00:00
self.start_line = min(self.start_line + n,
self.content_height - self.view_height);
}
2016-07-11 02:11:21 +00:00
/// Scroll up by the given number of lines.
///
/// Never above the top of the view.
pub fn scroll_up(&mut self, n: usize) {
self.start_line -= min(self.start_line, n);
}
2015-05-31 23:58:55 +00:00
/// 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
///
/// ```
2015-06-04 18:40:35 +00:00
/// # use cursive::view::ScrollBase;
2016-07-14 06:25:54 +00:00
/// # use cursive::Printer;
/// # use cursive::theme;
2015-06-04 18:40:35 +00:00
/// # let scrollbase = ScrollBase::new();
/// # let printer = Printer::new((5,1), theme::load_default());
2015-06-04 18:40:35 +00:00
/// # let printer = &printer;
/// let lines = ["Line 1", "Line number 2"];
2015-05-31 23:58:55 +00:00
/// scrollbase.draw(printer, |printer, i| {
/// printer.print((0,0), lines[i]);
/// });
/// ```
pub fn draw<F>(&self, printer: &Printer, line_drawer: F)
2016-03-15 22:37:57 +00:00
where F: Fn(&Printer, usize)
{
2016-07-17 05:05:28 +00:00
if self.view_height == 0 {
return;
}
2015-05-31 23:58:55 +00:00
// Print the content in a sub_printer
2016-07-10 02:05:51 +00:00
let max_y = min(self.view_height,
self.content_height - self.start_line);
2016-03-15 22:37:57 +00:00
let w = if self.scrollable() {
2016-07-20 03:44:20 +00:00
if printer.size.x < 2 {
return;
}
printer.size.x - 2 + self.scrollbar_padding // TODO: 2
2016-03-15 22:37:57 +00:00
} else {
printer.size.x
};
2015-05-31 23:58:55 +00:00
for y in 0..max_y {
// Y is the actual coordinate of the line.
// The item ID is then Y + self.start_line
2016-07-10 02:05:51 +00:00
line_drawer(&printer.sub_printer(Vec2::new(0, y),
Vec2::new(w, 1),
true),
2016-03-15 22:37:57 +00:00
y + self.start_line);
}
2015-05-31 23:58:55 +00:00
// And draw the scrollbar if needed
if self.view_height < self.content_height {
2016-07-11 02:11:21 +00:00
// We directly compute the size of the scrollbar
// (that way we avoid using floats).
// (ratio) * max_height
// Where ratio is ({start or end} / content.height)
2016-07-10 02:05:51 +00:00
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
2016-07-10 02:05:51 +00:00
let start = steps * self.start_line /
(1 + self.content_height - self.view_height);
2016-03-15 22:37:57 +00:00
let color = if printer.focused {
ColorStyle::Highlight
2016-03-15 22:37:57 +00:00
} else {
ColorStyle::HighlightInactive
2016-03-15 22:37:57 +00:00
};
// TODO: use 1 instead of 2
let scrollbar_x = printer.size.x - 1 - self.scrollbar_padding;
printer.print_vline((scrollbar_x, 0), printer.size.y, "|");
printer.with_color(color, |printer| {
printer.print_vline((scrollbar_x, start), height, " ");
});
}
}
}