pub fn offset(height: usize, cursor: Option<usize>) -> usize {
match cursor {
Some(c) => c.saturating_sub(height.saturating_sub(1)),
None => 0,
}
}
pub fn offset_while(cursor: Option<usize>, fits: impl Fn(usize) -> bool) -> usize {
let Some(cursor) = cursor else { return 0 };
(0..cursor).find(|&from| fits(from)).unwrap_or(cursor)
}
#[cfg(test)]
mod tests {
use super::{offset, offset_while};
#[test]
fn an_unfocused_card_never_scrolls() {
assert_eq!(offset(1, None), 0);
assert_eq!(offset(3, None), 0);
assert_eq!(offset_while(None, |_| false), 0);
}
#[test]
fn a_cursor_already_on_screen_does_not_scroll() {
for cursor in 0..5 {
assert_eq!(offset(5, Some(cursor)), 0, "cursor {cursor} fits a 5-row card");
}
}
#[test]
fn scrolling_is_the_minimum_that_keeps_the_cursor_on_screen() {
assert_eq!(offset(5, Some(5)), 1);
assert_eq!(offset(5, Some(9)), 5);
assert_eq!(offset(0, Some(3)), 3);
}
#[test]
fn variable_cost_search_takes_the_topmost_window_that_fits() {
assert_eq!(offset_while(Some(6), |from| from >= 2), 2);
assert_eq!(offset_while(Some(6), |_| true), 0);
}
#[test]
fn variable_cost_search_terminates_when_nothing_fits() {
assert_eq!(offset_while(Some(6), |_| false), 6);
assert_eq!(offset_while(Some(0), |_| false), 0);
}
}