Closed cylewitruk closed 11 months ago
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:
Component
trait instead of Widget
and store your sized componentsFor 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.
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 aBox<dyn Widget>
to an owned/sizedWidget
... (i.e. keeping a currently active interchangeableWidget
in app state).https://github.com/ratatui-org/ratatui/blob/c597b87f72bc578cdd65d533506db5bdc49af608/src/terminal.rs#L600
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 :sweat: