Gum gum-cross-platform-unification
Rules for unifying per-platform runtime files (MonoGame/Raylib/Skia/KNI/FNA) into a single source with #if directives. Load this when consolidating duplicate Runtime classes (SpriteRuntime, NineSliceRuntime, etc.) into MonoGameGum/GueDeriving/*.cs and linking them into Runtimes/RaylibGum and Runtimes/SkiaGum csprojs.
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-cross-platform-unification" ~/.claude/skills/vchelaru-gum-gum-cross-platform-unification && rm -rf "$T"
.claude/skills/gum-cross-platform-unification/SKILL.mdGum Cross-Platform Runtime Unification
The Pattern
Per-platform runtimes (e.g.
ColoredRectangleRuntime, TextRuntime, ContainerRuntime) historically live as three separate files — one each in MonoGameGum/GueDeriving/, Runtimes/RaylibGum/GueDeriving/, and Runtimes/SkiaGum/GueDeriving/. Unification collapses them into one source file in MonoGameGum/GueDeriving/ with #if RAYLIB / #if SKIA / #if XNALIKE directives, then links that file into the Raylib and Skia csprojs via <Compile Include="..\..\MonoGameGum\GueDeriving\FooRuntime.cs" Link="GueDeriving\FooRuntime.cs" />.
Reference implementations:
TextRuntime.cs (#2509, #2510) and ContainerRuntime.cs (#2511). Read those before doing a new one — they set the idioms for using aliasing (Color, renderable type), XNALIKE symbol, namespace switching.
Disagreements Are the Whole Job
When three files diverge, every difference falls into one of two buckets:
- Platform-necessary divergence — the platforms genuinely can't do the same thing. Keep under
. Examples: XNA#if
vs Raylib's absent blend state; Skia'sBlendState
vs XNASKColor
; Raylib's lack of anColor
property onAlpha
.SolidRectangle - Historical inconsistency — one or more platforms drifted from the others, probably by accident. One of them is wrong and needs to be corrected, not preserved.
You cannot tell these apart by reading the code. The file doesn't know whether a difference is intentional. You have to ask.
Always-Ask Checklist
Before writing a single line of the unified file, diff these across all three platforms and surface every mismatch to the user with a recommendation:
- Base class.
vsGraphicalUiElement
is not cosmetic —InteractiveGue
absorbs pointer events viaInteractiveGue
. Promoting a decorative runtime (ColoredRectangle, Sprite, NineSlice) toHasEvents
silently breaks click-through in every downstream project, with no compile error and no runtime error.InteractiveGue
default. ExplicitHasEvents
/HasEvents = true
/ unset. Container sets it true on purpose. Most runtimes should leave it at the default (false).false- Constructor defaults.
,DefaultWidth
,DefaultHeight
, initialDefaultColor
, initialWidth/Height
,Text
, etc. If one platform defaults to 50×50 and another to 0×0, that's a bug in one of them — decide which.Font - Renderable type. Skia ColoredRectangleRuntime uses
while MG/Raylib useRoundedRectangle
. Changing the renderable class affects draw order, batching, and clip behavior. Preserve per-platform unless the user explicitly signs off on unifying.SolidRectangle - Property coverage. If platform A exposes
/Alpha
/BlendState
and platform B doesn't, decide whether B should gain it (via the underlying renderable's capability, e.g.MaxLettersToShow
on Raylib) or stay gated underColor.A
.#if
override. Some per-platform versions reset cached renderable fields, others don't. Missing aClone()
override leaks a staleClone()
pointer after cloning. Add it to all unified runtimes.mContainedX
obsolete wrapper. Present on MG, often absent on Raylib/Skia. Should usually be added everywhere for API parity.AddToManagers()
on setters. MG typically has it, Raylib/Skia often don't. Usually safe to add everywhere (binding/data-flow consumers benefit).NotifyPropertyChanged
The Rule That Matters
Any disagreement on base class or
gets surfaced to the user with options before any code is written. These two silently change input behavior across every downstream project. They are never safe to "just pick one."HasEvents
For other disagreements: pick a default, but state the disagreement and the chosen resolution in your message before writing the code — not after in a summary. If the user disagrees, you've lost two minutes, not a release.
Lessons From Past Breakage
- ColoredRectangleRuntime unification first pass promoted MG+Raylib from
toGraphicalUiElement
to match Skia. This would have made every decorative colored rectangle in every consumer project start absorbing clicks. Caught and reverted before merge. The correct resolution was the opposite direction — correct Skia down toInteractiveGue
, since nothing in a decorative rectangle should eat events.GraphicalUiElement - Takeaway: when two platforms agree and one doesn't, the outlier is more often wrong than the other two. Default to the majority behavior unless there's a platform-specific reason.
Mechanical Steps
- Read all three per-platform source files end to end. Write down every difference.
- Classify each difference as platform-necessary or historical inconsistency. Ask the user about anything you can't classify with certainty — and always ask about base class and
.HasEvents - Write the unified file in
, using the TextRuntime / ContainerRuntime idioms for:MonoGameGum/GueDeriving/
symbol at the top (XNALIKE
).#if MONOGAME || FNA || KNI
andusing Color = ...;
aliases per platform.using ContainedXType = ...;- Namespace switch:
(Raylib),Gum.GueDeriving
(Skia),SkiaGum.GueDeriving
(default).MonoGameGum.GueDeriving
- Add
to<Compile Include="..\..\MonoGameGum\GueDeriving\FooRuntime.cs" Link="GueDeriving\FooRuntime.cs" />
andRuntimes/RaylibGum/RaylibGum.csproj
.Runtimes/SkiaGum/SkiaGum.csproj - Delete the old per-platform files.
- Build
— not individual csprojs. Plugin post-build scripts referenceAllLibraries.sln
.$(SolutionDir) - Run any
-filtered tests.FooRuntime
What This Skill Is Not
Not a general refactoring guide. Not a pattern for unifying non-runtime files. Specifically: the runtime unification pattern (shared source +
#if + csproj linking) is appropriate because the Raylib and Skia runtime projects are small wrappers around the MonoGame-style API. Do not apply this pattern to tool code, to GumCommon code (which already lives in one place and is shared differently), or to Forms controls (which are linked as a directory glob).