gum-property-assignment
$
npx mdskill add vchelaru/Gum/gum-property-assignmentAssign renderable properties via typed setters or string paths.
- Updates text fonts, sprites, and UI elements directly.
- Depends on Gum's CustomSetPropertyOnRenderable bridge.
- Executes via ApplyState or SetProperty functions.
- Modifies underlying renderable objects immediately.
SKILL.md
.github/skills/gum-property-assignmentView on GitHub ↗
---
name: gum-property-assignment
description: Reference guide for how Gum applies variables and sets properties on renderables. Load this when working on ApplyState, SetProperty, SetVariablesRecursively, CustomSetPropertyOnRenderable, font loading, IsAllLayoutSuspended, or isFontDirty.
---
# Gum Property Assignment Reference
## Two Paths for Setting Properties on a GUE
**Direct property setters** (e.g. `textRuntime.Font = "Arial"`) — these are typed C# properties
on `TextRuntime` or `GraphicalUiElement` that immediately call helpers like `UpdateToFontValues()`.
**String-based path** (e.g. `SetProperty("Font", "Arial")`) — used by `ApplyState` and
`SetVariablesRecursively` when processing state variables. This path goes through:
```
ApplyState / SetVariablesRecursively
→ GraphicalUiElement.SetProperty(string, object)
→ CustomSetPropertyOnRenderable.SetPropertyOnRenderable(...)
→ TrySetPropertyOnText / TrySetPropertyOnSprite / etc.
→ modifies the underlying renderable directly
```
`CustomSetPropertyOnRenderable` is the bridge between the string-based variable system and the
actual renderable objects. It lives in `Gum/Wireframe/CustomSetPropertyOnRenderable.cs` (with
parallel copies in `Runtimes/SkiaGum/` and `Runtimes/RaylibGum/`).
The delegate `GraphicalUiElement.UpdateFontFromProperties` is wired to the static
`CustomSetPropertyOnRenderable.UpdateToFontValues(IText, GUE)` at startup. This is how the
string path and the instance method path both ultimately call the same font loading logic.
All `CustomSetPropertyOnRenderable` statics (including `FontService`) are wired by
`EditorTabPlugin_XNA.StartUp()` in the Gum tool. The class itself must not reference DI
containers or service locators. Game runtimes assign `FontService` directly.
---
## Font Deferred-Loading System
Loading a `.fnt` file is expensive. A text element has ~6 font-related properties (Font,
FontSize, IsBold, IsItalic, UseFontSmoothing, OutlineThickness), so without deferral each
property assignment during a screen load triggers a separate disk read.
### The Flag: `isFontDirty`
`GraphicalUiElement.isFontDirty` is set to `true` instead of loading the font when layout is
suspended. It is consumed (font loaded, flag cleared) by `UpdateFontRecursive()`.
### Where deferral happens
| Path | Defers for `IsAllLayoutSuspended`? | Defers for `IsLayoutSuspended`? |
|------|------------------------------------|---------------------------------|
| `GUE.UpdateToFontValues()` (direct setters) | Yes | Yes |
| `CustomSetPropertyOnRenderable.UpdateToFontValues` (string path) | Yes | **No** |
The string path deliberately does **not** defer for instance-level `IsLayoutSuspended`. Doing so
would cause cascading parent layout calls when `UpdateFontRecursive` later assigns the `BitmapFont`
to a `Text` with `RelativeToChildren` dimensions inside `ResumeLayoutUpdateIfDirtyRecursive`. See
the long comment at the top of that static method for the full explanation.
### Where fonts actually load
Two code paths consume `isFontDirty`:
1. **`WireframeObjectManager`** (Gum tool screen load): after `IsAllLayoutSuspended = false`,
calls `RootGue.UpdateFontRecursive()` then `RootGue.UpdateLayout()`. At this point all
elements have `mIsLayoutSuspended = false` (because `ApplyState` skips `SuspendLayout` when
the global flag is set), so every dirty text element loads its font in one pass.
2. **`ResumeLayoutUpdateIfDirtyRecursive`** (instance-level suspension): sets
`mIsLayoutSuspended = false` on the current element *before* calling `UpdateFontRecursive()`,
then recurses to children. This ordering is critical — if `mIsLayoutSuspended` were still true
when `UpdateFontRecursive` runs, that element's font load would be skipped.
### Known gap
When fonts are set via the string path (`SetProperty`) during an `ApplyState` that uses
instance-level `SuspendLayout` (not `IsAllLayoutSuspended`), fonts still load immediately —
one disk read per property assignment. This is the `MonoGame ApplyState` path; fixing it
requires solving the cascading layout problem described in `CustomSetPropertyOnRenderable`.
---
## Key Files
| File | Role |
|------|------|
| `GumRuntime/GraphicalUiElement.cs` | `SetProperty`, `ApplyState`, `UpdateToFontValues` (instance), `UpdateFontRecursive`, `isFontDirty` |
| `Gum/Wireframe/CustomSetPropertyOnRenderable.cs` | String-path dispatch + static `UpdateToFontValues(IText, GUE)` |
| `GumRuntime/ElementSaveExtensions.cs` | `SetVariablesRecursively` — iterates state variables and calls `ApplyState` |
| `Gum/Wireframe/WireframeObjectManager.cs` | Sets `IsAllLayoutSuspended` around screen load, calls `UpdateFontRecursive` after |
| `Tool/EditorTabPlugin_XNA/MainEditorTabPlugin.cs` | Wires all `CustomSetPropertyOnRenderable` statics in `StartUp()` |