Tryst::UI::Realizer
Walks a Document and realizes it into a live Tryst::App - two passes:
- create - creates every widget, allocates a hierarchical/ meaningful Tk path per node, fills each node's realized slot.
- link - applies layout (plain top-to-bottom pack, for now - see this class's own doc comment for what's deferred) and wires event bindings, resolving target: references by name. Runs after create has finished the WHOLE tree, so a target declared later in the build already has a live path by the time it's looked up - that ordering is what makes forward references work.
Every widget creation and mutation goes through Tryst::App#command, so tryst's interceptor/leak-cleanup layer applies automatically.
@app is AppContract (see app_contract.cr), not the concrete Tryst::App - dispatched dynamically, so a Realizer built against FakeApp (spec/support/fake_app.cr) runs the exact same code with no real Tk interpreter involved, which is what makes this class headless-testable at all.
The generic create/link passes, plain top-to-bottom pack, and the auto-scrollbar a natively_scrollable type gets for free (#create_native_scrollable) are what this class owns directly. Everything type-specific - column/row flow layout, grid layout, ui.scrollable's own embedded-viewport case, menu_bar/context_menu's bespoke traversal, :window/:pane/:tab's own post_create setup - lives on the owning type's own WidgetType subclass instead (see widget_type.cr's own doc comment), reaching back into this class only through the small public service surface those bodies actually need: #app, #pack_plain, #create_children, #allocate_path, #filtered_opts, #wire_scrollbars, #add_bindtag, #arrange_flow.
Constants
Opts keys that are read on this side rather than handed to Tk, so they never reach a widget-creation call as a bogus -option. Every entry is one a type's own realize step consumes out of node.opts: :window's wm setup (title/geometry/resizable/transient/modal), :scroll (#resolve_scroll) and :x/:y (#scroll_axis?, which scrollbars a scrolling widget gets), :tab_label (a page's label), and :pane_weight (how much of a split's leftover space one pane takes).
Which types may legitimately carry each of these is enforced at the declaration - see WidgetDSL#validate_reserved_opts!. Adding a key here means teaching that check about it too, or it becomes an option that silently does nothing on every type but one.
An intent with a typed Node slot is not listed here - it never entered opts to begin with. See WidgetDSL#extract_dsl_opts. A Set, not a list: #filtered_opts asks about every option on every node.
Constructors
Instance methods
Appends tag to path's bindtags, keeping the ones Tk already gave
it (its own path, its widget class, its toplevel, "all") - a bare
bindtags <path> <tag> would REPLACE them, silently costing the
widget every class binding that makes it behave like itself.
Shared by #adopt_content_bindtag above (generic infra) and
ScrollableType's own wheel-binding setup (widget_types/
scrollable.cr) - public for the latter to reach from outside this
class.
Claims this node's own hierarchical/meaningful Tk path segment under parent_path and records it, so a later rebuild of the same subtree gets the same segment back (Handle#destroy!'s own unlink!). Public - MenuHostType's own relocated create/link replacement (widget_types/menu_host_type.cr) needs this from outside this class; #create still uses it directly.
The live app this realizer drives Tk through - the one thing every relocated WidgetType hook body needs and none of their signatures pass directly (only #post_create's does). Public so a subclass's #arrange/#custom_children/#custom_create/#post_create override can reach it via the realizer parameter each is handed.
Runs a column/row's flow-pack layout - gap:/align:/pad: driven, with each child's own grow: absorbing leftover space along the container's main axis. Public (not private, unlike ruby's own version, which reaches it via realizer.send(:arrange_flow, ...) from a WidgetType's computed arrange: hook - Crystal has no private-bypass equivalent to send) - generic layout STRATEGY any type can opt into via the flow: constructor field (WidgetType# arrange's own default body dispatches here automatically), not any one type's own bespoke logic, so it stays here rather than moving into column.cr/row.cr specifically.
The generic "create every child under this node's own path" step
- also WidgetType#custom_children's own default body, so a type with nothing custom to do here gets this for free. Public for the same reason #pack_plain is: a WidgetType subclass whose override only wants to run this unconditionally (rather than override #custom_children at all) can call it directly.
node.opts, keyed by String (App#command's Hash overload) with every RESERVED_OPTIONS key stripped - none of those are real Tk options. Public - MenuHostType's own relocated create/link replacement (widget_types/menu_host_type.cr) needs this from outside this class; #create_native_scrollable and #create still use it directly since they're both defined here.
Plain top-to-bottom pack, with none of a flow container's own gap:/align:/pad: options - the fallback every unregistered node (:root) gets, and also WidgetType#arrange's own default body for a type with no #flow and nothing else overridden. Public for the same reason #create_children is.
grow: still means what it does in a flow: the child takes all the leftover space. That's what a window's single body column wants (a toplevel is plain-packed, having no flow of its own), and with nothing honoring it here no DSL-built window could ever resize its content.
Realize a single already-built (but not-yet-realized) node - and its descendants - into an already-running app, scoped under a parent that's realized already. Reuses the exact same create/link machinery #realize uses for the initial tree, just entered at an arbitrary node instead of the document root - for adding widgets to an already-running app (Session#add) or realizing an on-demand lazy: true subtree.
Builds a vertical and/or horizontal ttk::scrollbar around target_path, gridded into path (the wrapper frame), and wires each to auto-hide when its content fits. Shared by #create_native_scrollable above (generic infra) and ScrollableType#custom_children (widget_types/scrollable.cr, the arbitrary-content case) - public for the latter to reach from outside this class.