Variable Lifecycle Deep Dive
SkillDev toolsFull variable lifecycle, VariableSave on ElementSave through runtime application on GraphicalUiElement and Forms controls. Triggers: styling, theming, RefreshStyles, value flow from save data to live visuals.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Variable Lifecycle Deep Dive skill
What this skill tells your AI
The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-variable-deep-dive/SKILL.md and read by ahel’s review.
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 | ElementSave, StateSave, VariableSave, StateSaveCategory | Serialized project data (XML). Edited by the Gum tool. |
| Visual runtime | GraphicalUiElement / InteractiveGue | Live layout + rendering. Holds references to StateSave objects. |
| Forms behavioral | FrameworkElement 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
InstanceSave, callsinstance.ToGraphicalUiElement()recursively (each child gets its own full instantiation) - SetInitialState →
SetVariablesRecursively— applies default state values including instance-qualified variables (e.g.,"MyButton.Width") that set properties on children - AfterFullCreation — triggers Forms wrapping (e.g.,
DefaultFromFileButtonRuntimesetsFormsControlAsObject = 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/ComponentSaveare present.ToGraphicalUiElementcreates the full tree. States on the GUE reference the ElementSave's states (shared instances). - Without a project:
ElementSaveclasses are not used. ButStateSave,StateSaveCategory, andVariableSaveare still used directly — code can create states in code and callApplyState()on any GUE.
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:
GraphicalUiElement.RefreshStyles()— recursively re-applies default states, current Forms categorical states, and runtime properties on a subtreeGumService.Default.RefreshStyles()— convenience that callsRefreshStyles()on all three roots (Root, PopupRoot, ModalRoot)
Three-pass architecture
RefreshStyles uses a three-pass approach to preserve runtime state:
- Save — walks the tree calling
FrameworkElement.SaveRuntimeProperties()on each Forms control. Controls save values that are stored on the visual layer and would be lost during state re-application. - Refresh — walks the tree calling
SetVariablesRecursively(children first, then parent instance-qualified variables on top). - Restore — walks the tree calling
UpdateState()+ApplyRuntimeProperties()on each Forms control. This re-applies the current categorical state and restores any saved runtime values.
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 | — | UpdateThumbPositionAccordingToValue() |
| ScrollBar | — | UpdateThumbPositionAccordingToValue() |
| ScrollViewer | — | Re-syncs innerPanel.Y/X from ScrollBar values |
| TextBoxBase | Saves text, caret index, selection length | Restores text, caret, selection; calls UpdatePlaceholderVisibility, UpdateToSelection, UpdateToIsFocused |
| ComboBox | Saves displayed text | Restores displayed text |
| CheckBox/RadioButton | — | No override needed; UpdateState() factors in IsChecked |
| Expander | — | No override needed; UpdateState() sets contentContainer.Visible |
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();
Signals
- GitHub stars
- 620
- Forks
- 80
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gum-variable-deep-dive- Source
- github.com/vchelaru/gum