class

Tryst::UI::Node

Inherits Reference < Object

A single element of the retained-mode tree - a widget, layout container, reactive var, or deferred build-time op (the categories from the architecture doc; this class itself is generic across all of them). Plain Crystal, no Tk: constructible, mutable, and traversable with no interpreter, which is what makes the tree headless-testable.

key is this node's stable identity - the explicit name if given, else whatever the owning Document assigns. realized stays nil for the whole build phase; a realizer fills it in later with a RealizedNode.

opts is Hash(Symbol, TclArgValue) rather than ruby's bare Hash - Crystal needs a concrete value type, and TclArgValue (from App) is already the exact set of values a widget option can ultimately become once it reaches App#command.

Constructors

new(type : Symbol, name : Symbol | Nil = nil, key : String | Nil = nil, opts : Hash(Symbol, TclArgValue) = {} of Symbol => TclArgValue, scope : Scope = Scope::TOP_LEVEL, document : Document | Nil = nil)
Source

Instance methods

add_child(node : Node) : Node

Add node as a child, and record self as its parent.

Source
align

Where this flow container's children sit on its cross axis - the align: option.

Source
align=(align : FlowAlign)

Where this flow container's children sit on its cross axis - the align: option.

Source
cell_position

This node's own position inside its parent ui.grid, if any.

Source
cell_position=(cell_position : CellPosition | Nil)

This node's own position inside its parent ui.grid, if any.

Source
children
Source
claimed_segment

The (parent_path, segment) pair Document#claim_path_segment returned for this node's own real Tk path segment, if any - set by Realizer#allocate_path right after claiming. nil for a node that never claimed one (a structural node or :root, which reuse their parent's exact path, or a node that's never been realized at all). Read and cleared by Document#release_path_segment on destroy - see its own comment for why that matters.

Source
claimed_segment=(claimed_segment : Tuple(String, String) | Nil)

The (parent_path, segment) pair Document#claim_path_segment returned for this node's own real Tk path segment, if any - set by Realizer#allocate_path right after claiming. nil for a node that never claimed one (a structural node or :root, which reuse their parent's exact path, or a node that's never been realized at all). Read and cleared by Document#release_path_segment on destroy - see its own comment for why that matters.

Source
close_handler

This window's close handler, from ui.window(on_close:) or from Handle#on_close before realize - Realizer#link wires it to the real window once there is one.

Source
close_handler=(close_handler : CloseHandler | Nil)

This window's close handler, from ui.window(on_close:) or from Handle#on_close before realize - Realizer#link wires it to the real window once there is one.

Source
column_configs

Per-column/row grid configuration - weight (leftover-space share) and min_size (grid columnconfigure/rowconfigure -minsize), keyed by column/row index. Populated by WidgetDSL#stretch (weight: 1, plus an optional shared min_size: across every listed index) and by WidgetDSL#column/#row (precise per-index control). An index absent from the hash never had grid columnconfigure/rowconfigure called for it at all.

Source
column_configs=(column_configs : Hash(Int32, Tryst::UI::GridAxisConfig))

Per-column/row grid configuration - weight (leftover-space share) and min_size (grid columnconfigure/rowconfigure -minsize), keyed by column/row index. Populated by WidgetDSL#stretch (weight: 1, plus an optional shared min_size: across every listed index) and by WidgetDSL#column/#row (precise per-index control). An index absent from the hash never had grid columnconfigure/rowconfigure called for it at all.

Source
declared_layout

Which of grow:/gap:/pad:/align: the declaration actually spelled out, as opposed to the defaults every node carries in #grow?/ #gap/#pad/#align regardless. What LayoutIntentValidator checks against the container - an intent given where nothing honours it is an error, not a silent no-op.

Source
declared_layout=(declared_layout : Array(Symbol))

Which of grow:/gap:/pad:/align: the declaration actually spelled out, as opposed to the defaults every node carries in #grow?/ #gap/#pad/#align regardless. What LayoutIntentValidator checks against the container - an intent given where nothing honours it is an error, not a silent no-op.

Source
display_name

A short, human label for this node - its type, plus the explicit name if it has one (e.g. "column" or "column(:ctrl)"). Used by TreeInspector and the build stack's own current_path breadcrumb (WidgetDSL#current_path) - deliberately bare (no leading marker, no "unnamed" filler text) since both of those read as a sequence of these, not a single prose sentence the way Realizer's own private describe does.

Source
document
Source
document=(document : Document | Nil)

@api private - only for Document#initialize's own root node (see the comment there): Crystal can't let self escape into Node.new via document: self before Document's own ivars are all assigned (doing so makes @root itself fail to type-check, a sharper variant of the usual "self escaped early" nilability quirk), so the root Node is built with document: nil first and patched here immediately after.

Source
each

Depth-first, pre-order traversal of this node and its descendants.

Source
events
Source
find

The first node in this subtree the block accepts, searching the same depth-first pre-order as #each and stopping there. #each can't do this itself: it recurses through a captured block, and Crystal won't let a captured block return from its caller.

Source
gap

Spacing between this container's children, in pixels - the gap: option on a flow container or a grid.

Source
gap=(gap : Int32)

Spacing between this container's children, in pixels - the gap: option on a flow container or a grid.

Source
grow=(grow : Bool)

Whether this child takes the leftover space on its parent flow container's main axis - the grow: option, and what ui.spacer is.

Source
grow?

Whether this child takes the leftover space on its parent flow container's main axis - the grow: option, and what ui.spacer is.

Source
images

Images declared while this node was the open build container (the top of WidgetDSL's @stack at the time - see WidgetDSL#image), so Handle#destroy! knows which photos belong to a subtree it's tearing down. Not necessarily every Image a descendant widget's image: option names - a shared image declared elsewhere and reused here stays owned by wherever it was declared, since destroying one user of a shared image must not pull the photo out from under the others.

Source
key=(key : String | Nil)
Source
lazy=(lazy : Bool)

Whether this node is excluded from the ambient create/link tree walk (Realizer#realize, Realizer#realize_subtree) - true only for a container built with lazy: true (see WidgetDSL#append_container). A lazy node stays a normal, attached member of the retained tree; it just never gets a real Tk widget until something explicitly realizes it (see Handle#realize!).

Source
lazy?

Whether this node is excluded from the ambient create/link tree walk (Realizer#realize, Realizer#realize_subtree) - true only for a container built with lazy: true (see WidgetDSL#append_container). A lazy node stays a normal, attached member of the retained tree; it just never gets a real Tk widget until something explicitly realizes it (see Handle#realize!).

Source
logical_path

This node's address, computed purely from the retained tree (name/key + #parent) - no Tk involved, correct before realize. For an ordinary widget this already equals the real Tk path (Realizer#allocate_path walks this identical parent/segment structure); for anything without an independent Tk path of its own (a menu entry, say), an Addressing strategy extends past this with its own marker rather than pretending it's a real one. The other documented exception: a reusable component mounted more than once under the same real parent - Realizer#allocate_path only discovers that repeat (and disambiguates the later mounts' paths) at realize, so this can't predict it ahead of time either. A node that isn't attached anywhere yet (parent nil, and not itself the root) is treated as top-level - the best answer available without a tree to place it in.

Source
name
Source
opts
Source
overlay_anchor

This node's placement anchor on its parent ui.canvas, if any, set by WidgetDSL#overlay - one of OverlayAnchors::POSITIONS's keys. A single Symbol, so it needs no record of its own the way CellPosition's several fields do.

Source
overlay_anchor=(overlay_anchor : Symbol | Nil)

This node's placement anchor on its parent ui.canvas, if any, set by WidgetDSL#overlay - one of OverlayAnchors::POSITIONS's keys. A single Symbol, so it needs no record of its own the way CellPosition's several fields do.

Source
pad

Spacing between this flow container's own edges and its children, in pixels - the pad: option.

Source
pad=(pad : Int32)

Spacing between this flow container's own edges and its children, in pixels - the pad: option.

Source
parent
Source
pending_destroy=(pending_destroy : Bool)

Whether a deferred Handle#destroy! is currently scheduled (via "after idle") but hasn't run yet - lets a second destroy! call on the same still-pending handle no-op instead of double-scheduling.

Source
pending_destroy?

Whether a deferred Handle#destroy! is currently scheduled (via "after idle") but hasn't run yet - lets a second destroy! call on the same still-pending handle no-op instead of double-scheduling.

Source
raw_block

WidgetDSL#raw's deferred block, for a :raw_op node only - run by Realizer#run_raw_op with the live app once realized. Ruby stuffs this into node.opts[:block] instead (opts is a bare Hash there); here it's a dedicated field because opts is Hash(Symbol, TclArgValue), and a block taking the live app doesn't fit TclArgValue's closed union (which only has room for the bind-shaped Proc(Array(String), CallbackSignal, Nil) a widget option callback needs) without either widening that union for one edge case or giving core's own TclArgValue a dependency on tryst-ui's AppContract - both worse than a dedicated field.

Source
raw_block=(raw_block : Proc(AppContract, Nil) | Nil)

WidgetDSL#raw's deferred block, for a :raw_op node only - run by Realizer#run_raw_op with the live app once realized. Ruby stuffs this into node.opts[:block] instead (opts is a bare Hash there); here it's a dedicated field because opts is Hash(Symbol, TclArgValue), and a block taking the live app doesn't fit TclArgValue's closed union (which only has room for the bind-shaped Proc(Array(String), CallbackSignal, Nil) a widget option callback needs) without either widening that union for one edge case or giving core's own TclArgValue a dependency on tryst-ui's AppContract - both worse than a dedicated field.

Source
realized
Source
realized=(new_realized : RealizedNode | Nil) : RealizedNode | Nil

Keeps Document's path index (Document#node_destroyed's own lookup table) in sync with whatever this node's real Tk path currently is - a plain property can't do that, since Document has to hear about both the old path going away and the new one taking its place. This is the ONLY thing that makes a real Tk <Destroy> (whether from an explicit Handle#destroy! or Tk's own doing, e.g. closing a window's WM close button) findable back to the node it belongs to - see Document#node_destroyed for the other half.

Source
remove_child(node : Node) : Node

Unlinks node from this node's own children - the symmetric counterpart to #add_child, used by Handle#destroy! so a destroyed node stops being reachable from the retained tree at all (not just Tk-dead - actually gone from children, so a later sibling addition never iterates it, and it becomes collectable once nothing else references it).

Source
row_configs
Source
row_configs=(row_configs : Hash(Int32, Tryst::UI::GridAxisConfig))
Source
scope
Source
to_a

This node and its descendants collected into an Array, in the same depth-first pre-order #each yields them. Eager, not an Iterator - a build tree is small enough that materializing it costs nothing worth avoiding, and #find is there when the walk can stop early.

Source
type
Source
vars

Vars declared while this node was the open build container - same ownership rule as #images above (see WidgetDSL#var), so Handle#destroy! can release a subtree's own Tcl globals, write traces, and change callbacks along with it.

Source