Skip to main content

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.

Every Terminality UI is a tree of nodes. The framework owns the tree through VisualTree, a process-global singleton. Each node in the tree is a VisualTreeNode, and every concrete control you write or use inherits from ControlBase, which extends VisualTreeNode with layout, input handling, focus management, and the reactive property system.

Core types

VisualTree

The process-global root of the UI. Holds a stack of UILayer objects, runs the layout pipeline, and dispatches dirty-rect rendering to a RenderBuffer.

VisualTreeNode

Abstract base class for every node. Declares the Measure / Arrange / Render contract, dirty flags, parent pointer, and focus/input callbacks.

UILayer

A VisualTree layer owns one root VisualTreeNode and a FocusManager. Layers stack — pushing a modal dialog pushes a new layer; popping removes it.

ControlBase

The concrete base class for all user-facing controls. Adds the Property<> system, Event<> members (PropertyChanged, KeyDown, KeyUp), alignment, margin, colours, focus, and hotkey registration.

VisualTree and UILayer

VisualTree::Current() returns the singleton. The framework starts a UI loop by pushing one root control as a UILayer:
HostApplication& app = HostApplication::Current();
app.EnterTerminal();
app.RunUILoop(std::make_unique<TestApp::MessangerTest>());
app.ExitTerminal();
You can push additional layers at runtime — for example, to display a modal dialog on top of the existing UI — and pop them when the dialog closes. PeekLayer() returns the topmost active layer’s root node.

VisualTreeNode

VisualTreeNode is the abstract interface that VisualTree operates on. It carries three dirty flags:
FlagCleared by
measureDirty_Measure()
arrangeDirty_Arrange()
visualDirty_Render()
Calling InvalidateMeasure(), InvalidateArrange(), or InvalidateVisual() on a node bubbles the notification up to the tree via OnChildInvalidated(), so the framework knows which subtree needs re-layout. Each node exposes VisualChildrenCount() and GetVisualChild(index) so the tree can traverse its children without knowing the concrete type.

ControlBase

ControlBase derives from VisualTreeNode and is the class you subclass to write your own controls. It provides:
  • Layout propertiesMinSize, MaxSize, ExpSize, Margin, HorizontalAlignment, VerticalAlignment
  • Colour propertiesForegroundColor, BackgroundColor, FocusedForegroundColor, FocusedBackgroundColor
  • VisibilityIsVisible
  • Reactive callbacksPropertyChanged, KeyDown, KeyUp
  • Focus/tab orderSetFocusable(), SetTabStop(), SetTabIndex()
  • Hotkey registrationOnHotkey(modifier, key, callback)
  • Tree queryQueryByTag<T>()
  • Child iterationchild_begin() / child_end()

MeasureOverride / ArrangeOverride / RenderOverride

The three protected pure virtuals are the extension points you override in every control:
class MyWidget : public ControlBase
{
protected:
    Size MeasureOverride(const Size& availableSize) override
    {
        // Return the natural content size
        return Size(20, 3);
    }

    void ArrangeOverride(const Rect& finalRect) override
    {
        // Position children inside finalRect
    }

    void RenderOverride(RenderContext& context) override
    {
        auto stream = context.BeginText();
        stream << "Hello from MyWidget";
    }
};

The init<T>() factory helper

Building a widget tree by hand with std::make_unique and sequential setter calls produces verbose, hard-to-read code. The init<T>() helper template solves this:
template <typename T>
std::unique_ptr<T> init(std::function<void(T*)> init);
It allocates a T, passes the raw pointer to the lambda so you can configure it, then returns the unique_ptr. The overload without a lambda just allocates:
template <typename T>
std::unique_ptr<T> init();
Nested lambdas naturally mirror the widget hierarchy in the source code:
AddChild(0, 0, init<Border>([&](Border* chatBorder)
{
    chatBorder->Margin = Thickness(0, 1, 0, 0);
    chatBorder->HeaderText = L"Chat: Tamerlan";
    chatBorder->Content = init<ItemsControl<MessageModel>>([&](ItemsControl<MessageModel>* list)
    {
        list->Scrollable = true;
        list->AutoScrollToEnd = true;
    });
}));
Each level of nesting corresponds to one level of the visual tree, which makes the structure obvious at a glance.

AddChild

Panel controls such as Grid and StackPanel expose AddChild(). Grid::AddChild takes a row, column, and optionally row/column spans:
grid->AddChild(0, 0, init<Label>([](Label* lbl)
{
    lbl->Text = L"Row 0, Col 0";
}));

// With spans
grid->AddChild(0, 0, /*rowSpan=*/2, /*colSpan=*/1, init<Border>());
StackPanel::AddChild takes only the child pointer because panels stack children sequentially along their orientation axis.

QueryByTag

Every control has a Tag property (a std::string). QueryByTag<T>(tag) performs a depth-first search from any node and returns the first match cast to T*, or nullptr:
// Set a tag during construction
init<TextBox>([](TextBox* tb)
{
    tb->Tag = "inputField";
    tb->Text = L"";
});

// Retrieve it later from the root
TextBox* field = root->QueryByTag<TextBox>("inputField");
if (field)
    field->Text = L"populated";

Child iteration

ControlBase exposes a standard forward iterator over its visual children:
for (auto it = control->child_begin(); it != control->child_end(); ++it)
{
    VisualTreeNode* child = *it;
    // ...
}
The iterator calls GetVisualChild(index) internally, so it works uniformly across Grid, StackPanel, Border, and any custom panel you create.
VisualTreeNode does not support copy or move semantics — controls are exclusively owned by their parent through std::unique_ptr. Never store a raw pointer to a control that outlives its tree.

Build docs developers (and LLMs) love