一键导入
gum-tool-undo
Gum undo/redo. Triggers: History tab, UndoManager, UndoPlugin, UndoSnapshot, stale references after undo.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Gum undo/redo. Triggers: History tab, UndoManager, UndoPlugin, UndoSnapshot, stale references after undo.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Gum tool C# code generation. Triggers: CodeGenerator, CodeOutputPlugin, generated code structure, .codsj settings, OutputLibrary selection, Forms codegen, state generation. For CLI/headless codegen see gum-cli.
Where runtime-feature demo screens go. Triggers: adding a sample/demo page for a new runtime feature, NineSliceScreen/SpriteScreen, shape demos, MonoGameGumInCode, Samples/raylib, SilkNetGum.
Forms DefaultVisuals — code-only visual classes backing Forms controls. Triggers: ButtonVisual, any *Visual class in DefaultVisuals/, Styling, DefaultFormsTemplates registration, custom code-only Forms visuals.
Building or modifying a Gum theme package (Themes/Gum.Themes.*) — restyling Forms controls by subclassing their V3 default visuals. Triggers: any file under Themes/, custom *Visual subclassing Gum.Forms.DefaultVisuals.V3.*, theme entry-point methods like EditorTheme.Apply / DarkProTheme.Apply.
Test-first discipline for Gum. Triggers: behavior changes (bug fix or feature) under GumCommon/, Gum/, MonoGameGum/, RenderingLibrary/, KniGum/, FnaGum/, SkiaGum/, RaylibGum/, Tools/Gum.ProjectServices/. Skip for docs, renames, csproj/projitems plumbing, style-only edits.
Unifying per-platform runtime files (MonoGame/Raylib/Skia/KNI/FNA, plus the Apos.Shapes ↔ SkiaGum shape-runtime pair) into one source with
Gum has a snapshot-based undo/redo system scoped per-element. Undo history is displayed in the History tab in the Gum UI tool.
Undo history is stored separately for each open element (Screen, Component, or StandardElement). Switching between elements does not share or merge history — each element maintains its own independent undo stack.
Undos do not record or restore the user's selection state. After undoing or redoing an operation, the selected object in the tree view or canvas may not match what was selected when the change was originally made.
Undo history is entirely in-memory and is cleared when the project is loaded or Gum is closed. There is no way to undo changes made in a previous session.
When an element (Screen, Component, or StandardElement) is deleted, its entire undo history is discarded along with it. Deleting an element cannot be undone.
Undo/redo does not currently work for behavior-related changes. Changes to behaviors (adding, removing, or modifying) on an element may not be correctly undoable.
The History tab in the Gum UI tool displays a human-readable list of all recorded undo actions for the currently selected element. Each entry shows a description of what changed, such as:
Modify element variables: X=10Add instances: MySpriteRemove instances: MySpriteAdd behaviors: MyBehaviorExposed variables: MyVarThe list is built by working backwards through undo snapshots and diffing consecutive states, so descriptions reflect the actual change rather than raw data.
The undo system records changes to:
.ganx sidecar) — folded into the element's UndoSnapshot.Animations
so an animation edit undoes atomically with the state rename/delete it was made next to (#3406).
Keyframes reference states by name, so the two are coupled and must restore together or the
reference desyncs. ElementUndoStrategy captures/diffs/applies animations through the headless
IAnimationUndoProvider seam; the animation plugin implements it (reading the live tab or the
.ganx) and flushes a record from its HandleDataChange choke point via an empty RequestLock.The system uses a two-phase record approach:
RecordState() — Captures a snapshot of the element's current state before a change begins. Called automatically by UndoPlugin on element selection, state selection, etc. Do NOT call this manually from feature code.RecordUndo() — Compares the current state against the recorded snapshot; if anything changed, saves an undo action. Called automatically when an UndoLock is disposed.Always use RequestLock() — never call RecordState() or RecordUndo() manually:
using var undoLock = _undoManager.RequestLock();
// make your changes here
// lock disposal fires RecordUndo() automatically
RequestLock() adds an UndoLock to UndoLocks. When the lock is disposed (end of using block), it removes itself; when UndoLocks reaches 0, HandleUndoLockChanged fires RecordUndo(). The RecordState() baseline is already set by the framework when the user selected the element.
Why not RecordState() manually? RecordState() is a no-op when any locks are held, and calling it outside of that flow risks overwriting the correct baseline snapshot.
Both element and behavior snapshots use CloneElement/CloneBehavior, so every saved snapshot contains new object instances with different references than the live data. When undo is applied, the restored instances replace the live ones — meaning any code holding a reference to the pre-undo instance now has a stale reference that no longer exists in the element or behavior.
Consequence: after an undo, _selectedState.SelectedInstance may point to a stale object. Reference-based lookups (e.g. tree node searches using ==) will fail. Name-based fallback is required to re-locate the logically equivalent node. If undo also changes the instance's name, selection cannot be restored and is silently dropped — this is considered acceptable.
| File | Purpose |
|---|---|
Gum/Undo/UndoManager.cs | Core undo/redo logic; per-element history with Dictionary<ElementSave, ElementHistory> |
Gum/Undo/UndoPlugin.cs | Event handlers that call RecordState() / RecordUndo() |
Gum/Undo/UndoSnapshot.cs | Snapshot structure and diff/comparison logic (UndoComparison) |
Gum/Plugins/InternalPlugins/Undos/UndosViewModel.cs | History tab display and description generation |
Gum/Plugins/InternalPlugins/Undos/UndoDisplay.xaml | WPF ListBox UI for the History tab |
Gum/Plugins/InternalPlugins/Undos/UndoItemViewModel.cs | Individual history item (display text + undo/redo direction) |
Tool/Tests/GumToolUnitTests/Managers/UndoManagerTests.cs | Unit tests for undo behavior |
| Limitation | Details |
|---|---|
| No global undo | Each element has its own undo stack; cross-element changes are not grouped |
| No selection restore | Selection state is not captured or restored on undo/redo |
| No persistence | History is cleared on project load or app close |
| No element-deletion undo | Deleting an element removes its history permanently |
| Behaviors not supported | Behavior changes are not reliably undoable |