#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct View {
pub top_line: usize,
pub left_col: usize,
}
impl View {
pub fn scroll_to(
&mut self,
line: usize,
col: usize,
height: usize,
width: usize,
scrolloff: usize,
) {
if height == 0 || width == 0 {
return;
}
let margin = scrolloff.min(height.saturating_sub(1) / 2);
let top_limit = line.saturating_sub(margin);
if top_limit < self.top_line {
self.top_line = top_limit;
}
let bottom_limit = line + margin + 1;
if bottom_limit > self.top_line + height {
self.top_line = bottom_limit - height;
}
if col < self.left_col {
self.left_col = col;
} else if col >= self.left_col + width {
self.left_col = col - width + 1;
}
}
pub fn scroll_lines(&mut self, delta: isize, last_line: usize) {
let top = if delta < 0 {
self.top_line.saturating_sub(delta.unsigned_abs())
} else {
self.top_line.saturating_add(delta.unsigned_abs())
};
self.top_line = top.min(last_line);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scrolling_down_keeps_the_margin() {
let mut view = View::default();
view.scroll_to(20, 0, 10, 80, 2);
assert_eq!(view.top_line, 13);
}
#[test]
fn scrolling_up_keeps_the_margin() {
let mut view = View {
top_line: 50,
left_col: 0,
};
view.scroll_to(52, 0, 10, 80, 3);
assert_eq!(view.top_line, 49);
}
#[test]
fn margin_is_capped_on_short_windows() {
let mut view = View::default();
view.scroll_to(5, 0, 3, 80, 10);
assert_eq!(view.top_line, 4);
}
#[test]
fn horizontal_scrolling_follows_the_caret() {
let mut view = View::default();
view.scroll_to(0, 100, 10, 40, 0);
assert_eq!(view.left_col, 61);
view.scroll_to(0, 3, 10, 40, 0);
assert_eq!(view.left_col, 3);
}
#[test]
fn a_visible_caret_does_not_scroll() {
let mut view = View {
top_line: 10,
left_col: 0,
};
view.scroll_to(14, 5, 10, 80, 2);
assert_eq!(view.top_line, 10);
}
}