# 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. Most apps register a tree once
on `on-init` and let the framework drive events:
defimpl MyApp AppLifecycle { on-init {|| set state [atom %{ :input [ui/text-input %{ :id "search" :placeholder "Search..." }] :results @{} :selected nil |}] tui/set-widget-tree $state # opts into framework dispatch | } }
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 [Widgets and Protocols](widgets-and-protocols) for the full
widget pattern, and [Event Dispatch](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](graphics).
## State and Atom Pattern
Most apps hold their state in an atom at module scope:
def state [atom %{ :counter 0 :items @{} :filter :all |}] def bump {|| swap! $state { |s| assoc $s :counter ($s~counter + 1) | } }
The atom is shared across closures and the framework's protocol
methods. Reads via `deref` (or `$state` shorthand); updates via
`swap!` or `reset!`. See [Language: Stdlib](../01-Language/stdlib).
## Cross-Panel Communication
Three mechanisms:
- **`pub/*`** — broadcast events any panel can subscribe to. The
default for loose coupling. See [Pubsub](../02-Platform/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](../02-Platform/rpc).
- **`config/*`** — shared, persistent preferences. See
[Config](../02-Platform/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 $atom]` | 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
- [Widgets and Protocols](widgets-and-protocols) — the widget pattern
- [Event Dispatch](event-dispatch) — focus, target+bubble, key routing
- [Dialogs](dialogs) — themed input/menu/file dialogs
- [Graphics](graphics) — pixel-precise canvas, image, 3D