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).