use crate::keymap::Mode;
use crate::ui::{get_cursor, SEARCH_ICON};
use ratatui::{prelude::*, widgets::*};
pub struct FilterControl {
pub text: String,
pub is_focused: bool,
}
pub struct FilterConfig<'a> {
pub filter_text: &'a str,
pub placeholder: &'a str,
pub mode: Mode,
pub is_input_focused: bool,
pub controls: Vec<FilterControl>,
pub area: Rect,
}
pub fn render_filter_bar(frame: &mut Frame, config: FilterConfig) {
let cursor = get_cursor(config.mode == Mode::FilterInput && config.is_input_focused);
let controls_width = if config.mode == Mode::FilterInput {
let separators_width = 3 + (config.controls.len().saturating_sub(1) * 3); let controls_text_width: usize = config.controls.iter().map(|c| c.text.len()).sum();
(separators_width + controls_text_width) as u16
} else {
let controls_text: String = config
.controls
.iter()
.map(|c| c.text.as_str())
.collect::<Vec<_>>()
.join(" ⋮ ");
(3 + controls_text.len()) as u16
};
let inner_area = Rect {
x: config.area.x + 2,
y: config.area.y + 1,
width: config.area.width.saturating_sub(4),
height: 1,
};
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(0), Constraint::Length(controls_width)])
.split(inner_area);
frame.render_widget(
Block::default()
.title(SEARCH_ICON)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(if config.mode == Mode::FilterInput {
Style::default().fg(Color::Yellow)
} else {
Style::default()
}),
config.area,
);
let mut input_spans = vec![];
if config.filter_text.is_empty() {
if config.mode == Mode::FilterInput {
input_spans.push(Span::raw(""));
} else {
input_spans.push(Span::styled(
config.placeholder,
Style::default().fg(Color::DarkGray),
));
}
} else {
input_spans.push(Span::raw(config.filter_text));
}
if config.mode == Mode::FilterInput && config.is_input_focused {
input_spans.push(Span::styled(cursor, Style::default().fg(Color::Yellow)));
}
frame.render_widget(
Paragraph::new(Line::from(input_spans))
.alignment(Alignment::Left)
.style(Style::default()),
chunks[0],
);
let mut control_spans = vec![Span::raw(" ⋮ ")];
if config.mode == Mode::FilterInput {
let highlight = Style::default().fg(Color::Yellow);
for (i, control) in config.controls.iter().enumerate() {
if i > 0 {
control_spans.push(Span::raw(" ⋮ "));
}
control_spans.push(Span::styled(
&control.text,
if control.is_focused {
highlight
} else {
Style::default()
},
));
}
} else {
let controls_text: String = config
.controls
.iter()
.map(|c| c.text.as_str())
.collect::<Vec<_>>()
.join(" ⋮ ");
control_spans.push(Span::styled(controls_text, Style::default()));
}
frame.render_widget(
Paragraph::new(Line::from(control_spans))
.alignment(Alignment::Right)
.style(Style::default()),
chunks[1],
);
}
pub struct SimpleFilterConfig<'a> {
pub filter_text: &'a str,
pub placeholder: &'a str,
pub pagination: &'a str,
pub mode: Mode,
pub is_input_focused: bool,
pub is_pagination_focused: bool,
}
pub fn render_simple_filter(frame: &mut Frame, area: Rect, config: SimpleFilterConfig) {
render_filter_bar(
frame,
FilterConfig {
filter_text: config.filter_text,
placeholder: config.placeholder,
mode: config.mode,
is_input_focused: config.is_input_focused,
controls: vec![FilterControl {
text: config.pagination.to_string(),
is_focused: config.is_pagination_focused,
}],
area,
},
);
}