termit 0.3.1

Terminal UI over crossterm
Documentation

Welcome to the mound! Termit implements TUI - yet another terminal UI.

Tl;DR:

//! This is an almost minimal hello world termit example.
//!
//! There is no input processing, no async, no looping. Just print some nice screen.
use std::io;
use termit::prelude::*;
fn main() -> io::Result<()> {
    // The Termit facilitator:
    // * Use the Config to set up your terminal experience.
    // * Initialize Termit with some `Write` output.
    let mut termit = Config::default().init_termit(io::stdout())?;

    // The TUI tree:
    // * Canvas sets the default style for the contained widget.
    // * "Hello World!" - &str/String - is a widget.
    // * We just place the text in the middle with some placement constraints.
    let mut ui = Canvas::new(
        Color::DarkBlue,
        "Hello World!"
            .place()
            .width(6)
            .height(2)
    );

    // Render and update:
    termit.update(&mut (), &[], &mut ui)
    //                  |   |
    //   the mutable    |   |
    //   application    |   |
    // model goes here -/   |
    //                       \- this is an empty 
    //                           set of events
}

hello world

For a little more sophisticated example with input event loop and custom widgets, cargo run --example color. A simplistic Delta.Chat client inspired by the dreamer project is in examples/dech. There are more examples.

Here's a replay of the editor example:

asciicast

Why?

Back to basics, less line drawing, more content and elegance.

Usage

In your Cargo.toml:

[dependencies]
termit = "*"

Please note that the API is still evolving. Feedback is most welcome. Ergonomic static UI initialization and dynamic UI updates are somewhat at odds with each other.

Concepts

Termit relies heavily on crossterm. It actually re-exports it as crosterm structs are part of the Termit model.

Widgets maintain their own state

Unlike in some other TUI libs, widgets are not ephemeral unless you want to. You can implement a stateful Widget easily:

use termit::prelude::*;
struct UpdateCounter {
    state: usize
}
impl<M> Widget<M> for UpdateCounter {
    fn update(
        &mut self,
        _model: &mut M,
        _input: Option<&Event>,
        screen: &mut Screen,
        painter: &Painter,
    ) -> Window {
        self.state +=1;
        painter.paint(&format!("{}", self.state), screen)
    }
}

This is usefult if your widget tracks multiple internal state values which are irrelevant to the application as a whole. For instance, a text box editor doesn't need to polute application state with cursor position.

You may also recreate the whole or part of the UI tree in each update cycle if you want to. Then your widgets must keep their state in the model.

Widgets update the app model directly

Widgets can manipulate the application state model directly.

However, these changes should be near instant. More intensive work (io, network) should be sent to the application as a command and processed independently and ideally asynchronously. This is done in the dech example.

use termit::prelude::*;
struct AppState {
    state: usize
}
struct UpdateCounter;
impl Widget<AppState> for UpdateCounter {
    fn update(
        &mut self,
        model: &mut AppState,
        _input: Option<&Event>,
        screen: &mut Screen,
        painter: &Painter,
    ) -> Window {
        model.state +=1;
        painter.paint(&format!("{}", model.state), screen)
    }
}

The widget generally accesses the whole app state mutably. It should be possible, though, to create a widget adapter that will focus on a subset of the model or another model alltogether.

Simple widgets, composition and decoration

Avoid creating complex widgets. Instead, add features by wrapping other widgets in decorators or by composing a complex subtree from other widgets.

Look at the [crate::widget::WidgetBinder] for instance which allows us to load and save any widget, any property of the widget or even replcace it altogether on update. It is a decorator implemented for all impl Widget's with .bind_with():

# use termit::prelude::*;
# fn sample () {
struct AppState {
    banner: String
}
let bound_widget = Canvas::new(Color::DarkBlue,String::new())
        .bind_with(
            |w,m:&AppState| w.content =  m.banner.clone(),
            |w,m:&mut AppState| m.banner =  w.content.clone()
        );
# }

The same with [crate::widget::WidgetPropertyBinder]:

# use termit::prelude::*;
# fn sample () {
struct AppState {
    banner: String
}
let bound_widget = Canvas::new(Color::DarkBlue,String::new())
        .bind_property(|w| &mut w.content, |m: &mut AppState| &mut m.banner);
# }

Company

These are probably some more advanced options.