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_type(BorderType::Rounded)
59 .border_style(if config.mode == Mode::FilterInput {
60 Style::default().fg(Color::Yellow)
61 } else {
62 Style::default()
63 }),
64 config.area,
65 );
66
67 let mut input_spans = vec![];
69 if config.filter_text.is_empty() {
70 if config.mode == Mode::FilterInput {
71 input_spans.push(Span::raw(""));
72 } else {
73 input_spans.push(Span::styled(
74 config.placeholder,
75 Style::default().fg(Color::DarkGray),
76 ));
77 }
78 } else {
79 input_spans.push(Span::raw(config.filter_text));
80 }
81
82 if config.mode == Mode::FilterInput && config.is_input_focused {
83 input_spans.push(Span::styled(cursor, Style::default().fg(Color::Yellow)));
84 }
85
86 frame.render_widget(
87 Paragraph::new(Line::from(input_spans))
88 .alignment(Alignment::Left)
89 .style(Style::default()),
90 chunks[0],
91 );
92
93 let mut control_spans = vec![Span::raw(" ⋮ ")];
95 if config.mode == Mode::FilterInput {
96 let highlight = Style::default().fg(Color::Yellow);
97 for (i, control) in config.controls.iter().enumerate() {
98 if i > 0 {
99 control_spans.push(Span::raw(" ⋮ "));
100 }
101 control_spans.push(Span::styled(
102 &control.text,
103 if control.is_focused {
104 highlight
105 } else {
106 Style::default()
107 },
108 ));
109 }
110 } else {
111 let controls_text: String = config
112 .controls
113 .iter()
114 .map(|c| c.text.as_str())
115 .collect::<Vec<_>>()
116 .join(" ⋮ ");
117 control_spans.push(Span::styled(controls_text, Style::default()));
118 }
119
120 frame.render_widget(
121 Paragraph::new(Line::from(control_spans))
122 .alignment(Alignment::Right)
123 .style(Style::default()),
124 chunks[1],
125 );
126}
127
128pub struct SimpleFilterConfig<'a> {
130 pub filter_text: &'a str,
131 pub placeholder: &'a str,
132 pub pagination: &'a str,
133 pub mode: Mode,
134 pub is_input_focused: bool,
135 pub is_pagination_focused: bool,
136}
137
138pub fn render_simple_filter(frame: &mut Frame, area: Rect, config: SimpleFilterConfig) {
139 render_filter_bar(
140 frame,
141 FilterConfig {
142 filter_text: config.filter_text,
143 placeholder: config.placeholder,
144 mode: config.mode,
145 is_input_focused: config.is_input_focused,
146 controls: vec![FilterControl {
147 text: config.pagination.to_string(),
148 is_focused: config.is_pagination_focused,
149 }],
150 area,
151 },
152 );
153}