1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use std::{io::stdout, path::PathBuf};

use crate::gui::error::GrapeGuiError;

use crossterm::{
    event::{self, KeyCode, KeyEventKind},
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use ratatui::{
    prelude::*,
    widgets::{
        block::{Position, Title},
        *,
    },
    Terminal,
};

/// Generates a TUI that allows the user to select which serial
/// device is attached to the u-blox antenna.
pub fn device_selector(
    mut available_ports: Vec<PathBuf>,
) -> Result<Option<PathBuf>, GrapeGuiError> {
    enable_raw_mode()?;
    stdout().execute(EnterAlternateScreen)?;
    let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
    terminal.clear()?;

    let mut cursor = 0;
    let mut list_state = ListState::default().with_selected(Some(cursor));
    let n_ports = available_ports.len();
    let mut selected_port = None;
    loop {
        let title = Title::from(" Device Selector ".magenta().bold());
        let instructions = Title::from(Line::from(vec![
            " Navigate ".into(),
            "<Up>/<Down>".magenta().bold(),
            " Select ".into(),
            "<Enter>".magenta().bold(),
            " Quit ".into(),
            "<Q> ".magenta().bold(),
        ]));
        let block = Block::default()
            .title(title.alignment(Alignment::Center))
            .title(
                instructions
                    .alignment(Alignment::Center)
                    .position(Position::Bottom),
            )
            .borders(Borders::ALL);
        let port_names = available_ports.iter().map(|p| p.to_string_lossy());
        let list = List::new(port_names)
            .style(Style::default().fg(Color::White))
            .highlight_symbol(">>")
            .highlight_style(Style::default().fg(Color::Magenta))
            .block(block);
        list_state.select(Some(cursor));
        terminal
            .draw(|frame| {
                let area = frame.size();
                frame.render_stateful_widget(list, area, &mut list_state);
            })
            .unwrap();
        if event::poll(std::time::Duration::from_millis(16)).unwrap() {
            if let event::Event::Key(key) = event::read().unwrap() {
                if key.kind == KeyEventKind::Press {
                    match key.code {
                        KeyCode::Down => {
                            cursor = (cursor + 1) % n_ports;
                        }
                        KeyCode::Up => {
                            cursor = (cursor + n_ports - 1) % n_ports;
                        }
                        KeyCode::Enter => {
                            selected_port = Some(cursor);
                            break;
                        }
                        KeyCode::Char('q') => break,
                        _ => {}
                    }
                }
            }
        }
    }

    disable_raw_mode()?;
    stdout().execute(LeaveAlternateScreen)?;

    Ok(selected_port.map(|i| available_ports.swap_remove(i)))
}