use ratatui::buffer::Buffer as Surface;
use ratatui::layout::Rect;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, Widget};
use crate::filesystem::tree::Entry;
use crate::theme::Theme;
pub struct FileTree<'a> {
pub entries: &'a [Entry],
pub selected: usize,
pub focused: bool,
pub title: &'a str,
pub theme: &'a Theme,
}
impl FileTree<'_> {
fn scroll_offset(&self, height: usize) -> usize {
if height == 0 || self.selected < height {
return 0;
}
self.selected - height + 1
}
fn label(entry: &Entry) -> String {
let marker = if entry.is_dir {
if entry.is_open { "▾ " } else { "▸ " }
} else {
" "
};
format!("{}{marker}{}", " ".repeat(entry.depth), entry.name)
}
}
impl Widget for FileTree<'_> {
fn render(self, area: Rect, surface: &mut Surface) {
if area.is_empty() {
return;
}
let border_style = if self.focused {
self.theme.popup_border
} else {
self.theme.gutter
};
let block = Block::default()
.borders(Borders::RIGHT)
.border_type(BorderType::Plain)
.border_style(border_style)
.title(Span::styled(self.title, self.theme.tree_directory));
let inner = block.inner(area);
surface.set_style(area, self.theme.text);
block.render(area, surface);
let height = inner.height as usize;
let offset = self.scroll_offset(height);
for (row, entry) in self.entries.iter().skip(offset).take(height).enumerate() {
let index = offset + row;
let mut style = if entry.is_dir {
self.theme.tree_directory
} else {
self.theme.tree_file
};
if index == self.selected {
style = style.patch(if self.focused {
self.theme.selection
} else {
self.theme.cursor_line
});
}
let y = inner.y + u16::try_from(row).unwrap_or(u16::MAX);
let line = Rect {
y,
height: 1,
..inner
};
surface.set_style(line, style);
Line::from(Span::styled(Self::label(entry), style)).render(line, surface);
}
}
}