UI, Terminal & Graphic¶
UI¶
The UI module provides base types shared by all UI backends (Terminal and Graphic).
Two backends extend AppEvent:
- Terminal — TUI via ANSI escape codes. Zero external dependencies.
- Graphic — GUI via SDL2 + Nuklear. Requires SDL2. Resolved automatically via
pkg-config.
Types¶
AppEvent¶
Algebraic event type driving the renders loop. Abstract — extend via TerminalAppEvent or GraphicAppEvent.
type AppEvent is
Draw(state Value)
| Tick(state Value)
| KeyDown(key Key)
| KeyUp(key Key)
| MouseDown(button Integer, x Integer, y Integer)
| MouseUp(button Integer, x Integer, y Integer)
| Scroll(dx Integer, dy Integer)
| MousePos(x Integer, y Integer)
| Resize(width Integer, height Integer)
| Exit(state Value)
Key:[Lookup]¶
Bidirectional keyboard mapping. Resolve by variant (Key:Escape), by name (Key:"escape"), or by keycode (Key:27).
Keycodes above 1000 are virtual codes for non-printable keys; printable characters use ASCII directly.
Color:[Lookup]¶
Bidirectional color mapping. Resolve by variant (Color:Red), by name (Color:"red"), by ANSI SGR code (Color:31), or by hex (Color:"#FF0000").
TextStyle:[Lookup]¶
Bidirectional text style mapping. Resolve by variant (TextStyle:Bold), by name (TextStyle:"bold"), or by ANSI SGR code (TextStyle:1).
Variants: Reset, Bold, Dim, Italic, Underline, Inverse, Strikethrough.
Position¶
Screen position struct with x (column) and y (row) fields.
The renders and listens Verbs¶
renders and listens are language-level async verbs for building event-driven UI applications.
renders — event loop with state¶
Owns the event loop, manages mutable state, dispatches events to match arms.
renders app(listeners List<Listens>)
event_type MyEvent
state_init MyState(initial_value)
from
Draw(state) => // render UI
Exit(state) => Unit
Annotations:
event_type— algebraic event type (must extendTerminalAppEventorGraphicAppEvent)state_init— initial state value (type inferred)
Rules:
- First parameter must be
List<Listens>(registered event translators) - Body must be a single match expression with an
Exitarm - No return type —
rendersalways returnsUnit - Backend auto-detected:
TerminalAppEventchain = TUI,GraphicAppEventchain = GUI
Self-dispatch: Calling a variant name inside a match arm (e.g. Draw(state)) re-enqueues that event. This is how state mutations trigger redraws.
listens — event translator¶
Translates raw platform events (e.g. KeyDown) into app-specific events. Called inline by renders when a matching raw event arrives.
listens on_key(attached_verbs List<Attached>) MyEvent
event_type KeyDown
state_type MyState
from
Key:Escape => Exit(state)
Key:"k" => Draw(state)
_ => Tick(state)
Annotations:
event_type— which raw event type this listener handles (e.g.KeyDown)state_type— type of the renders state (gives read/write access tostate)
Rules:
- Return type is the app event type (what gets dispatched in
renders) - Match arms use
LookupPatternfor key matching:Key:Escape,Key:"k",Key:Space - State mutations in
listensare propagated back torenders
Terminal¶
TUI primitives via ANSI escape codes. Zero external dependencies.
Types¶
TerminalAppEvent¶
Extends AppEvent for the terminal backend. Use as the base type for your app's event type:
Functions¶
| Verb | Name | Signature | Description |
|---|---|---|---|
validates | terminal | () | Check if stdout is an interactive terminal |
outputs | raw | () | Enable raw mode (disable echo, line buffering) |
outputs | cooked | () | Restore normal terminal mode |
outputs | terminal | (text String) | Write text at current cursor position |
outputs | terminal | (x Integer, y Integer, text String) | Write text at screen position |
outputs | clear | () | Clear screen and reset cursor to (0,0) |
outputs | cursor | (x Integer, y Integer) | Move cursor to position |
derives | size | () Position | Get terminal dimensions (cols, rows) |
derives | ansi | (name String) String | Convert a Color or TextStyle name to ANSI escape sequence |
Example: Terminal Todo List¶
A full interactive TUI application with keyboard navigation, state management, and event translation.
module TodoApp
Terminal types TerminalAppEvent outputs terminal clear raw cooked
Math derives max min
UI types Key
Sequence derives set remove
type TodoItem is
text String
done Boolean
pos Integer
type TodoState is
items List<TodoItem>
selected Integer
type TodoEvent is TerminalAppEvent
| ToggleDone
| AddItem
| RemoveItem
transforms checkboxes(item TodoItem, state TodoState) String
trusted: "For now"
from
prefix as String = match item.pos == state.selected
true => "> "
false => " "
check as String = match item.done
true => "[x]"
false => "[ ]"
f"{prefix}{check} {item.text}"
renders interface(registered_listens_verbs List<Listens>)
event_type TodoEvent
state_init TodoState([TodoItem("Learn Prove", false, 0),
TodoItem("Build a TUI app", false, 1),
TodoItem("Ship it", false, 2)], 0)
from
Draw(state) =>
clear()
terminal(0, 0, "=== Todo List ===")
terminal(0, 1, "j/k: navigate | space: toggle | d: delete | ESC: quit")
terminal(0, 2, "")
each(state.items, |item| terminal(0, item.pos + 3, checkboxes(item, state)))
Tick(state) => Draw(state)
ToggleDone =>
item as TodoItem = state.items[state.selected]
state.items = set(state.items, state.selected,
TodoItem(item.text, !item.done, item.pos))
Draw(state)
RemoveItem =>
state.items = remove(state.items, state.selected)
state.selected = max(0, state.selected - 1)
Draw(state)
Exit(state) =>
cooked()
Unit
_ => Unit
listens on_key(attached_verbs List<Attached>) TodoEvent
event_type KeyDown
state_type TodoState
from
Key:Escape => Exit(state)
Key:"k" =>
state.selected = max(0, state.selected - 1)
Draw(state)
Key:"j" =>
state.selected = min(state.items.length - 1, state.selected + 1)
Draw(state)
Key:Space => ToggleDone()
Key:"d" => RemoveItem()
_ => Tick(state)
main()
from
raw()
interface([on_key])&
Graphic¶
GUI primitives via SDL2 + Nuklear immediate-mode rendering. Continuous vsync-paced rendering at ~60fps.
Prerequisites: SDL2 must be installed. The compiler resolves paths via pkg-config.
Types¶
GraphicAppEvent¶
Extends AppEvent with GUI-specific platform events:
Functions¶
| Verb | Name | Signature | Description |
|---|---|---|---|
outputs | window | (title String, width Integer, height Integer) | Create/begin a named window. Call once per frame before widgets |
outputs | button | (label String) Boolean | Clickable button. Returns true on click frame |
outputs | label | (text String) | Static text label |
outputs | text_input | (label String, value String) String | Editable text field. Returns current contents |
outputs | checkbox | (label String, checked Boolean) Boolean | Checkbox. Returns current state |
outputs | slider | (label String, min Float, max Float, value Float) Float | Horizontal slider. Returns current value |
outputs | progress | (current Integer, max Integer) | Progress bar |
outputs | quit | () | Close window and exit render loop |
Example: GUI Counter¶
A minimal GUI application with a button that increments a counter.
module Counter
Graphic types GraphicAppEvent outputs window button label
Types creates string
type CounterState is
count Integer
type CounterApp is GraphicAppEvent
renders app(listeners List<Listens>)
event_type CounterApp
state_init CounterState(0)
from
Draw(state) =>
window("Counter", 400, 300)
label(f"Count: {string(state.count)}")
match button("Increment")
true =>
state.count += 1
Draw(state)
false => Draw(state)
Tick(state) => Draw(state)
Exit(state) => Unit
main()
from
app([])&