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.

Layout controls in Terminality act as containers that measure, arrange, and render their children. You never position controls manually — instead, you express structure through Grid rows and columns, StackPanel orientation, or ScrollViewer clipping, and the layout engine resolves sizes automatically at each frame.

Grid

Grid divides its space into rows and columns. You define the dimensions of each track with a GridLength, then place child controls into specific cells. Children can span multiple rows or columns.

GridLength

Every row height and column width is described by a GridLength value, which combines a numeric value with a sizing mode.
GridLength::Auto()
static factory
Size the track to the largest DesiredSize of any child in that track. The track takes exactly as much space as its contents need.
GridLength::Cell(n)
static factory
Size the track to exactly n terminal cells (characters). Use this for fixed-width columns or fixed-height rows.
GridLength::Star(weight = 1.0f)
static factory
Distribute remaining space proportionally. A track with Star(2.0f) gets twice the remaining space as a Star(1.0f) track. This is the default when you default-construct a GridLength.

RowDefinition and ColumnDefinition

RowDefinition.Height
GridLength
default:"GridLength::Star()"
Sizing mode for this row.
RowDefinition.MinHeight
int32_t
default:"0"
Minimum height in cells. The row will never shrink below this value.
RowDefinition.MaxHeight
int32_t
default:"-1"
Maximum height in cells. -1 means no limit.
ColumnDefinition.Width
GridLength
default:"GridLength::Star()"
Sizing mode for this column.
ColumnDefinition.MinWidth
int32_t
default:"0"
Minimum width in cells.
ColumnDefinition.MaxWidth
int32_t
default:"-1"
Maximum width in cells. -1 means no limit.

Methods

AddRow(const RowDefinition& def)
method
Append a row definition. Rows are indexed in the order they are added, starting from 0.
AddColumn(const ColumnDefinition& def)
method
Append a column definition. Columns are indexed in the order they are added, starting from 0.
AddChild(row, col, child)
method
Place a control in a single cell at (row, col). The control occupies exactly one row and one column.
AddChild(row, col, rowSpan, colSpan, child)
method
Place a control starting at (row, col) and extending across rowSpan rows and colSpan columns. Useful for headers or panels that need to span the full width.

Example — messenger layout

The MessangerTest in the test app uses a three-row grid: a star-sized chat area, a fixed-height input panel, and a fixed-height status bar.
class MessangerTest : public Grid
{
public:
    MessangerTest()
    {
        HorizontalAlignment = HorizontalAlign::Stretch;
        VerticalAlignment   = VerticalAlign::Stretch;

        // Row 0: chat history — takes all remaining space
        AddRow(RowDefinition{ GridLength::Star(1.0f) });

        // Row 1: input panel — exactly 3 rows tall
        AddRow(RowDefinition{ GridLength::Cell(3) });

        // Row 2: status bar — exactly 2 rows tall
        AddRow(RowDefinition{ GridLength::Cell(2) });

        // Chat history in row 0
        AddChild(0, 0, init<Border>([&](Border* chatBorder)
        {
            chatBorder->HeaderText = L"Chat: Tamerlan";
            chatBorder->Content = init<ItemsControl<MessageModel>>([&](auto* list)
            {
                list->SetItemsSource(&chatHistory_);
                list->Scrollable    = true;
                list->AutoScrollToEnd = true;
            });
        }));

        // Input grid in row 1
        AddChild(1, 0, std::make_unique<Border>(init<Grid>([&](Grid* inputGrid)
        {
            inputGrid->AddColumn(ColumnDefinition{ GridLength::Auto() });   // spinner
            inputGrid->AddColumn(ColumnDefinition{ GridLength::Auto() });   // label
            inputGrid->AddColumn(ColumnDefinition{ GridLength::Star() });   // text box

            inputGrid->AddChild(0, 0, init<Spinner>([](Spinner* s) { s->Margin = Thickness(1, 0, 1, 0); }));
            inputGrid->AddChild(0, 1, init<Label>([](Label* l) { l->Text = L"Rikitav@Tamerlan> "; }));
            inputGrid->AddChild(0, 2, init<TextBox>([](TextBox* tb)
            {
                tb->HorizontalAlignment = HorizontalAlign::Stretch;
                tb->AcceptsReturn = false;
            }));
        })));
    }
};
When you omit AddRow/AddColumn calls entirely, Grid creates a single implicit Star row and column so it still renders its first child.

StackPanel

StackPanel arranges children in a single line — either vertically (the default) or horizontally. It is the go-to container when you need a simple list of controls without the overhead of a grid.

Properties

ContentOrientation
Orientation
default:"Orientation::Vertical"
Direction in which children are stacked. Set to Orientation::Horizontal for a row of controls.
HorizontalContentAlignment
HorizontalAlign
default:"HorizontalAlign::Stretch"
How each child is aligned on the horizontal axis within the panel’s width.
VerticalContentAlignment
VerticalAlign
default:"VerticalAlign::Stretch"
How each child is aligned on the vertical axis within the panel’s height.
Scrollable
bool
default:"false"
Allow the panel to scroll when children overflow. Requires InvalidationKind::Arrange to take effect after the first layout pass.
AutoScrollToEnd
bool
default:"false"
Automatically advance the scroll offset after every child addition so the last item is always visible. Combine with Scrollable = true for a live-updating log or chat list.
Looping
bool
default:"false"
When focus reaches the last child and the user navigates forward (or vice versa), wrap focus back to the opposite end instead of leaving the panel.

Methods

AddChild(child)
method
Append a control to the end of the stack.
Insert(index, child)
method
Insert a control at the given zero-based position, shifting later children down.
RemoveChild(predicate)
method
Find and remove the first child matching predicate. Returns the removed control as a unique_ptr so you can reuse it.
RemoveAt(index)
method
Remove the child at index and return it.
Clear()
method
Remove all children at once.

Example — vertical menu

auto menu = std::make_unique<StackPanel>();
menu->ContentOrientation = Orientation::Vertical;
menu->HorizontalContentAlignment = HorizontalAlign::Stretch;
menu->Looping = true;

menu->AddChild(init<Button>([](Button* b) { b->Text = L"New game"; }));
menu->AddChild(init<Button>([](Button* b) { b->Text = L"Load game"; }));
menu->AddChild(init<Button>([](Button* b) { b->Text = L"Settings"; }));
menu->AddChild(init<Button>([](Button* b) { b->Text = L"Quit"; }));
StackPanel is the base class of ItemsControl<T>. All properties and methods above are inherited by ItemsControl.

ScrollViewer

ScrollViewer wraps a single child control and provides panning when the child’s desired size exceeds the viewport. The user can scroll with the arrow keys when the control is focused.

Properties

Content
unique_ptr<ControlBase>
The single child to display and scroll. Assign with std::move or init<T>.
ScrollX
int
default:"0"
Horizontal scroll offset in cells. Increasing this value pans the content left.
ScrollY
int
default:"0"
Vertical scroll offset in cells. Increasing this value pans the content up.

Measurement methods

GetExtentWidth()
int
The total width of the scrollable content in cells (the child’s desired width).
GetExtentHeight()
int
The total height of the scrollable content in cells.
GetViewportWidth()
int
The visible width of the ScrollViewer in cells.
GetViewportHeight()
int
The visible height of the ScrollViewer in cells.

Example

auto viewer = std::make_unique<ScrollViewer>();
viewer->HorizontalAlignment = HorizontalAlign::Stretch;
viewer->VerticalAlignment   = VerticalAlign::Stretch;

viewer->Content = init<Label>([](Label* l)
{
    l->Text = L"A very long line that extends beyond the terminal width...";
    l->TextWrapping = TextWrap::NoWrap;
});

// Programmatically jump to position (20, 0)
viewer->ScrollX = 20;

ItemsControl<T>

ItemsControl<T> extends StackPanel with data-binding: you supply an ObservableCollection<T> as the data source and a factory lambda as the item template, and the control keeps its children in sync with the collection automatically.

Key API

SetItemsSource(ObservableCollection<T>*)
method
Bind the control to a collection. ItemsControl subscribes to ItemAdded, ItemRemoved, ItemReplaced, and CollectionCleared events and rebuilds or patches its children accordingly. Pass nullptr to detach.
SetItemTemplate(lambda)
method
Provide a factory function with signature std::unique_ptr<ControlBase>(const T&). Called once per item when the control is bound or when a new item is added. Rebuilds all items immediately when set.
GetItemsSource()
method
Returns the currently bound ObservableCollection<T>*, or nullptr if none is set.

ObservableCollection<T>

ObservableCollection<T> is a std::vector-like container that fires events on every mutation. You own the collection instance; ItemsControl only holds a pointer to it.
EventSignatureFired when
ItemAdded(size_t index, const T&)push_back or insert
ItemRemoved(size_t index, const T&)pop_back or erase
ItemReplaced(size_t index, const T& oldItem, const T& newItem)replace
CollectionCleared()clear
Standard mutation methods (push_back, insert, erase, replace, pop_back, clear) mirror the std::vector API and fire the corresponding events automatically.

Example — chat history

struct MessageModel
{
    bool        isAuthor;
    std::wstring Timestamp;
    std::wstring Text;
};

class ChatView : public Border
{
    ObservableCollection<MessageModel> history_;

public:
    ChatView()
    {
        HeaderText = L"Chat";
        Content = init<ItemsControl<MessageModel>>([&](auto* list)
        {
            list->Scrollable     = true;
            list->AutoScrollToEnd = true;

            list->SetItemsSource(&history_);

            list->SetItemTemplate([](const MessageModel& msg) -> std::unique_ptr<ControlBase>
            {
                return init<Label>([&](Label* l)
                {
                    l->Text = msg.Timestamp + L" " + msg.Text;
                });
            });
        });
    }

    void PushMessage(MessageModel msg)
    {
        // ItemsControl reacts immediately — no manual refresh needed.
        history_.push_back(std::move(msg));
    }
};
Call SetItemsSource before SetItemTemplate if possible. When the template is set, ItemsControl rebuilds all existing items immediately, so setting the source first avoids a double rebuild.

Build docs developers (and LLMs) love