tui-dialog 0.4.0

A widget for entering a single line of text in a dialog for Ratatui.
Documentation
use std::io::{Stdout, stdout};

use crossterm::{
    event::{self, Event, KeyCode, KeyEventKind},
    execute,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
    Frame, Terminal,
    backend::CrosstermBackend,
    layout::{Constraint, Layout},
    prelude::Stylize,
    symbols::border,
    text::Line,
    widgets::{Borders, Paragraph, block::*},
};
use tui_dialog::{Dialog, centered_rect};

pub const SIDEBAR_SIZE: u16 = 40;

/// The data and state of the app.
pub struct App {
    pub dialog: Dialog,
    pub text: String,
    pub exit: bool,
}

fn main() {
    // Initialize the app.
    let mut app = App {
        dialog: Dialog::default(),
        text: "Hello world!".to_string(),
        exit: false,
    };

    // Initialize the terminal.
    let mut terminal = match Terminal::new(CrosstermBackend::new(stdout())) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("Error creating new terminal interface: {e}");
            return;
        }
    };

    if let Err(e) = execute!(terminal.backend_mut(), EnterAlternateScreen) {
        eprintln!("Errow switching to alternate screen: {e}");
        return;
    }

    if let Err(e) = enable_raw_mode() {
        eprintln!("Error entering raw mode: {e}");
        return;
    }

    // Run the app.
    if let Err(e) = run(&mut app, &mut terminal) {
        eprintln!("Error running app: {e}");
        return;
    }

    // Restore the terminal to its original state, then exit.
    if let Err(e) = disable_raw_mode() {
        eprintln!("Error disabling raw mode: {e}");
        return;
    }

    if let Err(e) = execute!(terminal.backend_mut(), LeaveAlternateScreen) {
        eprintln!("Error leaving alternate screen: {e}");
    }
}

/// Run the application's main loop until the user quits.
fn run(
    app: &mut App,
    terminal: &mut Terminal<CrosstermBackend<Stdout>>,
) -> Result<(), std::io::Error> {
    while !app.exit {
        // Redraw the frame every time.
        terminal.draw(|frame| render(frame, app))?;

        // Handle user input.
        match event::read()? {
            Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
                // Pass all `key_event.code`s to a dialog if open.
                // (The dialog handles closing itself, when the user presses `Enter` or `Esc`.)
                if app.dialog.open {
                    app.dialog.key_action(&key_event.code);
                    if app.dialog.submitted {
                        // Here is where you'd do something more significant.
                        app.text = app.dialog.submitted_input.clone();
                    }
                // Otherwise handle them here.
                } else {
                    match key_event.code {
                        KeyCode::Char('q') => app.exit = true,
                        // Your app needs to open the dialog.
                        KeyCode::Char('d') => app.dialog.open = true,
                        _ => (),
                    }
                }
            }
            _ => (),
        };
    }
    Ok(())
}

fn render(frame: &mut Frame, app: &mut App) {
    // Layout the rectangles of the UI.
    let horizontal = Layout::horizontal([Constraint::Fill(1), Constraint::Length(SIDEBAR_SIZE)]);

    let [left, right] = horizontal.areas(frame.area());

    // Left - main content.
    let main_block = Block::default()
        .title_top(Line::from(" tui-dialog example ").bold().centered())
        .borders(Borders::ALL)
        .border_set(border::THICK)
        .padding(Padding::horizontal(1));

    // Right - Controls menu
    let controls_block = Block::default()
        .title_top(Line::from(" Controls").centered().bold())
        .borders(Borders::ALL)
        .border_set(border::THICK);

    let controls_content: Vec<Line<'_>> = vec![
        vec!["d".blue(), " Show dialog".into()].into(),
        vec!["q".blue(), " Quit".into()].into(),
    ];

    let controls = Paragraph::new(controls_content).block(controls_block);
    frame.render_widget(controls, right);
    frame.render_widget(Paragraph::new(app.text.clone()).block(main_block), left);

    let dialog_area = centered_rect(frame.area(), 45, 5, -(SIDEBAR_SIZE as i16), 0);
    frame.render_widget(app.dialog.title_top("Enter text:"), dialog_area);
}