//! 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, 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 = 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 } }