feat: add ratatui::run() method (#1707)

This introduces a new `ratatui::run()` method which runs a closure with
a terminal initialized with reasonable defaults for most applications.
This calls `ratatui::init()` before running the closure and
`ratatui::restore()` after the closure completes, and returns the result
of the closure.

A minimal hello world example using the new `ratatui::run()` method:

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    ratatui::run(|terminal| {
        loop {
            terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?;
            if crossterm::event::read()?.is_key_press() {
                break Ok(());
            }
        }
    })
}
```

Of course, this also works both with apps that use free methods and
structs:

```rust
fn run(terminal: &mut DefaultTerminal) -> Result<(), AppError> { ... }

ratatui::run(run)?;
```

```rust
struct App { ... }

impl App {
    fn new() -> Self { ... }
    fn run(mut self, terminal: &mut DefaultTerminal) -> Result<(), AppError> { ... }
}

ratatui::run(|terminal| App::new().run(terminal))?;
```
This commit is contained in:
Josh McKinney 2025-06-25 03:20:42 -07:00 committed by GitHub
parent b6fbfcdd1c
commit 7bc78bca1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 442 additions and 546 deletions

View File

@ -21,10 +21,7 @@ use ratatui::widgets::{Widget, WidgetRef};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::default().run(terminal);
ratatui::restore();
result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default)]
@ -36,15 +33,15 @@ struct App {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while !self.should_quit {
self.draw(&mut terminal)?;
self.render(terminal)?;
self.handle_events()?;
}
Ok(())
}
fn draw(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
fn render(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
tui.draw(|frame| frame.render_widget(self, frame.area()))?;
Ok(())
}

View File

@ -22,21 +22,18 @@ use time::{Date, Month, OffsetDateTime};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
ratatui::run(run)
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
fn run(terminal: &mut DefaultTerminal) -> Result<()> {
let mut selected_date = OffsetDateTime::now_local()?.date();
let mut calendar_style = StyledCalendar::Default;
loop {
terminal.draw(|frame| render(frame, calendar_style, selected_date))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('q') => break Ok(()),
KeyCode::Char('s') => calendar_style = calendar_style.next(),
KeyCode::Char('n') | KeyCode::Tab => selected_date = next_month(selected_date),
KeyCode::Char('p') | KeyCode::BackTab => selected_date = prev_month(selected_date),

View File

@ -21,10 +21,7 @@ use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
struct App {
@ -78,7 +75,7 @@ impl App {
}
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(250);
let mut last_tick = Instant::now();
loop {

View File

@ -11,40 +11,35 @@
use color_eyre::Result;
use crossterm::event;
use itertools::Itertools;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = run(terminal);
ratatui::restore();
app_result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {
let layout = Layout::vertical([
let [named, indexed_colors, indexed_greys] = Layout::vertical([
Constraint::Length(30),
Constraint::Length(17),
Constraint::Length(2),
])
.split(frame.area());
.areas(frame.area());
render_named_colors(frame, layout[0]);
render_indexed_colors(frame, layout[1]);
render_indexed_grayscale(frame, layout[2]);
render_named_colors(frame, named);
render_indexed_colors(frame, indexed_colors);
render_indexed_grayscale(frame, indexed_greys);
}
const NAMED_COLORS: [Color; 16] = [

View File

@ -31,10 +31,7 @@ use ratatui::widgets::Widget;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Debug, Default)]
@ -91,7 +88,7 @@ impl App {
/// Run the app
///
/// This is the main event loop for the app.
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
pub fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while self.is_running() {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
self.handle_events()?;

View File

@ -24,10 +24,7 @@ use strum::{Display, EnumIter, FromRepr};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default)]
@ -82,7 +79,7 @@ struct SpacerBlock;
// App behaviour
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
self.insert_test_defaults();
while self.is_running() {

View File

@ -36,10 +36,7 @@ const FILL_COLOR: Color = tailwind::SLATE.c950;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Default, Clone, Copy)]
@ -72,7 +69,7 @@ enum AppState {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
self.update_max_scroll_offset();
while self.is_running() {
terminal.draw(|frame| frame.render_widget(self, frame.area()))?;

View File

@ -51,7 +51,7 @@ where
{
let mut last_tick = Instant::now();
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if !event::poll(timeout)? {

View File

@ -42,7 +42,7 @@ where
{
let events = events(tick_rate);
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
match events.recv()? {
Event::Input(key) => match key {

View File

@ -36,7 +36,7 @@ fn run_app(
) -> Result<(), Box<dyn Error>> {
let mut last_tick = Instant::now();
loop {
terminal.draw(|frame| ui::draw(frame, &mut app))?;
terminal.draw(|frame| ui::render(frame, &mut app))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if let Some(input) = terminal

View File

@ -10,7 +10,7 @@ use ratatui::{Frame, symbols};
use crate::app::App;
pub fn draw(frame: &mut Frame, app: &mut App) {
pub fn render(frame: &mut Frame, app: &mut App) {
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(frame.area());
let tabs = app
.tabs

View File

@ -27,10 +27,7 @@ use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
const EXAMPLE_DATA: &[(&str, &[Constraint])] = &[
@ -160,7 +157,7 @@ enum SelectedTab {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
// increase the layout cache to account for the number of layout events. This ensures that
// layout is not generally reprocessed on every frame (which would lead to possible janky
// results when there are more than one possible solution to the requested layout). This

View File

@ -43,14 +43,11 @@ enum AppState {
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while self.state != AppState::Quitting {
terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
self.handle_events()?;

View File

@ -21,17 +21,14 @@ use ratatui::{DefaultTerminal, Frame};
/// and exits when the user presses 'q'.
fn main() -> Result<()> {
color_eyre::install()?; // augment errors / panics with easy to read messages
let terminal = ratatui::init();
let app_result = run(terminal).context("app loop failed");
ratatui::restore();
app_result
ratatui::run(run).context("failed to run app")
}
/// 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(mut terminal: DefaultTerminal) -> Result<()> {
fn run(terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if should_quit()? {

View File

@ -7,9 +7,8 @@
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use crossterm::event;
use itertools::Itertools;
use ratatui::DefaultTerminal;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Stylize;
@ -18,35 +17,18 @@ use ratatui::widgets::Widget;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
}
struct App {
hyperlink: Hyperlink<'static>,
}
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
let hyperlink = Hyperlink::new(text, "https://example.com");
impl App {
fn new() -> Self {
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
let hyperlink = Hyperlink::new(text, "https://example.com");
Self { hyperlink }
}
fn run(self, mut terminal: DefaultTerminal) -> Result<()> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget(&self.hyperlink, frame.area()))?;
if event::read()?
.as_key_press_event()
.is_some_and(|key| matches!(key.code, KeyCode::Char('q') | KeyCode::Esc))
{
break;
terminal.draw(|frame| frame.render_widget(&hyperlink, frame.area()))?;
if event::read()?.is_key_press() {
break Ok(());
}
}
Ok(())
}
})
}
/// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8].

View File

@ -26,12 +26,8 @@ use serde::Serialize;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::default().run(terminal);
ratatui::restore();
// serialize the form to JSON if the user submitted it, otherwise print "Canceled"
match result {
match ratatui::run(|terminal| App::default().run(terminal)) {
Ok(Some(form)) => println!("{}", serde_json::to_string_pretty(&form)?),
Ok(None) => println!("Canceled"),
Err(err) => eprintln!("{err}"),
@ -54,7 +50,7 @@ enum AppState {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<Option<InputForm>> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<Option<InputForm>> {
while self.state == AppState::Running {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;

View File

@ -1,28 +1,23 @@
/// A minimal example of a Ratatui application.
///
/// This is a bare minimum example. There are many approaches to running an application loop,
/// so this is not meant to be prescriptive. See the [examples] folder for more complete
/// examples. In particular, the [hello-world] example is a good starting point.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [examples]: https://github.com/ratatui/ratatui/blob/main/examples
/// [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
use crossterm::event;
use ratatui::text::Text;
fn main() {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
.expect("failed to draw frame");
if event::read().expect("failed to read event").is_key_press() {
break;
//! A minimal example of a Ratatui application.
//!
//! This is a bare minimum example. There are many approaches to running an application loop,
//! so this is not meant to be prescriptive. See the [examples] folder for more complete
//! examples. In particular, the [hello-world] example is a good starting point.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
fn main() -> Result<(), Box<dyn std::error::Error>> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?;
if crossterm::event::read()?.is_key_press() {
break Ok(());
}
}
}
ratatui::restore();
})
}

View File

@ -12,29 +12,24 @@ use std::{error::Error, iter::once, result};
use crossterm::event;
use itertools::Itertools;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use ratatui::{DefaultTerminal, Frame};
type Result<T> = result::Result<T, Box<dyn Error>>;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = run(terminal);
ratatui::restore();
app_result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {

View File

@ -21,10 +21,7 @@ use ratatui::{DefaultTerminal, Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = MouseDrawingApp::default().run(terminal);
ratatui::restore();
result
ratatui::run(|terminal| MouseDrawingApp::default().run(terminal))
}
#[derive(Default)]
@ -40,7 +37,7 @@ struct MouseDrawingApp {
}
impl MouseDrawingApp {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
execute!(std::io::stdout(), EnableMouseCapture)?;
while !self.should_exit {
terminal.draw(|frame| self.render(frame))?;

View File

@ -31,69 +31,58 @@
/// [Color Eyre recipe]: https://ratatui.rs/recipes/apps/color-eyre
use color_eyre::{Result, eyre::bail};
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::text::Line;
use ratatui::widgets::{Block, Paragraph};
use ratatui::{DefaultTerminal, Frame};
#[derive(Debug)]
enum PanicHandlerState {
Enabled,
Disabled,
}
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
}
struct App {
hook_enabled: bool,
}
impl App {
const fn new() -> Self {
Self { hook_enabled: true }
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let mut panic_hook_state = PanicHandlerState::Enabled;
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| self.render(frame))?;
terminal.draw(|frame| render(frame, &panic_hook_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('p') => panic!("intentional demo panic"),
KeyCode::Char('e') => bail!("intentional demo error"),
KeyCode::Char('h') => {
let _ = std::panic::take_hook();
self.hook_enabled = false;
panic_hook_state = PanicHandlerState::Disabled;
}
KeyCode::Char('q') => return Ok(()),
_ => {}
}
}
}
}
fn render(&self, frame: &mut Frame) {
let text = vec![
if self.hook_enabled {
Line::from("HOOK IS CURRENTLY **ENABLED**")
} else {
Line::from("HOOK IS CURRENTLY **DISABLED**")
},
Line::from(""),
Line::from("Press `p` to cause a panic"),
Line::from("Press `e` to cause an error"),
Line::from("Press `h` to disable the panic hook"),
Line::from("Press `q` to quit"),
Line::from(""),
Line::from("When your app panics without a panic hook, you will likely have to"),
Line::from("reset your terminal afterwards with the `reset` command"),
Line::from(""),
Line::from("Try first with the panic handler enabled, and then with it disabled"),
Line::from("to see the difference"),
];
let paragraph = Paragraph::new(text)
.block(Block::bordered().title("Panic Handler Demo"))
.centered();
frame.render_widget(paragraph, frame.area());
}
})
}
fn render(frame: &mut Frame, state: &PanicHandlerState) {
let text = vec![
Line::from(format!("Panic hook is currently: {state:?}")),
Line::from(""),
Line::from("Press `p` to cause a panic"),
Line::from("Press `e` to cause an error"),
Line::from("Press `h` to disable the panic hook"),
Line::from("Press `q` to quit"),
Line::from(""),
Line::from("When your app panics without a panic hook, you will likely have to"),
Line::from("reset your terminal afterwards with the `reset` command"),
Line::from(""),
Line::from("Try first with the panic handler enabled, and then with it disabled"),
Line::from("to see the difference"),
];
let paragraph = Paragraph::new(text)
.block(Block::bordered().title("Panic Handler Demo"))
.centered();
frame.render_widget(paragraph, frame.area());
}

View File

@ -1,77 +1,67 @@
/// A Ratatui example that demonstrates how to handle popups.
// See also https://github.com/joshka/tui-popup and
// https://github.com/sephiroth74/tui-confirm-dialog
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! A Ratatui example that demonstrates how to handle popups.
//! See also:
//! - <https://github.com/joshka/tui-popup> and
//! - <https://github.com/sephiroth74/tui-confirm-dialog>
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::style::Stylize;
use ratatui::widgets::{Block, Clear, Paragraph, Wrap};
use ratatui::{DefaultTerminal, Frame};
use ratatui::text::Line;
use ratatui::widgets::{Block, Clear};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
}
#[derive(Default)]
struct App {
show_popup: bool,
}
// This flag will be toggled when the user presses 'p'. This could be stored in an app struct
// if you have more state to manage than just this flag.
let mut show_popup = false;
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| self.render(frame))?;
terminal.draw(|frame| render(frame, show_popup))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('p') => self.show_popup = !self.show_popup,
KeyCode::Char('p') => show_popup = !show_popup,
_ => {}
}
}
}
}
})
}
fn render(&self, frame: &mut Frame) {
let area = frame.area();
fn render(frame: &mut Frame, show_popup: bool) {
let area = frame.area();
let vertical = Layout::vertical([Constraint::Percentage(20), Constraint::Percentage(80)]);
let [instructions, content] = vertical.areas(area);
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]);
let [instructions, content] = vertical.areas(area);
let text = if self.show_popup {
"Press p to close the popup"
} else {
"Press p to show the popup"
};
let paragraph = Paragraph::new(text.slow_blink())
.centered()
.wrap(Wrap { trim: true });
frame.render_widget(paragraph, instructions);
frame.render_widget(
Line::from("Press 'p' to toggle popup, 'q' to quit").centered(),
instructions,
);
let block = Block::bordered().title("Content").on_blue();
frame.render_widget(block, content);
frame.render_widget(Block::bordered().title("Content").on_blue(), content);
if self.show_popup {
let block = Block::bordered().title("Popup");
let area = popup_area(area, 60, 20);
frame.render_widget(Clear, area); //this clears out the background
frame.render_widget(block, area);
}
if show_popup {
let popup = Block::bordered().title("Popup");
let popup_area = centered_area(area, 60, 20);
// clears out any background in the area before rendering the popup
frame.render_widget(Clear, popup_area);
frame.render_widget(popup, popup_area);
}
}
/// helper function to create a centered rect using up certain percentage of the available rect `r`
fn popup_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
/// Create a centered rect using up certain percentage of the available rect
fn centered_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect {
let vertical = Layout::vertical([Constraint::Percentage(percent_y)]).flex(Flex::Center);
let horizontal = Layout::horizontal([Constraint::Percentage(percent_x)]).flex(Flex::Center);
let [area] = vertical.areas(area);

View File

@ -29,14 +29,11 @@ struct App {
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(250);
let mut last_tick = Instant::now();
loop {

View File

@ -34,10 +34,7 @@ const ITEM_HEIGHT: usize = 4;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
struct TableColors {
buffer_bg: Color,
@ -120,6 +117,7 @@ impl App {
items: data_vec,
}
}
pub fn next_row(&mut self) {
let i = match self.state.selected() {
Some(i) => {
@ -171,7 +169,7 @@ impl App {
self.colors = TableColors::new(&PALETTES[self.color_index]);
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(|frame| self.render(frame))?;

View File

@ -27,10 +27,7 @@ const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
/// This struct holds the current state of the app. In particular, it has the `todo_list` field
@ -124,7 +121,7 @@ impl TodoItem {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while !self.should_exit {
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if let Some(key) = event::read()?.as_key_press_event() {

View File

@ -30,10 +30,7 @@ use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::new().run(terminal))
}
/// App holds the state of the application
@ -127,7 +124,7 @@ impl App {
self.reset_cursor();
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
loop {
terminal.draw(|frame| self.render(frame))?;

View File

@ -9,63 +9,35 @@
//! [`BarChart`]: https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use crossterm::event;
use rand::{Rng, rng};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::Line;
use ratatui::widgets::{Bar, BarChart, BarGroup};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
app_result
let temperatures: Vec<u8> = (0..24).map(|_| rng().random_range(50..90)).collect();
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &temperatures))?;
if event::read()?.is_key_press() {
break Ok(());
}
}
})
}
struct App {
should_exit: bool,
temperatures: Vec<u8>,
}
fn render(frame: &mut Frame, temperatures: &[u8]) {
let [title, main] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)])
.spacing(1)
.areas(frame.area());
impl App {
fn new() -> Self {
let mut rng = rng();
let temperatures = (0..24).map(|_| rng.random_range(50..90)).collect();
Self {
should_exit: false,
temperatures,
}
}
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while !self.should_exit {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
}
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
if event::read()?
.as_key_press_event()
.is_some_and(|key| key.code == KeyCode::Char('q'))
{
self.should_exit = true;
}
Ok(())
}
fn render(&self, frame: &mut Frame) {
let [title, main] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)])
.spacing(1)
.areas(frame.area());
frame.render_widget("Weather demo".bold().into_centered_line(), title);
frame.render_widget(vertical_barchart(&self.temperatures), main);
}
frame.render_widget("Weather demo".bold().into_centered_line(), title);
frame.render_widget(vertical_barchart(temperatures), main);
}
/// Create a vertical bar chart from the temperatures data.

View File

@ -1,39 +1,34 @@
/// An example of how to use [`WidgetRef`] to store heterogeneous widgets in a container.
///
/// This example creates a `StackContainer` widget that can hold any number of widgets of
/// different types. It creates two widgets, `Greeting` and `Farewell`, and stores them in a
/// `StackContainer` with a vertical layout. The `StackContainer` widget renders each of its
/// child widgets in the order they were added.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! An example of how to use [`WidgetRef`] to store heterogeneous widgets in a container.
//!
//! This example creates a `StackContainer` widget that can hold any number of widgets of
//! different types. It creates two widgets, `Greeting` and `Farewell`, and stores them in a
//! `StackContainer` with a vertical layout. The `StackContainer` widget renders each of its
//! child widgets in the order they were added.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use std::iter::zip;
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Block, Paragraph, Widget, WidgetRef};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
}
}
}
})
}
fn render(frame: &mut Frame) {

View File

@ -18,28 +18,22 @@ use core::iter::zip;
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Bar, BarChart, BarGroup};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with a barchart on the left and right side.

View File

@ -16,28 +16,22 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::Stylize;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Bar, BarChart};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with a title and two barcharts.

View File

@ -16,28 +16,22 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with various blocks.

View File

@ -16,30 +16,24 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Padding};
use ratatui::{DefaultTerminal, Frame};
use ratatui_widgets::calendar::{CalendarEventStore, Monthly};
use time::{Date, Month, OffsetDateTime};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with 2 monthly calendars side by side.

View File

@ -16,30 +16,24 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::symbols::Marker;
use ratatui::text::{Line as TextLine, Span};
use ratatui::widgets::canvas::{Canvas, Line, Map, MapResolution, Rectangle};
use ratatui::{DefaultTerminal, Frame};
use ratatui_widgets::canvas::Points;
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with a canvas widget.

View File

@ -16,29 +16,23 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::symbols::Marker;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Axis, Chart, Dataset, GraphType};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with a chart.

View File

@ -20,24 +20,18 @@ use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Gauge, LineGauge};
use ratatui::{DefaultTerminal, Frame, symbols};
use ratatui::{Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with various progress bars.

View File

@ -28,10 +28,7 @@ use ratatui::widgets::{LineGauge, Paragraph, Widget};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let app_result = App::default().run(terminal);
ratatui::restore();
app_result
ratatui::run(|terminal| App::default().run(terminal))
}
#[derive(Debug, Default, Clone, Copy)]
@ -50,7 +47,7 @@ enum AppState {
}
impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while self.state != AppState::Quit {
terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;
self.handle_events()?;

View File

@ -14,36 +14,30 @@
//! [widget examples]: https://github.com/ratatui/ratatui/blob/main/ratatui-widgets/examples
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{List, ListDirection, ListState};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
let mut list_state = ListState::default().with_selected(Some(0));
loop {
terminal.draw(|frame| render(frame, &mut list_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('j') | KeyCode::Down => list_state.select_next(),
KeyCode::Char('k') | KeyCode::Up => list_state.select_previous(),
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
_ => {}
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &mut list_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('j') | KeyCode::Down => list_state.select_next(),
KeyCode::Char('k') | KeyCode::Up => list_state.select_previous(),
KeyCode::Char('q') | KeyCode::Esc => break Ok(()),
_ => {}
}
}
}
}
})
}
/// Render the UI with various lists.

View File

@ -43,7 +43,7 @@ fn run(mut terminal: DefaultTerminal, size: RatatuiLogoSize) -> Result<()> {
loop {
terminal.draw(|frame| render(frame, size))?;
if event::read()?.is_key_press() {
return Ok(());
break Ok(());
}
}
}

View File

@ -16,28 +16,22 @@
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::text::{Line, Masked, Span};
use ratatui::widgets::{Paragraph, Wrap};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
return Ok(());
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
}
}
})
}
/// Render the UI with various text.

View File

@ -16,38 +16,33 @@
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Margin, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::symbols::scrollbar::Set;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
let mut vertical = ScrollbarState::new(100);
let mut horizontal = ScrollbarState::new(100);
loop {
terminal.draw(|frame| render(frame, &mut vertical, &mut horizontal))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => vertical.next(),
KeyCode::Char('k') | KeyCode::Up => vertical.prev(),
KeyCode::Char('l') | KeyCode::Right => horizontal.next(),
KeyCode::Char('h') | KeyCode::Left => horizontal.prev(),
_ => {}
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &mut vertical, &mut horizontal))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break Ok(()),
KeyCode::Char('j') | KeyCode::Down => vertical.next(),
KeyCode::Char('k') | KeyCode::Up => vertical.prev(),
KeyCode::Char('l') | KeyCode::Right => horizontal.next(),
KeyCode::Char('h') | KeyCode::Left => horizontal.prev(),
_ => {}
}
}
}
}
})
}
/// Render the UI with vertical/horizontal scrollbars.

View File

@ -22,28 +22,19 @@ use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{RenderDirection, Sparkline};
use ratatui::{DefaultTerminal, Frame, symbols};
use ratatui::{Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
// Ensure that the animations renders at 50 FPS (GIF speed)
if !event::poll(Duration::from_secs_f64(1.0 / 50.0))? {
continue;
let frame_timeout = Duration::from_secs_f64(1.0 / 60.0); // run at 60 FPS
ratatui::run(|terminal| {
loop {
terminal.draw(render)?;
if event::poll(frame_timeout)? && event::read()?.is_key_press() {
break Ok(());
}
}
if event::read()?.is_key_press() {
return Ok(());
}
}
})
}
/// Render the UI with various sparklines.

View File

@ -16,40 +16,35 @@
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Row, Table, TableState};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
let mut table_state = TableState::default();
table_state.select_first();
table_state.select_first_column();
loop {
terminal.draw(|frame| render(frame, &mut table_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => table_state.select_next(),
KeyCode::Char('k') | KeyCode::Up => table_state.select_previous(),
KeyCode::Char('l') | KeyCode::Right => table_state.select_next_column(),
KeyCode::Char('h') | KeyCode::Left => table_state.select_previous_column(),
KeyCode::Char('g') => table_state.select_first(),
KeyCode::Char('G') => table_state.select_last(),
_ => {}
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &mut table_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('j') | KeyCode::Down => table_state.select_next(),
KeyCode::Char('k') | KeyCode::Up => table_state.select_previous(),
KeyCode::Char('l') | KeyCode::Right => table_state.select_next_column(),
KeyCode::Char('h') | KeyCode::Left => table_state.select_previous_column(),
KeyCode::Char('g') => table_state.select_first(),
KeyCode::Char('G') => table_state.select_last(),
_ => {}
}
}
}
}
})
}
/// Render the UI with a table.

View File

@ -20,32 +20,25 @@ use ratatui::layout::{Alignment, Constraint, Layout, Offset, Rect};
use ratatui::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Paragraph, Tabs};
use ratatui::{DefaultTerminal, Frame, symbols};
use ratatui::{Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
/// Run the application.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
let mut selected_tab = 0;
loop {
terminal.draw(|frame| render(frame, selected_tab))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('l') | KeyCode::Right | KeyCode::Tab => {
selected_tab = (selected_tab + 1) % 3;
let mut selection = 0;
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, selection))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break Ok(()),
KeyCode::Char('l') | KeyCode::Right => selection = (selection + 1) % 3,
KeyCode::Char('h') | KeyCode::Left => selection = (selection + 2) % 3,
_ => {}
}
KeyCode::Char('h') | KeyCode::Left => selected_tab = (selected_tab + 2) % 3,
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
_ => {}
}
}
}
})
}
/// Render the UI with tabs.

View File

@ -14,6 +14,116 @@ use ratatui_crossterm::crossterm::terminal::{
/// use [`Terminal`] and a [backend][`crate::backend`] of your choice directly.
pub type DefaultTerminal = Terminal<CrosstermBackend<Stdout>>;
/// Run a closure with a terminal initialized with reasonable defaults for most applications.
///
/// This function creates a new [`DefaultTerminal`] with [`init`] and then runs the given closure
/// with a mutable reference to the terminal. After the closure completes, the terminal is restored
/// to its original state with [`restore`].
///
/// This function is a convenience wrapper around [`init`] and [`restore`], and is useful for simple
/// applications that need a terminal with reasonable defaults for the entire lifetime of the
/// application.
///
/// # Examples
///
/// A simple example where the app logic is contained in the closure:
///
/// ```rust,no_run
/// use crossterm::event;
///
/// fn main() -> Result<(), Box<dyn std::error::Error>> {
/// ratatui::run(|terminal| {
/// loop {
/// terminal.draw(|frame| frame.render_widget("Hello, world!", frame.area()))?;
/// if event::read()?.is_key_press() {
/// break Ok(());
/// }
/// }
/// })
/// }
/// ```
///
/// A more complex example where the app logic is contained in a separate function:
///
/// ```rust,no_run
/// use crossterm::event;
///
/// type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
///
/// fn main() -> Result<()> {
/// ratatui::run(app)
/// }
///
/// fn app(terminal: &mut ratatui::DefaultTerminal) -> Result<()> {
/// const GREETING: &str = "Hello, world!";
/// loop {
/// terminal.draw(|frame| frame.render_widget(format!("{GREETING}"), frame.area()))?;
/// if matches!(event::read()?, event::Event::Key(_)) {
/// break Ok(());
/// }
/// }
/// }
/// ```
///
/// Once the app logic becomes more complex, it may be beneficial to move the app logic into a
/// separate struct. This allows the app logic to be split into multiple methods with each having
/// access to the state of the app. This can make the app logic easier to understand and maintain.
///
/// ```rust,no_run
/// use crossterm::event;
///
/// type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
///
/// fn main() -> Result<()> {
/// let mut app = App::new();
/// ratatui::run(|terminal| app.run(terminal))
/// }
///
/// struct App {
/// should_quit: bool,
/// name: String,
/// }
///
/// impl App {
/// fn new() -> Self {
/// Self {
/// should_quit: false,
/// name: "world".to_string(),
/// }
/// }
///
/// fn run(&mut self, terminal: &mut ratatui::DefaultTerminal) -> Result<()> {
/// while !self.should_quit {
/// terminal.draw(|frame| frame.render_widget("Hello, world!", frame.area()))?;
/// self.handle_events()?;
/// }
/// Ok(())
/// }
///
/// fn render(&mut self, frame: &mut ratatui::Frame) -> Result<()> {
/// let greeting = format!("Hello, {}!", self.name);
/// frame.render_widget(greeting, frame.area());
/// Ok(())
/// }
///
/// fn handle_events(&mut self) -> Result<()> {
/// if event::read()?.is_key_press() {
/// self.should_quit = true;
/// }
/// Ok(())
/// }
/// }
/// ```
pub fn run<F, R>(f: F) -> R
where
F: FnOnce(&mut DefaultTerminal) -> R,
{
let mut terminal = init();
let result = f(&mut terminal);
restore();
result
}
/// Initialize a terminal with reasonable defaults for most applications.
///
/// This will create a new [`DefaultTerminal`] and initialize it with the following defaults:

View File

@ -351,7 +351,8 @@ pub use ratatui_termwiz::termwiz;
#[cfg(feature = "crossterm")]
pub use crate::init::{
DefaultTerminal, init, init_with_options, restore, try_init, try_init_with_options, try_restore,
DefaultTerminal, init, init_with_options, restore, run, try_init, try_init_with_options,
try_restore,
};
/// Re-exports for the backend implementations.