The "basic" example does not render correctly (I'm using macOS Terminal.app). It looks like this:
Text Label
> Slider [0 ââââââââââââââââ 100]: 50
Checkboxes
[ ] Checkbox 1
[ ] Checkbox 2
[ ] Checkbox 3
ââââââââââŽ
â Submit â
â°âââââââââ¯
Press TAB to switch, SPACE to toggle, ESC to exit
It's caused by printing \n
with crossterm::enable_raw_mode()
. There is a note in the documentation that notes the newline character will not be processed.
It can be fixed with the queue!()
macro:
for line in ui.render().split('\n') {
queue!(stdout, style::Print(line), cursor::MoveToNextLine(1))?;
}
A good suggestion to start with is using cargo clippy
. It will provide its own suggestions that range from minor cleanups to performance improvements. (Note that not everyone likes the default lints, and they can be configured to your tastes.)
One ergonomic idea is allowing Ui::add()
to accept any type that implements Into<WidgetType>
. This will help callers by not forcing the widgets to be first wrapped in the WidgetType
. I'll show you what the example will look like when it's done:
use tui::*;
fn main() -> std::io::Result<()> {
let mut ui = UI::new();
ui.add(Label::new("Text Label"));
ui.add(Slider::new("Slider", 0, 100, 50));
ui.add(Label::new("Checkboxes"));
ui.add(Checkbox::new("Checkbox 1"));
ui.add(Checkbox::new("Checkbox 2"));
ui.add(Checkbox::new("Checkbox 3"));
ui.add(Button::new("Submit"));
ui.add(Label::new(
"Press TAB to switch, SPACE to toggle, ESC to exit",
));
run_ui(ui)?;
Ok(())
}
And how you do this is a two-step process. First, add the trait constraint:
impl UI {
pub fn add(&mut self, widget: impl Into<WidgetType>) {
let widget = widget.into();
if self.focused_index.is_none() && widget.is_focusable() {
self.focused_index = Some(self.widgets.len());
}
self.widgets.push(widget);
}
}
Second, add From
implementations for each of your widget types:
impl From<Label> for WidgetType {
fn from(value: Label) -> Self {
Self::Label(value)
}
}
impl From<Button> for WidgetType {
fn from(value: Button) -> Self {
Self::Button(value)
}
}
impl From<Checkbox> for WidgetType {
fn from(value: Checkbox) -> Self {
Self::Checkbox(value)
}
}
impl From<Slider> for WidgetType {
fn from(label: Slider) -> Self {
Self::Slider(label)
}
}
It's a little bit of boilerplate in the library, but it removes boilerplate on the caller side, and that's usually a more valuable tradeoff.
I haven't done a full review. There are some relatively minor things I would change, like making tui::run_ui()
a method of Ui
, and recommending blocking event reads or async EventStream
over polling periodically . And ultimately, taking over the event loop is probably not really what you want to do (though it is easier when getting started).