Apps
Forms
# Forms — Visual Deft form designer
A Visual Basic / Glade-style designer for Deft form documents, running in the
GL hosts (`retro`, `hydra`). It edits a `%FormDoc{}` document — a cell-grid
canvas of frame/group containers and ui-kit controls — binds control events
to function names, and emits a generated form module plus implementation
stubs.
## Launching
```sh
retro app deft/forms # empty designer
````
Open an existing form from the workspace File menu (Ctrl+O). A host app
can pre-open a form with `[tui/set "vd-file" <path>]` before launching.
## Workflow
1. Drag a container (Frame / Column / Row) from the floating Toolbox
onto the canvas. Leaf controls can only be dropped inside a container.
2. Drag controls (Button, Text Input, Checkbox, List, …) into a
container. Drag a placed control to move it; drag its yellow handles to
resize. Arrow keys nudge (Shift = 5 cells); Delete removes.
3. Select a control to edit it in the Inspector: geometry (x/y/w/h),
type-specific properties, and events.
4. Bind events: pick an event from the "add event…" dropdown. Each event
maps to a handler function name (auto-named, editable).
5. Link code: set a code file in the inspector (or `Link…`), then `Stub`
to create/append stubs. `Edit` opens the file in the editor.
6. Save (Ctrl+S, or File → Save) emits the form module; Preview
(F5, or File → Preview) opens it in a split panel.
The designer never imports the edited form (deftypes are write-once per
runtime); loading is data-only and preview launches a fresh app runtime.
## The generated form module
Saving `<name>.frm.dft` writes one module containing:
- a `deftype <Name>` interface with every bound handler as a `:fn` field,
- the `%FormDoc{}` document block (the editable source of truth),
- `import "<code path>" as impl` plus a `def handlers` table binding each
handler name to the implementation's function,
- the app-facing API:
| Function | Purpose |
|---|---|
| `[form/draw $ctx $bounds]` | draw the form in a panel cell rect |
| `[form/handle $ev]` | dispatch the current input event into the form |
| `[form/set id prop value]` | set a control field (`:text :value :checked :visible …`) |
| `[form/get id prop]` | read a control field |
| `[form/focus id]` | focus a control (text inputs) |
| `[form/get-panel]` | standalone panel (used by preview / `retro app form.frm.dft`) |
`draw` remembers its last bounds, so `handle` needs no arguments when the app
always draws the form in the same place.
## Writing the implementation
The code file declares `implements "<Name>"` — every handler the form needs
must exist or the form fails to load with a conformance error naming the
missing export. Handlers receive a per-event map and update controls with the
forms library (the current form instance is bound during dispatch):
import "deft/ui/forms" as forms implements "Login" def on-submit {|ev| set user [forms/get-prop "user" :value] [forms/set-prop "status" :text [str "hello " $user "!"]] nil }
Event map fields: `:type :control`, `:id`, `:event` (`:on-click` …),
`:value` (the widget's callback value when it has one), `:args`, `:form`.
Which events a control offers (the inspector's "add event…" list):
buttons fire `:on-click`; checkboxes / radio groups / sliders fire
`:on-change`; text inputs fire `:on-change` and `:on-submit`; lists fire
`:on-select` (click / arrow moves, `:value` = index) and `:on-change`
(Enter); dropdowns are managed by the runtime — clicking the header
toggles the list and picking an item updates the header and closes it
(an `:on-change` handler is still notified).
`forms/set-prop-on` / `forms/get-prop-on` / `forms/focus-control-on` take an
explicit instance (its handle is `ev~form` or the module's `inst` value);
`forms/current-instance` returns the instance during dispatch.
## Hosting a form in an app
The app drives the form — it decides where the form lives:
import "./login.frm.dft" as login defimpl MyApp Panel { draw {|ctx| [login/draw $ctx %{ :x 1 :y 2 :w 50 :h 14 }] } on-event {|ev| [login/handle $ev] $self } }
See `examples/app.dft` for a complete host (its header + a form in a
sub-rect).
## File format
The form file is data + generated code; the `%FormDoc{}` block is parsed with
`deft/parse` (comments elsewhere in the file are ignored). The document is
deterministic (sorted keys, fmt-canonical output on designer saves) so form
files diff cleanly. `forms/parse` / `forms/emit` in `deft/ui/forms` are the
reader/writer used by the designer and tests.
## The palette component
The floating Toolbox is `deft/ui/components/palette` — a reusable GL
pixel-space palette: `[pal/palette ...]`, `[pal/draw $ctx $p]`,
`[pal/hit $p px py]` → `:title | :close | :pick | :none`, plus
`pal/move`, `pal/at`, `pal/hover`, `pal/sync-cells`, `pal/layout` and
`pal/item` (glyph, optional group, custom `:draw` icons). Apps that want a
graphical toolbox can reuse it directly.
## Limits (v1)
- GL hosts only (retro/hydra); ANSI hosts render the pixel chrome as a
no-op.
- Control geometry is cell-snapped (the designer itself works in pixels).
- No undo/redo, copy/paste, multi-select or z-order controls yet.
- Containers cap at 64 children (widget-tree engine limit).