Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ownership in Frame::render_widget #591

Closed
cylewitruk opened this issue Oct 24, 2023 · 1 comment
Closed

Ownership in Frame::render_widget #591

cylewitruk opened this issue Oct 24, 2023 · 1 comment

Comments

@cylewitruk
Copy link

cylewitruk commented Oct 24, 2023

Is there a reason why widget: W needs to be owned here? Causing me some headaches when trying to keep different screens in the same field as I can't go from a Box<dyn Widget> to an owned/sized Widget... (i.e. keeping a currently active interchangeable Widget in app state).

widget.render(area, self.buffer);

Maybe I'm going about things completely wrong and there's another way to achieve this, but nothing I've found during all of today anyway 😓

@joshka
Copy link
Member

joshka commented Oct 24, 2023

Historical reasons to some extent. See the following for some more details:

TL;DR: widget was designed for tui-rs to not be retained. It was intentionally designed as if it was a parameter object created again on each Frame and so should generally be lightweight. We've thought a bit about changing this to pass by ref instead, but that might break TUI / Ratatui app and widget in the wild. It could be time to revisit this however.

Put in terms of your problem, there can't be an active Widget because on each frame the Widget is created anew. You'll currently need to store something else for the current state. There are a few options for this that are useful:

  • implement your own Component trait instead of Widget and store your sized components
  • if there can be only one current screen, then use an enum with a variant for each state
  • your component / enum can either just have a method that draws to the frame (skipping the Widget impl), or have a method that creates a widget to represent the component.

For the method that creates a widget you could even fairly easily make the render part of the component by creating a small intermediary like:

struct DynamicWidget<'a> {
    render_fn: Box<dyn FnOnce(Rect, &mut Buffer) + 'a>,
}

impl<'a> DynamicWidget<'a> {
    fn new<F: FnOnce(Rect, &mut Buffer) + 'a>(render_fn: F) -> Self {
        Self {
            render_fn: Box::new(render_fn),
        }
    }
}

impl Widget for DynamicWidget<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        (self.render_fn)(area, buf);
    }
}

struct SomeComponent {
    greeting: String,
}

impl SomeComponent {
    fn to_widget<'a>(&'a self) -> DynamicWidget {
        DynamicWidget::new(|area, buf| self.render(area, buf))
    }

    fn render(&self, area: Rect, buf: &mut Buffer) {
        Paragraph::new(self.greeting.as_str()).render(area, buf);
    }
}

It's also worth taking a look at https://ratatui.rs/concepts/application-patterns/index.html for some ideas about how to handle this as well as looking at how some larger examples structure their code (I was particularly inspired by EDMA when building toot-rs and yazi took some of those ideas a bit further. Working out how to handle multiple views was a difficult thing for me to understand when I first started playing with Ratatui, and there's more examples of this out there (see also @kdheepak's ratatui async template as one option).

P.S. I'm going to convert this to a discussion rather than an issue.

@ratatui-org ratatui-org locked and limited conversation to collaborators Oct 24, 2023
@joshka joshka converted this issue into discussion #592 Oct 24, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants