photon-ui 0.2.0

Blazing fast minimal TUI
Documentation

photon-ui

A blazing fast, minimal terminal UI framework for Rust.

Photon UI is a lightweight, high-performance TUI library built on crossterm. It features a custom differential renderer that only redraws what changed, ANSI-aware text wrapping, OSC 8 hyperlink support, terminal image rendering (Kitty and iTerm2 protocols), a Cassowary constraint-based layout engine, and a component-based architecture.

Features

  • Differential renderer — Only updates cells that changed between frames, minimizing terminal output and improving performance.
  • Component-based architecture — Build UIs by composing simple, reusable Component trait implementations.
  • Built-in components — 27 components including text, input, editor, buttons, tables, trees, sidebars, tabs, modals, panels, progress bars, markdown, and image widgets.
  • Constraint-based layout — Split terminal space using the Cassowary solver via Layout, Constraint, and Rect.
  • Dual editing modes — Both Emacs and vim keybindings for Input and Editor components, switchable at runtime.
  • Terminal image support — Render images inline using the Kitty graphics protocol or iTerm2 inline images.
  • ANSI-aware text handling — Properly measures and wraps text containing ANSI escape sequences, Unicode, and grapheme clusters.
  • Overlays and modals — Position floating content with Anchor-based constraints and modal dialogs that capture input.
  • Focus management — The TUI runtime handles focus cycling and routes input events to the focused component.
  • Zero unnecessary dependencies — Core functionality relies only on crossterm, unicode-width, unicode-segmentation, pulldown-cmark, base64, thiserror, and kasuari.

Quick start

Add photon-ui to your Cargo.toml:

[dependencies]
photon-ui = "0.1"

Build a simple app:

use photon_ui::{Component, TUI, TestTerminal};
use photon_ui::components::Text;

let mut tui = TUI::new(Box::new(TestTerminal::new(80, 24)));
tui.mount(Box::new(Text::new("Hello, world!", 0, 0)));
tui.render_frame().unwrap();

For a real application, use ProcessTerminal to drive the actual terminal:

use photon_ui::{TUI, Event};
use photon_ui::terminal::{ProcessTerminal, Terminal};
use photon_ui::components::{Text, Input};
use std::time::Duration;

fn main() -> std::io::Result<()> {
    let mut term = ProcessTerminal::new();
    term.start()?;

    let mut tui = TUI::new(Box::new(term));
    tui.mount(Box::new(Text::new("Name:", 0, 0)));
    tui.mount(Box::new(Input::new()));

    loop {
        tui.render_frame()?;

        if crossterm::event::poll(Duration::from_millis(100))? {
            let event = match crossterm::event::read()? {
                crossterm::event::Event::Key(key) => Event::Key(key),
                crossterm::event::Event::Resize(w, h) => Event::Resize(w, h),
                other => continue,
            };
            tui.handle_input(&event);
        }
    }
}

Components

Component Description
Text Static text with optional horizontal and vertical padding.
TruncatedText Text that truncates with an ellipsis when wider than the container.
Box A container with configurable vertical padding and optional background styling.
Spacer Empty vertical space.
Input Single-line text input with Emacs and vim modes, kill-ring, and undo.
Editor Multi-line text editor with Emacs and vim modes, kill-ring, and undo.
SelectList Scrollable list with keyboard navigation and selection.
SettingsList Toggle list for boolean settings.
Loader Animated spinner with optional colored message.
CancellableLoader Spinner that can be cancelled via Ctrl+C.
Markdown Renders CommonMark markdown (headings, bold, italic, inline code, lists).
ImageWidget Displays images inline via Kitty or iTerm2 graphics protocols.
Button Clickable button with primary, ghost, cream, text, and dark variants.
Breadcrumbs Hierarchical breadcrumb navigation trail.
Container Generic container for composing child components.
Div Layout container that splits space among children with borders and padding.
Divider Horizontal or vertical divider line, optionally with a label.
Header Page header with title and right-aligned actions.
Modal Modal dialog overlay with title, border, and focus capture.
Panel Panel container with optional rounded, thin, thick, or double borders.
ProgressBar Progress bar with optional label and percentage display.
Sidebar Sidebar navigation with icons and keyboard selection.
StatusBar Status bar with left, center, and right segments.
Table Table with sortable columns, row selection, and keyboard navigation.
Tabs Tab bar with keyboard navigation between tabs.
TreeView Collapsible tree view with expand/collapse and keyboard navigation.

Architecture

Component trait

Every visible element implements the Component trait:

pub trait Component {
    fn render(&self, width: u16) -> Result<Rendered, RenderError>;
    fn render_rect(&self, rect: Rect) -> Result<Rendered, RenderError>;
    fn handle_input(&mut self, event: &Event) -> InputResult;
    fn wants_key_release(&self) -> bool;
    fn as_focusable(&self) -> Option<&dyn Focusable>;
    fn as_focusable_mut(&mut self) -> Option<&mut dyn Focusable>;
}
  • render produces the lines of text (and optional cursor or image commands) for the current frame.
  • render_rect renders into a specific rectangular area, used by layout-aware components.
  • handle_input receives events when the component has focus.
  • Focusable components participate in the tab order managed by TUI.

Renderer

The Renderer tracks the previous frame and computes a minimal set of cursor movements and writes needed to update the terminal. This avoids clearing the screen or redrawing unchanged content. It automatically selects one of three strategies:

  • FirstRender — outputs all lines (assumes a clean alternate screen).
  • FullRedraw — clears the screen and redraws everything (used on resize).
  • Diff — computes the first and last changed line and only rewrites that region.

Layout engine

The layout system uses the kasuari Cassowary constraint solver. A Layout splits a Rect into sub-rects based on Constraint values (Length, Min, Max, Percentage, Ratio, Fill) with configurable Direction, Flex, Margin, and Spacing.

use photon_ui::layout::{Constraint, Direction, Rect};
use photon_ui::layout::layout::Layout;

let layout = Layout::vertical([
    Constraint::Length(3),   // header
    Constraint::Min(10),     // main content
    Constraint::Length(1),   // status bar
]);
let areas = layout.split(Rect::new(0, 0, 80, 24));

TUI runtime

TUI owns the terminal backend, manages a stack of components, handles focus cycling (Tab / Shift+Tab), and supports overlays positioned with Anchor and OverlayConstraints. Modal dialogs capture all input until dismissed with Esc.

Input modes

Both Input and Editor support Emacs and vim editing styles:

Emacs Vim Action
Ctrl+A 0 or ^ Move to start of line
Ctrl+E $ Move to end of line
Ctrl+K D Kill to end of line
Ctrl+Y p Yank (paste)
Ctrl+W daw Kill word
Ctrl+F / Ctrl+B l / h Move forward / backward
i / Esc Enter / exit insert mode

Enable vim mode at construction time or toggle it at runtime with set_vim_mode_enabled.

Running the demo

The included demo showcases every component across multiple pages:

cargo run --example demo

Controls:

Key Action
15 Switch demo page
Tab / Shift+Tab Cycle focus
q or Ctrl+C Quit