1use std::{default::Default, rc::Rc};
10
11use super::super::utils::centered_rect_fixed;
12
13use color_eyre::Result;
14use crossterm::event::{KeyCode, KeyEvent};
15use ratatui::{
16 layout::{Alignment, Constraint, Direction, Layout, Rect},
17 style::{Style, Stylize},
18 text::{Line, Span},
19 widgets::{
20 Block, Borders, HighlightSpacing, List, ListItem, ListState, Padding, Paragraph, Wrap,
21 },
22};
23use strum::IntoEnumIterator;
24
25use crate::{
26 action::{Action, OptionsActions},
27 components::Component,
28 connection_mode::ConnectionMode,
29 mode::{InputMode, Scene},
30 style::{
31 clear_area, COOL_GREY, DARK_GUNMETAL, EUCALYPTUS, GHOST_WHITE, INDIGO, LIGHT_PERIWINKLE,
32 VIVID_SKY_BLUE,
33 },
34};
35
36#[derive(Default)]
37enum ChangeConnectionModeState {
38 #[default]
39 Selection,
40 ConfirmChange,
41}
42
43#[derive(Default)]
44pub struct ChangeConnectionModePopUp {
45 active: bool,
46 state: ChangeConnectionModeState,
47 items: StatefulList<ConnectionModeItem>,
48 connection_mode_selection: ConnectionModeItem,
49 connection_mode_initial_state: ConnectionModeItem,
50 can_select: bool, }
52
53impl ChangeConnectionModePopUp {
54 pub fn new(connection_mode: ConnectionMode) -> Result<Self> {
55 let mut selected_connection_mode: ConnectionModeItem = ConnectionModeItem::default();
56 let connection_modes_items: Vec<ConnectionModeItem> = ConnectionMode::iter()
57 .filter(|cm| cm != &ConnectionMode::HomeNetwork)
58 .map(|connection_mode_item| ConnectionModeItem {
59 connection_mode: connection_mode_item,
60 status: if connection_mode == connection_mode_item {
61 selected_connection_mode = ConnectionModeItem {
62 connection_mode: connection_mode_item,
63 status: ConnectionModeStatus::Selected,
64 };
65 ConnectionModeStatus::Selected
66 } else {
67 ConnectionModeStatus::NotSelected
68 },
69 })
70 .collect::<Vec<ConnectionModeItem>>();
71 debug!("Connection Mode in Config: {:?}", connection_mode);
72 let items = StatefulList::with_items(connection_modes_items);
73 Ok(Self {
74 active: false,
75 state: ChangeConnectionModeState::Selection,
76 items,
77 connection_mode_selection: selected_connection_mode.clone(),
78 connection_mode_initial_state: selected_connection_mode.clone(),
79 can_select: false,
80 })
81 }
82
83 fn deselect_all(&mut self) {
88 for item in &mut self.items.items {
89 item.status = ConnectionModeStatus::NotSelected;
90 }
91 }
92 fn assign_connection_mode_selection(&mut self) {
95 self.deselect_all();
96 if let Some(i) = self.items.state.selected() {
97 self.items.items[i].status = ConnectionModeStatus::Selected;
98 self.connection_mode_selection = self.items.items[i].clone();
99 }
100 }
101 fn select_connection_mode(&mut self) {
104 self.deselect_all();
105 for (index, item) in self.items.items.iter_mut().enumerate() {
106 if item.connection_mode == self.connection_mode_selection.connection_mode {
107 item.status = ConnectionModeStatus::Selected;
108 self.items.state.select(Some(index));
109 break;
110 }
111 }
112 }
113 fn return_selection(&mut self) -> ConnectionModeItem {
116 if let Some(i) = self.items.state.selected() {
117 return self.items.items[i].clone();
118 }
119 ConnectionModeItem::default()
120 }
121
122 fn draw_selection_state(
125 &mut self,
126 f: &mut crate::tui::Frame<'_>,
127 layer_zero: Rect,
128 layer_one: Rc<[Rect]>,
129 ) -> Paragraph {
130 let pop_up_border: Paragraph = Paragraph::new("").block(
131 Block::default()
132 .borders(Borders::ALL)
133 .title(" Connection Mode ")
134 .bold()
135 .title_style(Style::new().fg(VIVID_SKY_BLUE))
136 .padding(Padding::uniform(2))
137 .border_style(Style::new().fg(VIVID_SKY_BLUE)),
138 );
139 clear_area(f, layer_zero);
140
141 let layer_two = Layout::new(
142 Direction::Vertical,
143 [
144 Constraint::Length(10),
146 Constraint::Length(3),
148 Constraint::Length(1),
150 ],
151 )
152 .split(layer_one[1]);
153
154 let items: Vec<ListItem> = self
156 .items
157 .items
158 .iter()
159 .enumerate()
160 .map(|(i, connection_mode_item)| {
161 connection_mode_item.to_list_item(i, layer_two[0].width as usize)
162 })
163 .collect();
164
165 let items = List::new(items)
166 .block(Block::default().padding(Padding::uniform(1)))
167 .highlight_style(Style::default().bg(INDIGO))
168 .highlight_spacing(HighlightSpacing::Always);
169
170 f.render_stateful_widget(items, layer_two[0], &mut self.items.state);
171
172 let dash = Block::new()
174 .borders(Borders::BOTTOM)
175 .border_style(Style::new().fg(GHOST_WHITE));
176 f.render_widget(dash, layer_two[1]);
177
178 let buttons_layer =
180 Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
181 .split(layer_two[2]);
182
183 let button_no = Line::from(vec![Span::styled(
184 "Cancel [Esc]",
185 Style::default().fg(LIGHT_PERIWINKLE),
186 )]);
187
188 f.render_widget(
189 Paragraph::new(button_no)
190 .block(Block::default().padding(Padding::horizontal(2)))
191 .alignment(Alignment::Left),
192 buttons_layer[0],
193 );
194
195 let button_yes = Line::from(vec![
196 Span::styled(
197 "Select ",
198 if self.can_select {
199 Style::default().fg(EUCALYPTUS)
200 } else {
201 Style::default().fg(COOL_GREY)
202 },
203 ),
204 Span::styled("[Enter]", Style::default().fg(LIGHT_PERIWINKLE).bold()),
205 ])
206 .alignment(Alignment::Right);
207
208 f.render_widget(
209 Paragraph::new(button_yes)
210 .block(Block::default().padding(Padding::horizontal(2)))
211 .alignment(Alignment::Right),
212 buttons_layer[1],
213 );
214
215 pop_up_border
216 }
217
218 fn draw_confirm_change(
219 &mut self,
220 f: &mut crate::tui::Frame<'_>,
221 layer_zero: Rect,
222 layer_one: Rc<[Rect]>,
223 ) -> Paragraph {
224 let pop_up_border = Paragraph::new("").block(
226 Block::default()
227 .borders(Borders::ALL)
228 .title(" Confirm & Reset ")
229 .bold()
230 .title_style(Style::new().fg(VIVID_SKY_BLUE))
231 .padding(Padding::uniform(2))
232 .border_style(Style::new().fg(VIVID_SKY_BLUE)),
233 );
234 clear_area(f, layer_zero);
235
236 let layer_two = Layout::new(
238 Direction::Vertical,
239 [
240 Constraint::Length(9),
242 Constraint::Length(3),
244 Constraint::Length(1),
246 ],
247 )
248 .split(layer_one[1]);
249
250 let paragraph_text = Paragraph::new(vec![
251 Line::from(Span::styled("\n\n", Style::default())),
252 Line::from(Span::styled("\n\n", Style::default())),
253 Line::from(vec![
254 Span::styled(
255 "Changing connection mode will ",
256 Style::default().fg(LIGHT_PERIWINKLE),
257 ),
258 Span::styled("reset all nodes.", Style::default().fg(GHOST_WHITE)),
259 ]),
260 Line::from(Span::styled("\n\n", Style::default())),
261 Line::from(Span::styled("\n\n", Style::default())),
262 Line::from(Span::styled("\n\n", Style::default())),
263 Line::from(vec![
264 Span::styled("You’ll need to ", Style::default().fg(LIGHT_PERIWINKLE)),
265 Span::styled("Add", Style::default().fg(GHOST_WHITE)),
266 Span::styled(" and ", Style::default().fg(LIGHT_PERIWINKLE)),
267 Span::styled("Start", Style::default().fg(GHOST_WHITE)),
268 Span::styled(
269 " them again afterwards. Are you sure you want to continue?",
270 Style::default().fg(LIGHT_PERIWINKLE),
271 ),
272 ]),
273 ])
274 .alignment(Alignment::Left)
275 .wrap(Wrap { trim: true })
276 .block(Block::default().padding(Padding::horizontal(2)));
277
278 f.render_widget(paragraph_text, layer_two[0]);
279
280 let dash = Block::new()
281 .borders(Borders::BOTTOM)
282 .border_style(Style::new().fg(GHOST_WHITE));
283 f.render_widget(dash, layer_two[1]);
284
285 let buttons_layer =
286 Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
287 .split(layer_two[2]);
288
289 let button_no = Line::from(vec![Span::styled(
290 " Cancel [Esc]",
291 Style::default().fg(LIGHT_PERIWINKLE),
292 )]);
293 let button_yes_style = if self.can_select {
294 Style::default().fg(EUCALYPTUS)
295 } else {
296 Style::default().fg(LIGHT_PERIWINKLE)
297 };
298 f.render_widget(button_no, buttons_layer[0]);
299
300 let button_yes = Line::from(vec![
301 Span::styled("Yes, Change Mode ", button_yes_style),
302 Span::styled("[Enter]", Style::default().fg(GHOST_WHITE)),
303 ]);
304 f.render_widget(button_yes, buttons_layer[1]);
305
306 pop_up_border
307 }
308}
309
310impl Component for ChangeConnectionModePopUp {
311 fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
312 if !self.active {
313 return Ok(vec![]);
314 }
315 let send_back: Vec<Action> = match &self.state {
316 ChangeConnectionModeState::Selection => match key.code {
317 KeyCode::Enter => {
318 let connection_mode = self.return_selection();
319 self.connection_mode_initial_state = self.connection_mode_selection.clone();
320 if connection_mode.connection_mode == ConnectionMode::CustomPorts {
321 vec![
322 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
323 ConnectionMode::CustomPorts,
324 )),
325 Action::SwitchScene(Scene::ChangePortsPopUp {
326 connection_mode_old_value: Some(
327 self.connection_mode_initial_state.connection_mode,
328 ),
329 }),
330 ]
331 } else {
332 self.state = ChangeConnectionModeState::ConfirmChange;
333 vec![]
334 }
335 }
336 KeyCode::Esc => {
337 debug!("Got Esc, switching to Options");
338 vec![Action::SwitchScene(Scene::Options)]
339 }
340 KeyCode::Up => {
341 if self.items.items.len() > 1 {
342 self.items.previous();
343 let connection_mode = self.return_selection();
344 self.can_select = connection_mode.connection_mode
345 != self.connection_mode_selection.connection_mode;
346 }
347 vec![]
348 }
349 KeyCode::Down => {
350 if self.items.items.len() > 1 {
351 self.items.next();
352 let connection_mode = self.return_selection();
353 self.can_select = connection_mode.connection_mode
354 != self.connection_mode_selection.connection_mode;
355 }
356 vec![]
357 }
358 _ => {
359 vec![]
360 }
361 },
362 ChangeConnectionModeState::ConfirmChange => match key.code {
363 KeyCode::Enter => {
364 self.state = ChangeConnectionModeState::Selection;
365 let connection_mode = self.return_selection();
368 if connection_mode.connection_mode
369 != self.connection_mode_selection.connection_mode
370 {
371 debug!(
372 "Got Enter and there's a new selection, storing value and switching to Options"
373 );
374 debug!("Connection Mode selected: {:?}", connection_mode);
375 self.connection_mode_initial_state = self.connection_mode_selection.clone();
376 self.assign_connection_mode_selection();
377 vec![
378 Action::StoreConnectionMode(
379 self.connection_mode_selection.connection_mode,
380 ),
381 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
382 connection_mode.clone().connection_mode,
383 )),
384 Action::SwitchScene(Scene::Status),
385 ]
386 } else {
387 debug!("Got Enter, but no new selection. We should not do anything");
388 vec![Action::SwitchScene(Scene::ChangeConnectionModePopUp)]
389 }
390 }
391 KeyCode::Esc => {
392 self.state = ChangeConnectionModeState::Selection;
393 vec![Action::SwitchScene(Scene::Options)]
394 }
395 _ => {
396 vec![]
397 }
398 },
399 };
400 Ok(send_back)
401 }
402
403 fn update(&mut self, action: Action) -> Result<Option<Action>> {
404 let send_back = match action {
405 Action::SwitchScene(scene) => match scene {
406 Scene::ChangeConnectionModePopUp => {
407 self.active = true;
408 self.can_select = false;
409 self.select_connection_mode();
410 Some(Action::SwitchInputMode(InputMode::Entry))
411 }
412 _ => {
413 self.active = false;
414 None
415 }
416 },
417 Action::OptionsActions(OptionsActions::UpdateConnectionMode(connection_mode)) => {
419 self.connection_mode_selection.connection_mode = connection_mode;
420 self.select_connection_mode();
421 None
422 }
423 _ => None,
424 };
425 Ok(send_back)
426 }
427
428 fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
429 if !self.active {
430 return Ok(());
431 }
432
433 let layer_zero = centered_rect_fixed(52, 15, area);
434
435 let layer_one = Layout::new(
436 Direction::Vertical,
437 [
438 Constraint::Length(1),
440 Constraint::Min(1),
442 Constraint::Length(1),
444 ],
445 )
446 .split(layer_zero);
447
448 let pop_up_border: Paragraph = match self.state {
449 ChangeConnectionModeState::Selection => {
450 self.draw_selection_state(f, layer_zero, layer_one)
451 }
452 ChangeConnectionModeState::ConfirmChange => {
453 self.draw_confirm_change(f, layer_zero, layer_one)
454 }
455 };
456
457 f.render_widget(pop_up_border, layer_zero);
458
459 Ok(())
460 }
461}
462
463#[derive(Default)]
464struct StatefulList<T> {
465 state: ListState,
466 items: Vec<T>,
467 last_selected: Option<usize>,
468}
469
470impl<T> StatefulList<T> {
471 fn with_items(items: Vec<T>) -> Self {
472 StatefulList {
473 state: ListState::default(),
474 items,
475 last_selected: None,
476 }
477 }
478
479 fn next(&mut self) {
480 let i = match self.state.selected() {
481 Some(i) => {
482 if i >= self.items.len() - 1 {
483 0
484 } else {
485 i + 1
486 }
487 }
488 None => self.last_selected.unwrap_or(0),
489 };
490 self.state.select(Some(i));
491 }
492
493 fn previous(&mut self) {
494 let i = match self.state.selected() {
495 Some(i) => {
496 if i == 0 {
497 self.items.len() - 1
498 } else {
499 i - 1
500 }
501 }
502 None => self.last_selected.unwrap_or(0),
503 };
504 self.state.select(Some(i));
505 }
506}
507
508#[derive(Default, Debug, Copy, Clone)]
509enum ConnectionModeStatus {
510 Selected,
511 #[default]
512 NotSelected,
513}
514
515#[derive(Default, Debug, Clone)]
516pub struct ConnectionModeItem {
517 connection_mode: ConnectionMode,
518 status: ConnectionModeStatus,
519}
520
521impl ConnectionModeItem {
522 fn to_list_item(&self, _index: usize, _width: usize) -> ListItem {
523 let line = match self.status {
524 ConnectionModeStatus::NotSelected => Line::from(vec![
525 Span::raw(" "),
526 Span::styled(
527 self.connection_mode.to_string(),
528 Style::default().fg(VIVID_SKY_BLUE),
529 ),
530 ]),
531 ConnectionModeStatus::Selected => Line::from(vec![
532 Span::styled(" ►", Style::default().fg(EUCALYPTUS)),
533 Span::raw(" "),
534 Span::styled(
535 self.connection_mode.to_string(),
536 Style::default().fg(VIVID_SKY_BLUE),
537 ),
538 ]),
539 };
540
541 ListItem::new(line).style(Style::default().bg(DARK_GUNMETAL))
542 }
543}