1use tui::{
2 Frame,
3 layout::{Alignment, Constraint, Direction, Layout, Rect},
4 style::Style,
5 text::{Line, Span},
6 widgets::Paragraph,
7};
8use unicode_segmentation::UnicodeSegmentation;
9
10use crate::{
11 app::{App, AppSearchState},
12 canvas::{
13 Painter,
14 components::data_table::{DrawInfo, SelectionState},
15 drawing_utils::widget_block,
16 },
17};
18
19const SORT_MENU_WIDTH: u16 = 7;
20
21impl Painter {
22 pub fn draw_process(
26 &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
27 ) {
28 if let Some(proc_widget_state) = app_state.states.proc_state.widget_states.get(&widget_id) {
29 let is_basic = app_state.app_config_fields.use_basic_mode;
30 let search_height = if !is_basic { 5 } else { 3 };
31 let is_sort_open = proc_widget_state.is_sort_open;
32
33 let mut proc_draw_loc = draw_loc;
34 if proc_widget_state.is_search_enabled() {
35 let processes_chunk = Layout::default()
36 .direction(Direction::Vertical)
37 .constraints([Constraint::Min(0), Constraint::Length(search_height)])
38 .split(draw_loc);
39 proc_draw_loc = processes_chunk[0];
40
41 self.draw_search_field(f, app_state, processes_chunk[1], widget_id + 1);
42 }
43
44 if is_sort_open {
45 let processes_chunk = Layout::default()
46 .direction(Direction::Horizontal)
47 .constraints([Constraint::Length(SORT_MENU_WIDTH + 4), Constraint::Min(0)])
48 .split(proc_draw_loc);
49 proc_draw_loc = processes_chunk[1];
50
51 self.draw_sort_table(f, app_state, processes_chunk[0], widget_id + 2);
52 }
53
54 self.draw_processes_table(f, app_state, proc_draw_loc, widget_id);
55 }
56
57 if let Some(proc_widget_state) = app_state
58 .states
59 .proc_state
60 .widget_states
61 .get_mut(&widget_id)
62 {
63 if proc_widget_state.force_rerender {
65 proc_widget_state.force_rerender = false;
66 }
67 }
68 }
69
70 fn draw_processes_table(
73 &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
74 ) {
75 let should_get_widget_bounds = app_state.should_get_widget_bounds();
76 if let Some(proc_widget_state) = app_state
77 .states
78 .proc_state
79 .widget_states
80 .get_mut(&widget_id)
81 {
82 let recalculate_column_widths =
83 should_get_widget_bounds || proc_widget_state.force_rerender;
84
85 let is_on_widget = widget_id == app_state.current_widget.widget_id;
86
87 let draw_info = DrawInfo {
88 loc: draw_loc,
89 force_redraw: app_state.is_force_redraw,
90 recalculate_column_widths,
91 selection_state: SelectionState::new(app_state.is_expanded, is_on_widget),
92 };
93
94 proc_widget_state.table.draw(
95 f,
96 &draw_info,
97 app_state.widget_map.get_mut(&widget_id),
98 self,
99 );
100 }
101 }
102
103 fn draw_search_field(
107 &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
108 ) {
109 fn build_query_span(
110 search_state: &AppSearchState, available_width: usize, is_on_widget: bool,
111 currently_selected_text_style: Style, text_style: Style,
112 ) -> Vec<Span<'_>> {
113 let start_index = search_state.display_start_char_index;
114 let cursor_index = search_state.grapheme_cursor.cur_cursor();
115 let mut current_width = 0;
116 let query = search_state.current_search_query.as_str();
117
118 if is_on_widget {
119 let mut res = Vec::with_capacity(available_width);
120 for ((index, grapheme), lengths) in
121 UnicodeSegmentation::grapheme_indices(query, true)
122 .zip(search_state.size_mappings.values())
123 {
124 if index < start_index {
125 continue;
126 } else if current_width > available_width {
127 break;
128 } else {
129 let styled = if index == cursor_index {
130 Span::styled(grapheme, currently_selected_text_style)
131 } else {
132 Span::styled(grapheme, text_style)
133 };
134
135 res.push(styled);
136 current_width += lengths.end - lengths.start;
137 }
138 }
139
140 if cursor_index == query.len() {
141 res.push(Span::styled(" ", currently_selected_text_style))
142 }
143
144 res
145 } else {
146 vec![Span::styled(query.to_string(), text_style)]
150 }
151 }
152
153 let is_basic = app_state.app_config_fields.use_basic_mode;
154
155 if let Some(proc_widget_state) = app_state
156 .states
157 .proc_state
158 .widget_states
159 .get_mut(&(widget_id - 1))
160 {
161 let is_selected = widget_id == app_state.current_widget.widget_id;
162 let num_columns = usize::from(draw_loc.width);
163 const SEARCH_TITLE: &str = "> ";
164 let offset = 4;
165 let available_width = if num_columns > (offset + 3) {
166 num_columns - offset
167 } else {
168 num_columns
169 };
170
171 proc_widget_state
172 .proc_search
173 .search_state
174 .get_start_position(available_width, app_state.is_force_redraw);
175
176 let query_with_cursor = build_query_span(
178 &proc_widget_state.proc_search.search_state,
179 available_width,
180 is_selected,
181 self.styles.selected_text_style,
182 self.styles.text_style,
183 );
184
185 let mut search_text = vec![Line::from({
186 let mut search_vec = vec![Span::styled(
187 SEARCH_TITLE,
188 if is_selected {
189 self.styles.table_header_style
190 } else {
191 self.styles.text_style
192 },
193 )];
194 search_vec.extend(query_with_cursor);
195
196 search_vec
197 })];
198
199 let case_style = if !proc_widget_state.proc_search.is_ignoring_case {
201 self.styles.selected_text_style
202 } else {
203 self.styles.text_style
204 };
205
206 let whole_word_style = if proc_widget_state.proc_search.is_searching_whole_word {
207 self.styles.selected_text_style
208 } else {
209 self.styles.text_style
210 };
211
212 let regex_style = if proc_widget_state.proc_search.is_searching_with_regex {
213 self.styles.selected_text_style
214 } else {
215 self.styles.text_style
216 };
217
218 let (case, whole, regex) = {
221 cfg_if::cfg_if! {
222 if #[cfg(target_os = "macos")] {
223 ("Case(F1)", "Whole(F2)", "Regex(F3)")
224 } else {
225 ("Case(Alt+C)", "Whole(Alt+W)", "Regex(Alt+R)")
226 }
227 }
228 };
229 let option_text = Line::from(vec![
230 Span::styled(case, case_style),
231 Span::raw(" "),
232 Span::styled(whole, whole_word_style),
233 Span::raw(" "),
234 Span::styled(regex, regex_style),
235 ]);
236
237 search_text.push(Line::from(Span::styled(
238 if let Some(err) = &proc_widget_state.proc_search.search_state.error_message {
239 err.as_str()
240 } else {
241 ""
242 },
243 self.styles.invalid_query_style,
244 )));
245 search_text.push(option_text);
246
247 let current_border_style =
248 if proc_widget_state.proc_search.search_state.is_invalid_search {
249 self.styles.invalid_query_style
250 } else if is_selected {
251 self.styles.highlighted_border_style
252 } else {
253 self.styles.border_style
254 };
255
256 let process_search_block = {
257 let mut block = widget_block(is_basic, is_selected, self.styles.border_type)
258 .border_style(current_border_style);
259
260 if !is_basic {
261 block = block.title_top(
262 Line::styled(" Esc to close ", current_border_style).right_aligned(),
263 )
264 }
265
266 block
267 };
268
269 let margined_draw_loc = Layout::default()
270 .constraints([Constraint::Percentage(100)])
271 .horizontal_margin(u16::from(is_basic && !is_selected))
272 .direction(Direction::Horizontal)
273 .split(draw_loc)[0];
274
275 f.render_widget(
276 Paragraph::new(search_text)
277 .block(process_search_block)
278 .style(self.styles.text_style)
279 .alignment(Alignment::Left),
280 margined_draw_loc,
281 );
282
283 if app_state.should_get_widget_bounds() {
284 if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {
286 widget.top_left_corner = Some((margined_draw_loc.x, margined_draw_loc.y));
287 widget.bottom_right_corner = Some((
288 margined_draw_loc.x + margined_draw_loc.width,
289 margined_draw_loc.y + margined_draw_loc.height,
290 ));
291 }
292 }
293 }
294 }
295
296 fn draw_sort_table(
300 &self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
301 ) {
302 let should_get_widget_bounds = app_state.should_get_widget_bounds();
303 if let Some(pws) = app_state
304 .states
305 .proc_state
306 .widget_states
307 .get_mut(&(widget_id - 2))
308 {
309 let recalculate_column_widths = should_get_widget_bounds || pws.force_rerender;
310
311 let is_on_widget = widget_id == app_state.current_widget.widget_id;
312
313 let draw_info = DrawInfo {
314 loc: draw_loc,
315 force_redraw: app_state.is_force_redraw,
316 recalculate_column_widths,
317 selection_state: SelectionState::new(app_state.is_expanded, is_on_widget),
318 };
319
320 pws.sort_table.draw(
321 f,
322 &draw_info,
323 app_state.widget_map.get_mut(&widget_id),
324 self,
325 );
326 }
327 }
328}