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.

ControlBase is the concrete base class that all Terminality widgets inherit from. It wires together the reactive Property<T> system, the three-phase layout pipeline (MeasureArrangeRender), keyboard events and hotkeys, focus management, and child iteration. You never instantiate ControlBase directly — you subclass it and implement the three pure-virtual override points, or use one of the built-in controls such as Button, TextBox, or Grid.

Creating controls with init<T>

The init<T> factory helper creates a widget, runs an optional initialization lambda over it, and returns a unique_ptr<T>. It is the idiomatic way to build widget trees in Terminality:
auto button = init<Button>([](Button* b)
{
    b->Text = L"Click me";
    b->HorizontalAlignment = HorizontalAlign::Stretch;
    b->Clicked += []() { HostApplication::Current().RequestStop(); };
});

init<T> overloads

template <typename T>
std::unique_ptr<T> init(std::function<void(T*)> init);
Constructs T with make_unique<T>(), calls init(widget.get()) if the function is non-null, and returns the unique_ptr. Ownership passes to the caller.
init
std::function<void(T*)>
required
Initialization lambda. Receives a raw pointer to the newly constructed widget. Set properties, connect events, and add children here.
return
std::unique_ptr<T>
Owning pointer to the fully initialized widget.
template <typename T>
std::unique_ptr<T> init();
Shorthand when no initialization is needed. Equivalent to std::make_unique<T>().
return
std::unique_ptr<T>
Owning pointer to the default-constructed widget.

Properties

Every property is an instance of Property<ControlBase, T>. Assigning to a property automatically fires PropertyChanged and schedules the appropriate invalidation pass (measure, arrange, or visual).
// Read
Size s = button->MinSize.Get();      // explicit getter
Size s2 = button->MinSize;           // implicit conversion

// Write
button->MinSize = Size(20, 1);       // operator=
button->MinSize.Set(Size(20, 1));    // fluent setter, returns ControlBase&

Identity and tagging

Property<ControlBase, std::string> Tag;
An arbitrary string label for this control. Used by QueryByTag<T>() to locate a specific node in the tree. Does not trigger any invalidation when changed (InvalidationKind::None).Default: ""

Sizing

Property<ControlBase, Size> MinSize;   // default: Size::Auto
Property<ControlBase, Size> MaxSize;   // default: Size::Auto
Property<ControlBase, Size> ExpSize;   // default: Size::Auto
MinSize clamps the measured content size from below; MaxSize clamps from above. Setting ExpSize is a shorthand that writes the same value to both MinSize and MaxSize, pinning the control to an exact size.Use Size::Auto (negative component values) to indicate no constraint. All three properties trigger a measure pass on change.

Layout

Property<ControlBase, Thickness> Margin;
Space in character cells between this control’s edge and its parent’s slot boundary. Thickness has Left, Top, Right, Bottom fields.Default: Thickness::Zero
Property<ControlBase, HorizontalAlign> HorizontalAlignment;
Controls how the control is placed within the horizontal slot allocated by its parent.
ValueBehavior
HorizontalAlign::LeftAnchors to the left edge
HorizontalAlign::CenterCenters within the slot
HorizontalAlign::RightAnchors to the right edge
HorizontalAlign::StretchExpands to fill the slot (clamped by MinSize/MaxSize)
Default: HorizontalAlign::Stretch
Property<ControlBase, VerticalAlign> VerticalAlignment;
Controls how the control is placed within the vertical slot allocated by its parent.
ValueBehavior
VerticalAlign::TopAnchors to the top edge
VerticalAlign::CenterCenters within the slot
VerticalAlign::BottomAnchors to the bottom edge
VerticalAlign::StretchExpands to fill the slot (clamped by MinSize/MaxSize)
Default: VerticalAlign::Stretch

Colors

Property<ControlBase, Color> ForegroundColor;   // default: Color::WHITE
Property<ControlBase, Color> BackgroundColor;   // default: Color::BLACK
The text and fill colors used when the control does not have focus. Triggers a visual-only invalidation on change.
Property<ControlBase, Color> FocusedForegroundColor;   // default: Color::BLACK
Property<ControlBase, Color> FocusedBackgroundColor;   // default: Color::WHITE
Colors applied when the control holds keyboard focus. Triggers a visual-only invalidation on change. The inverted default (black on white) provides a visible focus indicator without extra rendering logic.

Visibility and context menu

Property<ControlBase, bool> IsVisible;
When false, the control is hidden. This triggers a visual invalidation but does not remove the control from the layout tree.Default: true
Property<ControlBase, std::unique_ptr<ContextMenu>> CtxMenu;
An optional ContextMenu shown when OpenContextMenu() is called. Build and assign the menu with init<ContextMenu>:
bubble->CtxMenu = init<ContextMenu>([](ContextMenu* menu)
{
    menu->AddItem(L"Delete", []() { /* ... */ });
});

Events

Events use the Event<Args...> template. Subscribe with operator+= for fire-and-forget handlers, or Connect() for a scoped EventConnection that auto-disconnects on destruction.
// Fire-and-forget
control->KeyDown += [](InputEvent e) { /* ... */ };

// Scoped connection (auto-disconnects when conn goes out of scope)
auto conn = control->KeyDown.Connect([](InputEvent e) { /* ... */ });
EventSignatureDescription
PropertyChangedEvent<const char*>Fires after any property or internal field changes. The argument is the property name string.
KeyDownEvent<InputEvent>Fires when a key-down event is dispatched to this control.
KeyUpEvent<InputEvent>Fires when a key-up event is dispatched to this control.

Methods

Tree attachment

void SetParent(VisualTreeNode* parent) override;
Called by container controls when this control is added as a child. Sets parent_ and fires PropertyChanged("Parent"). Passing nullptr detaches the control and calls OnDettachedFromTree().
parent
VisualTreeNode*
The new parent node, or nullptr to detach.
void SetLayer(UILayer* layer) override;
Propagates the owning UILayer reference down to this control and all of its visual children. Called automatically when the root node of a layer is constructed.
layer
UILayer*
The layer this control belongs to, or nullptr to clear.

Focus configuration

void SetFocusable(bool value) override;
void SetTabStop(bool value)   override;
void SetTabIndex(int value)   override;
Configure how this control participates in keyboard focus navigation. All three fire PropertyChanged when the value actually changes.
SetFocusable.value
bool
When false, the control cannot receive focus at all.
SetTabStop.value
bool
When true, the control is included in the Tab/Shift-Tab cycle.
SetTabIndex.value
int
Explicit ordering within the tab cycle. Lower values come first.

Layout pipeline

Size Measure(const Size& availableSize) override;
First layout phase. Applies Margin, MinSize, and MaxSize constraints to availableSize, then delegates to MeasureOverride for the content size. Returns the total desired size including margins.
availableSize
const Size&
required
The space offered by the parent. Negative component values indicate an unconstrained axis.
return
Size
The total space this control wants to occupy, including its margin.
void Arrange(const Rect& finalRect) override;
Second layout phase. Subtracts margins from finalRect, applies HorizontalAlignment and VerticalAlignment to compute the actual drawing rectangle, then calls ArrangeOverride with the result.
finalRect
const Rect&
required
The slot assigned by the parent, including any margin space.
void Render(RenderContext& context) override;
Third layout phase. Fills the control’s background using ForegroundColor and BackgroundColor, clears the visual-dirty flag, then calls RenderOverride for content drawing.
context
RenderContext&
required
The render context scoped to this control’s arranged rectangle.

Invalidation

void ApplyInvalidation(InvalidationKind invalidation);
Schedules one or more layout phases for the next frame. Properties call this automatically when their value changes. You can call it directly when internal state changes without going through a property.
invalidation
InvalidationKind
required
A bitmask of phases to invalidate.
ValueEffect
InvalidationKind::NoneNo invalidation
InvalidationKind::VisualRedraws this control’s pixels
InvalidationKind::ArrangeReruns arrange and render
InvalidationKind::MeasureReruns measure, arrange, and render

Input handling

bool OnKeyDown(InputEvent input) override;
bool OnKeyUp(InputEvent input)   override;
Called by the UI loop when keyboard events are dispatched to the focused control. OnKeyDown emits the KeyDown event, checks registered hotkeys, and handles arrow-key focus movement. OnKeyUp emits the KeyUp event. Return true to indicate the event was consumed and should not bubble to the parent.
input
InputEvent
required
The keyboard event, carrying Key, Modifier, and Pressed fields.
return
bool
true if the event was handled; false to let it bubble up the tree.
void OnHotkey(InputModifier modifier, InputKey key, HotkeyCallback callback);
Registers a keyboard shortcut on this control. When the control has focus and the matching modifier + key combination is pressed, callback is invoked with a pointer to this control. Throws std::runtime_error if the same combination is registered twice.
inputBox->OnHotkey(InputModifier::None, InputKey::RETURN, [](ControlBase* self)
{
    auto* box = static_cast<TextBox*>(self);
    // process submitted text
    box->Text = L"";
});
modifier
InputModifier
required
The required modifier keys (e.g., InputModifier::None, InputModifier::LeftCtrl).
key
InputKey
required
The primary key for the shortcut.
callback
HotkeyCallback
required
std::function<void(ControlBase*)> invoked when the shortcut fires.

Context menu

void OpenContextMenu();
Opens the ContextMenu stored in CtxMenu as a nested UI layer positioned just below this control’s arranged rectangle. Does nothing if CtxMenu is nullptr.

Tree queries

template<typename T = ControlBase>
T* QueryByTag(std::string_view tag);
Performs a depth-first search of this control’s subtree and returns the first ControlBase whose Tag property equals tag, cast to T*. Returns nullptr if no match is found or if the found node cannot be cast to T.
auto* inputBox = root->QueryByTag<TextBox>("messageInput");
if (inputBox)
    inputBox->Text = L"";
tag
std::string_view
required
The tag string to search for.
return
T*
Raw pointer to the matched control, or nullptr.
const ChildIterator child_begin() const;
const ChildIterator child_end()   const;
Forward iterators over the control’s direct visual children. ChildIterator dereferences to VisualTreeNode*. The base ControlBase implementation returns empty iterators (zero children); container subclasses override VisualChildrenCount() and GetVisualChild() to expose their children.
for (auto it = container->child_begin(); it != container->child_end(); ++it)
{
    VisualTreeNode* child = *it;
    child->InvalidateVisual();
}
void Close();
Sets layer_->Running to false, causing the NestUILoop that owns this layer to exit on the next tick. This is how modal dialogs and overlay controls close themselves.

Virtual override points

Subclass ControlBase and implement all three pure-virtual methods:
class MyLabel : public ControlBase
{
protected:
    Size MeasureOverride(const Size& availableSize) override
    {
        // Return the natural content size (excluding margins — those are handled by Measure()).
        return Size(static_cast<int>(text_.size()), 1);
    }

    void ArrangeOverride(const Rect& finalRect) override
    {
        // Store layout-derived state if needed. finalRect is already margin-adjusted.
    }

    void RenderOverride(RenderContext& context) override
    {
        // Draw content into context. The background has already been filled by Render().
        auto writer = context.BeginText();
        writer << text_;
    }

private:
    std::wstring text_;
};
virtual Size MeasureOverride(const Size& availableSize) = 0;
Return the intrinsic content size given the space offered. Margins have already been subtracted from availableSize by the time this is called. Use negative component values (Size::Auto) to indicate that an axis is unconstrained.
availableSize
const Size&
required
Available space after margins and MaxSize constraints have been applied.
return
Size
Desired content size, not including margins.
virtual void ArrangeOverride(const Rect& finalRect) = 0;
Position any children within finalRect. For leaf controls this is often a no-op. Container controls forward finalRect slices to each child via their Arrange methods.
finalRect
const Rect&
required
The final drawing rectangle after alignment and margin subtraction.
virtual void RenderOverride(RenderContext& context) = 0;
Draw the control’s content into context. The base Render() has already cleared the background. Use context.BeginText() to write characters, or delegate to children by calling their Render methods.
context
RenderContext&
required
Render context whose coordinate origin is the top-left of this control’s arranged rectangle.

Build docs developers (and LLMs) love