Files
Cagire/crates/ratatui/src/file_browser.rs

122 lines
3.4 KiB
Rust

//! File/directory browser modal widget.
use crate::theme;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use super::ModalFrame;
/// Modal listing files and directories with a filter input line.
pub struct FileBrowserModal<'a> {
title: &'a str,
input: &'a str,
entries: &'a [(String, bool, bool)],
selected: usize,
scroll_offset: usize,
border_color: Option<Color>,
width: u16,
height: u16,
}
impl<'a> FileBrowserModal<'a> {
pub fn new(title: &'a str, input: &'a str, entries: &'a [(String, bool, bool)]) -> Self {
Self {
title,
input,
entries,
selected: 0,
scroll_offset: 0,
border_color: None,
width: 60,
height: 16,
}
}
pub fn selected(mut self, idx: usize) -> Self {
self.selected = idx;
self
}
pub fn scroll_offset(mut self, offset: usize) -> Self {
self.scroll_offset = offset;
self
}
pub fn border_color(mut self, c: Color) -> Self {
self.border_color = Some(c);
self
}
pub fn width(mut self, w: u16) -> Self {
self.width = w;
self
}
pub fn height(mut self, h: u16) -> Self {
self.height = h;
self
}
pub fn render_centered(self, frame: &mut Frame, term: Rect) -> Rect {
let colors = theme::get();
let border_color = self.border_color.unwrap_or(colors.ui.text_primary);
let inner = ModalFrame::new(self.title)
.width(self.width)
.height(self.height)
.border_color(border_color)
.render_centered(frame, term);
let rows = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner);
// Input line
frame.render_widget(
Paragraph::new(Line::from(vec![
Span::raw("> "),
Span::styled(self.input, Style::new().fg(colors.input.text)),
Span::styled("", Style::new().fg(colors.input.cursor)),
])),
rows[0],
);
// Entries list
let visible_height = rows[1].height as usize;
let visible_entries = self
.entries
.iter()
.skip(self.scroll_offset)
.take(visible_height);
let lines: Vec<Line> = visible_entries
.enumerate()
.map(|(i, (name, is_dir, is_cagire))| {
let abs_idx = i + self.scroll_offset;
let is_selected = abs_idx == self.selected;
let prefix = if is_selected { "> " } else { " " };
let display = if *is_dir {
format!("{prefix}{name}/")
} else {
format!("{prefix}{name}")
};
let color = if is_selected {
colors.browser.selected
} else if *is_dir {
colors.browser.directory
} else if *is_cagire {
colors.browser.project_file
} else {
colors.browser.file
};
Line::from(Span::styled(display, Style::new().fg(color)))
})
.collect();
frame.render_widget(Paragraph::new(lines), rows[1]);
inner
}
}