4 min read

Getting started in vtui

Table of Contents
index

Considering the wealth of rendering frameworks and TUI applications that exist in the wild, terminal UIs should be easy to make. But once you start trying to write your own, you quickly start running into the complexity of having to roll your own rendering loop, state handling, focus system, scrolling framework, etc. - none of which relate to what most application developers want to make in the first place.

I created a framework named vtui which solves these problems by providing an expressive and minimal foundation that provides these basic features for application developers. Here are some of the useful features that might be of interest:

Event system

Instead of embedding domain logic directly into the rendering loop like in most TUIs, you can do this in vtui:

#[component]
fn App(c: Component) -> Node {
    // vtui's event loop will directly dispatch to this event on KeyPress
    c.listen::<KeyPress>(|event| {
        do_some_stuff();
    });

    // Listeners occur in the same order they are written, so you never need to worry about
    // race conditions and can simply reason that do_some_stuff always run on `KeyPress`
    c.listen::<KeyPress>(|event| {
        if event.key == KeyCode::Char('q') {
            event.request_shutdown();
        }
    });

    vtui! {}
}

Also, you never need to worry about event loop optimizations like mouse event dropping on your own. vtui handles scheduling so your applications can run at 60 fps without jitters or lag.

Child system

You can finally make composable applications that scale without massive match expressions. Also, it automatically handles layouts for you!

#[component]
fn App(c: Component) -> Node {
    vtui! {
        // Flow is an optional expression which will change the axis used by this layout.
        Flow::Horizontal,
        Sidebar { Measure::Percentage(0.1) },
        Body { Measure::Percentage(0.9) },
    }
}

#[component]
fn Sidebar(c: Component) -> Node {
    ...
}

#[component]
fn Body(c: Component) -> Node {
    ...
}

First-class ratatui widget support

ratatui is an extremely well-written library with a large ecosystem of pre-made widgets. vtui allows you to take advantage of this as the draw loop is directly based off ratatui:

#[component]
fn App(c: Component) -> Node {
    c.draw(|canvas| {
        let block = Block::default().borders(Borders::ALL);
        let paragraph = Paragraph::new("Hello world!").block(block);
        canvas.render_widget(canvas.area(), paragraph);
    });

    vtui! {}
}

Infinite canvas with scrolling

Scrolling can often become an afterthought feature which has to get hacked into an application. As a result, vtui solves this from the beginning by implementing a highly optimized infinite canvas that will never panic even if you write out of the terminal buffer. Some informal testing has shown me that vtui’s infinite canvas can render a scrolling view of 1,000,000 simple ratatui widgets without breaking a sweat:

#[component]
fn App(c: Component) -> Node {
    c.draw(|canvas| {
        let block = Block::default().borders(Borders::ALL);
        let paragraph = Paragraph::new("Hello world!").block(block);

        for _ in 0..1_000_000 {
            let area = canvas.area();
            let area = area.with_offset(0, i * area.height);
            canvas.render_widget(area, &paragraph);
        }
    });

    vtui! {}
}

The future

There are a lot of other features planned:

  • Focus system with priorities
  • Component props implementation
  • Constraint-based layout system
  • Async integration + background task threadpool

If you use vtui in any way, I would love to hear about it through my email, hello@joshprk.me!