1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
use crate::div::div_up;
use crate::theme::ColorStyle;
use crate::Printer;
use crate::Vec2;
use std::cmp::{max, min};
/// Provide scrolling functionalities to a view.
///
/// This is a legacy helper utility to define scrollable views.
///
/// The [`scroll`] module is the preferred way to achieve that.
///
/// [`scroll`]: ./scroll/index.html
#[derive(Default, Debug)]
#[deprecated(
since = "0.16.0",
note = "`ScrollBase` is being deprecated in favor of the view::scroll module."
)]
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_offset` columns to the left.
///
/// (Useful when each item includes its own side borders,
/// to draw the scrollbar inside.)
pub scrollbar_offset: usize,
/// Blank between the text and the scrollbar.
pub right_padding: usize,
/// Initial position of the cursor when dragging.
pub thumb_grab: Option<usize>,
}
#[allow(deprecated)]
impl ScrollBase {
/// Creates a new, uninitialized scrollbar.
pub fn new() -> Self {
ScrollBase {
start_line: 0,
content_height: 0,
view_height: 0,
scrollbar_offset: 0,
right_padding: 1,
thumb_grab: None,
}
}
/// 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.
#[must_use]
pub fn scrollbar_offset(mut self, offset: usize) -> Self {
self.scrollbar_offset = offset;
self
}
/// Sets the number of blank cells between the text and the scrollbar.
///
/// Defaults to 1.
#[must_use]
pub fn right_padding(mut self, padding: usize) -> Self {
self.right_padding = padding;
self
}
/// 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;
// eprintln!("Setting heights: {} in {}", content_height, view_height);
if self.scrollable() {
self.start_line =
min(self.start_line, self.content_height - self.view_height);
} else {
self.start_line = 0;
}
}
/// Returns `TRUE` if the view needs to scroll.
pub fn scrollable(&self) -> bool {
self.view_height < self.content_height
}
/// Returns `TRUE` unless we are at the top.
pub fn can_scroll_up(&self) -> bool {
self.start_line > 0
}
/// Returns `TRUE` unless we are at the bottom.
pub fn can_scroll_down(&self) -> bool {
self.start_line + self.view_height < self.content_height
}
/// Scroll to the top of the view.
pub fn scroll_top(&mut self) {
self.start_line = 0;
}
/// 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;
}
}
/// Scroll to the bottom of the view.
pub fn scroll_bottom(&mut self) {
if self.scrollable() {
self.start_line = self.content_height - self.view_height;
}
}
/// Scroll down by the given number of line.
///
/// 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,
);
}
}
/// 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.
// eprintln!("Scrolling to {}", thumb_y);
self.start_line = min(
div_up(
(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.
pub fn scroll_up(&mut self, n: usize) {
if self.scrollable() {
self.start_line -= min(self.start_line, n);
}
}
/// 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?
let scrollbar_x = self.scrollbar_x(width);
// eprintln!("Grabbed {} for {}", position.x, scrollbar_x);
if position.x != scrollbar_x {
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 = Some(position.y - thumb_y);
} else {
// Just jump a bit...
self.thumb_grab = Some((height - 1) / 2);
// eprintln!("Grabbed at {}", self.thumb_grab);
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.
// eprintln!("Dragged: {:?}", position);
// eprintln!("thumb: {:?}", self.thumb_grab);
if let Some(grab) = self.thumb_grab {
let height = self.scrollbar_thumb_height();
self.scroll_to_thumb(position.y.saturating_sub(grab), height);
}
}
/// Returns `true` if we are in the process of dragging the scroll thumb.
pub fn is_dragging(&self) -> bool {
self.thumb_grab.is_some()
}
/// Stops grabbing the scrollbar.
pub fn release_grab(&mut self) {
self.thumb_grab = None;
}
/// 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
/// # let scrollbase = cursive_core::view::ScrollBase::new();
/// # let b = cursive_core::backend::Dummy::init();
/// # let t = cursive_core::theme::load_default();
/// # let printer = &cursive_core::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, line_drawer: F)
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 w = if self.scrollable() {
// We have to remove the bar width and the padding.
printer.size.x.saturating_sub(1 + self.right_padding)
} else {
printer.size.x
};
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.offset((0, y)).cropped((w, 1)),
y + self.start_line,
);
}
// And draw the scrollbar if needed
if self.view_height < self.content_height {
// 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)
let height = self.scrollbar_thumb_height();
let start = self.scrollbar_thumb_y(height);
let color = if printer.focused {
ColorStyle::highlight()
} else {
ColorStyle::highlight_inactive()
};
let scrollbar_x = self.scrollbar_x(printer.size.x);
// eprintln!("Drawing bar at x={}", scrollbar_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)
}
}