WIP: half broken
This commit is contained in:
170
crates/ratatui/src/sample_browser.rs
Normal file
170
crates/ratatui/src/sample_browser.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Borders, Paragraph};
|
||||
use ratatui::Frame;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TreeLineKind {
|
||||
Root { expanded: bool },
|
||||
Folder { expanded: bool },
|
||||
File,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TreeLine {
|
||||
pub depth: u8,
|
||||
pub kind: TreeLineKind,
|
||||
pub label: String,
|
||||
pub folder: String,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
pub struct SampleBrowser<'a> {
|
||||
entries: &'a [TreeLine],
|
||||
cursor: usize,
|
||||
scroll_offset: usize,
|
||||
search_query: &'a str,
|
||||
search_active: bool,
|
||||
focused: bool,
|
||||
}
|
||||
|
||||
impl<'a> SampleBrowser<'a> {
|
||||
pub fn new(entries: &'a [TreeLine], cursor: usize) -> Self {
|
||||
Self {
|
||||
entries,
|
||||
cursor,
|
||||
scroll_offset: 0,
|
||||
search_query: "",
|
||||
search_active: false,
|
||||
focused: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_offset(mut self, offset: usize) -> Self {
|
||||
self.scroll_offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn search(mut self, query: &'a str, active: bool) -> Self {
|
||||
self.search_query = query;
|
||||
self.search_active = active;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn focused(mut self, focused: bool) -> Self {
|
||||
self.focused = focused;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render(self, frame: &mut Frame, area: Rect) {
|
||||
let border_style = if self.focused {
|
||||
Style::new().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
};
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(border_style)
|
||||
.title(" Samples ");
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(block, area);
|
||||
|
||||
if inner.height == 0 || inner.width == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let show_search = self.search_active || !self.search_query.is_empty();
|
||||
let (search_area, list_area) = if show_search {
|
||||
let [s, l] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(inner);
|
||||
(Some(s), l)
|
||||
} else {
|
||||
(None, inner)
|
||||
};
|
||||
|
||||
if let Some(sa) = search_area {
|
||||
self.render_search(frame, sa);
|
||||
}
|
||||
self.render_tree(frame, list_area);
|
||||
}
|
||||
|
||||
fn render_search(&self, frame: &mut Frame, area: Rect) {
|
||||
let style = if self.search_active {
|
||||
Style::new().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::new().fg(Color::DarkGray)
|
||||
};
|
||||
let cursor = if self.search_active { "_" } else { "" };
|
||||
let text = format!("/{}{}", self.search_query, cursor);
|
||||
let line = Line::from(Span::styled(text, style));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
}
|
||||
|
||||
fn render_tree(&self, frame: &mut Frame, area: Rect) {
|
||||
let height = area.height as usize;
|
||||
if self.entries.is_empty() {
|
||||
let msg = if self.search_query.is_empty() {
|
||||
"No samples loaded"
|
||||
} else {
|
||||
"No matches"
|
||||
};
|
||||
let line = Line::from(Span::styled(msg, Style::new().fg(Color::DarkGray)));
|
||||
frame.render_widget(Paragraph::new(vec![line]), area);
|
||||
return;
|
||||
}
|
||||
|
||||
let visible_end = (self.scroll_offset + height).min(self.entries.len());
|
||||
let mut lines: Vec<Line> = Vec::with_capacity(height);
|
||||
|
||||
for i in self.scroll_offset..visible_end {
|
||||
let entry = &self.entries[i];
|
||||
let is_cursor = i == self.cursor;
|
||||
let indent = " ".repeat(entry.depth as usize);
|
||||
|
||||
let (icon, icon_color) = match entry.kind {
|
||||
TreeLineKind::Root { expanded: true } | TreeLineKind::Folder { expanded: true } => {
|
||||
("\u{25BC} ", Color::Cyan)
|
||||
}
|
||||
TreeLineKind::Root { expanded: false }
|
||||
| TreeLineKind::Folder { expanded: false } => ("\u{25B6} ", Color::Cyan),
|
||||
TreeLineKind::File => ("\u{266A} ", Color::DarkGray),
|
||||
};
|
||||
|
||||
let label_style = if is_cursor && self.focused {
|
||||
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
} else if is_cursor {
|
||||
Style::new().fg(Color::White)
|
||||
} else {
|
||||
match entry.kind {
|
||||
TreeLineKind::Root { .. } => {
|
||||
Style::new().fg(Color::White).add_modifier(Modifier::BOLD)
|
||||
}
|
||||
TreeLineKind::Folder { .. } => Style::new().fg(Color::Cyan),
|
||||
TreeLineKind::File => Style::default(),
|
||||
}
|
||||
};
|
||||
|
||||
let icon_style = if is_cursor && self.focused {
|
||||
label_style
|
||||
} else {
|
||||
Style::new().fg(icon_color)
|
||||
};
|
||||
|
||||
let spans = vec![
|
||||
Span::raw(indent),
|
||||
Span::styled(icon, icon_style),
|
||||
Span::styled(&entry.label, label_style),
|
||||
];
|
||||
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
frame.render_widget(Paragraph::new(lines), area);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user