Teek::UI::WidgetDSL Module
The build surface: ui.<widget> methods that APPEND nodes to the
Document tree. They never touch Tk - widgets become live only when
the realizer runs at realize.
Names are deliberately Tk-free (the litmus test: if decoding a name needs Tk knowledge, the name is wrong) - see the design sketch for the full vocabulary rationale.
Mixed into Session rather than living on a separate accessor, so the
DSL reads as ui.button(...), not ui.widgets.button(...).
Included classes must provide @document (a Document), @stack
(an Array of Node, current-parent stack seeded with
@document.root), @scope_stack (an Array of Scope, current-scope
stack seeded with [Scope::TOP_LEVEL] - see #component), @vars
(an Array of Var), and @images (an Array of Image) - Session
sets all five up in initialize. They must also provide
#build_open? (a predicate the tree-mutating methods below check
via #raise_if_closed! - true before the initial realize and again
for the duration of an #add block, false otherwise).
Instance Methods ↑
[](name)
Look up a named widget declared in the CURRENT scope: at the top
level outside any #component, that’s everything built outside
one; inside a component’s own block, only that component’s own
names - a sibling component’s (or the top level’s) same-named
node is never found this way, and vice versa. See #component.
Parameters
nameSymbol
Returns Handle, nil
box(name nil, **opts, &block)
box is a bare alternate spelling of panel - same node type, so
the realizer only ever has to know about :panel.
cell(row:, col:, span: 1)
Position the single widget declared in the block at (row, col) in
the enclosing ui.grid. Only valid directly inside a grid’s block.
Parameters
rowIntegercolIntegerspanInteger— how many columns this cell spans
Returns void
@yield _self
@yieldparam _self Teek::UI::WidgetDSL the object that the method was called on
component(label nil, &block)
Opens a fresh Scope around the block, so names declared inside
it (ui.button(:save), …) never collide with the same name
used elsewhere - in another component, or at the top level - no
matter how many components share the same (or no) label, since
Scope identity, not label, is what makes two scopes distinct.
Splices its content directly into whatever’s currently open -
this is scope isolation only, not an extra layer of nesting, so
a component built inside ui.panel(:p) attaches as an
ordinary child of `:p`, exactly like any other widget declared
right there would. The common 80% case - a plain method that
takes `ui` and appends into whatever's already open
(`def toolbar(ui) = ui.row `...) - needs none of this; reach
for #component only when scope isolation itself is the point
(reuse across files, avoiding name collisions).
The returned ComponentHandle is the disciplined way for the
caller to reach into the component’s own named widgets afterward
(screen.handle(:action)/screen[:action]) - the global ui[]
never sees into a component’s scope (see #[]), so a component
built in one file and mounted from another stays reachable only
through the facade it hands back, not by guessing its internal
names.
Parameters
labelSymbol, String, nil— a human-readable label for error messages/debugging - has no bearing on uniqueness
Returns ComponentHandle
@yieldparam c self build the component's content with the
ordinary widget DSL, same as any other block
current_path
The current build-parent ancestry, as a readable breadcrumb (e.g.
"column > row") - derived from @stack, the one thing only the
builder (not the Document) knows: which containers are
currently open. Useful in a build-time error message ("X added
outside a Y; current parent: #current_path") or just to orient
yourself while poking around mid-build.
Returns String — "(top level)" when nothing is currently open
dialog(name nil, modal: true, resizable: false, **opts, &block)
window with dialog-appropriate defaults - modal and fixed-size,
for the common “small modal window” case (confirmations, pickers).
Same underlying node type as window, just different defaults for
modal:/resizable: - both still overridable.
Returns Handle
image(path, **opts)
Declare an image, loaded from a file. Its Tcl image name is
allocated now (no interpreter needed - it’s just a string); the
backing Teek::Photo - and the actual file load - only becomes
real at realize. Pass it as a widget’s image: option (or a
later handle.configure(image: ...)) to display it.
Parameters
pathString— path to an image file (any format Tk's own `image create photo -file` accepts - PNG, GIF, ...)optsHash— forwarded toTeek::Photo.new- e.g.width:/height:/format:/palette:/gamma:
Returns Image
overlay(at:)
Floats the single widget declared in the block on top of the
enclosing ui.canvas, positioned at a fixed corner/edge/center
anchor via Tk’s place geometry manager - a “use sparingly”
escape valve for the one legitimate absolute-position case (a
status readout or button bar layered over canvas content), not a
general-purpose layout mode. Stays correctly positioned across a
canvas resize with nothing to redo by hand - place’s relative
coordinates are fractions of the canvas’s current size, recomputed
live by Tk on every resize. Only valid directly inside a
ui.canvas block.
Parameters
atSymbol— one ofOverlayAnchors::POSITIONS's keys
Returns void
@raise ArgumentError if declared anywhere other than directly
inside ui.canvas, given an unrecognized at:, or its block builds
anything other than exactly one widget
pane(name nil, weight: nil, **opts, &block)
One region of an enclosing ui.split. Only valid directly inside a
ui.split block; its own block builds the pane’s content with the
ordinary widget DSL, same as any other container.
Parameters
nameSymbol, nil— for `ui[:name]` lookup, same as any widgetweightInteger, nil— how much of the leftover space this pane absorbs when the split is resized, relative to its sibling panes' weights - unset panes get Tk's own default (0, fixed size until dragged). The same plain word `ttk::panedwindow` itself uses for this.
Returns Handle
@raise ArgumentError if declared anywhere other than directly inside ui.split
raw(&block)
The build-time escape hatch. A widget has no Tk path yet during
build, so app.command(handle.path, ...) mid-build can’t work -
ui.raw defers the block instead, running it at realize with the
live app in scope. It’s a closure, so it can still reference sibling
widgets by name (ui[:other].path) even if they’re declared later -
by the time any raw block runs, the whole tree has already been
realized once over (same forward-reference guarantee event target:
gets). For anything after realize, a live Handle/session.app is
the escape hatch instead - see the README for the full split.
Returns nil
@yieldparam app Teek::App
screens
A push/pop stack for content screens - see Screens. One stack per
build, created on first access.
Returns Screens
split(name nil, orientation: :horizontal, **opts, &block)
A draggable split - two or more #pane-declared regions, resizable
by dragging the sash between them. Maps to ttk::panedwindow.
Parameters
nameSymbol, nil— for `ui[:name]` lookup, same as any widgetorientationSymbol—:horizontal(panes side by side, a vertical sash) or:vertical(panes stacked, a horizontal sash)
Returns Handle
@raise ArgumentError if orientation isn't :horizontal or :vertical
@yieldparam s self build panes with `s.pane ...`
stretch(columns: [], rows: [])
Mark which columns/rows of the enclosing ui.grid absorb leftover
space - the named replacement for grid columnconfigure -weight.
Only valid directly inside a grid’s block.
Parameters
columnsArray<Integer>rowsArray<Integer>
Returns void
tab(label, name nil, **opts, &block)
One page of an enclosing ui.tabs, labeled label in the tab bar.
Only valid directly inside a ui.tabs block; its own block builds
the pane’s content with the ordinary widget DSL, same as any other
container.
Parameters
labelString— the tab's title, shown in the tab barnameSymbol, nil— for `ui[:name]` lookup, same as any widget
Returns Handle
@raise ArgumentError if declared anywhere other than directly inside ui.tabs
var(initial)
Declare a reactive variable. Its Tcl variable name is allocated now
(no interpreter needed - it’s just a string); the variable itself
only becomes real at realize. Bind it to widgets with bind:.
Parameters
initialObject— initial value - its class decides howVar#valuecoerces later (Integer/Float/Boolean typed, else String)
Returns Var
Attributes ↑
modal [R]
A push/pop stack for modal window handles - see ModalStack. nil
until assigned; unlike #screens it isn’t created automatically,
since its callbacks (on_enter:/on_exit:) are mandatory and
app-specific: ui.modal = Teek::UI::ModalStack.new(on_enter:, on_exit:).
Returns ModalStack, nil