chain word and better save/load UI
This commit is contained in:
103
crates/ratatui/src/list_select.rs
Normal file
103
crates/ratatui/src/list_select.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
|
||||
pub struct ListSelect<'a> {
|
||||
items: &'a [String],
|
||||
selected: usize,
|
||||
cursor: usize,
|
||||
focused: bool,
|
||||
visible_count: usize,
|
||||
scroll_offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> ListSelect<'a> {
|
||||
pub fn new(items: &'a [String], selected: usize, cursor: usize) -> Self {
|
||||
Self {
|
||||
items,
|
||||
selected,
|
||||
cursor,
|
||||
focused: false,
|
||||
visible_count: 5,
|
||||
scroll_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focused(mut self, focused: bool) -> Self {
|
||||
self.focused = focused;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scroll_offset(mut self, offset: usize) -> Self {
|
||||
self.scroll_offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn visible_count(mut self, n: usize) -> Self {
|
||||
self.visible_count = n;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u16 {
|
||||
let item_lines = self.items.len().min(self.visible_count) as u16;
|
||||
if self.items.len() > self.visible_count {
|
||||
item_lines + 1
|
||||
} else {
|
||||
item_lines
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(self, frame: &mut Frame, area: Rect) {
|
||||
let cursor_style = Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD);
|
||||
let selected_style = Style::new().fg(Color::Cyan);
|
||||
let normal_style = Style::default();
|
||||
let indicator_style = Style::new().fg(Color::DarkGray);
|
||||
|
||||
let visible_end = (self.scroll_offset + self.visible_count).min(self.items.len());
|
||||
let has_above = self.scroll_offset > 0;
|
||||
let has_below = visible_end < self.items.len();
|
||||
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
|
||||
for i in self.scroll_offset..visible_end {
|
||||
let name = &self.items[i];
|
||||
let is_cursor = self.focused && i == self.cursor;
|
||||
let is_selected = i == self.selected;
|
||||
|
||||
let style = if is_cursor {
|
||||
cursor_style
|
||||
} else if is_selected {
|
||||
selected_style
|
||||
} else {
|
||||
normal_style
|
||||
};
|
||||
|
||||
let prefix = if is_selected { "● " } else { " " };
|
||||
let mut spans = vec![
|
||||
Span::styled(prefix.to_string(), style),
|
||||
Span::styled(name.clone(), style),
|
||||
];
|
||||
|
||||
if has_above && i == self.scroll_offset {
|
||||
spans.push(Span::styled(" ▲", indicator_style));
|
||||
} else if has_below && i == visible_end - 1 {
|
||||
spans.push(Span::styled(" ▼", indicator_style));
|
||||
}
|
||||
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
if self.items.len() > self.visible_count {
|
||||
let position = self.cursor + 1;
|
||||
let total = self.items.len();
|
||||
lines.push(Line::from(Span::styled(
|
||||
format!(" ({position}/{total})"),
|
||||
indicator_style,
|
||||
)));
|
||||
}
|
||||
|
||||
frame.render_widget(Paragraph::new(lines), area);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user