use ratatui::{
Frame,
layout::Rect,
style::Style,
widgets::{Scrollbar, ScrollbarOrientation, ScrollbarState},
};
use crate::theme;
pub fn clamp_scroll(
selected: usize,
scroll_offset: usize,
visible_rows: usize,
total_rows: usize,
) -> usize {
let visible_rows = visible_rows.max(1);
let max_offset = total_rows.saturating_sub(visible_rows);
let mut offset = scroll_offset;
if selected < offset {
offset = selected;
} else if selected >= offset + visible_rows {
offset = selected + 1 - visible_rows;
}
offset.min(max_offset)
}
pub fn render_scrollbar(
frame: &mut Frame,
area: Rect,
scroll_offset: usize,
visible_rows: usize,
total_rows: usize,
) {
if total_rows <= visible_rows {
return;
}
let t = theme::active();
let mut state = ScrollbarState::new(total_rows.saturating_sub(visible_rows))
.position(scroll_offset)
.viewport_content_length(visible_rows);
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.style(Style::default().fg(t.dim))
.thumb_style(Style::default().fg(t.accent))
.begin_symbol(None)
.end_symbol(None);
frame.render_stateful_widget(scrollbar, area, &mut state);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamp_keeps_selection_in_window_when_already_visible() {
assert_eq!(clamp_scroll(3, 0, 10, 50), 0);
}
#[test]
fn clamp_advances_window_when_selection_moves_below() {
assert_eq!(clamp_scroll(12, 0, 10, 50), 3);
}
#[test]
fn clamp_snaps_window_up_when_selection_moves_above() {
assert_eq!(clamp_scroll(2, 10, 10, 50), 2);
}
#[test]
fn clamp_holds_offset_within_max_when_list_shrinks() {
assert_eq!(clamp_scroll(14, 15, 10, 20), 10);
}
#[test]
fn clamp_yields_zero_when_list_fits() {
assert_eq!(clamp_scroll(4, 0, 10, 5), 0);
assert_eq!(clamp_scroll(4, 7, 10, 5), 0); }
#[test]
fn clamp_handles_zero_visible_rows_without_panic() {
let _ = clamp_scroll(3, 0, 0, 50);
}
}