# TUI Overview The Deft binary drives two TUI hosts: - `retro` — a panelised development environment (editor, git, file manager, terminal, AI chat, system monitor). Inspired by Emacs and Blender. Uses a native rendering backend (OpenGL where available, ANSI fallback otherwise). - `retra` — functionally identical to `retro` but pure ANSI, so it works over bare SSH. Both hosts run the same Deft apps, expose the same `tui/*` stdlib functions, and follow the same architectural model: each panel runs an app in its own OS thread with an isolated runtime. ## The Panel Model A TUI workspace is a tree of panels — rectangular regions of the screen, each running one app. The workspace holds the panels; the binary renders them. ``` Workspace ├── Panel 1 (editor) ─── editor.dft, its own runtime/thread ├── Panel 2 (files) ─── files.dft, its own runtime/thread └── Panel 3 (terminal) ─── shell.dft, its own runtime/thread ```` Each panel has: - A numeric ID (used by `tui/find-panel`, focus/resize, etc.). - An optional string tag (set via `[tui/tag ...]` or `[tui/auto-tag ...]`). - A focused state — one panel always has keyboard focus. - A root widget tree that handles events and draws. - An AppThread driving its event loop on a dedicated OS thread. ## Panel Management
# Split the focused panel
tui/split :vertical                          # or :horizontal

# Focus by index, direction, or tag/id
tui/focus 2
tui/focus-right
tui/focus-id "editor"

# Find a panel by tag
set ed-id [tui/find-panel "editor"]

# Tag the calling panel
tui/tag "diff"
tui/auto-tag "editor"                        # auto-assigns editor, editor2, ...

# Close the focused panel
tui/close

# Resize
tui/resize 0.05                              # +/- a small amount
## App Lifecycle Every panel runs an app that implements the `AppLifecycle` protocol:
defimpl MyApp AppLifecycle {
    on-init {||
        @doc "Called once when the app starts. Set up state, subscribe to topics."
        # ...
    }

    on-suspend {||
        @doc "Called when the panel loses focus or the workspace backgrounds."
        # ...
    }

    on-resume {||
        @doc "Called when the panel regains focus."
        # ...
    }

    on-deinit {||
        @doc "Called once when the app is shutting down. Clean up resources."
        # ...
    }
}
The framework calls these automatically; you implement whichever ones your app needs. ## The Widget Tree A panel's content is a tree of widgets — tagged maps implementing the relevant protocols. Build interfaces with UI Composition (`deft/ui/compose`) and register the root ref once on `on-init`; the framework drives events and rebuilds from then on:
from "deft/ui/compose" import children, props
from "deft/ui/components/column" import column
from "deft/ui/components/text-input" import text-input
from "deft/ui/components/select-list" import select-list

def widget-tree [ref [column --direction :vertical]]

defimpl MyApp AppLifecycle {
    on-init {||
        [tui/set-widget-tree $widget-tree]     # opts into framework dispatch
        [rebuild]
    |
    }
}

def rebuild {||
    set input
        [text-input --id "search" --placeholder "Search…" --flex 1]
        |> [props --on-change on-query]

    set results
        [select-list --items $_results]

    [reset! $widget-tree
        [column --direction :vertical --gap 1]
        |> [children @{ $input $results }]]
}
Once the widget tree is registered, the framework runs the target phase (focused widget gets key events; hit-test dispatches mouse events) before the panel's typed event methods. See UI Composition for construction, [Widgets and Protocols](widgets-and-protocols) for the component library, and Event Dispatch for how events flow through a panel. ## Drawing The framework calls a panel's `draw` method (from the Zig-registered `Panel` protocol) every frame. Within draw, use `draw/*` stdlib functions to render:
defimpl MyApp Panel {
    draw {|ctx|
        @doc "Render the panel into the draw context."
        draw/fill-bg $ctx %{ :r 0 :g 0 :b 0 }
        draw/text $ctx 0 0 "Hello, TUI!"
    |
    }
}
For pixel-precise graphics (canvas, images, 3D), see Graphics. ## State and Ref Pattern Most apps hold their state in an ref at module scope:
def state [ref %{
    :counter 0
    :items @{}
    :filter :all
|}]

def bump {||
    [swap! $state { |s|
       [assoc $s :counter ($s~counter + 1)]
    
    }]
}
The ref is shared across closures and the framework's protocol methods. Reads via `deref` (or `$state` shorthand); updates via `swap!` or `reset!`. See Stdlib. ## Cross-Panel Communication Three mechanisms: - `pub/*` — broadcast events any panel can subscribe to. The default for loose coupling. See Pubsub. - `@rpc` + `rpc` — direct call to a function exposed by another panel's runtime (addressed by the panel's app/tag name). Returns a Future. Runtime-level, so it works in every host. See RPC. - `config/*` — shared, persistent preferences. See Config. ## Common TUI Stdlib functions ### Workspace | Function | Purpose | |---|---| | `[tui/split :direction]` | Split focused panel | | `[tui/close]` / `[tui/close $id]` | Close a panel | | `[tui/focus $n]` / `[tui/focus-left]` / `[tui/focus-id $id]` | Move focus | | `[tui/resize $delta]` | Resize focused panel | | `[tui/zoom]` | Toggle zoom (maximise/restore) | | `[tui/panels]` / `[tui/groups]` | Enumerate layout | | `[tui/focused-panel]` | Focused panel's tag | | `[tui/self]` | Calling panel's own id | ### Tagging | Function | Purpose | |---|---| | `[tui/tag $tag]` | Set a string tag on calling panel | | `[tui/auto-tag $base]` | Auto-assign a unique instance tag | | `[tui/find-panel $tag]` | Look up panel id by tag | ### Panel content | Function | Purpose | |---|---| | `[tui/mount $panel-id $instance]` | Mount a Deft component | | `[tui/mount-widget $panel-id $widget]` | Mount a widget tree | | `[tui/set-widget-tree $ref]` | Register tree for framework dispatch | | `[tui/panel-label $id $text]` | Set a plain text label | | `[tui/panel-status-bar $id $left ?$right]` | Status bar | ### Focus | Function | Purpose | |---|---| | `[tui/focus-widget "id"]` | Set widget-level focus | | `[tui/focused-widget-id]` | Get focused widget id | | `[tui/cycle-focus :forward $tree]` | Tab-style cycling | | `[tui/clear-focus]` | Drop widget focus | ### Launching apps | Function | Purpose | |---|---| | `[tui/launch $panel-id $name $path]` | Launch an app in a panel | | `[tui/adopt $panel-id $name]` | Reuse a docked app instance | See `[info/sig ...]` or the `*-doc` text on each stdlib function for full argument lists. ## Lifecycle of a Panel App ``` 1. Workspace starts panel → AppThread spawns → New runtime created → File loaded → AppLifecycle/on-init called 2. Panel takes focus → AppLifecycle/on-resume called 3. User types → Framework dispatches to focused widget → Bubbles to panel's on-key (if not consumed) 4. User switches panels → AppLifecycle/on-suspend called on old → AppLifecycle/on-resume called on new 5. Panel closed → AppLifecycle/on-deinit called → AppThread exits, runtime destroyed ```` ## Where To Go Next - UI Composition — build and compose widget trees - Widgets and Protocols — the component library and protocols - Event Dispatch — focus, target+bubble, key routing - Dialogs — themed input/menu/file dialogs - Graphics — pixel-precise canvas, image, 3D