rusticity_term/ui/
filter.rs1use crate::keymap::Mode;
2use crate::ui::{get_cursor, SEARCH_ICON};
3use ratatui::{prelude::*, widgets::*};
4
5pub struct FilterControl {
6 pub text: String,
7 pub is_focused: bool,
8}
9
10pub struct FilterConfig<'a> {
11 pub filter_text: &'a str,
12 pub placeholder: &'a str,
13 pub mode: Mode,
14 pub is_input_focused: bool,
15 pub controls: Vec<FilterControl>,
16 pub area: Rect,
17}
18
19pub fn render_filter_bar(frame: &mut Frame, config: FilterConfig) {
21 let cursor = get_cursor(config.mode == Mode::FilterInput && config.is_input_focused);
22
23 let controls_width = if config.mode == Mode::FilterInput {
25 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();
28 (separators_width + controls_text_width) as u16
29 } else {
30 let controls_text: String = config
32 .controls
33 .iter()
34 .map(|c| c.text.as_str())
35 .collect::<Vec<_>>()
36 .join(" ⋮ ");
37 (3 + controls_text.len()) as u16
38 };
39
40 let inner_area = Rect {
42 x: config.area.x + 2,
43 y: config.area.y + 1,
44 width: config.area.width.saturating_sub(4),
45 height: 1,
46 };
47
48 let chunks = Layout::default()
49 .direction(Direction::Horizontal)
50 .constraints([Constraint::Min(0), Constraint::Length(controls_width)])
51 .split(inner_area);
52
53 frame.render_widget(
55 Block::default()
56 .title(SEARCH_ICON)
57 .borders(Borders::ALL)
58 .border_style(if config.mode == Mode::FilterInput {
59 Style::default().fg(Color::Yellow)
60 } else {
61 Style::default()
62 }),
63 config.area,
64 );
65
66 let mut input_spans = vec![];
68 if config.filter_text.is_empty() {
69 if config.mode == Mode::FilterInput {
70 input_spans.push(Span::raw(""));
71 } else {
72 input_spans.push(Span::styled(
73 config.placeholder,
74 Style::default().fg(Color::DarkGray),
75 ));
76 }
77 } else {
78 input_spans.push(Span::raw(config.filter_text));
79 }
80
81 if config.mode == Mode::FilterInput && config.is_input_focused {
82 input_spans.push(Span::styled(cursor, Style::default().fg(Color::Yellow)));
83 }
84
85 frame.render_widget(
86 Paragraph::new(Line::from(input_spans))
87 .alignment(Alignment::Left)
88 .style(Style::default()),
89 chunks[0],
90 );
91
92 let mut control_spans = vec![Span::raw(" ⋮ ")];
94 if config.mode == Mode::FilterInput {
95 let highlight = Style::default().fg(Color::Yellow);
96 for (i, control) in config.controls.iter().enumerate() {
97 if i > 0 {
98 control_spans.push(Span::raw(" ⋮ "));
99 }
100 control_spans.push(Span::styled(
101 &control.text,
102 if control.is_focused {
103 highlight
104 } else {
105 Style::default()
106 },
107 ));
108 }
109 } else {
110 let controls_text: String = config
111 .controls
112 .iter()
113 .map(|c| c.text.as_str())
114 .collect::<Vec<_>>()
115 .join(" ⋮ ");
116 control_spans.push(Span::styled(controls_text, Style::default()));
117 }
118
119 frame.render_widget(
120 Paragraph::new(Line::from(control_spans))
121 .alignment(Alignment::Right)
122 .style(Style::default()),
123 chunks[1],
124 );
125}
126
127pub struct SimpleFilterConfig<'a> {
129 pub filter_text: &'a str,
130 pub placeholder: &'a str,
131 pub pagination: &'a str,
132 pub mode: Mode,
133 pub is_input_focused: bool,
134 pub is_pagination_focused: bool,
135}
136
137pub fn render_simple_filter(frame: &mut Frame, area: Rect, config: SimpleFilterConfig) {
138 render_filter_bar(
139 frame,
140 FilterConfig {
141 filter_text: config.filter_text,
142 placeholder: config.placeholder,
143 mode: config.mode,
144 is_input_focused: config.is_input_focused,
145 controls: vec![FilterControl {
146 text: config.pagination.to_string(),
147 is_focused: config.is_pagination_focused,
148 }],
149 area,
150 },
151 );
152}