Gum gum-layout-engine
git clone https://github.com/vchelaru/Gum
T=$(mktemp -d) && git clone --depth=1 https://github.com/vchelaru/Gum "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/gum-layout-engine" ~/.claude/skills/vchelaru-gum-gum-layout-engine && rm -rf "$T"
.claude/skills/gum-layout-engine/SKILL.mdGum Layout Engine Internals
For user-facing layout concepts (units, stacking, wrapping, Anchor/Dock), see the gum-layout skill. This skill is for people debugging, optimizing, or extending the engine itself.
All layout logic lives in
GumRuntime/GraphicalUiElement.cs.
UpdateLayout Call Chain
Entry point:
UpdateLayout(ParentUpdateType, int childrenUpdateDepth, XOrY?)
Flow (in order)
-
Resolve
— evaluateupdateParent
flags against actual parent state (stacks? depends on children? has ratio children?).ParentUpdateType -
Early out — suspended or invisible — if layout is suspended (
ormIsLayoutSuspended
) OR the element is invisible and not needed for parent update, callIsAllLayoutSuspended
and return. Invisible elements also exit if parent is invisible (unless render target).MakeDirty() -
Early out — propagate to parent — if
is true ANDupdateParent
is true, callGetIfShouldCallUpdateOnParent()
withparent.UpdateLayout()
and return. The parent's layout will update this element as a child. This is how child changes bubble up.childrenUpdateDepth + 1 -
Clear dirty state —
. This is critical: it prevents double-updates duringcurrentDirtyState = null
(see below).ResumeLayoutUpdateIfDirtyRecursive -
Pre-children dimensions — update dimensions that do NOT depend on children (Absolute, PercentageOfParent, etc.) so children have correct parent sizes when they lay out.
-
First children pass (if dimensions depend on children) — update children with absolute layout types so the parent can measure them. With
, only the first child is updated here (O(1) instead of O(n)).UseFixedStackChildrenSize -
Post-children dimensions — update RelativeToChildren / RelativeToMaxParentOrChildren dimensions now that children have been measured.
-
Wrapped children pass — if
, updateWrapsChildren
children and re-measure dimensions with wrapping considered.StackedWrapped -
UpdatePosition — calculate X/Y based on units, origin, parent stacking. For stacked children, this calls
to find position relative to the previous sibling.GetWhatToStackAfter -
RefreshParentRowColumnDimensionForThis — if parent stacks, update the per-row/column max dimension.
-
Full children pass —
updates all children. Children already updated in step 6 are skipped viaUpdateChildren(depth, ChildType.All, ...)
set.alreadyUpdated -
Post-layout dimension check — if size changed and parent depends on children, re-update dimensions. If still changed, update parent.
UpdateChildren Internals
Two-pass ordering for Ratio dependencies
When some children use
Ratio width/height and siblings use complex units
(RelativeToChildren, PercentageOfOtherDimension, MaintainFileAspectRatio,
ScreenPixel, RelativeToMaxParentOrChildren), those complex-unit siblings must be
updated first. Ratio children need sibling sizes to compute remaining space.
Pass 1 (conditional): update children with
DoesDimensionNeedUpdateFirstForRatio
units. Pass 2: update all remaining children.
shouldFlagAsUpdated
For
Regular layout, children are flagged as updated to avoid redundant work.
For stacked layouts, children are not flagged — they need a second pass to
update positions in order (stacking depends on sibling order).
_cachedSiblingIndex
Set on each child (
child._cachedSiblingIndex = i) in the iteration loop
before calling UpdateLayout. Used by GetWhatToStackAfter to avoid an
O(n) IndexOf call. Falls back to IndexOf if the cache is stale (element
not at expected position).
Stacking Position Pipeline
UpdatePosition → TryAdjustOffsetsByParentLayoutType → GetWhatToStackAfter
GetWhatToStackAfter
Finds the previous visible sibling and computes the offset to stack after it.
-
Find sibling index — uses
(O(1)) with_cachedSiblingIndex
fallback (O(n)). The cache is valid duringIndexOf
but may be stale for individual property-change-triggered layouts.UpdateChildren -
Find previous visible sibling — walks backward from
skipping invisible elements.thisIndex -
Determine wrap — if wrapping, increments
and sumsStackedRowOrColumnIndex
for all previous rows/columns.StackedRowOrColumnDimensions -
Compute offset — for non-wrapping: previous sibling's position + size +
. For wrapping: row/column dimension sum.StackSpacing
RefreshParentRowColumnDimensionForThis
Maintains
parent.StackedRowOrColumnDimensions[rowOrColumnIndex] — the max
cross-axis dimension for each row (LeftToRight) or column (TopToBottom).
O(1) fast path: if this child's dimension >= stored max, just set it. This is the common case during sequential layout (e.g., populating a ListBox).
O(n) fallback: if this child's dimension < stored max, it may have been the max-holder and shrunk. Must rescan all siblings in the same row/column to find the true max.
Dirty State and Suspension
MakeDirty
Called when
UpdateLayout is invoked on a suspended or invisible element.
Accumulates into currentDirtyState:
— OR'd together across multiple callsParentUpdateType
— max of all callsChildrenUpdateDepth
— set to null if different axes were dirtied (means update both)XOrY
ResumeLayoutUpdateIfDirtyRecursive
Called when layout is resumed after suspension. Walks the tree:
- Clear
mIsLayoutSuspended - If
, callcurrentDirtyState != null
with accumulated stateUpdateLayout - Recurse into children
No double-update: the parent's
UpdateLayout (step 2) calls
UpdateChildren, which calls each child's UpdateLayout, which clears that
child's currentDirtyState. When recursion (step 3) reaches that child, its
dirty state is already null — it skips the UpdateLayout call.
EffectiveDirtyStateParentUpdateType
Combines
currentDirtyState.ParentUpdateType with runtime checks:
GetIfParentHasRatioChildren() and GetIfParentStacks(). These are checked
at resume time, not when dirtied, so they reflect current state.
Upward Propagation
GetIfShouldCallUpdateOnParent
Returns true if:
- Parent dimensions depend on children (
)GetIfDimensionsDependOnChildren - Parent stacks children (any non-Regular
)ChildrenLayout - Any sibling uses
width/heightRatio
When true,
UpdateLayout delegates to the parent (step 3 above) instead of
laying out the element directly. The parent will re-lay out all children.
Performance Patterns
| Optimization | What it avoids | Where |
|---|---|---|
| O(n) per child → O(n²) total | |
fast path | O(n) rescan per child → O(n²) total | |
| Iterating all children in | step 6, |
parameter | Recalculating unchanged axis | Throughout |
| Unbounded recursion | decrements per level |
set | Re-updating children measured in pre-pass | |
Diagnostic Counters
— total layout calls (incremented after parent propagation check)UpdateLayoutCallCount
— times a child triggered parent relayoutChildrenUpdatingParentLayoutCalls