Gum gum-tool-selection
Reference guide for Gum's editor selection system. Load this when working on click/drag selection, the rectangle/marquee selector, input handlers (move, resize, rotate, polygon points), the IsActive flag, locked instance behavior, SelectionManager coordination, or the selection event cascade (plugin events, forced default state, tree view sync).
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-tool-selection" ~/.claude/skills/vchelaru-gum-gum-tool-selection && rm -rf "$T"
.claude/skills/gum-tool-selection/SKILL.mdGum Editor Selection System Reference
Overview
Selection in the wireframe (XNA) editor is coordinated by
SelectionManager. It delegates specific interactions to a set of input handlers, each responsible for one type of gesture (move, resize, rotate, polygon point editing). A separate rectangle selector handles marquee/rubber-band multi-selection. Locking (InstanceSave.Locked) cuts across all of these.
Input Handlers
Base class:
Tool/EditorTabPlugin_XNA/Editors/Handlers/InputHandlerBase.cs
Each handler represents one interaction mode. Concrete handlers:
| Handler | File | Responsibility |
|---|---|---|
| | Drag-to-move selected instance(s) |
| | Resize handle dragging |
| | Rotation handle dragging |
| | Polygon vertex select/move/add/delete |
Handler Lifecycle
Mouse down → HandlePush(x, y) → returns true to claim gesture; sets IsActive = true Mouse drag → OnDrag() → only meaningful when IsActive; applies transform Mouse up → OnRelease() → cleans up; resets IsActive to false
HandlePush returns bool: true means this handler claims the gesture and sets IsActive = true; false passes to the next handler or the rectangle selector.
IsActive
Flag
IsActiveIsActive = true signals that a handler owns the current drag gesture. It suppresses the rectangle selector — SelectionManager passes isHandlerActive = true to RectangleSelector.HandleDrag, which returns immediately. Must be set in HandlePush when claiming a gesture and reset in OnRelease.
The base class
HandlePush automatically checks Context.IsSelectionLocked() and returns false if locked. Handlers that override HandlePush must replicate or explicitly call this check.
Rectangle Selector (Marquee Selection)
File:
Tool/EditorTabPlugin_XNA/RectangleSelector.cs
The rectangle selector activates on drag when no handler is active and the cursor is not over the element body (or Shift is held for additive selection), after a minimum drag distance is exceeded.
SelectionManager passes isHandlerActive based on whether any handler's IsActive is true.
GetElementsInRectangle() finds visible elements whose bounds intersect the drag rectangle, skipping ScreenSave elements and instances where Locked == true. On release, it either replaces the selection or toggles additively (Shift held).
Locking (InstanceSave.Locked
)
InstanceSave.LockedInstanceSave.Locked is defined in GumDataTypes/InstanceSave.cs. The helper EditorContext.IsSelectionLocked() (in Tool/EditorTabPlugin_XNA/Editors/EditorContext.cs) returns true when the selected instance is locked.
Where Locking Is Enforced
| Location | File | What It Prevents |
|---|---|---|
| | Base lock check; handlers that don't override inherit this |
| | Overrides base; manually checks lock before allowing vert select/add |
| | Prevents DEL key from deleting verts |
| | Hides the "add point" sprite on polygon edges |
| | Skips locked instances in multi-selection moves |
| | Skips locked instances during resize |
| | Skips locked instances during axis-lock correction |
| | Skips locked instances when writing axis-lock to state |
| | Skips locked instances during snap-to-unit |
| | Excludes locked instances from marquee results |
| | Prevents click-selection of locked instances on canvas |
(variable grid) | | Disables Add/Delete/Edit in list variables (e.g. polygon Points) |
Locked + IsActive Interaction (Critical)
When a locked instance is selected and the cursor is over one of its polygon verts,
PolygonPointInputHandler.HandlePush must: detect the vert, set IsActive = true (to suppress the rectangle selector), but not set _grabbedIndex (so OnDrag is a no-op), and return true to consume the push. Without setting IsActive, the rectangle selector activates on drag because the cursor over a vert is typically not "over body".
Locked Selection Display
LockedSelectionVisual draws a dashed bounding rectangle for a locked selected instance, replacing the resize handles that would normally appear. It shows regardless of the instance's Visible property. Registered in StandardWireframeEditor; not used in PolygonWireframeEditor.
Locked Instances Are Still Tree-Selectable
Locked instances cannot be canvas-clicked or rectangle-selected, but can always be selected via the tree view — the only way to select a locked instance to unlock it. Multi-selection of mixed locked/unlocked is supported; transforms apply only to unlocked members.
_lastPushWasOnLockedBody
_lastPushWasOnLockedBodyTracked in
SelectionManager.ProcessInputForSelection() — set to true when the selected instance is locked and the cursor is over the body. Used in ProcessRectangleSelection() to prevent deselection when the user releases the mouse over a locked body without dragging.
Selection Event Cascade
When the user selects an instance (via tree view or wireframe),
SelectedState orchestrates a synchronous cascade of plugin events:
User selects instance → SelectedState.HandleSelectedInstances() → PerformAfterSelectInstanceLogic() → SelectedStateSave = element.States[0] (forced default state) → PluginManager.ReactToStateSaveSelected() ← fires FIRST → PluginManager.InstanceSelected() ← fires SECOND
Key behaviors:
- State selection fires BEFORE instance selection (from inside
). State is only force-selected when the current state doesn't belong to the new element (checked viaPerformAfterSelectInstanceLogic
).AllStates.Contains - Both events trigger
inRefreshEntireGrid
. AMainVariableGridPlugin
flag prevents the double refresh — set by_stateJustRefreshedGrid
, checked and consumed byHandleStateSelected
.HandleInstanceSelected
responds toMainTreeViewPlugin
by syncing the tree view node. It setsInstanceSelected
onSuppressCallAfterClickSelect
so theElementTreeViewManager
methods update the visual tree node without re-firingSelect
, which would cause a redundant plugin cascade.CallAfterClickSelect
vs IsInUiInitiatedSelection
: SuppressCallAfterClickSelect
IsInUiInitiatedSelection is set during OnSelect to prevent programmatic Select calls from re-entering while the tree view processes a user-initiated selection — but it's cleared before plugin events fire, so it doesn't prevent the MainTreeViewPlugin sync path. SuppressCallAfterClickSelect handles that case specifically.
Cascade Key Files
| File | Purpose |
|---|---|
| , , |
| , event dispatch |
| Tree view sync with |
| methods, , both suppression flags |
| double-refresh guard |
Key Files Summary
| File | Purpose |
|---|---|
| Main coordinator; manages , routes events to handlers, passes to rectangle selector |
| Marquee selection; activation gated on and |
| Base class; provides default with lock check |
| Move gesture; also handles axis lock and snap-to-unit for multi-selection |
| Resize handle gestures |
| Polygon vertex editing; overrides (must manage lock manually) |
| Provides helper used throughout handlers |
| Dashed bounding outline for locked selected instances; display-only, no interaction |
| property definition |
| ; skips locked instances in multi-move |
| Variable grid list control; respects (driven by ) |