Stacks¶
Tick UI describes a screen with stacks. Layout, clipping, identity, animation, and panel containment each have their own stack that grows as you nest into a section of the screen and shrinks back as you finish with it. Top-down code stays composable because the current state at any line is the top of every stack.
This page is a concept overview. For the procedures that work with each stack, refer to the linked guides.
What every stack has in common¶
Every stack follows the same contract:
- A push gives you a smaller, narrower context to work in.
- A pop returns to the previous one.
- Pushes and pops must balance per frame. Tick UI asserts this in
EndFrame(ValidateStacks); a leak fires aDebug.Assert. - The
Auto()scope captures stack depth on creation and restores on dispose, so you rarely callPopdirectly for layout.
The rect stack: m_rectStack¶
The layout stack. The most-used stack in any Tick UI screen.
Every layout helper (PushRect, Pad, Center, Inset, Top,
Bottom, Left, Right, and the schemas PushSeries*, PushSized,
PushGrid, PushSplit, PushStack*, PushRadial) pushes onto
this stack. Pop() removes one entry.
The top of the rect stack is the current rect: the area drawing
calls render into. Peek() returns it. LayoutToScreen() converts
to screen pixels.
Schema layouts push a container plus a current-element rect. Advance
with Next() (or Next(Size) for stack mode). Terminate with
Pop().
For the full set of helpers and the chainable Auto() scope, refer
to Layout.
The clip stack: m_clipRectStack¶
The scissor stack. PushClipRect adds a clipping rectangle (in
screen space, after Y-flip), and subsequent draw calls are clipped to
it on the GPU. PopClip removes the top entry and restores the
previous clip, or disables the scissor entirely if the stack
empties.
MergeClip and MergeClipFromTop are convenience helpers that
intersect the passed rect with the current top before pushing, so
the new clip is always at least as tight as its parent.
Tick UI seeds this stack with a full-screen clip in BeginFrame
and pops it in EndFrame. Every push you make sits on top of that
seed, and your Pop returns to it.
The ID stack: m_idStack¶
Widget identity. Buttons, textboxes, scroll views, and panels all need a stable ID across frames so Tick UI can remember their state (focused, hovered, scroll position).
PushID(label) and PopID() namespace the IDs computed inside the
scope, so the same widget code can run twice without colliding. Use
this around any code that draws a list of similar items: call
PushID(item.Id) per row.
In editor builds, the TICK_CHECK_DUPLICATE_IDS flag asserts when
two widgets in the same frame share an ID. Define it during
development and any collision shows up immediately with a callstack.
The animation stack: m_animationStack¶
PushAnimate("id") opens an animation context: a fluent builder
that defines Init, Enter, and Leave states and per-event
transitions. The opened context lives on the animation stack until
PopAnimate closes it.
While the context is open, GetColor, GetFloat, GetRect, and
GetInt on the returned context return the animated values for the
current frame, blended according to the active state. Drawing calls
read from these to get smooth hover, press, and reveal effects.
For the full DSL, refer to Animation.
The panel stack: m_panelStack¶
Panels are persistent containers: windows, modals, popups, the
debugger overlay. BeginPanel(id, rect, config) pushes the panel
onto the stack, and EndPanel pops it. Nested BeginPanel calls
record parent-and-child relationships, so a child panel can be
hidden when its parent hides.
Modal behaviour is a per-panel flag. Calling ShowModal(id) sets
the panel's BlocksInput flag, and the input-priority walk in
BeginFrame gives any blocking panel preference over non-blocking
ones. A single flag drives the behaviour, with no separate modal
stack to manage.
Panels also have a per-instance state machine separate from the
push and pop stack: Visible, Hiding, Hidden, plus a
UsedLastFrame flag so first-frame logic can run via
IsPanelFirstShown.
For the full panel workflow, refer to Panels and modals.
Other stacks¶
A few smaller stacks support specific subsystems:
- Color stack (
m_colorStack):PushColorandPopColortint subsequent draws. BarePopdoesn't clear it. Use the explicitPopColor. - Timeline stack (
m_timelineStack):BeginTimelineandEndTimeline. Used by the animation system to scope time-based state.
These follow the same push and pop balance contract.
End-of-frame validation¶
In EndFrame, Tick UI calls ValidateStacks() to assert that
the rect, clip, panel, ID, timeline, and measure stacks are all empty
(the seed clip rect having been popped by EndFrame itself). A leak
means you missed a Pop, EndPanel, or PopID somewhere. The
assert message names which stack leaked. Fix the imbalance before
shipping; in non-development builds the asserts are stripped, so
the leaked state silently corrupts the next frame.
Prefer Auto() for layout (it can't leak rect-stack entries) and
guard non-Auto helpers with using scopes wherever you can.
Additional resources¶
- Frame lifecycle: when stacks are seeded
(
BeginFrame) and validated (EndFrame). - Panels and modals: working with the panel and modal stacks.
- Architecture: how the stack-using code is
organised across the
UIpartial class. - Layout: the
Auto()scope and the rect-stack helpers in detail.