use crate::select::SelectDialog;
use crate::select::filter::{FilteredItem, apply_filter};
use crate::text::Text;
use crate::theme::Theme;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::prelude::{Line, Span, StatefulWidget, Widget};
use ratatui::widgets::{HighlightSpacing, List, Scrollbar, ScrollbarOrientation, ScrollbarState};
use std::cmp::max;
use std::sync::OnceLock;
fn width(select_dialog: &SelectDialog) -> u16 {
static CELL: OnceLock<u16> = OnceLock::new();
*CELL.get_or_init(|| {
let max_item_width = select_dialog
.items
.iter()
.map(|i| i.content.len())
.max()
.unwrap_or(0);
let prefix_length = select_dialog.theme.select_indicator.len();
let width = max(25, prefix_length + max_item_width + 3);
u16::try_from(width).unwrap_or(u16::MAX)
})
}
impl<'a> Widget for &mut SelectDialog<'a> {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
let (header_area, separator_area, list_area, scrollbar_area) = {
let (header_area, separator_area, list_scroll_area) = {
let filter_height = if self.filter_dialog.is_some() { 1 } else { 0 };
let separator_height = filter_height;
let rects = Layout::vertical(vec![
Constraint::Length(filter_height),
Constraint::Length(separator_height),
Constraint::Percentage(100),
])
.split(area);
(rects[0], rects[1], rects[2])
};
let rects =
Layout::horizontal(vec![Constraint::Length(width(self)), Constraint::Length(1)])
.split(list_scroll_area);
(header_area, separator_area, rects[0], rects[1])
};
if let Some(ref mut filter_dialog) = self.filter_dialog {
filter_dialog.render(header_area, buf);
Text::from("────────────────────").render(separator_area, buf);
}
let lines: Vec<Line> = if let Some(ref mut filter_dialog) = self.filter_dialog {
let filtered = apply_filter(
self.items.as_slice(),
&mut self.list_state,
filter_dialog.current_content().as_str(),
);
filtered
.iter()
.map(|s| render_filtered_item(s, &self.theme))
.collect()
} else {
self.items
.iter()
.map(|i| i.content.as_ref().into())
.collect()
};
let len = lines.len();
let list = List::new(lines)
.highlight_symbol(self.theme.select_indicator.as_str())
.highlight_style(self.theme.selected_style)
.highlight_spacing(HighlightSpacing::Always);
StatefulWidget::render(list, list_area, buf, &mut self.list_state);
if len > list_area.height as usize {
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
let mut scrollbar_state = ScrollbarState::default()
.content_length(len - list_area.height as usize)
.position(self.list_state.offset());
StatefulWidget::render(scrollbar, scrollbar_area, buf, &mut scrollbar_state);
}
}
}
fn render_filtered_item(item: &FilteredItem, theme: &Theme) -> Line<'static> {
let char_spans: Vec<Span> = item
.item
.content
.chars()
.enumerate()
.map(|(index, char)| {
let string = char.to_string();
if item.matching_chars.contains(&index) {
Span::styled(string, theme.match_style)
} else {
Span::raw(string)
}
})
.collect();
char_spans.into()
}