# 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