The visual tree system is the runtime backbone of a Terminality application.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Rikitav/Terminality/llms.txt
Use this file to discover all available pages before exploring further.
VisualTreeNode defines the abstract contract — layout, rendering, focus, and input — that every widget must fulfill. UILayer groups a root node with a FocusManager and a running flag, enabling multiple independent trees to coexist as a layer stack. VisualTree owns that stack, drives layout and render passes across all layers each frame, and provides the singleton that HostApplication uses to coordinate the loop. ControlBase is the first concrete implementation of VisualTreeNode, adding the reactive property system, events, and the full layout pipeline described in its own reference page.
VisualTreeNode
VisualTreeNode is the non-copyable, non-movable abstract base for all objects that can appear in the tree. You do not inherit from it directly in application code; inherit from ControlBase instead.
Tree structure
GetParent() — read the parent pointer
GetParent() — read the parent pointer
nullptr if this node is the root of its layer or has not yet been attached.nullptr.SetParent(parent) — assign the parent (pure virtual)
SetParent(parent) — assign the parent (pure virtual)
PropertyChanged, call OnDettachedFromTree). Passing nullptr signals detachment.nullptr to detach.SetLayer(layer) — bind to a UILayer (pure virtual)
SetLayer(layer) — bind to a UILayer (pure virtual)
UILayer reference to this node and, in container implementations, recursively to all children. Called when the layer’s root node is first set up.nullptr to clear.GetVisualChild(index), VisualChildrenCount() — child access (pure virtual)
GetVisualChild(index), VisualChildrenCount() — child access (pure virtual)
VisualChildrenCount() returns the number of direct children; GetVisualChild(i) returns the child at position i. Leaf controls return 0 and nullptr respectively. Container controls expose their children here.index >= VisualChildrenCount().nullptr from leaf controls.Layout (pure virtual)
The three-phase layout protocol is defined here and implemented inControlBase. The VisualTree calls these on each root node every frame.
Measure, Arrange, Render — layout phases
Measure, Arrange, Render — layout phases
- Measure — nodes report their desired size given the available space.
- Arrange — the tree assigns final rectangles to each node.
- Render — nodes write characters into the
RenderBuffervia aRenderContext.
Layout invalidation
InvalidateMeasure, InvalidateArrange, InvalidateVisual
InvalidateMeasure, InvalidateArrange, InvalidateVisual
measureDirty_, arrangeDirty_, visualDirty_). The layout loop checks these flags to decide which phases to rerun. Calling InvalidateMeasure() implicitly requires the arrange and visual phases as well.IsMeasureDirty, IsArrangeDirty, IsVisualDirty — dirty state queries
IsMeasureDirty, IsArrangeDirty, IsVisualDirty — dirty state queries
VisualTree walks the tree checking IsVisualDirty() to determine whether a render pass is necessary.Attachment state
IsAttached, OnAttachedToTree, OnDettachedFromTree
IsAttached, OnAttachedToTree, OnDettachedFromTree
IsAttached() returns true once the node has been added to a live tree. The two virtual callbacks fire when attachment state changes and can be overridden by subclasses to register or release resources.Focus management
IsFocusable, IsTabStop, GetTabIndex — focus queries
IsFocusable, IsTabStop, GetTabIndex — focus queries
IsFocusable() defaults to true; a non-focusable node is skipped by the focus manager entirely.SetFocusable, SetTabStop, SetTabIndex — focus configuration (pure virtual)
SetFocusable, SetTabStop, SetTabIndex — focus configuration (pure virtual)
ControlBase to write to the protected fields and fire PropertyChanged.OnGotFocus, OnLostFocus — focus events
OnGotFocus, OnLostFocus — focus events
FocusManager when this node gains or loses keyboard focus. The default implementations set focused_ and call InvalidateVisual() so the focused/unfocused color pair is applied immediately. Override to add custom focus behavior.MoveFocusNext(direction, modifiers) — programmatic focus movement
MoveFocusNext(direction, modifiers) — programmatic focus movement
FocusManager to move focus to the next focusable node in direction. The ControlBase default OnKeyDown calls PopFocus (which delegates here) when arrow keys or Tab/Shift-Tab are pressed. Override in container controls to implement custom traversal.Direction::Up, Direction::Down, Direction::Left, Direction::Right, Direction::Next, Direction::Previous.InputModifier::None.true if focus was successfully moved.Input handling
OnKeyDown(input), OnKeyUp(input) — keyboard dispatch (pure virtual)
OnKeyDown(input), OnKeyUp(input) — keyboard dispatch (pure virtual)
OnKeyDown or OnKeyUp on the currently focused node. If the call returns false, the loop walks up GetParent() and tries the parent, enabling event bubbling. Implementations should return true if they consumed the event.Key, Modifier, and Pressed.true to stop bubbling; false to pass the event up to the parent.Size and position accessors
GetActualSize, GetArrangedRect — post-layout geometry
GetActualSize, GetArrangedRect — post-layout geometry
Measure and the rectangle assigned during Arrange. These values are only meaningful after a layout pass has completed for the current frame.UILayer
UILayer groups a root node, its FocusManager, and an atomic running flag into a single unit that the layer stack owns. Each call to VisualTree::PushLayer() creates a new UILayer; NestUILoop drives one layer at a time.
| Field | Type | Description |
|---|---|---|
RootNode | unique_ptr<VisualTreeNode> | The root control for this layer. Owns the entire subtree. |
Running | atomic<bool> | The loop condition. Set to false (via Close() or RequestStop()) to exit the nested loop. |
Focus | FocusManager | Per-layer focus state. The focused node receives all keyboard events while this layer is active. |
UILayer is non-copyable and non-movable. Construct it only through VisualTree::PushLayer().
VisualTree
VisualTree is the singleton that owns the layer stack and coordinates layout and rendering across all active layers.
Singleton access
VisualTree instance. Use this to push/pop layers, query layer count, or access the FocusManager from outside the render pipeline.
Layer stack
PushLayer(root) → UILayer& — add an overlay layer
PushLayer(root) → UILayer& — add an overlay layer
layerRoot into a new UILayer, appends it to the layer stack, sets initial focus to the root node, and marks the tree as dirty for a full re-render. Returns a reference to the new layer so the caller can pass it to NestUILoop.VisualTree.PopLayer() — remove the top layer
PopLayer() — remove the top layer
LayerCount() — number of active layers
LayerCount() — number of active layers
Root() — bottom layer root node
Root() — bottom layer root node
nullptr if the stack is empty. This is the main application widget passed to RunUILoop.PeekLayer() — top layer root node
PeekLayer() — top layer root node
nullptr if the stack is empty.Focus
GetFocusManager() — access the top layer's focus state
GetFocusManager() — access the top layer's focus state
FocusManager for the topmost layer. Throws if the stack is empty. The UI loop calls this to dispatch keyboard events to the currently focused node.Layout and rendering
RunLayout(viewportSize) — measure and arrange all layers
RunLayout(viewportSize) — measure and arrange all layers
Measure and then Arrange on each layer’s root node, passing the full viewport rectangle. Also collects dirty-visual flags from the traversal. The UI loop calls this before every render attempt.HostBackend::QueryViewportSize().Render(buffer) — render the top layer to the buffer
Render(buffer) — render the top layer to the buffer
buffer if HasDirtyVisual() is true, then clears the dirty flag. Only the top layer is rendered per call; the base layers are not redrawn unless they are themselves dirty.Invalidate(dirtyRect) — mark a region for redraw
Invalidate(dirtyRect) — mark a region for redraw
HasDirtyVisual() — check if a render pass is needed
HasDirtyVisual() — check if a render pass is needed
true if any node has signalled a visual change since the last Render call. The UI loop checks this before calling Render to avoid unnecessary work.Relationship between VisualTreeNode, UILayer, and ControlBase
VisualTreeNode defines the protocol; ControlBase provides the full implementation with properties, events, and hotkeys. UILayer bundles a root node with per-layer focus state. VisualTree manages the layer stack and drives the frame loop.