Terminality does not redraw the terminal on every frame. Instead it maintains a double-buffered cell grid, compares the new frame to the previous snapshot, and writes only the cells that changed. This dirty-rect approach keeps output minimal even when many controls update simultaneously, which matters for flicker-free rendering in a terminal environment.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.
RenderBuffer
RenderBuffer is the in-memory representation of the terminal screen. It holds two grids of CellInfo structs — the live buffer being written to, and a snapshot of what was last flushed — along with a Rect dirtyRect that grows to enclose every cell modified since the last flush.
| Method | Purpose |
|---|---|
SetCell(x, y, cell) | Write one cell and expand dirtyRect to include it |
GetCell(x, y) | Read a cell from the live buffer |
Snapshot() | Copy the live buffer into the snapshot buffer |
DiffRender(out) | Emit ANSI escape sequences only for cells that differ from the snapshot |
BulkRender(out) | Emit the full screen, ignoring the snapshot |
Resize(w, h) | Resize both buffers; capped at MAX_WIDTH (512) × MAX_HEIGHT (256) |
Clear(cell) | Fill the live buffer with a given cell |
DiffRender finishes, VisualTree calls Snapshot() so the next diff is against the current output, not stale data.
Dirty-rect tracking
VisualTree keeps its own Rect dirtyRect_ alongside the RenderBuffer’s cell-level dirty rect. When a node calls InvalidateVisual(), the dirty flag propagates up the tree via OnChildInvalidated(). On the next frame, VisualTree::Render() collects the union of all dirty node rectangles and passes them to RenderBuffer as the region to flush.
This means a single property change — say, a ProgressBar value update — results in only that control’s cells being compared and written, regardless of how large the rest of the UI is.
RenderContext
RenderContext is passed to your RenderOverride() method. It wraps the RenderBuffer with a clipping rectangle scoped to the control’s arranged bounds, so you cannot accidentally draw outside your allocated area.
CreateInner(rect) returns a child context clipped to rect within the current context — useful when a panel delegates rendering to a sub-region.
RenderText overloads
RenderText accepts std::string, std::wstring, const char*, and const wchar_t*. Set wrap = true to hard-wrap at the context boundary.
RenderRectangle and RenderLine
RenderRectangle fills a rectangular region. Overloads accept a RectangleStyle function pointer — wchar_t (*)(const Point&, const Size&) — so you can provide custom border characters:
RenderLine draws a horizontal or vertical line using a VectorStyle function for custom glyphs.
RenderStream
RenderStream provides a stream-style API for building text output inside RenderOverride. Obtain one from context.BeginText(), then use operator<< to set position, colours, and text:
| Input type | Effect |
|---|---|
Point | Move the write cursor to that position |
RenderStreamColor | Change foreground and/or background colour |
std::wstring / std::string / const wchar_t* / const char* | Write text at the current cursor position |
int32_t, uint32_t, float, double | Write the formatted numeric value |
endl | Advance to the next line |
InvalidationKind and when rendering is triggered
Controls never call rendering APIs directly outside ofRenderOverride. Instead, property changes raise an InvalidationKind that schedules the appropriate pass:
InvalidationKind::Visual
Only
RenderOverride is called again. Position and size are unchanged. Use this for colour or text-content changes.InvalidationKind::Arrange
ArrangeOverride runs, then RenderOverride. Use this when child positions change but measured sizes do not.InvalidationKind::Measure
Full pipeline:
MeasureOverride → ArrangeOverride → RenderOverride. Use this when the control’s natural size changes.InvalidationKind::None
No layout pass. Only
OnPropertyChanged and PropertyChanged fire. Use this for metadata like Tag or ContextMenu.The three override extension points
Every control that inherits fromControlBase implements these three protected pure virtuals:
MeasureOverride
Called byControlBase::Measure() after margins and size constraints have been applied. Return the natural content size. For a leaf control this is typically computed from content length; for a panel it is the union of all children’s measured sizes.
ArrangeOverride
Called byControlBase::Arrange() after alignment and margin have been resolved into arrangedRect_. Use it to position child controls by calling child->Arrange(childRect).
RenderOverride
Called byControlBase::Render() after the background rectangle has been painted. Receives a RenderContext clipped to arrangedRect_. Draw text, shapes, and lines here.
ControlBase::Render() always fills the background using ForegroundColor and BackgroundColor before calling RenderOverride. You do not need to clear the background yourself unless you need a non-uniform fill.