1use crate::{
2 app::{
3 state::{Focus, KeyBindingEnum},
4 App,
5 },
6 constants::{SCROLLBAR_BEGIN_SYMBOL, SCROLLBAR_END_SYMBOL, SCROLLBAR_TRACK_SYMBOL},
7 ui::{
8 rendering::{
9 common::{draw_title, render_close_button, render_logs},
10 utils::{
11 check_if_active_and_get_style, get_button_style, get_mouse_focusable_field_style,
12 get_scrollable_widget_row_bounds,
13 },
14 view::ConfigMenu,
15 },
16 Renderable,
17 },
18};
19use ratatui::{
20 layout::{Alignment, Constraint, Direction, Layout, Margin},
21 style::Style,
22 text::{Line, Span},
23 widgets::{
24 Block, BorderType, Borders, Cell, Paragraph, Row, Scrollbar, ScrollbarOrientation,
25 ScrollbarState, Table,
26 },
27 Frame,
28};
29
30impl Renderable for ConfigMenu {
31 fn render(rect: &mut Frame, app: &mut App, is_active: bool) {
32 let chunks = Layout::default()
33 .direction(Direction::Vertical)
34 .constraints(
35 [
36 Constraint::Length(3),
37 Constraint::Fill(1),
38 Constraint::Length(3),
39 Constraint::Length(5),
40 Constraint::Length(5),
41 ]
42 .as_ref(),
43 )
44 .split(rect.area());
45
46 let reset_btn_chunks = Layout::default()
47 .direction(Direction::Horizontal)
48 .constraints([Constraint::Fill(1), Constraint::Fill(1)].as_ref())
49 .split(chunks[2]);
50
51 let reset_both_style = get_button_style(
52 app,
53 Focus::SubmitButton,
54 Some(&reset_btn_chunks[0]),
55 is_active,
56 true,
57 );
58 let reset_config_style = get_button_style(
59 app,
60 Focus::ExtraFocus,
61 Some(&reset_btn_chunks[1]),
62 is_active,
63 true,
64 );
65 let scrollbar_style = check_if_active_and_get_style(
66 is_active,
67 app.current_theme.inactive_text_style,
68 app.current_theme.progress_bar_style,
69 );
70 let config_text_style = check_if_active_and_get_style(
71 is_active,
72 app.current_theme.inactive_text_style,
73 app.current_theme.general_style,
74 );
75 let default_style =
76 get_mouse_focusable_field_style(app, Focus::ConfigTable, &chunks[1], is_active, false);
77
78 let config_table =
79 draw_config_table_selector(app, config_text_style, default_style, is_active);
80
81 let all_rows = app.config.to_view_list();
82 let total_rows = all_rows.len();
83 let current_index = app
84 .state
85 .app_table_states
86 .config
87 .selected()
88 .unwrap_or(0)
89 .min(total_rows - 1);
90
91 if is_active {
93 let available_height = (chunks[1].height - 2) as usize;
94 let (row_start_index, _) = get_scrollable_widget_row_bounds(
95 all_rows.len(),
96 current_index,
97 app.state.app_table_states.config.offset(),
98 available_height,
99 );
100 let current_mouse_y_position = app.state.current_mouse_coordinates.1;
101 let hovered_index = if current_mouse_y_position > chunks[1].y
102 && current_mouse_y_position < (chunks[1].y + chunks[1].height - 1)
103 {
104 Some(
105 ((current_mouse_y_position - chunks[1].y - 1) + row_start_index as u16)
106 as usize,
107 )
108 } else {
109 None
110 };
111 if hovered_index.is_some()
112 && (app.state.previous_mouse_coordinates != app.state.current_mouse_coordinates)
113 {
114 app.state.app_table_states.config.select(hovered_index);
115 }
116 }
117
118 let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
119 .begin_symbol(SCROLLBAR_BEGIN_SYMBOL)
120 .style(scrollbar_style)
121 .end_symbol(SCROLLBAR_END_SYMBOL)
122 .track_symbol(SCROLLBAR_TRACK_SYMBOL)
123 .track_style(app.current_theme.inactive_text_style);
124
125 let mut scrollbar_state = ScrollbarState::new(total_rows).position(current_index);
126 let scrollbar_area = chunks[1].inner(Margin {
127 horizontal: 0,
128 vertical: 1,
129 });
130
131 let reset_both_button = Paragraph::new("Reset Config and KeyBindings to Default")
132 .block(
133 Block::default()
134 .title("Reset")
135 .borders(Borders::ALL)
136 .border_type(BorderType::Rounded),
137 )
138 .style(reset_both_style)
139 .alignment(Alignment::Center);
140
141 let reset_config_button = Paragraph::new("Reset Only Config to Default")
142 .block(
143 Block::default()
144 .title("Reset")
145 .borders(Borders::ALL)
146 .border_type(BorderType::Rounded),
147 )
148 .style(reset_config_style)
149 .alignment(Alignment::Center);
150
151 let config_help = draw_config_help(app, is_active);
152
153 rect.render_widget(draw_title(app, chunks[0], is_active), chunks[0]);
154 rect.render_stateful_widget(
155 config_table,
156 chunks[1],
157 &mut app.state.app_table_states.config,
158 );
159 rect.render_stateful_widget(scrollbar, scrollbar_area, &mut scrollbar_state);
160 rect.render_widget(reset_both_button, reset_btn_chunks[0]);
161 rect.render_widget(reset_config_button, reset_btn_chunks[1]);
162 rect.render_widget(config_help, chunks[3]);
163 render_logs(app, true, chunks[4], rect, is_active);
164 if app.config.enable_mouse_support {
165 render_close_button(rect, app, is_active)
166 }
167 }
168}
169
170fn draw_config_table_selector(
171 app: &mut App,
172 config_text_style: Style,
173 default_style: Style,
174 is_active: bool,
175) -> Table<'static> {
176 let config_list = app.config.to_view_list();
177 let rows = config_list.iter().map(|item| {
178 let height = item
179 .iter()
180 .map(|content| content.chars().filter(|c| *c == '\n').count())
181 .max()
182 .unwrap_or(0)
183 + 1;
184 let cells = item.iter().map(|c| Cell::from(c.to_string()));
185 Row::new(cells).height(height as u16)
186 });
187
188 let highlight_style = check_if_active_and_get_style(
189 is_active,
190 app.current_theme.inactive_text_style,
191 app.current_theme.list_select_style,
192 );
193
194 Table::new(
195 rows,
196 [Constraint::Percentage(40), Constraint::Percentage(60)],
197 )
198 .block(
199 Block::default()
200 .title("Config Editor")
201 .borders(Borders::ALL)
202 .style(config_text_style)
203 .border_style(default_style)
204 .border_type(BorderType::Rounded),
205 )
206 .row_highlight_style(highlight_style)
207 .highlight_symbol(">> ")
208}
209
210fn draw_config_help<'a>(app: &mut App, is_active: bool) -> Paragraph<'a> {
211 let help_box_style = get_button_style(app, Focus::ConfigHelp, None, is_active, false);
212 let help_key_style = check_if_active_and_get_style(
213 is_active,
214 app.current_theme.inactive_text_style,
215 app.current_theme.help_key_style,
216 );
217 let help_text_style = check_if_active_and_get_style(
218 is_active,
219 app.current_theme.inactive_text_style,
220 app.current_theme.help_text_style,
221 );
222
223 let up_key = app
224 .get_first_keybinding(KeyBindingEnum::Up)
225 .unwrap_or("".to_string());
226 let down_key = app
227 .get_first_keybinding(KeyBindingEnum::Down)
228 .unwrap_or("".to_string());
229 let next_focus_key = app
230 .get_first_keybinding(KeyBindingEnum::NextFocus)
231 .unwrap_or("".to_string());
232 let prv_focus_key = app
233 .get_first_keybinding(KeyBindingEnum::PrvFocus)
234 .unwrap_or("".to_string());
235 let accept_key = app
236 .get_first_keybinding(KeyBindingEnum::Accept)
237 .unwrap_or("".to_string());
238 let cancel_key = app
239 .get_first_keybinding(KeyBindingEnum::GoToPreviousViewOrCancel)
240 .unwrap_or("".to_string());
241
242 let help_spans = Line::from(vec![
243 Span::styled("Use ", help_text_style),
244 Span::styled(up_key, help_key_style),
245 Span::styled(" and ", help_text_style),
246 Span::styled(down_key, help_key_style),
247 Span::styled(" or scroll with the mouse", help_text_style),
248 Span::styled(" to navigate. To edit a value press ", help_text_style),
249 Span::styled(accept_key.clone(), help_key_style),
250 Span::styled(" or ", help_text_style),
251 Span::styled("<Mouse Left Click>", help_key_style),
252 Span::styled(". Press ", help_text_style),
253 Span::styled(cancel_key, help_key_style),
254 Span::styled(
255 " to cancel. To Reset Keybindings or config to Default, press ",
256 help_text_style,
257 ),
258 Span::styled(next_focus_key, help_key_style),
259 Span::styled(" or ", help_text_style),
260 Span::styled(prv_focus_key, help_key_style),
261 Span::styled(
262 " to highlight respective Reset Button then press ",
263 help_text_style,
264 ),
265 Span::styled(accept_key, help_key_style),
266 Span::styled(" to reset", help_text_style),
267 ]);
268
269 Paragraph::new(help_spans)
270 .alignment(Alignment::Left)
271 .block(
272 Block::default()
273 .title("Help")
274 .borders(Borders::ALL)
275 .style(help_box_style)
276 .border_type(BorderType::Rounded),
277 )
278 .alignment(Alignment::Center)
279 .wrap(ratatui::widgets::Wrap { trim: true })
280}