SuperLightTUI

repository·main·Indexed 41 days ago

https://github.com/subinium/superlighttui

A lightweight, ergonomic immediate-mode TUI library for Rust (v0.25.0) designed for high performance and low dependency overhead. It uses a declarative grammar to define UIs in a single closure called every frame, supporting full-screen, inline, and WASM/browser runtimes. Features include a flexbox-based layout engine, a variety of stateless and stateful widgets, and support for async messages, bidirectional text, and syntax highlighting.

Tokens
114.9K
Snippets
286
Records
455
Agent score
51%

What's inside SuperLightTUI

  1. Overview of SuperLightTUI (SLT) capabilities

    main

    SuperLightTUI (SLT) is a Rust-based TUI framework designed with an immediate-mode (closure-based) rendering model. It distinguishes itself from competitors like Ratatui or Textual through several specialized feature sets:

    • AI-Native Widgets: Built-in support for AI workflows via widgets like StreamingText, ToolApproval, ContextBar, and StreamingMarkdown.
    • Advanced Animation: A dedicated system supporting 5 types (Tween, Spring, Keyframes, Sequence, Stagger) with 9 easings.
    • Responsive Layouts: Support for 5 responsive breakpoint tiers (xs, sm, md, lg, xl) applied to 35 properties, similar to CSS media queries.
    • Rich Media & Protocols: Built-in support for image protocols (HalfBlock, Kitty, Sixel), syntax highlighting (15 languages via tree-sitter), and terminal protocols like OSC 8 links, OSC 52 clipboard, and OSC 11 color schemes.
    • Developer Experience: Features a minimal "3-line start" API, error boundaries (error_boundary()) to catch panics without crashing the app, and a comprehensive testing suite (TestBackend, EventBuilder, proptest).
  2. Navigate the SuperLightTUI documentation

    main

    The documentation is organized by user intent rather than file name. Use the following paths to find what you need:

    For rapid application development

    If you want to build an app quickly, follow this sequence:

    1. Quick Start: Installation, first app, layout, and input.
    2. Widgets: A categorized map of available widgets.
    3. Patterns: Common composition patterns for building complex UIs.
    4. Examples: A collection of runnable example applications.

    For backend and runtime development

    If you are working on the runtime, backends, or testing infrastructure:

    1. Backends: Custom backends, inline mode, static output, and the frame() method.
    2. Testing: Using TestBackend, EventBuilder, and snapshots.
    3. Debugging: Troubleshooting clipping, focus, and frame behavior.
    4. Architecture: Understanding the module map and frame lifecycle.

    For AI-assisted development

    Use the AI Guide for the fastest path when using AI coding agents or builders.

    For library contributors

    If you intend to contribute to the SuperLightTUI core:

    1. Design Principles: API rules and constraints.
    2. Architecture: Module maps and lifecycle.
    3. Testing: Contribution standards for testing.
    4. Changelog: Reviewing recent changes.
  3. Understand the SuperLightTUI Module Map

    main

    The project is organized into several functional domains. When building custom widgets or extending the library, use this map to locate relevant logic:

    • context/: The core engine. Contains core.rs (the Context struct), widget.rs (the Widget trait), container.rs (layout builders), and runtime.rs (hooks, focus, and notifications).
    • widgets/: State management for various widget types (e.g., input.rs, collections.rs, selection.rs).
    • layout/: The layout engine, including flexbox.rs for size/gap resolution and render.rs for clipping and viewport culling.
    • style/: Visual configuration, including color.rs and theme.rs (which includes 10 presets via ThemeBuilder).
    • terminal/: Backend implementations for Terminal (full-screen/alternate mode) and InlineTerminal (renders below cursor).
    • anim/: Animation primitives like Tween, Spring, and Keyframes.
    • chart/: Specialized logic for rendering charts and axes.
  4. Understand the SuperLightTUI immediate-mode mental model

    main

    SuperLightTUI (SLT) operates on an immediate-mode paradigm. This means you do not manage a persistent tree of components or a message-passing system (like Elm's Model/Update/View). Instead, you provide a closure to slt::run that is executed every single frame.

    Key Concepts:

    • Frame-based Rendering: Your closure describes what the UI looks like right now. The library handles layout (flexbox), diffing, and writing only changed cells to the terminal.
    • State Management:
      • Application State: Store your data in normal Rust variables or structs outside the run closure and mutate them within the closure.
      • Widget-local State: Use ui.use_state(|| ...) hooks for state that should persist specifically for a widget.
    • Control Flow: Use standard Rust if and for statements to conditionally show or repeat UI elements.
    • Interactivity: Interactive widgets return a Response object. You check properties like .clicked, .hovered, .changed, .focused, or .rect to react to user input.
    • Hit-testing: Note that Response.rect uses the layout from the previous frame. Therefore, Response.rect is only reliable from the second frame onward.
  5. Understand SLT Naming Conventions (Micro Tier)

    main

    SuperLightTUI (SLT) uses a 'shape-encoding' naming convention where the structure of a name (verb/noun/adjective, length, prefix, suffix) indicates its functional category. This allows developers and AI assistants to infer the purpose of an identifier (e.g., whether it performs an action, returns a value, or configures a builder) just by looking at its name.

    Key categories include:

    • Verbs (Actions): Perform side effects or state transitions (e.g., ui.quit()).
    • Nouns (Getters): Return values without side effects (e.g., ui.theme()).
    • Adjectives (Builder Modifiers): Configure containers (e.g., .bordered()).
    • Constructors: Create new instances (e.g., Type::new()).
  6. How to use Shared Context with provide and use_context

    main

    To avoid passing read-only application data (like Theme, tick, or user info) through every single function signature, use the provide and use_context pattern. This implements a scoped dependency injection system.

    Usage Rules

    • ui.provide(value, |ui| ...): Publishes a value to the context stack. The value is only available to widgets rendered within the provided closure. Once the closure returns, the value is popped off the stack.
    • ui.use_context::<T>(): Retrieves the nearest provided value of type T. Warning: This will panic if no provider for T is found on the current stack.
    • ui.try_use_context::<T>(): Safely attempts to retrieve a value of type T. Returns Option<&T>. Use this in helper functions that might be called outside of a provider (e.g., in tests or isolated components).
    • Best Practice: Use provide/use_context for read-only data. For data that requires mutation (like &mut MyState), pass the state explicitly as a parameter.
    • Type Requirements: provide boxes the value as dyn Any, so the type T must satisfy the 'static lifetime.
    #[derive(Clone, Copy)]
    struct AppCtx { theme: Theme, tick: u64, user: &'static str }
    
    fn main() -> std::io::Result<()> {
        slt::run(|ui: &mut Context| {
            let ctx = AppCtx { theme: *ui.theme(), tick: ui.tick(), user: "subin" };
    
            ui.provide(ctx, |ui| {
                render_header(ui);
            });
        })
    }
    
    fn render_header(ui: &mut Context) {
        // Retrieves the context provided in main
        let ctx = *ui.use_context::<AppCtx>();
        ui.text(format!("hi, {}", ctx.user));
    }
  7. Implement Dark Mode adaptation

    main

    SuperLightTUI provides several ways to make your UI adapt to light and dark terminal themes.

    1. Container-level Overrides

    On a ContainerBuilder, you can specify styles that only apply when dark mode is active using .dark_bg() and .dark_border_style().

    2. Inline Adaptation with light_dark()

    Use ui.light_dark(light_color, dark_color) to pick a color dynamically based on the current theme. This is useful for text colors that need to remain readable.

    3. ContainerStyle with Dark Mode

    Include .dark_bg() or .dark_border_style() directly in your const ContainerStyle definitions to ensure containers adapt automatically.

    use slt::{Border, Color, Style};
    
    // Container-level adaptation
    slt::run(|ui| {
        ui.bordered(Border::Rounded)
            .bg(Color::Rgb(248, 250, 252))           // light mode bg
            .dark_bg(Color::Rgb(30, 30, 46))          // dark mode bg
            .dark_border_style(Style::new().fg(Color::Rgb(88, 91, 112)))
            .col(|ui| {
                ui.text("Adapts to dark/light mode");
            });
    
        // Inline adaptation
        let text_color = ui.light_dark(
            Color::Rgb(15, 23, 42),   // dark text for light mode
            Color::Rgb(205, 214, 244) // light text for dark mode
        );
        ui.text("Adaptive").fg(text_color);
    });
  8. How the SLT Rendering Pipeline Works

    main

    SuperLightTUI uses a 4-stage DFS (Depth-First Search) pipeline to keep the public API simple while maintaining performance:

    1. Command Recording: ui.*() calls record intents into a flat list of commands. No tree building or layout happens here.
    2. Tree Building & Flexbox: The engine processes commands to build a layout tree and calculates positions using Flexbox.
    3. Collection: A single DFS pass collects interaction and feedback data (hit areas, focus rects, scroll regions, etc.).
    4. Render & Diff: The engine renders to a back buffer, compares it with the previous frame, and flushes only the changed ANSI attributes to the terminal.

    This architecture allows for immediate-mode simplicity (no manual App traits or Model/Update/View boilerplate) while providing automatic interaction (hover, click, focus) and high performance via double buffering.

  9. Extending SuperLightTUI with Custom Components and Backends

    main

    While the public API is compact, SLT provides escape hatches for advanced use cases:

    • Custom Widgets: Implement the Widget trait to create reusable high-level UI components.
    • Custom Backends: Implement the Backend trait if you need to target non-terminal environments, use an external event loop, or create an embedded runtime. You can drive the UI by calling frame().
    • Headless Testing: Use TestBackend to perform headless rendering verification and stable interaction testing without a physical terminal.
  10. Understand the core design philosophy: Predictability

    main

    The fundamental goal of SuperLightTUI (SLT) is Predictability. A developer or AI assistant should be able to predict the next method call without consulting documentation.

    SLT achieves this by mixing styles (like ratatui's builder pattern and egui's immediate-mode interaction) while maintaining strict rules to ensure the mixing is itself predictable.

    Key Predictability Patterns:

    • Builder Pattern: Use chained setters for configuration (e.g., ui.gauge(0.6).width(w).color(c) instead of passing all arguments into a single function).
    • Family Boundaries: While different widget families may have different shapes, the boundary between families must be obvious to prevent 'surprise on next-widget' calls.
  11. The default shape of a real SLT app

    main

    For medium-sized applications, SuperLightTUI (SLT) recommends a specific architectural pattern to maintain readability as complexity grows. Instead of putting all logic inside a single closure, follow this structure:

    • App State: Use a plain Rust struct to hold your application's domain state.
    • Entry Point: Use a single top-level run(...) or run_with(...) closure.
    • Modular Rendering: Break large panels or screens into separate render_* helper functions.
    • Local State: Use hooks only for truly local, persistent UI state that doesn't belong in the main app struct.
  12. How one-frame delay feedback works

    main

    Because SuperLightTUI is an immediate-mode UI, widget positions are not known until the layout phase, which occurs after your user closure has finished executing.

    To handle interaction (like hover or click), the engine uses a one-frame delay. Layout-computed data (like hit_areas or scroll_bounds) is stored in prev_* fields on the Context and is made available to your closure in the next frame.

    The Flow:

    • Frame N: Your closure runs $\rightarrow$ Layout is computed $\rightarrow$ hit_areas are stored in Context.
    • Frame N+1: Your closure reads prev_hit_areas $\rightarrow$ Widget makes interaction decisions.

    At 60 FPS, this delay is imperceptible to the user but allows for efficient immediate-mode rendering.