diff --git a/examples/apps/advanced-widget-impl/src/main.rs b/examples/apps/advanced-widget-impl/src/main.rs index 616f0941..14442672 100644 --- a/examples/apps/advanced-widget-impl/src/main.rs +++ b/examples/apps/advanced-widget-impl/src/main.rs @@ -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(()) } diff --git a/examples/apps/calendar-explorer/src/main.rs b/examples/apps/calendar-explorer/src/main.rs index 6837197f..d57e0984 100644 --- a/examples/apps/calendar-explorer/src/main.rs +++ b/examples/apps/calendar-explorer/src/main.rs @@ -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), diff --git a/examples/apps/chart/src/main.rs b/examples/apps/chart/src/main.rs index 3c47d02c..7c9cb7f2 100644 --- a/examples/apps/chart/src/main.rs +++ b/examples/apps/chart/src/main.rs @@ -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 { diff --git a/examples/apps/color-explorer/src/main.rs b/examples/apps/color-explorer/src/main.rs index c2728fe3..2ad8b229 100644 --- a/examples/apps/color-explorer/src/main.rs +++ b/examples/apps/color-explorer/src/main.rs @@ -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] = [ diff --git a/examples/apps/colors-rgb/src/main.rs b/examples/apps/colors-rgb/src/main.rs index f554948b..427af60b 100644 --- a/examples/apps/colors-rgb/src/main.rs +++ b/examples/apps/colors-rgb/src/main.rs @@ -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()?; diff --git a/examples/apps/constraint-explorer/src/main.rs b/examples/apps/constraint-explorer/src/main.rs index 4ebe2ec6..c4cca16e 100644 --- a/examples/apps/constraint-explorer/src/main.rs +++ b/examples/apps/constraint-explorer/src/main.rs @@ -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() { diff --git a/examples/apps/constraints/src/main.rs b/examples/apps/constraints/src/main.rs index 89107c6b..a7441ada 100644 --- a/examples/apps/constraints/src/main.rs +++ b/examples/apps/constraints/src/main.rs @@ -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()))?; diff --git a/examples/apps/demo/src/crossterm.rs b/examples/apps/demo/src/crossterm.rs index 9b656c59..2af6b920 100644 --- a/examples/apps/demo/src/crossterm.rs +++ b/examples/apps/demo/src/crossterm.rs @@ -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)? { diff --git a/examples/apps/demo/src/termion.rs b/examples/apps/demo/src/termion.rs index ec6f0a16..6bf76a1d 100644 --- a/examples/apps/demo/src/termion.rs +++ b/examples/apps/demo/src/termion.rs @@ -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 { diff --git a/examples/apps/demo/src/termwiz.rs b/examples/apps/demo/src/termwiz.rs index a3ec63de..d2df0ead 100644 --- a/examples/apps/demo/src/termwiz.rs +++ b/examples/apps/demo/src/termwiz.rs @@ -36,7 +36,7 @@ fn run_app( ) -> Result<(), Box> { 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 diff --git a/examples/apps/demo/src/ui.rs b/examples/apps/demo/src/ui.rs index 4c22b927..f05099f1 100644 --- a/examples/apps/demo/src/ui.rs +++ b/examples/apps/demo/src/ui.rs @@ -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 diff --git a/examples/apps/flex/src/main.rs b/examples/apps/flex/src/main.rs index 98d088bb..fab36ad7 100644 --- a/examples/apps/flex/src/main.rs +++ b/examples/apps/flex/src/main.rs @@ -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 diff --git a/examples/apps/gauge/src/main.rs b/examples/apps/gauge/src/main.rs index 9310aca0..e120f1a2 100644 --- a/examples/apps/gauge/src/main.rs +++ b/examples/apps/gauge/src/main.rs @@ -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()?; diff --git a/examples/apps/hello-world/src/main.rs b/examples/apps/hello-world/src/main.rs index 63a4e742..ef1448be 100644 --- a/examples/apps/hello-world/src/main.rs +++ b/examples/apps/hello-world/src/main.rs @@ -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()? { diff --git a/examples/apps/hyperlink/src/main.rs b/examples/apps/hyperlink/src/main.rs index 9f007124..0330888e 100644 --- a/examples/apps/hyperlink/src/main.rs +++ b/examples/apps/hyperlink/src/main.rs @@ -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]. diff --git a/examples/apps/input-form/src/main.rs b/examples/apps/input-form/src/main.rs index 515ea786..a590185c 100644 --- a/examples/apps/input-form/src/main.rs +++ b/examples/apps/input-form/src/main.rs @@ -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> { + fn run(mut self, terminal: &mut DefaultTerminal) -> Result> { while self.state == AppState::Running { terminal.draw(|frame| self.render(frame))?; self.handle_events()?; diff --git a/examples/apps/minimal/src/main.rs b/examples/apps/minimal/src/main.rs index 2f307986..90134b8d 100644 --- a/examples/apps/minimal/src/main.rs +++ b/examples/apps/minimal/src/main.rs @@ -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> { + 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(); + }) } diff --git a/examples/apps/modifiers/src/main.rs b/examples/apps/modifiers/src/main.rs index a4bf7021..1e596959 100644 --- a/examples/apps/modifiers/src/main.rs +++ b/examples/apps/modifiers/src/main.rs @@ -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 = result::Result>; 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) { diff --git a/examples/apps/mouse-drawing/src/main.rs b/examples/apps/mouse-drawing/src/main.rs index 29d046e0..9a988520 100644 --- a/examples/apps/mouse-drawing/src/main.rs +++ b/examples/apps/mouse-drawing/src/main.rs @@ -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))?; diff --git a/examples/apps/panic/src/main.rs b/examples/apps/panic/src/main.rs index ea71ad68..a61af2b7 100644 --- a/examples/apps/panic/src/main.rs +++ b/examples/apps/panic/src/main.rs @@ -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()); } diff --git a/examples/apps/popup/src/main.rs b/examples/apps/popup/src/main.rs index 244df311..58d7e3ad 100644 --- a/examples/apps/popup/src/main.rs +++ b/examples/apps/popup/src/main.rs @@ -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: +//! - and +//! - +//! +//! 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); diff --git a/examples/apps/scrollbar/src/main.rs b/examples/apps/scrollbar/src/main.rs index 7c9d7571..0dc652c7 100644 --- a/examples/apps/scrollbar/src/main.rs +++ b/examples/apps/scrollbar/src/main.rs @@ -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 { diff --git a/examples/apps/table/src/main.rs b/examples/apps/table/src/main.rs index 40b1ab75..0b154d03 100644 --- a/examples/apps/table/src/main.rs +++ b/examples/apps/table/src/main.rs @@ -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))?; diff --git a/examples/apps/todo-list/src/main.rs b/examples/apps/todo-list/src/main.rs index f442548e..524c3c8e 100644 --- a/examples/apps/todo-list/src/main.rs +++ b/examples/apps/todo-list/src/main.rs @@ -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() { diff --git a/examples/apps/user-input/src/main.rs b/examples/apps/user-input/src/main.rs index 4b6a925d..00b57ae8 100644 --- a/examples/apps/user-input/src/main.rs +++ b/examples/apps/user-input/src/main.rs @@ -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))?; diff --git a/examples/apps/weather/src/main.rs b/examples/apps/weather/src/main.rs index 688dd990..a266418f 100644 --- a/examples/apps/weather/src/main.rs +++ b/examples/apps/weather/src/main.rs @@ -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 = (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, -} +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. diff --git a/examples/apps/widget-ref-container/src/main.rs b/examples/apps/widget-ref-container/src/main.rs index a2603d56..bf852bd1 100644 --- a/examples/apps/widget-ref-container/src/main.rs +++ b/examples/apps/widget-ref-container/src/main.rs @@ -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) { diff --git a/ratatui-widgets/examples/barchart-grouped.rs b/ratatui-widgets/examples/barchart-grouped.rs index 6ace2e94..b2a30c76 100644 --- a/ratatui-widgets/examples/barchart-grouped.rs +++ b/ratatui-widgets/examples/barchart-grouped.rs @@ -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. diff --git a/ratatui-widgets/examples/barchart.rs b/ratatui-widgets/examples/barchart.rs index 61415fe7..63562e42 100644 --- a/ratatui-widgets/examples/barchart.rs +++ b/ratatui-widgets/examples/barchart.rs @@ -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. diff --git a/ratatui-widgets/examples/block.rs b/ratatui-widgets/examples/block.rs index a9444dda..7a1a7fac 100644 --- a/ratatui-widgets/examples/block.rs +++ b/ratatui-widgets/examples/block.rs @@ -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. diff --git a/ratatui-widgets/examples/calendar.rs b/ratatui-widgets/examples/calendar.rs index 3a285688..f33417db 100644 --- a/ratatui-widgets/examples/calendar.rs +++ b/ratatui-widgets/examples/calendar.rs @@ -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. diff --git a/ratatui-widgets/examples/canvas.rs b/ratatui-widgets/examples/canvas.rs index d4b94a26..1456908d 100644 --- a/ratatui-widgets/examples/canvas.rs +++ b/ratatui-widgets/examples/canvas.rs @@ -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. diff --git a/ratatui-widgets/examples/chart.rs b/ratatui-widgets/examples/chart.rs index ceef4b8d..d9a4548e 100644 --- a/ratatui-widgets/examples/chart.rs +++ b/ratatui-widgets/examples/chart.rs @@ -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. diff --git a/ratatui-widgets/examples/gauge.rs b/ratatui-widgets/examples/gauge.rs index 8e58c901..668d5b43 100644 --- a/ratatui-widgets/examples/gauge.rs +++ b/ratatui-widgets/examples/gauge.rs @@ -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. diff --git a/ratatui-widgets/examples/line_gauge.rs b/ratatui-widgets/examples/line_gauge.rs index 5ff97733..a58a1354 100644 --- a/ratatui-widgets/examples/line_gauge.rs +++ b/ratatui-widgets/examples/line_gauge.rs @@ -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()?; diff --git a/ratatui-widgets/examples/list.rs b/ratatui-widgets/examples/list.rs index 2fc406aa..23881cef 100644 --- a/ratatui-widgets/examples/list.rs +++ b/ratatui-widgets/examples/list.rs @@ -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. diff --git a/ratatui-widgets/examples/logo.rs b/ratatui-widgets/examples/logo.rs index 4113d947..cfd88607 100644 --- a/ratatui-widgets/examples/logo.rs +++ b/ratatui-widgets/examples/logo.rs @@ -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(()); } } } diff --git a/ratatui-widgets/examples/paragraph.rs b/ratatui-widgets/examples/paragraph.rs index 3a46bd03..df36cdba 100644 --- a/ratatui-widgets/examples/paragraph.rs +++ b/ratatui-widgets/examples/paragraph.rs @@ -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. diff --git a/ratatui-widgets/examples/scrollbar.rs b/ratatui-widgets/examples/scrollbar.rs index f0409ba6..f987bf02 100644 --- a/ratatui-widgets/examples/scrollbar.rs +++ b/ratatui-widgets/examples/scrollbar.rs @@ -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. diff --git a/ratatui-widgets/examples/sparkline.rs b/ratatui-widgets/examples/sparkline.rs index 55f029ab..3aa3119c 100644 --- a/ratatui-widgets/examples/sparkline.rs +++ b/ratatui-widgets/examples/sparkline.rs @@ -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. diff --git a/ratatui-widgets/examples/table.rs b/ratatui-widgets/examples/table.rs index 3cb46e68..7ff5ae79 100644 --- a/ratatui-widgets/examples/table.rs +++ b/ratatui-widgets/examples/table.rs @@ -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. diff --git a/ratatui-widgets/examples/tabs.rs b/ratatui-widgets/examples/tabs.rs index e8ccc20e..12f6fd2c 100644 --- a/ratatui-widgets/examples/tabs.rs +++ b/ratatui-widgets/examples/tabs.rs @@ -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. diff --git a/ratatui/src/init.rs b/ratatui/src/init.rs index 7ca3c41c..3312175f 100644 --- a/ratatui/src/init.rs +++ b/ratatui/src/init.rs @@ -14,6 +14,116 @@ use ratatui_crossterm::crossterm::terminal::{ /// use [`Terminal`] and a [backend][`crate::backend`] of your choice directly. pub type DefaultTerminal = Terminal>; +/// 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> { +/// 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 = std::result::Result>; +/// +/// 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 = std::result::Result>; +/// +/// 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: 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: diff --git a/ratatui/src/lib.rs b/ratatui/src/lib.rs index 80e1a23e..879cd5c8 100644 --- a/ratatui/src/lib.rs +++ b/ratatui/src/lib.rs @@ -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.