I have had a short discussion with the author of the tui-react create and it seems like it was born out of frustration with the tui-rs create’s apparent enforced immutability during the rendering path. Since StatefulWidget existed back then, I immediately wondered why StatefulWidget wasn’t enough.
Turns out that tui-react exists in large part to support a pretty cool disk utilization visualization program: GitHub - Byron/dua-cli: View disk space usage and delete unwanted data, fast.
And so I took a whack at porting dua off of tui-react’s custom Terminal API (that essentially does away with the Widget based rendering calls in favor of passing a Backend around directly).
To my surprise I found that it was pretty easy to pass mutable data to application level rendering code even using Widget as opposed to StatefulWidget. The answer, as almost always, is to introduce another level of indirection. The Widget passed to ratatui can be any struct, and those structs may hold mutable references to application data.
The result is what I called a FunctionWidget, reproduced here without code comments:
struct FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
render: F,
}
impl<F> FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
fn new(function: F) -> FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
FunctionWidget { render: function }
}
}
impl<F> Widget for FunctionWidget<F>
where
F: FnOnce(Rect, &mut Buffer),
{
fn render(self, area: Rect, buf: &mut Buffer) {
(self.render)(area, buf);
}
}
And here is how you might use it:
terminal.draw(|frame| {
frame.render_widget(
FunctionWidget::new(|area, buf| {
window.render(props, area, buf, cursor);
}),
frame.size(),
);
})?;
Above, props is moved into and owned by the closure, window is a mutable reference to application level object that doesn’t implement any ratatui trait, area and buf are the usual values passed from the Frame to the render call, and cursor is a mutable reference expected by the rest of the custom rendering code used by the dua program.
Does this pattern seem generally useful?
The tui-react create is essentially a soft fork of the Terminal API, born out of a perceived inflexibility in the API. I wonder if dua’s author would have ever bothered to create tui-react had FunctionWidget been part of tui-rs.