tui-realm

repository·main·Indexed 21 days ago

https://github.com/veeso/tui-realm

A tui-rs framework for building terminal user interfaces using ratatui, inspired by React's component-based model and Elm's message-passing architecture. Version 4.1.0 includes the tuirealm_derive procedural macro for automating Component trait delegation, a standard library (tui-realm-stdlib) for rendering utilities, and specialized components such as tui-realm-textarea and tui-realm-treeview.

Tokens
48.6K
Snippets
128
Records
182
Agent score
76%

What's inside tui-realm

  1. Introduction to tui-realm

    main

    tui-realm is a framework built on top of ratatui designed for implementing stateful terminal user interfaces (TUIs). It follows an event-driven architecture inspired by React and Elm (specifically The Elm Architecture or TEA).

    Core Architecture Pattern

    The application lifecycle follows a continuous loop: -> Event -> Msg -> Update -> View ->

    Key Features

    • Event-driven: Uses an Event -> Msg approach. Ports (event listeners like stdin or HTTP clients) produce Events, which are forwarded to Components to produce Messages. Messages then trigger logic in your application Model.
    • Component-based: Uses a Component concept (similar to React) where each component represents a UI instance with its own State and Properties.
    • Automated Management: The View automatically manages component mounting/unmounting and handles focus management, ensuring only one AppComponent is active at a time.
    • Adaptable: Provides advanced concepts like custom Ports and Subscriptions for complex use cases.
  2. What is tui-realm?

    main

    tui-realm is a framework for ratatui designed to simplify terminal user interface (TUI) implementation. It combines architectural patterns from React and Elm:

    • React-inspired: Components are reusable, featuring properties and state management.
    • Elm-inspired: Components communicate with the UI engine via Messages and Events, allowing for update routines to handle logic.
    • View Management: A View manages the lifecycle of components, including mounting/unmounting, focus management, and event forwarding.

    It is an event-driven framework that aims to reduce boilerplate and provide easy management of single focus and application states.

  3. Understand the difference between Component and AppComponent

    main

    In tui-realm, UI elements are split into two distinct layers to allow for reusable, hardware-agnostic components that can be used in any application.

    Component

    • Purpose: A generic, reusable UI primitive (e.g., a Label, an Input field, or a Checkbox).
    • Scope: Agnostic of application-specific logic, events, or keybindings.
    • Responsibility: Handles rendering (via view), properties, and internal state. It reacts to Commands (Cmd) and returns a Command Result (CmdResult).
    • Key Rule: Components should be distributed in libraries (like tui-realm-stdlib) and should not know about your application's specific Message or UserEvent types.

    AppComponent

    • Purpose: An application-specific implementation that wraps a Component to give it meaning within your app (e.g., a UsernameInput wrapping a generic Input component).
    • Scope: Tied to your specific application's logic.
    • Responsibility: Consumes application-specific Events (Event<UserEvent>) and produces application-specific Messages (Msg). It acts as the bridge that translates hardware/user events into commands for the underlying component.
    • Key Rule: AppComponents are unique to your application. You should not reuse the same AppComponent implementation for different purposes; instead, wrap different generic Components in new AppComponents.
    // Conceptual relationship
    pub struct UsernameInput {
        component: Input, // Input implements `Component`
    }
    
    impl Component for UsernameInput { /* Passthrough to self.component */ }
    impl AppComponent<MyMsg, MyUserEvent> for UsernameInput { ... }
  4. Common properties for all tui-realm components

    main

    Every component in tui-realm-stdlib supports two universal properties managed by the View:

    • Attribute::Display(AttrValue::Flag): If set to False, the component will not be rendered.
    • Attribute::Focus(AttrValue::Flag): Indicates whether the component is active. This property is automatically handled by the View.

    Additionally, custom attributes are prefixed with $ in documentation. These are accessed via tui_realm_stdlib::props::$KEY_NAME using Attribute::Custom.

  5. How focus and the focus stack work in tui-realm

    main

    The View manages focus as a state to determine which component receives interaction Events.

    Focus Rules

    1. Only one component can have focus at a time.
    2. Events are forwarded to the component that currently owns focus.
    3. A component gains focus via the active() method.
    4. When a component is focused, its Attribute::Focus property is set to AttrValue::Flag(true); otherwise, it is false.
    5. Focus Stack: When a component gains focus, the previously active component is pushed onto a Stack.
    6. Unmounting: If the focused component is unmounted, the most recent component in the Focus stack becomes active.
    7. Blurring: Using the blur() method makes a component lose focus and returns focus to the most recent component in the stack, but the blurred component is not pushed onto the stack.

    Focus State Transitions

    ActionFocusFocus StackComponents
    Active AAA, B, C
    Active BBAA, B, C
    Active CCB, AA, B, C
    BlurBAA, B, C
    Active CCB, AA, B, C
    Active AAC, BA, B, C
    Umount ACBB, C
    Mount DCBB, C, D
    Umount BCC, D
    Blur(err)CC, D
  6. How to update the tree in TreeView

    main

    The tree data is stored within the TreeView component structure rather than in props. There are two primary ways to update it:

    1. Remounting the component

    If you need to update the tree during an update routine, the most reliable way is to remount the component from scratch. To avoid losing state, ensure your constructor sets both the initial_node and the tree using .with_tree(tree) and .initial_node(id).

    2. Updating via the on method

    When implementing the Component trait, you can use the mutable reference to the component within the on() method to manipulate the tree directly. This is useful for responding to UserEvents from a Port.

    Available methods on the component instance:

    • pub fn tree(&self) -> &Tree: Returns a reference to the tree.
    • pub fn tree_mut(&mut self) -> &mut Tree: Returns a mutable reference to the tree.
    • pub fn set_tree(&mut self, tree: Tree): Replaces the current tree with a new one.
    • pub fn tree_state(&self) -> &TreeState: Gets a reference to the current tree state.
  7. Key architectural changes in tui-realm 1.x

    main

    When migrating from 0.x to 1.x, expect the following changes in core concepts:

    • Props System: Replaced the previous limited PropPayload or HashMap implementation with a map of Attribute and AttrValue, functioning similarly to CSS.
    • Messages (Msg): In 0.x, messages were static and predefined. In 1.x, you define your own custom messages specific to your application logic.
    • Events: Event and KeyEvent now support Eq because they wrap underlying crossterm structures.
    • Backends: crossterm is no longer mandatory; the framework now supports other backends like termion.
    • Application & View: The View concept is now managed through an application object that you hold in your program.
    • Component Trait: The old Component has been replaced. You must now implement the Component trait for all elements in your UI, while MockComponent is used for other purposes (replacing the old PropsBuilder pattern).
  8. Understand the tui-realm crate ecosystem

    main

    tui-realm is organized as a monorepo containing several specialized crates. Depending on your needs, you may want to include one or more of these in your project:

    • tuirealm: The core crate containing all basic framework functionality.
    • tui-realm-stdlib: A standard library providing convenience wrappers for standard ratatui widgets.
    • tuirealm_derive: A helper library used to derive the Component trait for components that primarily delegate functions to an underlying component.
    • tui-realm-treeview: A specific implementation of a Tree Component.
    • tui-realm-textarea: A specific implementation of a Text Area Component.
  9. How the Model, Update, and View interact

    main

    The tui-realm architecture follows a pattern similar to Elm but uses in-place mutation for the update cycle.

    • Model: Holds the application state, including the Application instance, the TerminalAdapter, and flags like quit or redraw.
    • Update: A function (often part of the Model) that receives a Msg, modifies the Model in-place, and returns an optional next Msg. This is where business logic and component orchestration (like changing focus via app.active()) happen.
    • View: A function that renders the current state. It uses the Application::view method to delegate rendering to specific component IDs within a layout.
  10. How Subscriptions work in tui-realm

    main

    A Subscription is a ruleset that instructs the Application to forward events to components even when they are not currently active (not focused).

    By default, tui-realm only forwards events to the active component. Subscriptions allow you to:

    • Make components listen for specific events (like remote data updates) regardless of focus.
    • Create 'invisible' components that handle global logic (like listening for <ESC> to close the app) so individual components don't have to repeat that logic.

    A subscription requires two parts to be satisfied for an event to be forwarded:

    1. Event Clause: Determines the type of event (e.g., Tick, Keyboard).
    2. Sub Clause: Determines the conditions under which the event is forwarded (e.g., Always, IsMounted).

    Note: If a component is already active, it will not receive the event a second time via a subscription.

    // Conceptual structure of a Subscription
    pub struct Sub<ComponentId, UserEvent>(EventClause<UserEvent>, Arc<SubClause<ComponentId>>);
  11. Define Component IDs and Application Messages

    main

    tui-realm 1.x requires explicit types for component identification and application logic:

    1. Component IDs: Instead of constants or strings, define an enum that implements Debug, Eq, PartialEq, Clone, and Hash. This enum serves as the unique identifier for your components.
    2. Messages (Msg): Define an enum representing the events your Model or View care about. Note that messages should represent high-level application events, not low-level component inputs.
    #[derive(Debug, Eq, PartialEq, Clone, Hash)]
    pub enum Id {
        AddressInput,
        PasswordInput,
        ProtocolRadio,
        GlobalListener,
    }
    
    #[derive(Debug, PartialEq)]
    pub enum Msg {
        AppClose,
        FormSubmit,
        ProtocolChanged(FileTransferProtocol),
        None,
    }
  12. Split Model from View in tui-realm 1.x

    main

    In 1.x, you can no longer hold the View inside your data structure. You must separate your application into a Model (holding state and Context) and an Application (holding the Model and the View logic). The Application manages the lifecycle, while the Model implements the Update trait.

    struct Activity {
        model: Model,
        application: Application<Id, Msg, NoUserEvent>,
    }
    
    struct Model {
        context: Context,
        protocol: FileTransferProtocol,
        address: String,
    }
    
    impl Update for Model {
        // ...
    }