Skip to content

NodeEditor: undo/redo, keymap commands, grid snapping and comment boxes - #469

Merged
matt-edmondson merged 3 commits into
mainfrom
claude/issues-467-468-g0hjrz
Sep 25, 2026
Merged

matt-edmondson merged 3 commits into
mainfrom
claude/issues-467-468-g0hjrz

Conversation

@matt-edmondson

@matt-edmondson matt-edmondson commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Closes #467
Closes #468

Undo/redo (#467)

As the comment on #467 suggested, this uses ktsu.UndoRedo and ktsu.Keybinding directly.

  • NodeEditorHistory records onto an IUndoRedoService. You can let it make its own stack, or pass in the host's stack so graph steps sit alongside the host's own undo steps. Each step stores only what changed: the graph is captured before and after the change runs inside Record(...). So undoing a delete puts back that node (same id, pins, values, seeded defaults and links) and leaves every node the layout moved since where it is.
    • Convenience methods cover link, delete, duplicate and comment-box operations. Anything else can go through Record("...", () => ...).
    • Pin values written by the inline editors or NodeInspectorPanel are recorded without the host doing anything. A new SetPinValue(int, object?, long? editGesture) overload plus a new PinValueChanged event let all the writes from one widget gesture (one slider drag, one typing session) merge into a single step.
    • Drags are recorded once, on mouse release, when the history is assigned to renderer.History.
    • Factory nodes get their instance back on undo through the new AttributeBasedNodeFactory.RestoreBinding.
    • engine.Clear() called outside a recording empties the history, because it restarts the id counters. Called inside Record it is a normal undoable step, as the demo's Reset/Clear buttons show.
  • NodeEditorInputHandler now reports UndoRequested and RedoRequested. If you give it an IKeybindingService, it reads the delete, duplicate, undo and redo chords from the active profile every frame. NodeEditorCommands.Register registers those commands and their default chords, and it never overwrites a chord the user has already bound. Without a keymap the keys are Delete/Backspace, Ctrl+D, Ctrl+Z, and Ctrl+Y or Ctrl+Shift+Z.

Grid snapping and comment boxes (#468)

  • Snapping: renderer.SnapToGrid and renderer.GridSpacing turn on ImNodes' own grid snapping for each frame. Nodes land on the grid that's drawn on screen, and a multi-node selection keeps its shape. SnapNodesToGrid snaps nodes that were placed in code. Only drags snap, so a graph meant to stay on the grid needs physics off or its nodes pinned.
  • CommentBox is a labelled, optionally coloured region drawn in ImNodes' background channel: above the grid, below every node and link.
    • Dragging the title moves the box, any boxes nested in it, and the nodes inside it. What's inside is decided when the drag starts.
    • The corner handle resizes the box, a double-click on the title renames it, and the × removes the box but leaves its nodes.
    • Every one of these gestures is undoable. The engine API covers create, create-around-nodes, move, resize, rename, recolour and remove.

Other changes

  • Fix: the renderer now forgets per-node state for nodes it didn't draw in a frame. Without this, a node restored by undo was read back from ImNodes as a drag to (0,0).
  • Demo: the Clean ImNodes tab now uses the history with an in-memory keymap. It gains Undo/Redo buttons, the snap toggle and spacing slider, "Snap Selection To Grid", "Comment Selection", and a sample comment box.
  • Docs: the NodeEditor README, DESCRIPTION and CLAUDE.md are updated.

Testing

  • NodeEditorHistoryTests (17) and CommentBoxTests (6) run against the engine alone.
  • NodeEditorGestureTests (15) run headless frames through the harness. They cover drag recording and undo, snapping, Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z, a rebound keymap, undo of a delete redrawing the node in place, and an inline-editor drag undoing in one step. For comment boxes they check placement, pixel-level draw order under nodes, no ImGui misuse errors, title drag, resize, close and rename.
  • The full NodeEditor suite passes (262 tests: the 224 that existed before plus the 38 above) and the whole solution builds in Release with no warnings.
  • I could not run ImGuiAppDemo.UITests locally: this checkout has no Git LFS, so icon.png is a pointer file and every test fails in setup. CI will exercise it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FF1fUCCNeDokGnSmbKnkTY

…ment boxes

Undo/redo (#467), built on ktsu.UndoRedo and ktsu.Keybinding:
- NodeEditorHistory records each change as the difference it made: node
  create/delete/duplicate, links, drags, comment boxes and pin values.
  Undoing a deletion restores the node under its original id with its pins,
  values, links and factory binding.
- Pin value edits from the inline editors and inspector are recorded
  automatically; writes within one widget gesture merge into one step.
- NodeEditorInputHandler reports UndoRequested/RedoRequested and can take
  its chords from an IKeybindingService; NodeEditorCommands registers the
  commands and default chords.

Layout tools (#468):
- NodeEditorRenderer.SnapToGrid / GridSpacing use ImNodes' grid snapping;
  SnapNodesToGrid snaps nodes placed in code.
- CommentBox: labelled regions drawn behind the nodes; drag the title to
  carry contained nodes, resize from the corner, double-click to rename,
  close with the cross.

The renderer now forgets nodes it did not draw, so a restored node is
repositioned in ImNodes instead of being read back as a drag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FF1fUCCNeDokGnSmbKnkTY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FF1fUCCNeDokGnSmbKnkTY
Comment thread ImGui.NodeEditor/PinEditGestures.cs Fixed
Comment thread ImGui.NodeEditor/NodeEditorRenderer.CommentBoxes.cs Fixed
Comment thread ImGui.NodeEditor/NodeEditorHistory.cs Fixed
Comment thread ImGui.NodeEditor/NodeEditorHistory.cs Fixed
Comment thread ImGui.NodeEditor/NodeEditorCommands.cs Fixed
…boxes

Make the filters and mappings the analyzer flagged explicit, keep the two
side-effecting loops as statements rather than lazy filters, and issue
gesture tokens from a static method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FF1fUCCNeDokGnSmbKnkTY
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants