use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Scrollbar, ScrollbarOrientation, ScrollbarState};
pub fn offset(selected: usize, len: usize, visible: usize) -> usize {
if visible == 0 {
return 0;
}
selected.saturating_sub(visible - 1).min(len.saturating_sub(visible))
}
pub fn render(
frame: &mut Frame,
area: Rect,
title: &str,
focused: bool,
selected: usize,
len: usize,
row: &dyn Fn(usize) -> Line<'static>,
) {
let border = if focused { Color::Green } else { Color::DarkGray };
let mut block = Block::bordered()
.title(Span::styled(
title.to_string(),
Style::new().fg(border).add_modifier(Modifier::BOLD),
))
.border_style(Style::new().fg(border));
if len > 0 {
block = block.title_bottom(
Line::styled(format!("{} of {}", selected + 1, len), Style::new().fg(border))
.right_aligned(),
);
}
let inner = block.inner(area);
frame.render_widget(block, area);
let visible = inner.height as usize;
if visible == 0 || len == 0 {
return;
}
let offset = offset(selected, len, visible);
for (i, idx) in (offset..len.min(offset + visible)).enumerate() {
let rect = Rect { y: inner.y + i as u16, height: 1, ..inner };
frame.render_widget(row(idx), rect);
if idx == selected {
let bg = if focused { Color::Blue } else { Color::Rgb(48, 48, 56) };
frame.buffer_mut().set_style(rect, Style::new().bg(bg));
}
}
if len > visible {
let mut state = ScrollbarState::new(len).position(selected);
frame.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None)
.track_symbol(None)
.thumb_symbol("┃")
.thumb_style(Style::new().fg(border)),
area,
&mut state,
);
}
}