minimal/
minimal.rs

1use std::io::{self, Stdout};
2use std::time::Duration;
3
4use crossterm::{
5    event::{self, Event, KeyCode},
6    execute,
7    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
8};
9use ratatui::{prelude::*, widgets::*};
10
11struct State {
12    form: Form,
13    submissions: Option<Vec<String>>,
14    should_quit: bool,
15}
16
17use tui_form_widget::{Form, FormSelection};
18
19fn main() -> io::Result<()> {
20    let mut state = State {
21        form: Form::from(vec!["Account", "Username / Email", "Password"]),
22        should_quit: false,
23        submissions: None,
24    };
25    let mut terminal = setup_terminal()?;
26    run(&mut terminal, &mut state)?;
27    restore_terminal(&mut terminal)?;
28    Ok(())
29}
30
31fn setup_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
32    let mut stdout = io::stdout();
33    enable_raw_mode()?;
34    execute!(stdout, EnterAlternateScreen)?;
35    Terminal::new(CrosstermBackend::new(stdout))
36}
37
38fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> io::Result<()> {
39    disable_raw_mode()?;
40    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
41    terminal.show_cursor()
42}
43
44fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: &mut State) -> io::Result<()> {
45    loop {
46        terminal.draw(|f| render_app(f, state))?;
47        handle_input(state)?;
48        if state.should_quit {
49            break;
50        }
51    }
52    Ok(())
53}
54
55fn render_app(frame: &mut Frame<CrosstermBackend<Stdout>>, state: &State) {
56    match &state.submissions {
57        Some(fields) => frame.render_widget(Paragraph::new(fields.join("\n")), frame.size()),
58        None => frame.render_widget(state.form.widget(), frame.size()),
59    }
60}
61
62fn handle_input(state: &mut State) -> io::Result<()> {
63    if event::poll(Duration::from_millis(250))? {
64        if let Event::Key(key) = event::read()? {
65            match state.form.selected() {
66                FormSelection::NoSelection => match key.code {
67                    KeyCode::Esc | KeyCode::Char('q') => state.should_quit = true,
68                    KeyCode::Char('s') => {
69                        let fields = state.form.submit();
70                        if fields.iter().any(|f| !f.is_valid()) {
71                        } else {
72                            // Field impls Into<String>
73                            state.submissions = Some(fields.into_iter().map(Into::into).collect());
74
75                            state.form.deselect();
76                        }
77                    }
78                    _ => {}
79                },
80                _ => {}
81            }
82
83            state.form.input(key.code);
84        }
85    }
86
87    Ok(())
88}