rust_kanban/ui/rendering/popup/
edit_general_config.rs1use crate::{
2 app::{
3 state::{AppStatus, Focus, KeyBindingEnum},
4 App, ConfigEnum,
5 },
6 ui::{
7 rendering::{
8 common::{render_blank_styled_canvas, render_close_button, render_logs},
9 popup::EditGeneralConfig,
10 utils::{
11 calculate_viewport_corrected_cursor_position, centered_rect_with_percentage,
12 check_if_active_and_get_style, check_if_mouse_is_in_area,
13 get_mouse_focusable_field_style,
14 },
15 },
16 Renderable,
17 },
18};
19use ratatui::{
20 layout::{Alignment, Constraint, Direction, Layout},
21 text::{Line, Span},
22 widgets::{Block, BorderType, Borders, Paragraph},
23 Frame,
24};
25use std::str::FromStr;
26
27impl Renderable for EditGeneralConfig {
28 fn render(rect: &mut Frame, app: &mut App, is_active: bool) {
29 let area = centered_rect_with_percentage(70, 70, rect.area());
30
31 let chunks = if app.config.enable_mouse_support {
32 Layout::default()
33 .direction(Direction::Vertical)
34 .constraints(
35 [
36 Constraint::Length(6),
37 Constraint::Fill(1),
38 Constraint::Length(5),
39 Constraint::Length(3),
40 ]
41 .as_ref(),
42 )
43 .split(area)
44 } else {
45 Layout::default()
46 .direction(Direction::Vertical)
47 .constraints(
48 [
49 Constraint::Length(6),
50 Constraint::Fill(1),
51 Constraint::Length(4),
52 ]
53 .as_ref(),
54 )
55 .split(area)
56 };
57
58 let edit_box_style = get_mouse_focusable_field_style(
59 app,
60 Focus::EditGeneralConfigPopup,
61 &chunks[1],
62 is_active,
63 true,
64 );
65 let help_text_style = check_if_active_and_get_style(
66 is_active,
67 app.current_theme.inactive_text_style,
68 app.current_theme.help_text_style,
69 );
70 let help_key_style = check_if_active_and_get_style(
71 is_active,
72 app.current_theme.inactive_text_style,
73 app.current_theme.help_key_style,
74 );
75 let general_style = check_if_active_and_get_style(
76 is_active,
77 app.current_theme.inactive_text_style,
78 app.current_theme.general_style,
79 );
80 let error_text_style = check_if_active_and_get_style(
81 is_active,
82 app.current_theme.inactive_text_style,
83 app.current_theme.error_text_style,
84 );
85 let card_status_active_style = check_if_active_and_get_style(
86 is_active,
87 app.current_theme.inactive_text_style,
88 app.current_theme.card_status_active_style,
89 );
90 let keyboard_focus_style = check_if_active_and_get_style(
91 is_active,
92 app.current_theme.inactive_text_style,
93 app.current_theme.keyboard_focus_style,
94 );
95 let mouse_focus_style = check_if_active_and_get_style(
96 is_active,
97 app.current_theme.inactive_text_style,
98 app.current_theme.mouse_focus_style,
99 );
100
101 let list_items = app.config.to_view_list();
102 let config_item_name = if let Some(index) = app.state.app_table_states.config.selected() {
103 list_items[index].first().unwrap()
104 } else {
105 "Theme Name"
107 };
108 if let Ok(config_enum) = ConfigEnum::from_str(config_item_name) {
109 app.state.path_check_state.path_check_mode = config_enum == ConfigEnum::SaveDirectory;
110 }
111 let config_item_value = if app.state.app_table_states.config.selected().is_some() {
112 list_items
113 .iter()
114 .find(|x| x.first().unwrap() == config_item_name)
115 .unwrap()
116 .get(1)
117 .unwrap()
118 .to_owned()
119 } else {
120 app.state.theme_being_edited.name.clone()
121 };
122
123 let accept_key = app
124 .get_first_keybinding(KeyBindingEnum::Accept)
125 .unwrap_or("".to_string());
126 let start_editing_key = app
127 .get_first_keybinding(KeyBindingEnum::TakeUserInput)
128 .unwrap_or("".to_string());
129 let cancel_key = app
130 .get_first_keybinding(KeyBindingEnum::GoToPreviousViewOrCancel)
131 .unwrap_or("".to_string());
132 let stop_editing_key = app
133 .get_first_keybinding(KeyBindingEnum::StopUserInput)
134 .unwrap_or("".to_string());
135
136 let paragraph_text = vec![
137 Line::from(vec![
138 Span::styled("Current Value is '", help_text_style),
139 Span::styled(config_item_value, help_key_style),
140 Span::styled("'", help_text_style),
141 ]),
142 Line::from(String::from("")),
143 Line::from(vec![
144 Span::styled("Press ", help_text_style),
145 Span::styled(start_editing_key, help_key_style),
146 Span::styled(" to edit, or ", help_text_style),
147 Span::styled(cancel_key, help_key_style),
148 Span::styled(" to cancel, Press ", help_text_style),
149 Span::styled(stop_editing_key, help_key_style),
150 Span::styled(" to stop editing and press ", help_text_style),
151 Span::styled(accept_key, help_key_style),
152 Span::styled(" on Submit to save", help_text_style),
153 ]),
154 ];
155 let paragraph_title = Line::from(vec![Span::raw(config_item_name)]);
156 let config_item = Paragraph::new(paragraph_text)
157 .block(
158 Block::default()
159 .title(paragraph_title)
160 .style(general_style)
161 .borders(Borders::ALL)
162 .border_type(BorderType::Rounded),
163 )
164 .wrap(ratatui::widgets::Wrap { trim: true });
165 let current_user_input = app.state.text_buffers.general_config.get_joined_lines();
166 let user_input = if app.state.path_check_state.path_check_mode {
167 if (current_user_input != app.state.path_check_state.path_last_checked)
168 || app.state.path_check_state.recheck_required
169 {
170 app.state.path_check_state.recheck_required = false;
171 app.state.path_check_state.potential_completion = None;
172 app.state
173 .path_check_state
174 .path_last_checked
175 .clone_from(¤t_user_input);
176 app.state.path_check_state.path_exists =
177 std::path::Path::new(¤t_user_input).is_dir();
178 if !app.state.path_check_state.path_exists {
179 let mut split_input = current_user_input
180 .split(std::path::MAIN_SEPARATOR)
181 .collect::<Vec<&str>>();
182 split_input.retain(|&x| !x.is_empty());
184 if !split_input.is_empty() {
185 let last_input = split_input.pop().unwrap();
186 if split_input.is_empty() {
187 let to_check = last_input;
188 let dir = std::fs::read_dir(std::path::MAIN_SEPARATOR.to_string());
189 if dir.is_ok() {
190 let dir = dir.unwrap();
191 let dir = dir.flatten();
193 for entry in dir {
194 let path = entry.path();
195 if path.to_str().unwrap().starts_with(
196 &(std::path::MAIN_SEPARATOR.to_string() + to_check),
197 ) && path.is_dir()
198 {
199 app.state.path_check_state.potential_completion = Some(
200 path.to_str()
201 .unwrap()
202 .to_string()
203 .strip_prefix(
204 &(std::path::MAIN_SEPARATOR.to_string()
205 + to_check),
206 )
207 .unwrap()
208 .to_string(),
209 );
210 break;
211 }
212 }
213 }
214 } else {
215 let to_check = std::path::MAIN_SEPARATOR.to_string()
216 + &split_input.join(std::path::MAIN_SEPARATOR_STR);
217 if std::path::Path::new(&to_check).is_dir() {
218 let dir = std::fs::read_dir(&to_check);
219 let dir = dir.unwrap();
220 let dir = dir.flatten();
222 for entry in dir {
223 let path = entry.path();
224 if path
225 .to_str()
226 .unwrap()
227 .starts_with(current_user_input.as_str())
228 && path.is_dir()
229 {
230 app.state.path_check_state.potential_completion = Some(
231 path.to_str()
232 .unwrap()
233 .strip_prefix(current_user_input.as_str())
234 .unwrap()
235 .to_string(),
236 );
237 break;
238 }
239 }
240 }
241 };
242 }
243 }
244 }
245 if !current_user_input.is_empty() {
246 if let Some(potential_completion) = &app.state.path_check_state.potential_completion
247 {
248 Line::from(vec![
249 Span::styled(current_user_input.clone(), general_style),
250 Span::styled(
251 potential_completion.clone(),
252 app.current_theme.inactive_text_style,
253 ),
254 Span::styled(
255 " (Press 'Tab' or 'Right Arrow' to autocomplete)",
256 help_text_style,
257 ),
258 ])
259 } else if app.state.path_check_state.path_exists {
260 Line::from(Span::styled(
261 current_user_input.clone(),
262 card_status_active_style,
263 ))
264 } else {
265 Line::from(vec![
266 Span::styled(
267 current_user_input.clone(),
268 error_text_style,
269 ),
270 Span::styled(
271 " (Path does not exist) - Press '%' to create a new directory at this location",
272 help_text_style,
273 ),
274 ])
275 }
276 } else {
277 Line::from(Span::styled(
278 "No input",
279 app.current_theme.inactive_text_style,
280 ))
281 }
282 } else {
283 Line::from(Span::styled(current_user_input, general_style))
284 };
285 let edit_item = Paragraph::new(user_input)
286 .block(
287 Block::default()
288 .title("Edit")
289 .style(general_style)
290 .borders(Borders::ALL)
291 .border_style(edit_box_style)
292 .border_type(BorderType::Rounded),
293 )
294 .wrap(ratatui::widgets::Wrap { trim: true });
295
296 let clear_area = centered_rect_with_percentage(80, 80, rect.area());
297 let clear_area_border = Block::default()
298 .title("Config Editor")
299 .style(general_style)
300 .borders(Borders::ALL)
301 .border_style(keyboard_focus_style)
302 .border_type(BorderType::Rounded);
303
304 render_blank_styled_canvas(rect, &app.current_theme, clear_area, is_active);
305 rect.render_widget(clear_area_border, clear_area);
306 rect.render_widget(config_item, chunks[0]);
307 rect.render_widget(edit_item, chunks[1]);
308 render_logs(app, false, chunks[2], rect, is_active);
309
310 if app.config.enable_mouse_support {
311 let submit_button_style =
312 if check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &chunks[3]) {
313 app.state.mouse_focus = Some(Focus::SubmitButton);
314 app.state.set_focus(Focus::SubmitButton);
315 mouse_focus_style
316 } else if app.state.app_status == AppStatus::KeyBindMode {
317 keyboard_focus_style
318 } else {
319 general_style
320 };
321 let submit_button = Paragraph::new("Submit")
322 .block(
323 Block::default()
324 .style(general_style)
325 .borders(Borders::ALL)
326 .border_style(submit_button_style)
327 .border_type(BorderType::Rounded),
328 )
329 .alignment(Alignment::Center);
330 rect.render_widget(submit_button, chunks[3]);
331 render_close_button(rect, app, is_active)
332 }
333
334 if app.state.app_status == AppStatus::UserInput {
335 let (x_pos, y_pos) = calculate_viewport_corrected_cursor_position(
336 &app.state.text_buffers.general_config,
337 &app.config.show_line_numbers,
338 &chunks[1],
339 );
340 rect.set_cursor_position((x_pos, y_pos));
341 }
342 }
343}