use std::cell::Cell;
use crossterm::event::{KeyCode, KeyEvent};
const PAGE_ROWS: i32 = 10;
#[derive(Debug, Default)]
pub struct Scroll {
offset: Cell<u16>,
max: Cell<u16>,
}
impl Scroll {
pub fn reset(&self) {
self.offset.set(0);
}
pub fn offset(&self) -> u16 {
self.offset.get()
}
pub fn scroll_by(&self, delta: i32) {
let max = i32::from(self.max.get());
let moved = i32::from(self.offset.get()).saturating_add(delta);
self.offset.set(moved.clamp(0, max) as u16);
}
pub fn handle_key(&self, key: KeyEvent) {
let delta = match key.code {
KeyCode::Up | KeyCode::Char('k') => -1,
KeyCode::Down | KeyCode::Char('j') => 1,
KeyCode::PageUp | KeyCode::Char('u') => -PAGE_ROWS,
KeyCode::PageDown | KeyCode::Char('d') => PAGE_ROWS,
KeyCode::Home | KeyCode::Char('g') => i32::MIN,
KeyCode::End | KeyCode::Char('G') => i32::MAX,
_ => return,
};
self.scroll_by(delta);
}
pub fn fit(&self, total: u16, viewport: u16) -> u16 {
self.max.set(total.saturating_sub(viewport));
let offset = self.offset.get().min(self.max.get());
self.offset.set(offset);
offset
}
}
#[cfg(test)]
mod tests {
use crossterm::event::KeyModifiers;
use super::*;
#[test]
fn fit_clamps_the_offset_to_the_new_range() {
let scroll = Scroll::default();
scroll.scroll_by(i32::MAX);
assert_eq!(scroll.offset(), 0);
assert_eq!(scroll.fit(30, 10), 0);
scroll.scroll_by(i32::MAX);
assert_eq!(scroll.offset(), 20);
assert_eq!(scroll.fit(30, 25), 5);
}
#[test]
fn a_body_that_fits_never_scrolls() {
let scroll = Scroll::default();
scroll.fit(5, 20);
scroll.scroll_by(3);
assert_eq!(scroll.offset(), 0);
}
#[test]
fn handle_key_maps_the_movement_keys() {
let scroll = Scroll::default();
scroll.fit(100, 10);
let press = |code| KeyEvent::new(code, KeyModifiers::NONE);
scroll.handle_key(press(KeyCode::Down));
assert_eq!(scroll.offset(), 1);
scroll.handle_key(press(KeyCode::Char('j')));
assert_eq!(scroll.offset(), 2);
scroll.handle_key(press(KeyCode::End));
assert_eq!(scroll.offset(), 90);
scroll.handle_key(press(KeyCode::Home));
assert_eq!(scroll.offset(), 0);
scroll.handle_key(press(KeyCode::Char('x')));
assert_eq!(scroll.offset(), 0);
}
}