use crossterm::event::{KeyCode, KeyEvent};
#[derive(Debug, PartialEq)]
pub enum Action {
Continue,
Exit,
NoChange,
}
#[derive(Debug)]
pub struct ScrollState {
pub top_line: usize,
pub viewport_height: u16,
pub total_lines: usize,
}
impl ScrollState {
pub fn new(viewport_height: u16) -> Self {
Self {
top_line: 0,
viewport_height,
total_lines: 0,
}
}
pub fn set_total_lines(&mut self, total: usize) {
self.total_lines = total;
self.clamp_scroll();
}
pub fn update_viewport(&mut self, height: u16) {
self.viewport_height = height;
self.clamp_scroll();
}
pub fn get_visible_range(&self) -> (usize, usize) {
let start = self.top_line;
let max_visible = self.viewport_height as usize;
let end = (start + max_visible).min(self.total_lines);
(start, end)
}
pub fn handle_key_event(&mut self, key: KeyEvent) -> Action {
match key.code {
KeyCode::PageUp => {
self.scroll_up(self.viewport_height as i32 - 1);
Action::Continue
}
KeyCode::PageDown => {
self.scroll_down(self.viewport_height as i32 - 1);
Action::Continue
}
KeyCode::Up => {
self.scroll_up(1);
Action::Continue
}
KeyCode::Down => {
self.scroll_down(1);
Action::Continue
}
KeyCode::Char('q') | KeyCode::Esc => Action::Exit,
_ => Action::NoChange,
}
}
pub fn scroll_up(&mut self, amount: i32) {
if amount <= 0 {
return;
}
self.top_line = self.top_line.saturating_sub(amount as usize);
}
pub fn scroll_down(&mut self, amount: i32) {
if amount <= 0 {
return;
}
let max_scroll = self.max_scroll();
self.top_line = (self.top_line + amount as usize).min(max_scroll);
}
pub fn total_lines(&self) -> usize {
self.total_lines
}
fn max_scroll(&self) -> usize {
self.total_lines
.saturating_sub(self.viewport_height as usize)
}
fn clamp_scroll(&mut self) {
let max_scroll = self.max_scroll();
self.top_line = self.top_line.min(max_scroll);
}
pub fn validate_viewport(&mut self) {
self.viewport_height = self.viewport_height.max(1);
let max_scroll = self.max_scroll();
self.top_line = self.top_line.min(max_scroll);
}
}
impl Default for ScrollState {
fn default() -> Self {
Self::new(24) }
}