ratatui / ratatui-macros

Macros for simplifying boilerplate for creating UI using Ratatui.
https://ratatui.rs
MIT License
24 stars 2 forks source link

feat: add main proc macro for terminal init/restore #72

Closed orhun closed 3 weeks ago

orhun commented 3 weeks ago

Makes it possible to write this code:

use ratatui::{
    crossterm::event::{self, Event},
    text::Text,
    Frame,
};

#[ratatui_macros::main]
fn main(mut terminal: Terminal<CrosstermBackend<Stdout>>) {
    loop {
        terminal
            .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
            .expect("failed to draw frame");
        if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
            break;
        }
    }
}

This was an idea brought up in my FrOSCon talk, I liked it and decided to give it a try.

joshka commented 3 weeks ago

I think this would seem like an improper use of macros that would cause more confusion than benefits.

This pattern seems like it's akin to tokio's main macro. Tokio requires this because it's translating an async function into a sync one by rewriting the async part to be an async closure that runs on the runtime. But we're not really doing anything like that in this. A strong downside is that it seems to make using color_eyre or anyhow more difficult

Instead of this, I'd suggest making the following method that accepts a Fn / FnMut / FnOnce like:

fn with_terminal<F, T, E>(f: F) -> Result<T, E>
where F: Fn(Terminal) -> Result<T, E> {
    let terminal = ratatui::init();
    let result = f(terminal);
    ratatui::restore();
    result
}

Then:

fn main() -> Result<()> {
    color_eyre::install()?;
    ratatui::with_terminal(|terminal| {
        loop {
            terminal
                .draw(|frame: &mut Frame| frame.render_widget(Text::raw("Hello World!"), frame.area()))
                .expect("failed to draw frame");
            if matches!(event::read().expect("failed to read event"), Event::Key(_)) {
                break;
            }
        }
    });
}

But given that we've just introduced ratatui::init, I'd suggest letting it breathe a little before introducing more complexity.

A question I'd use for when to use a macro or not is "is this a case where the macro code has a significant benefit over plain rust code?". I think the answer is probably "no" here.

orhun commented 3 weeks ago

Yeah, I agree. Adding a macro before we have more complexity seems unnecessary. It was a cool experiment nevertheless :)

We can come back to this again in the future if needed.

Would love to get @kdheepak's input on this as well.

kdheepak commented 3 weeks ago

I like the with_terminal approach but agree about exploring writing code withratatui::init for a bit.

orhun commented 3 weeks ago

Let's give it some time then :)