Gum gum-variable-deep-dive
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-variable-deep-dive" ~/.claude/skills/vchelaru-gum-gum-variable-deep-dive && rm -rf "$T"
.claude/skills/gum-variable-deep-dive/SKILL.mdVariable Lifecycle Deep Dive
For individual subsystem references, see: gum-tool-save-classes (save model), gum-property-assignment (instantiation + SetProperty), gum-forms-controls (Forms state machine), gum-runtime-variable-references (ApplyAllVariableReferences).
This skill connects them into one end-to-end picture.
The Three Layers
| Layer | Classes | Role |
|---|---|---|
| Save data | , , , | Serialized project data (XML). Edited by the Gum tool. |
| Visual runtime | / | Live layout + rendering. Holds references to StateSave objects. |
| Forms behavioral | subclasses (Button, CheckBox, etc.) | Logical controls. Drive categorical state changes (Highlighted, Disabled, etc.) on the Visual. |
Key Insight: Shared References, Not Copies
During
ToGraphicalUiElement → SetGraphicalUiElement, the method
AddStatesAndCategoriesRecursivelyToGue stores the same StateSave and
StateSaveCategory instances from the ElementSave into the GUE's mStates and
mCategories dictionaries. No cloning occurs.
This means: when
ApplyAllVariableReferences() modifies VariableSave.Value entries on an
ElementSave's states, those changes are immediately visible through the GUE's state
dictionaries. The data is already updated — but the live visuals haven't re-read it yet.
Instantiation Flow (One-Time)
ElementSave.ToGraphicalUiElement() → SetGraphicalUiElement():
- AddStatesAndCategoriesRecursivelyToGue — walks inheritance chain, stores state/category references on the GUE
- CreateGraphicalComponent — creates the underlying
IRenderable - AddExposedVariablesRecursively — registers exposed variable bindings
- CreateChildrenRecursively — for each
, callsInstanceSave
recursively (each child gets its own full instantiation)instance.ToGraphicalUiElement() - SetInitialState →
— applies default state values including instance-qualified variables (e.g.,SetVariablesRecursively
) that set properties on children"MyButton.Width" - AfterFullCreation — triggers Forms wrapping (e.g.,
setsDefaultFromFileButtonRuntime
)FormsControlAsObject = new Button(this)
After this, save data changes do NOT automatically propagate. The GUE must be told to re-read.
Runtime Usage: With vs Without a Gum Project
- With a loaded project:
/ElementSave
/ScreenSave
are present.ComponentSave
creates the full tree. States on the GUE reference the ElementSave's states (shared instances).ToGraphicalUiElement - Without a project:
classes are not used. ButElementSave
,StateSave
, andStateSaveCategory
are still used directly — code can create states in code and callVariableSave
on any GUE.ApplyState()
Applying States at Runtime
Default (uncategorized) state
SetInitialState() calls SetVariablesRecursively(elementSave, elementSave.DefaultState),
which walks the inheritance chain (base type defaults first), then applies the element's own
default state via ApplyState. This includes instance-qualified variables that set properties
on children.
Categorical states (Forms)
Forms controls call
UpdateState() in response to interactions (hover, click, disable).
UpdateState() determines the current state name (e.g., "Highlighted") and calls
Visual.SetProperty("ButtonCategoryState", stateName), which triggers ApplyState on the
matching StateSave within the category. This applies on top of the default state.
The ordering matters
During creation, the order is: children get their own defaults first, then the parent's default state applies instance-qualified overrides on top. Categorical states (Forms) are applied last.
Refreshing After Style Changes
After calling
ApplyAllVariableReferences() to propagate variable reference changes into
VariableSave values, call RefreshStyles() to push those values to live visuals:
— recursively re-applies default states, current Forms categorical states, and runtime properties on a subtreeGraphicalUiElement.RefreshStyles()
— convenience that callsGumService.Default.RefreshStyles()
on all three roots (Root, PopupRoot, ModalRoot)RefreshStyles()
Three-pass architecture
RefreshStyles uses a three-pass approach to preserve runtime state:
- Save — walks the tree calling
on each Forms control. Controls save values that are stored on the visual layer and would be lost during state re-application.FrameworkElement.SaveRuntimeProperties() - Refresh — walks the tree calling
(children first, then parent instance-qualified variables on top).SetVariablesRecursively - Restore — walks the tree calling
+UpdateState()
on each Forms control. This re-applies the current categorical state and restores any saved runtime values.ApplyRuntimeProperties()
The save must happen before ANY state re-application, and the restore must happen AFTER ALL state re-application. This prevents parent instance-qualified variables (e.g.,
"TextBoxInstance.Text" = "") from overwriting restored values.
Controls with runtime state preservation
| Control | SaveRuntimeProperties | ApplyRuntimeProperties |
|---|---|---|
| Slider | — | |
| ScrollBar | — | |
| ScrollViewer | — | Re-syncs from ScrollBar values |
| TextBoxBase | Saves text, caret index, selection length | Restores text, caret, selection; calls , , |
| ComboBox | Saves displayed text | Restores displayed text |
| CheckBox/RadioButton | — | No override needed; factors in |
| Expander | — | No override needed; sets |
The delegates
SaveFormsRuntimePropertiesAction and UpdateFormsStateAction on
GraphicalUiElement bridge the GumRuntime → MonoGameGum boundary (GumRuntime cannot
reference FrameworkElement directly).
Typical workflow:
// 1. Change a style variable var colorState = ObjectFinder.Self.GetElementSave("Standards/ColoredRectangle") .DefaultState; colorState.GetVariableSave("Red").Value = 255; // 2. Propagate variable references across the project ObjectFinder.Self.GumProjectSave.ApplyAllVariableReferences(); // 3. Push to all live visuals GumService.Default.RefreshStyles();