pub const SCROLL_MARGIN: usize = 4;
pub fn adjust_scroll(
scroll: usize,
viewport: usize,
item_start: usize,
item_end: usize,
margin: usize,
) -> usize {
if viewport == 0 {
return scroll;
}
let margin = margin.min(viewport.saturating_sub(1) / 2);
let mut scroll = scroll;
let want_bottom = item_end + margin;
if want_bottom >= scroll + viewport {
scroll = want_bottom + 1 - viewport;
}
let want_top = item_start.saturating_sub(margin);
if want_top < scroll {
scroll = want_top;
}
scroll
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_change_when_comfortably_visible() {
assert_eq!(adjust_scroll(0, 20, 10, 10, SCROLL_MARGIN), 0);
}
#[test]
fn viewport_zero_is_noop() {
assert_eq!(adjust_scroll(5, 0, 100, 100, SCROLL_MARGIN), 5);
}
#[test]
fn scrolls_down_with_bottom_margin() {
assert_eq!(adjust_scroll(0, 20, 18, 18, 4), 3);
}
#[test]
fn scrolls_up_with_top_margin() {
assert_eq!(adjust_scroll(10, 20, 11, 11, 4), 7);
}
#[test]
fn multiline_item_fully_revealed() {
assert_eq!(adjust_scroll(0, 20, 17, 21, 4), 6);
}
#[test]
fn tall_item_anchors_to_top() {
let scroll = adjust_scroll(0, 10, 30, 50, 4);
assert!(scroll <= 30, "first line of tall item must be visible");
assert_eq!(scroll, 26);
}
#[test]
fn margin_clamped_in_small_viewport() {
assert_eq!(adjust_scroll(0, 3, 10, 10, 4), 9);
}
#[test]
fn single_line_viewport() {
assert_eq!(adjust_scroll(0, 1, 7, 7, 4), 7);
}
}