gum-layout-engine

Deep reference for maintainers of the Gum layout engine — the UpdateLayout call chain, UpdateChildren ordering, stacking position pipeline, dirty state tracking, and performance optimizations inside GraphicalUiElement. Load when debugging layout performance, optimizing UpdateLayout/UpdateChildren, working on RefreshParentRowColumnDimensionForThis, GetWhatToStackAfter, MakeDirty, ResumeLayoutUpdateIfDirtyRecursive, or _cachedSiblingIndex.

447 stars

Best use case

gum-layout-engine is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Deep reference for maintainers of the Gum layout engine — the UpdateLayout call chain, UpdateChildren ordering, stacking position pipeline, dirty state tracking, and performance optimizations inside GraphicalUiElement. Load when debugging layout performance, optimizing UpdateLayout/UpdateChildren, working on RefreshParentRowColumnDimensionForThis, GetWhatToStackAfter, MakeDirty, ResumeLayoutUpdateIfDirtyRecursive, or _cachedSiblingIndex.

Teams using gum-layout-engine should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/gum-layout-engine/SKILL.md --create-dirs "https://raw.githubusercontent.com/vchelaru/Gum/main/.claude/skills/gum-layout-engine/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/gum-layout-engine/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How gum-layout-engine Compares

Feature / Agentgum-layout-engineStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Deep reference for maintainers of the Gum layout engine — the UpdateLayout call chain, UpdateChildren ordering, stacking position pipeline, dirty state tracking, and performance optimizations inside GraphicalUiElement. Load when debugging layout performance, optimizing UpdateLayout/UpdateChildren, working on RefreshParentRowColumnDimensionForThis, GetWhatToStackAfter, MakeDirty, ResumeLayoutUpdateIfDirtyRecursive, or _cachedSiblingIndex.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Gum 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)

1. **Resolve `updateParent`** — evaluate `ParentUpdateType` flags against actual
   parent state (stacks? depends on children? has ratio children?).

2. **Early out — suspended or invisible** — if layout is suspended
   (`mIsLayoutSuspended` or `IsAllLayoutSuspended`) OR the element is invisible
   and not needed for parent update, call `MakeDirty()` and return. Invisible
   elements also exit if parent is invisible (unless render target).

3. **Early out — propagate to parent** — if `updateParent` is true AND
   `GetIfShouldCallUpdateOnParent()` is true, call `parent.UpdateLayout()` with
   `childrenUpdateDepth + 1` and **return**. The parent's layout will update
   this element as a child. This is how child changes bubble up.

4. **Clear dirty state** — `currentDirtyState = null`. This is critical: it
   prevents double-updates during `ResumeLayoutUpdateIfDirtyRecursive` (see
   below).

5. **Pre-children dimensions** — update dimensions that do NOT depend on
   children (Absolute, PercentageOfParent, etc.) so children have correct parent
   sizes when they lay out.

6. **First children pass (if dimensions depend on children)** — update children
   with absolute layout types so the parent can measure them. With
   `UseFixedStackChildrenSize`, only the first child is updated here (O(1)
   instead of O(n)).

7. **Post-children dimensions** — update RelativeToChildren / RelativeToMaxParentOrChildren
   dimensions now that children have been measured.

8. **Wrapped children pass** — if `WrapsChildren`, update `StackedWrapped`
   children and re-measure dimensions with wrapping considered.

9. **UpdatePosition** — calculate X/Y based on units, origin, parent stacking.
   For stacked children, this calls `GetWhatToStackAfter` to find position
   relative to the previous sibling.

10. **RefreshParentRowColumnDimensionForThis** — if parent stacks, update the
    per-row/column max dimension.

11. **Full children pass** — `UpdateChildren(depth, ChildType.All, ...)` updates
    all children. Children already updated in step 6 are skipped via
    `alreadyUpdated` set.

12. **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.

1. **Find sibling index** — uses `_cachedSiblingIndex` (O(1)) with `IndexOf`
   fallback (O(n)). The cache is valid during `UpdateChildren` but may be stale
   for individual property-change-triggered layouts.

2. **Find previous visible sibling** — walks backward from `thisIndex` skipping
   invisible elements.

3. **Determine wrap** — if wrapping, increments `StackedRowOrColumnIndex` and
   sums `StackedRowOrColumnDimensions` for all previous rows/columns.

4. **Compute offset** — for non-wrapping: previous sibling's position + size +
   `StackSpacing`. For wrapping: row/column dimension sum.

### 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`:
- `ParentUpdateType` — OR'd together across multiple calls
- `ChildrenUpdateDepth` — max of all calls
- `XOrY` — set to null if different axes were dirtied (means update both)

### ResumeLayoutUpdateIfDirtyRecursive

Called when layout is resumed after suspension. Walks the tree:
1. Clear `mIsLayoutSuspended`
2. If `currentDirtyState != null`, call `UpdateLayout` with accumulated state
3. 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 `Ratio` width/height

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 |
|---|---|---|
| `_cachedSiblingIndex` | O(n) `IndexOf` per child → O(n²) total | `GetWhatToStackAfter` |
| `RefreshParentRowColumn` fast path | O(n) rescan per child → O(n²) total | `RefreshParentRowColumnDimensionForThis` |
| `UseFixedStackChildrenSize` | Iterating all children in `GetMaxCellHeight` | `UpdateLayout` step 6, `UpdateHeight` |
| `xOrY` parameter | Recalculating unchanged axis | Throughout |
| `childrenUpdateDepth` | Unbounded recursion | `UpdateChildren` decrements per level |
| `alreadyUpdated` set | Re-updating children measured in pre-pass | `UpdateChildren` |

### Diagnostic Counters

- `UpdateLayoutCallCount` — total layout calls (incremented after parent propagation check)
- `ChildrenUpdatingParentLayoutCalls` — times a child triggered parent relayout

Related Skills

gum-layout

447
from vchelaru/Gum

Reference guide for Gum's layout system — dimension units, position units, children layout modes, layout calculation flow, and layout suspension. Load when working on Width/HeightUnits, XUnits/YUnits, stacking, wrapping, auto-sizing, Anchor/Dock, or GraphicalUiElement layout logic. For deep engine internals (debugging/optimizing UpdateLayout, UpdateChildren, dirty state), see the gum-layout-engine skill instead.

validate-code-changes

447
from vchelaru/Gum

Validate all code changes on the current branch. Spawns QA and refactoring agents in parallel to review for correctness, edge cases, code quality, and pattern adherence. Use when ready to review branch changes before merging.

skills-writer

447
from vchelaru/Gum

Creates and updates skill files (.claude/skills/*/SKILL.md) by reading source code and condensing knowledge into concise reference guides. Use when asked to create a new skill, update an existing skill, or document a subsystem for Claude Code agent context.

gum-variable-deep-dive

447
from vchelaru/Gum

Deep dive into the full variable lifecycle — from VariableSave on ElementSave through runtime application on GraphicalUiElement and Forms controls. Load this when working on styling, theming, RefreshStyles, or when you need to understand how variable values flow from save data to live visuals.

gum-unit-tests

447
from vchelaru/Gum

Reference guide for writing unit tests in the Gum repository. Load this when writing or modifying tests in Gum.ProjectServices.Tests, Gum.Cli.Tests, or any other Gum test project.

gum-tool-viewmodels

447
from vchelaru/Gum

Reference guide for Gum tool ViewModel conventions. Load this when working on ViewModels, XAML views, data binding, DependsOn, or visibility properties in the Gum tool.

gum-tool-variable-references

447
from vchelaru/Gum

Reference guide for Gum's variable reference system — Excel-like cross-instance/cross-element variable binding using Roslyn-parsed assignment syntax. Load this when working on VariableReferenceLogic, EvaluatedSyntax, ApplyVariableReferences, VariableChangedThroughReference, or the VariableReferences VariableListSave.

gum-tool-variable-grid

447
from vchelaru/Gum

Reference guide for Gum's Variables tab and DataUiGrid system. Load this when working on the Variables tab, DataUiGrid control, MemberCategory, InstanceMember, category population, property grid refresh, or category expansion state persistence.

gum-tool-undo

447
from vchelaru/Gum

Reference guide for Gum's undo/redo system. Load this when working on undo/redo behavior, the History tab, UndoManager, UndoPlugin, UndoSnapshot, or stale reference issues after undo.

gum-tool-selection

447
from vchelaru/Gum

Reference guide for Gum's editor selection system. Load this when working on click/drag selection, the rectangle/marquee selector, input handlers (move, resize, rotate, polygon points), the IsActive flag, locked instance behavior, SelectionManager coordination, or the selection event cascade (plugin events, forced default state, tree view sync).

gum-tool-save-classes

447
from vchelaru/Gum

Reference guide for Gum's save/load data model. Load this when working with GumProjectSave, ScreenSave, ComponentSave, StandardElementSave, ElementSave, StateSave, VariableSave, InstanceSave, BehaviorSave, or any serialization/deserialization of Gum project files.

gum-tool-plugins

447
from vchelaru/Gum

Reference guide for the Gum tool's plugin system, including visualization plugins (EditorTabPlugin_XNA, TextureCoordinateSelectionPlugin). Load this when working on plugin registration, PluginBase, InternalPlugin, PluginManager, plugin events, visualization/rendering concerns, or finding which internal plugin owns a feature.