use crate::picker::Picker;
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, List, ListItem, Paragraph},
Frame,
};
use std::fmt::Display;
pub fn ui<T>(f: &mut Frame, app: &mut Picker<T>)
where
T: Sync + Send + Display,
{
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.area());
app.update_height(chunks[2].height - 3);
render_help(f, chunks[0], app);
render_search_input(f, app, chunks[1]);
render_items(f, app, chunks[2]);
}
fn render_help<T>(f: &mut Frame, area: Rect, app: &Picker<T>)
where
T: Sync + Send + Display,
{
let snapshot = app.snapshot();
let text = vec![Line::from(vec![
Span::raw("Press "),
Span::styled("↑/↓", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to navigate, "),
Span::styled("Tab", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to select, "),
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to confirm, "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to quit,"),
Span::raw(" matching against "),
Span::styled(
format!(
"{}/{}",
snapshot.matched_item_count(),
snapshot.item_count()
),
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(" items,"),
Span::raw(" ("),
Span::styled(
app.running_threads().to_string(),
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(" threads still indexing)"),
Span::raw(format!(
" ({} {} {} by {})",
app.first_visible_item_index(),
app.current_index,
app.last_visible_item_index(),
app.height()
)),
])];
let paragraph = Paragraph::new(text);
f.render_widget(paragraph, area);
}
fn render_search_input<T>(f: &mut Frame, app: &Picker<T>, area: Rect)
where
T: Sync + Send,
{
let before_cursor = app.query.chars().take(app.query_index).collect::<String>();
let cursor_char = app.query.chars().nth(app.query_index).unwrap_or(' ');
let after_cursor = app
.query
.chars()
.skip(app.query_index + 1)
.collect::<String>();
let line = Line::from(vec![
Span::raw(before_cursor),
Span::styled(cursor_char.to_string(), Style::default().bg(Color::Blue)),
Span::raw(after_cursor),
]);
let input = Paragraph::new(line)
.style(Style::default())
.block(Block::default().borders(Borders::ALL).title("Search"));
f.render_widget(input, area);
}
fn render_items<T>(f: &mut Frame, app: &mut Picker<T>, area: Rect)
where
T: Sync + Send + Display,
{
if app.matched_item_count() > 0 {
let items: Vec<ListItem> = app
.matched_items()
.iter()
.map(|item| {
let is_selected = item.is_selected();
let style = if is_selected {
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let prefix = if is_selected { "✓ " } else { " " };
let content = format!("{}{}", prefix, item);
ListItem::new(content).style(style)
})
.collect();
let items = List::new(items)
.block(Block::default().borders(Borders::ALL).title("Items"))
.highlight_style(
Style::default()
.bg(Color::Blue)
.add_modifier(Modifier::BOLD),
)
.highlight_symbol("> ");
f.render_stateful_widget(
items,
area,
&mut ratatui::widgets::ListState::default().with_selected(Some(
app.current_index
.saturating_sub(app.first_visible_item_index()) as usize,
)),
);
} else {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Min(1),
Constraint::Length(1),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.area());
let no_items_paragraph = Paragraph::new("No items found").alignment(Alignment::Center);
f.render_widget(no_items_paragraph, chunks[1]);
}
}