use crossterm::event::KeyCode;
#[derive(Debug, Clone)]
pub struct ScrollState {
pub offset: usize,
pub page_size: usize,
}
impl Default for ScrollState {
fn default() -> Self {
Self {
offset: 0,
page_size: 1,
}
}
}
impl ScrollState {
pub fn new() -> Self {
Self::default()
}
fn page_size_value(&self) -> usize {
self.page_size.max(1)
}
fn max_offset(&self, content_len: usize) -> usize {
content_len.saturating_sub(self.page_size_value())
}
pub fn scroll_up(&mut self) {
self.offset = self.offset.saturating_sub(1);
}
pub fn scroll_down(&mut self, content_len: usize) {
let max = self.max_offset(content_len);
self.offset = (self.offset + 1).min(max);
}
pub fn to_top(&mut self) {
self.offset = 0;
}
pub fn to_bottom(&mut self, content_len: usize) {
self.offset = self.max_offset(content_len);
}
pub fn page_up(&mut self) {
let ps = self.page_size_value();
self.offset = self.offset.saturating_sub(ps);
}
pub fn page_down(&mut self, content_len: usize) {
let ps = self.page_size_value();
let max = self.max_offset(content_len);
self.offset = (self.offset + ps).min(max);
}
pub fn handle_scroll_key(&mut self, key: KeyCode, content_len: usize) -> bool {
match key {
KeyCode::Char('j') | KeyCode::Down => {
self.scroll_down(content_len);
true
}
KeyCode::Char('k') | KeyCode::Up => {
self.scroll_up();
true
}
KeyCode::Char('g') | KeyCode::Home => {
self.to_top();
true
}
KeyCode::Char('G') | KeyCode::End => {
self.to_bottom(content_len);
true
}
KeyCode::PageDown => {
self.page_down(content_len);
true
}
KeyCode::PageUp => {
self.page_up();
true
}
_ => false,
}
}
pub fn reset(&mut self) {
self.offset = 0;
self.page_size = 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scroll_state_basic_navigation() {
let mut s = ScrollState::new();
s.page_size = 5;
s.scroll_down(20);
assert_eq!(s.offset, 1);
s.scroll_up();
assert_eq!(s.offset, 0);
s.scroll_up();
assert_eq!(s.offset, 0);
s.to_bottom(20);
assert_eq!(s.offset, 15);
s.to_top();
assert_eq!(s.offset, 0);
}
#[test]
fn scroll_state_page_navigation() {
let mut s = ScrollState::new();
s.page_size = 5;
s.page_down(20);
assert_eq!(s.offset, 5);
s.page_down(20);
assert_eq!(s.offset, 10);
s.page_up();
assert_eq!(s.offset, 5);
s.offset = 14;
s.page_down(20);
assert_eq!(s.offset, 15);
}
#[test]
fn scroll_state_handle_key() {
let mut s = ScrollState::new();
s.page_size = 5;
assert!(s.handle_scroll_key(KeyCode::Char('j'), 10));
assert_eq!(s.offset, 1);
assert!(s.handle_scroll_key(KeyCode::Char('k'), 10));
assert_eq!(s.offset, 0);
assert!(s.handle_scroll_key(KeyCode::Char('G'), 10));
assert_eq!(s.offset, 5);
assert!(s.handle_scroll_key(KeyCode::Char('g'), 10));
assert_eq!(s.offset, 0);
assert!(!s.handle_scroll_key(KeyCode::Char('x'), 10));
}
#[test]
fn scroll_state_reset() {
let mut s = ScrollState {
offset: 5,
page_size: 10,
};
s.reset();
assert_eq!(s.offset, 0);
assert_eq!(s.page_size, 1);
}
}