Agent Skillsvchelaru/Gum › gum-tool-selection

gum-tool-selection

GitHub

Gum编辑器选择系统参考,涵盖SelectionManager协调、输入处理器(移动/缩放/旋转/多边形)生命周期及矩形选区逻辑。

.claude/skills/gum-tool-selection/SKILL.md vchelaru/Gum

Trigger Scenarios

询问编辑器选择交互逻辑 排查SelectionManager或InputHandler相关Bug

Install

npx skills add vchelaru/Gum --skill gum-tool-selection -g -y
More Options

Non-standard path

npx skills add https://github.com/vchelaru/Gum/tree/main/.claude/skills/gum-tool-selection -g -y

Use without installing

npx skills use vchelaru/Gum@gum-tool-selection

指定 Agent (Claude Code)

npx skills add vchelaru/Gum --skill gum-tool-selection -a claude-code -g -y

安装 repo 全部 skill

npx skills add vchelaru/Gum --all -g -y

预览 repo 内 skill

npx skills add vchelaru/Gum --list

SKILL.md

Frontmatter
{
    "name": "gum-tool-selection",
    "description": "Gum editor selection — click\/drag, marquee, input handlers (move\/resize\/rotate\/polygon points), IsActive flag, locked instances, SelectionManager coordination, selection event cascade (plugin events, forced default state, tree view sync)."
}

Gum 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.

This migrated in two parts: MoveInputHandler/ResizeInputHandler/RotationInputHandler/InputHandlerBase, EditorContext, SelectionManager, and RectangleSelector now live under the headless Tools/Gum.Presentation/...; PolygonPointInputHandler and LockedSelectionVisual are still under Tool/EditorTabPlugin_XNA/Editors/....

Input Handlers

Base class: Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/InputHandlerBase.cs

Each handler represents one interaction mode. Concrete handlers:

Handler File Responsibility
MoveInputHandler Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/MoveInputHandler.cs Drag-to-move selected instance(s)
ResizeInputHandler Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/ResizeInputHandler.cs Resize handle dragging
RotationInputHandler Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/RotationInputHandler.cs Rotation handle dragging
PolygonPointInputHandler Tool/EditorTabPlugin_XNA/Editors/Handlers/PolygonPointInputHandler.cs 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

IsActive = 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: Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/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.Locked is defined in GumDataTypes/InstanceSave.cs. The helper EditorContext.IsSelectionLocked() (in Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/EditorContext.cs) returns true when the selected instance is locked.

Where Locking Is Enforced

Location File What It Prevents
InputHandlerBase.HandlePush() InputHandlerBase.cs Base lock check; handlers that don't override inherit this
PolygonPointInputHandler.HandlePush() PolygonPointInputHandler.cs Overrides base; manually checks lock before allowing vert select/add
PolygonPointInputHandler.TryHandleDelete() PolygonPointInputHandler.cs Prevents DEL key from deleting verts
PolygonPointInputHandler.UpdateHover() PolygonPointInputHandler.cs Hides the "add point" sprite on polygon edges
ElementCommands.MoveSelectedObjectsBy() Gum/ToolCommands/ElementCommands.cs Skips locked instances in multi-selection moves
ResizeInputHandler.ApplySizeChange() ResizeInputHandler.cs Skips locked instances during resize
MoveInputHandler.ApplyAxisLockIfNeeded() MoveInputHandler.cs Skips locked instances during axis-lock correction
MoveInputHandler.ApplyAxisLockToSelectedState() MoveInputHandler.cs Skips locked instances when writing axis-lock to state
MoveInputHandler.SnapSelectedToUnitValues() MoveInputHandler.cs Skips locked instances during snap-to-unit
RectangleSelector.GetElementsInRectangle() RectangleSelector.cs Excludes locked instances from marquee results
SelectionManager.ReverseLoopToFindIpso() SelectionManager.cs Prevents click-selection of locked instances on canvas
ListBoxDisplay (variable grid) WpfDataUi/Controls/ListBoxDisplay.xaml.cs 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

Tracked 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 PerformAfterSelectInstanceLogic). State is only force-selected when the current state doesn't belong to the new element (checked via AllStates.Contains).
  • Both events trigger RefreshEntireGrid in MainVariableGridPlugin. A _stateJustRefreshedGrid flag prevents the double refresh — set by HandleStateSelected, checked and consumed by HandleInstanceSelected.
  • MainTreeViewPlugin responds to InstanceSelected by syncing the tree view node. It sets SuppressCallAfterClickSelect on ElementTreeViewManager so the Select methods update the visual tree node without re-firing CallAfterClickSelect, which would cause a redundant plugin cascade.

IsInUiInitiatedSelection vs 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
Gum/ToolStates/SelectedState.cs HandleSelectedInstances, PerformAfterSelectInstanceLogic, HandleStateSaveSelected
Gum/Plugins/PluginManager.cs InstanceSelected, ReactToStateSaveSelected event dispatch
Gum/Plugins/InternalPlugins/TreeView/MainTreeViewPlugin.cs Tree view sync with SuppressCallAfterClickSelect
Gum/Plugins/InternalPlugins/TreeView/ElementTreeViewManager.cs Select methods, CallAfterClickSelect, both suppression flags
Gum/Plugins/InternalPlugins/VariableGrid/MainVariableGridPlugin.cs _stateJustRefreshedGrid double-refresh guard

Key Files Summary

File Purpose
Tools/Gum.Presentation/Wireframe/SelectionManager.cs Main coordinator; manages IsOverBody, routes events to handlers, passes isHandlerActive to rectangle selector
Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/RectangleSelector.cs Marquee selection; activation gated on isHandlerActive and IsOverBody
Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/InputHandlerBase.cs Base class; provides default HandlePush with lock check
Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/MoveInputHandler.cs Move gesture; also handles axis lock and snap-to-unit for multi-selection
Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/Handlers/ResizeInputHandler.cs Resize handle gestures
Tool/EditorTabPlugin_XNA/Editors/Handlers/PolygonPointInputHandler.cs Polygon vertex editing; overrides HandlePush (must manage lock manually)
Tools/Gum.Presentation/Plugins/InternalPlugins/EditorTab/Editors/EditorContext.cs Provides IsSelectionLocked() helper used throughout handlers
Tool/EditorTabPlugin_XNA/Editors/Visuals/LockedSelectionVisual.cs Dashed bounding outline for locked selected instances; display-only, no interaction
GumDataTypes/InstanceSave.cs Locked property definition
Tools/Gum.Presentation/ToolCommands/ElementCommands.cs MoveSelectedObjectsBy(); skips locked instances in multi-move
WpfDataUi/Controls/ListBoxDisplay.xaml.cs Variable grid list control; respects IsReadOnly (driven by Locked)

Version History

  • c93866f Current 2026-08-20 09:20

Same Skill Collection

.claude/skills/gum-cross-platform-unification/SKILL.md
.claude/skills/gum-issue-creation/SKILL.md
.claude/skills/gum-localization/SKILL.md
.claude/skills/gum-monthly-release/SKILL.md
.claude/skills/gum-runtime-binding/SKILL.md
.claude/skills/gum-runtime-syntax-version/SKILL.md
.claude/skills/refactoring-direction/SKILL.md
.claude/skills/tdd/SKILL.md

Metadata

Files
0
Version
c93866f
Hash
d7870da7
Indexed
2026-08-20 09:20

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-26 02:47
浙ICP备14020137号-1 $방문자$