mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 22:20:31 +00:00
082cbcbc50
This change simplifys UI code that uses the Frame type. E.g.: ```rust fn draw<B: Backend>(frame: &mut Frame<B>) { // ... } ``` Frame was generic over Backend because it stored a reference to the terminal in the field. Instead it now directly stores the viewport area and current buffer. These are provided at creation time and are valid for the duration of the frame. BREAKING CHANGE: Frame is no longer generic over Backend. Code that accepted `Frame<Backend>` will now need to accept `Frame`. To migrate existing code, remove any generic parameters from code that uses an instance of a Frame. E.g. the above code becomes: ```rust fn draw(frame: &mut Frame) { // ... } ```
80 lines
3.6 KiB
Rust
80 lines
3.6 KiB
Rust
use std::{
|
|
io::{self, Stdout},
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use crossterm::{
|
|
event::{self, Event, KeyCode},
|
|
execute,
|
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
};
|
|
use ratatui::{prelude::*, widgets::*};
|
|
|
|
/// This is a bare minimum example. There are many approaches to running an application loop, so
|
|
/// this is not meant to be prescriptive. It is only meant to demonstrate the basic setup and
|
|
/// teardown of a terminal application.
|
|
///
|
|
/// A more robust application would probably want to handle errors and ensure that the terminal is
|
|
/// restored to a sane state before exiting. This example does not do that. It also does not handle
|
|
/// events or update the application state. It just draws a greeting and exits when the user
|
|
/// presses 'q'.
|
|
fn main() -> Result<()> {
|
|
let mut terminal = setup_terminal().context("setup failed")?;
|
|
run(&mut terminal).context("app loop failed")?;
|
|
restore_terminal(&mut terminal).context("restore terminal failed")?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Setup the terminal. This is where you would enable raw mode, enter the alternate screen, and
|
|
/// hide the cursor. This example does not handle errors. A more robust application would probably
|
|
/// want to handle errors and ensure that the terminal is restored to a sane state before exiting.
|
|
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
|
|
let mut stdout = io::stdout();
|
|
enable_raw_mode().context("failed to enable raw mode")?;
|
|
execute!(stdout, EnterAlternateScreen).context("unable to enter alternate screen")?;
|
|
Terminal::new(CrosstermBackend::new(stdout)).context("creating terminal failed")
|
|
}
|
|
|
|
/// Restore the terminal. This is where you disable raw mode, leave the alternate screen, and show
|
|
/// the cursor.
|
|
fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|
disable_raw_mode().context("failed to disable raw mode")?;
|
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)
|
|
.context("unable to switch to main screen")?;
|
|
terminal.show_cursor().context("unable to show cursor")
|
|
}
|
|
|
|
/// Run the application loop. This is where you would handle events and update the application
|
|
/// state. This example exits when the user presses 'q'. Other styles of application loops are
|
|
/// possible, for example, you could have multiple application states and switch between them based
|
|
/// on events, or you could have a single application state and update it based on events.
|
|
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
|
|
loop {
|
|
terminal.draw(crate::render_app)?;
|
|
if should_quit()? {
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Render the application. This is where you would draw the application UI. This example just
|
|
/// draws a greeting.
|
|
fn render_app(frame: &mut Frame) {
|
|
let greeting = Paragraph::new("Hello World! (press 'q' to quit)");
|
|
frame.render_widget(greeting, frame.size());
|
|
}
|
|
|
|
/// Check if the user has pressed 'q'. This is where you would handle events. This example just
|
|
/// checks if the user has pressed 'q' and returns true if they have. It does not handle any other
|
|
/// events. There is a 250ms timeout on the event poll so that the application can exit in a timely
|
|
/// manner, and to ensure that the terminal is rendered at least once every 250ms.
|
|
fn should_quit() -> Result<bool> {
|
|
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
|
|
if let Event::Key(key) = event::read().context("event read failed")? {
|
|
return Ok(KeyCode::Char('q') == key.code);
|
|
}
|
|
}
|
|
Ok(false)
|
|
}
|