# ImGui Node Editor - Architecture Overview ## Table of Contents 1. [Introduction](#introduction) 2. [Core Architecture](#core-architecture) 3. [Editor Context](#editor-context) 4. [Rendering System](#rendering-system) 5. [Pin Rendering](#pin-rendering) 6. [Block System](#block-system) 7. [Link System](#link-system) 8. [Helper Functions](#helper-functions) --- ## Introduction The imgui-node-editor is a visual node graph editor built on top of Dear ImGui. This document describes the current architecture after recent refactoring that focused on modularity and maintainability. ### Key Characteristics - **Immediate Mode**: No retained scene graph - everything rebuilt each frame - **Type-Safe IDs**: `NodeId`, `PinId`, `LinkId` prevent mixing different entity types - **Modular Design**: Functionality split into focused files (_render, _links, _store, _selection, _tools, _animation) - **Precise Pin Positioning**: Pin renderers track exact pivot positions and bounds for accurate link connections --- ## Core Architecture ### File Structure The library is organized into specialized modules: ``` imgui-node-editor/ ├── imgui_node_editor.h # Public API ├── imgui_node_editor_internal.h # Internal structures and declarations ├── imgui_node_editor.cpp # Core editor context implementation ├── imgui_node_editor_render.cpp # Rendering logic ├── imgui_node_editor_links.cpp # Link interaction and control points ├── imgui_node_editor_store.cpp # Object storage and queries ├── imgui_node_editor_selection.cpp # Selection management ├── imgui_node_editor_tools.cpp # Utility functions ├── imgui_node_editor_animation.cpp # Animation controllers └── imgui_node_editor_api.cpp # Public API implementation ``` ### Namespace Structure ```cpp namespace ax::NodeEditor::Detail { // Internal implementation class EditorContext { /* ... */ }; // Common alias used throughout namespace ed = ax::NodeEditor::Detail; } ``` --- ## Editor Context ### EditorContext Class The heart of the editor is `EditorContext` (defined in `imgui_node_editor_internal.h`), which manages the entire editor state. **Key Responsibilities**: - Canvas navigation (pan, zoom) - Object management (nodes, pins, links) - User interactions (create, delete, select) - Drawing coordination - Animation playback ### The End() Method - Main Frame Coordinator `void ed::EditorContext::End()` in `imgui_node_editor_render.cpp` is the main orchestrator called at the end of each frame. It coordinates all rendering and interaction logic. ```cpp void ed::EditorContext::End() { auto& io = ImGui::GetIO(); const auto control = BuildControl(IsFocused()); // 1. Update hover/interaction state UpdateControlState(control); // 2. Handle control point dragging for guided links HandleControlPointDragging(); // 3. Draw all visual elements DrawNodes(); // Node visuals DrawLinks(); // Link curves DrawSelectionHighlights(control); // Selected objects DrawHoverHighlight(control, IsSelecting); // Hovered objects DrawAnimations(); // Flow animations // 4. Process user actions ProcessCurrentAction(control); // Active action (dragging, etc.) SelectNextAction(control); // Determine next action // 5. Manage Z-order and channels BringActiveNodeToFront(control, isDragging); SortNodesByGroupAndZOrder(sortGroups); ArrangeNodeChannels(); // 6. Finalize rendering DrawGrid(); // Background grid FinalizeDrawChannels(); // Transform clip rects MergeChannelsAndFinishCanvas(); // Merge and draw border // 7. Cleanup PostFrameCleanup(); // Reset state, save settings } ``` **Location**: `imgui_node_editor_render.cpp:394-445` ### Extracted Helper Methods The `End()` method was refactored from ~620 lines into focused helper methods: #### Rendering Helpers (`imgui_node_editor_render.cpp`) - `UpdateControlState()` - Updates hover and double-click states - `DrawNodes()` - Renders all visible nodes - `DrawLinks()` - Renders all visible links - `DrawSelectionHighlights()` - Highlights selected objects - `DrawHoverHighlight()` - Highlights hovered object - `DrawAnimations()` - Renders animation effects - `DrawGrid()` - Draws background grid - `ArrangeNodeChannels()` - Manages ImDrawList channels for proper Z-order - `FinalizeDrawChannels()` - Transforms clip rects for channels - `MergeChannelsAndFinishCanvas()` - Merges channels and draws border - `PostFrameCleanup()` - Post-frame cleanup and state reset #### Link Interaction Helpers (`imgui_node_editor_links.cpp`) - `HandleControlPointDragging()` - Manages dragging of link control points - `HandleGuidedLinkInteractions()` - Double-click to add/remove control points - `AddControlPointToGuidedLink()` - Adds a waypoint to a guided link - `ConvertLinkToGuidedMode()` - Converts auto link to guided mode - `ShowControlPointHoverCursor()` - Sets cursor when hovering control points #### Action Helpers (`imgui_node_editor_render.cpp`) - `ProcessCurrentAction()` - Processes the currently active action - `ProcessNavigateAction()` - Handles canvas navigation - `SelectNextAction()` - Determines next action based on priority #### Z-Order Helpers (`imgui_node_editor_render.cpp`) - `BringActiveNodeToFront()` - Brings active node/group to front - `SortNodesByGroupAndZOrder()` - Sorts nodes for correct rendering order --- ## Rendering System ### Drawing Channels The editor uses ImGui's channel system to control draw order: ```cpp // Channel allocation (from imgui_node_editor.cpp) const int c_BackgroundChannel_SelectionRect = 0; // Selection rectangle const int c_UserChannel_Content = 1; // User canvas content const int c_UserChannel_Grid = 2; // Background grid const int c_UserChannel_HintsBackground = 3; // Hint backgrounds const int c_UserChannel_Hints = 4; // Hint content const int c_LinkChannel_Selection = 5; // Selected links const int c_LinkChannel_Links = 6; // Regular links const int c_LinkChannel_Flow = 7; // Flow animations const int c_LinkChannel_NewLink = 8; // Link being created const int c_NodeStartChannel = 9; // First node channel // Each node gets 2 channels (background + foreground) const int c_ChannelsPerNode = 2; ``` ### Node Rendering Nodes are drawn by the application using the builder pattern. The editor provides the infrastructure: ```cpp ed::Begin("Node Editor"); // Application draws nodes for (auto& node : nodes) { ed::BeginNode(node.ID); // ... custom rendering ... ed::EndNode(); } // Application draws links for (auto& link : links) { ed::Link(link.ID, link.StartPin, link.EndPin); } ed::End(); // Coordinates everything via EditorContext::End() ``` --- ## Pin Rendering ### PinRenderer Architecture The blueprints example uses a specialized pin rendering system for precise positioning. **Location**: `examples/blueprints-example/utilities/pin_renderer.h/cpp` ### Class Hierarchy ```cpp // Base class for all pin renderers class PinRendererBase { public: virtual void BeginPin(ed::PinId id, ed::PinKind kind) = 0; virtual void EndPin() = 0; virtual ImVec2 GetPivotPosition() const = 0; // Link connection point virtual ImRect GetRenderBounds() const = 0; // Hit-test area virtual ImVec2 GetRelativeOffset() const = 0; // Offset from node origin protected: float m_Alpha = 1.0f; ImVec2 m_LastPivotPosition; // Cached pivot (screen space) ImRect m_LastRenderBounds; // Cached bounds (screen space) }; ``` ### ParameterPinRenderer Renders data parameter pins (Int, Float, String, etc.) with icons and labels. ```cpp class ParameterPinRenderer : public PinRendererBase { public: struct Config { float iconSize = 24.0f; float iconInnerScale = 0.75f; float spacing = 4.0f; bool showLabels = true; ImVec2 padding = ImVec2(8, 4); }; void Render(ed::PinId pinId, ed::PinKind kind, const Pin& pin, bool isLinked, App* app, const Config* overrideConfig = nullptr); // Returns exact pivot point where links should connect ImVec2 GetPivotPosition() const override; // Returns full visual bounds for hit-testing ImRect GetRenderBounds() const override; }; ``` **Key Features**: - Renders icon + label for parameter pins - Automatically positions icon based on pin direction (left/right) - Tracks exact pivot position for link connection - Supports alpha blending for inactive pins - Configurable appearance via `Config` struct **Usage**: ```cpp ParameterPinRenderer paramRenderer; // Render input pin paramRenderer.Render(pin.ID, ed::PinKind::Input, pin, isLinked, app); // Store position data for link routing pin.LastPivotPosition = paramRenderer.GetPivotPosition(); pin.LastRenderBounds = paramRenderer.GetRenderBounds(); pin.HasPositionData = true; ``` ### FlowPinRenderer Renders execution flow pins with square markers at node edges. ```cpp class FlowPinRenderer : public PinRendererBase { public: struct Config { float pinSize = 12.0f; float edgeOffset = 4.0f; float rounding = 2.0f; float borderWidth = 2.0f; ImU32 fillColor = IM_COL32(255, 255, 255, 255); ImU32 borderColor = IM_COL32(32, 32, 32, 255); }; void Render(ed::PinId pinId, ed::PinKind kind, const Pin& pin, App* app, const Config* overrideConfig = nullptr); }; ``` **Key Features**: - Renders small square at node edge - Positioned precisely at block boundary - Distinct visual from parameter pins - Minimal screen space usage ### Pin Position Tracking The `Pin` struct stores rendered position data: ```cpp struct Pin { ed::PinId ID; Node* Node; std::string Name; PinType Type; PinKind Kind; // Position tracking (updated during rendering) ImVec2 LastPivotPosition; // Where links connect (screen space) ImRect LastRenderBounds; // Full pin area for hit testing bool HasPositionData; // True after first render // Get position relative to node ImVec2 GetRelativePivotPosition() const; }; ``` **Why This Matters**: Accurate pin positions are critical for: - Link waypoint generation - Link end-point alignment - Hit-testing during link creation - Visual polish (no gaps or misalignments) --- ## Block System ### ParameterizedBlock Represents a standard blueprint-style block with parameters and flow pins. **Location**: `examples/blueprints-example/blocks/block.cpp` **Structure**: ```cpp class ParameterizedBlock : public BlockBase { std::vector InputParameters; // Data inputs (top area) std::vector OutputParameters; // Data outputs (top area) Pin FlowInputPin; // Execution input (left edge) Pin FlowOutputPin; // Execution output (right edge) }; ``` **Rendering Pattern** (lines 25-220): ```cpp void ParameterizedBlock::Render() { ed::BeginNode(ID); ImGui::BeginVertical("node"); // Header ImGui::BeginHorizontal("header"); // ... render title ... ImGui::EndHorizontal(); // Input Parameters (left side) ImGui::BeginHorizontal("inputs"); ImGui::BeginVertical("input_params"); for (auto& pin : InputParameters) { ParameterPinRenderer renderer; renderer.Render(pin.ID, ed::PinKind::Input, pin, isLinked, app); // Store position for link routing pin.LastPivotPosition = renderer.GetPivotPosition(); pin.LastRenderBounds = renderer.GetRenderBounds(); pin.HasPositionData = true; } ImGui::EndVertical(); // Middle spacer ImGui::Spring(1); // Output Parameters (right side) ImGui::BeginVertical("output_params"); for (auto& pin : OutputParameters) { ParameterPinRenderer renderer; renderer.Render(pin.ID, ed::PinKind::Output, pin, isLinked, app); pin.LastPivotPosition = renderer.GetPivotPosition(); pin.LastRenderBounds = renderer.GetRenderBounds(); pin.HasPositionData = true; } ImGui::EndVertical(); ImGui::EndHorizontal(); // Flow Pins (edges) FlowPinRenderer flowRenderer; // Flow input (left edge) flowRenderer.Render(FlowInputPin.ID, ed::PinKind::Input, FlowInputPin, app); FlowInputPin.LastPivotPosition = flowRenderer.GetPivotPosition(); FlowInputPin.HasPositionData = true; // Flow output (right edge) flowRenderer.Render(FlowOutputPin.ID, ed::PinKind::Output, FlowOutputPin, app); FlowOutputPin.LastPivotPosition = flowRenderer.GetPivotPosition(); FlowOutputPin.HasPositionData = true; ImGui::EndVertical(); ed::EndNode(); } ``` ### ParameterNode Compact nodes representing variables or constants (no flow pins). **Location**: `examples/blueprints-example/blocks/parameter_node.cpp` **Three Display Modes**: 1. **Name Only** (lines 96-154) - Just the variable name 2. **Name + Value** (lines 156-252) - Name with editable value 3. **Small Box** (lines 254-348) - Minimal representation **Rendering Example** (Name Only mode): ```cpp void ParameterNode::RenderNameOnly() { ed::BeginNode(ID); // Just the name and output pin ImGui::BeginHorizontal("content"); ImGui::TextUnformatted(Name.c_str()); ImGui::Spring(0); // Output pin ParameterPinRenderer paramRenderer; paramRenderer.Render(output.ID, ed::PinKind::Output, output, isLinked, app); // Store position output.LastPivotPosition = paramRenderer.GetPivotPosition(); output.LastRenderBounds = paramRenderer.GetRenderBounds(); output.HasPositionData = true; ImGui::EndHorizontal(); ed::EndNode(); } ``` --- ## Link System ### Link Structure ```cpp struct Link { ed::LinkId ID; ed::PinId StartPinID; // Output pin ed::PinId EndPinID; // Input pin ImColor Color; // Guided link waypoints std::vector ControlPoints; // User-defined waypoints bool IsGuided = false; // Guided vs auto-routed }; ``` ### Link Modes **Auto Mode** (default): - Editor automatically routes link curve - Uses bezier or spline curves - No user-defined waypoints **Guided Mode**: - User controls link path via waypoints - Double-click link to add control point - Drag control points to adjust path - Double-click control point to remove ### Link Rendering Links are drawn in `DrawLinks()` (`imgui_node_editor_render.cpp:129-170`): ```cpp void EditorContext::DrawLinks() { for (auto& link : m_Links) { if (!link.IsVisible()) continue; if (link.IsGuided()) { // Draw guided link with control points ed::Link(link.ID, link.StartPinID, link.EndPinID, link.Color, 2.0f, link.ControlPoints); } else { // Draw auto-routed link ed::Link(link.ID, link.StartPinID, link.EndPinID, link.Color, 2.0f); } } } ``` ### Control Point Interaction **Dragging** (`HandleControlPointDragging()` in `imgui_node_editor_links.cpp:25-50`): ```cpp void EditorContext::HandleControlPointDragging() { if (!m_DraggedControlPoint.IsValid()) return; // Update position while dragging if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { m_DraggedControlPoint.Position = ImGui::GetMousePos(); } // Finish drag if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { m_DraggedControlPoint.Clear(); } } ``` **Adding/Removing** (`HandleGuidedLinkInteractions()` in `imgui_node_editor_links.cpp:52-85`): ```cpp void EditorContext::HandleGuidedLinkInteractions(const Control& control) { if (control.HotLink && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { auto link = FindLink(control.HotLink); if (!link) return; if (control.HoveredControlPoint >= 0) { // Remove control point link->ControlPoints.erase( link->ControlPoints.begin() + control.HoveredControlPoint); } else { // Add control point at mouse position AddControlPointToGuidedLink(link, ImGui::GetMousePos()); } } } ``` ### Waypoint Generation When creating a link, waypoints are generated to connect pins accurately. **Critical Function**: `GetPinPosition()` in `app-render.cpp:48-112` ```cpp static ImVec2 GetPinPosition(Pin* pin, const ImVec2& nodePos, const ImVec2& nodeSize) { // Use stored position data from PinRenderer if available if (pin->HasPositionData) { // Get current node position ImVec2 currentNodePos = ed::GetNodePosition(pin->Node->ID); // Calculate relative offset ImVec2 relativeOffset = pin->LastPivotPosition - currentNodePos; // Apply offset to requested position // (Handles cases where nodePos != currentNodePos during creation) return nodePos + relativeOffset; } // Fallback to approximation for unrendered pins // ... (approximation based on node size and pin type) ... } ``` **Why This Works**: 1. Pin renderers store exact pivot position during rendering 2. Position is stored in screen space 3. Convert to relative offset from node origin 4. Apply offset to current/target node position 5. Result: pixel-perfect link connection ### Link Direction Link direction is determined by pin type and position: ```cpp static ImVec2 GetPinDirection(Pin* pin) { // Use pin renderer data if available if (pin->HasPositionData) { // Calculate from pin position relative to node center ImVec2 nodePos = ed::GetNodePosition(pin->Node->ID); ImVec2 nodeSize = ed::GetNodeSize(pin->Node->ID); ImVec2 nodeCenter = nodePos + nodeSize * 0.5f; ImVec2 pinPos = pin->LastPivotPosition; ImVec2 dir = pinPos - nodeCenter; float len = sqrtf(dir.x * dir.x + dir.y * dir.y); return len > 0.0f ? ImVec2(dir.x / len, dir.y / len) : ImVec2(1, 0); } // Fallback: standard left/right return pin->Kind == PinKind::Input ? ImVec2(-1, 0) : ImVec2(1, 0); } ``` --- ## Helper Functions ### app-render.cpp Utilities **Location**: `examples/blueprints-example/app-render.cpp` Key helper functions used throughout the blueprints example: #### Pin Queries (lines 48-145) ```cpp // Get exact pin position for link routing static ImVec2 GetPinPosition(Pin* pin, const ImVec2& nodePos, const ImVec2& nodeSize); // Get pin direction vector for link curves static ImVec2 GetPinDirection(Pin* pin); // Check if pin has an active connection bool IsPinLinked(ed::PinId pinId); ``` #### Icon Rendering (lines 147-320) ```cpp // Draw pin icon (circle, square, flow, etc.) void DrawPinIcon(const Pin& pin, bool connected, int alpha, const ImVec2& size); // Get icon color based on pin type ImColor GetIconColor(PinType type); // Icon types enum class IconType { Flow, Circle, Square, Grid, RoundSquare, Diamond }; ``` #### Link Rendering (lines 550-890) ```cpp // Render a single link with proper curve void DrawLink(const Link& link); // Render flow animation on link void DrawFlowAnimation(const Link& link, float time); // Get link curve points for rendering std::vector GetLinkCurve(const Link& link); ``` #### Interaction Helpers (lines 1450-1610) ```cpp // Handle link creation void HandleLinkCreation(); // Handle node/link deletion void HandleDeletion(); // Handle node selection void HandleSelection(); // Show context menu void ShowContextMenu(); ``` --- ## Quick Reference ### Core Editor Flow ``` Frame Start ↓ ImGui::NewFrame() ↓ ed::Begin() ↓ App Renders Nodes/Links ↓ ed::End() → EditorContext::End() ├─ UpdateControlState() ├─ HandleControlPointDragging() ├─ DrawNodes() ├─ DrawLinks() ├─ DrawSelectionHighlights() ├─ DrawHoverHighlight() ├─ DrawAnimations() ├─ ProcessCurrentAction() ├─ SelectNextAction() ├─ BringActiveNodeToFront() ├─ SortNodesByGroupAndZOrder() ├─ ArrangeNodeChannels() ├─ DrawGrid() ├─ FinalizeDrawChannels() ├─ MergeChannelsAndFinishCanvas() └─ PostFrameCleanup() ↓ ImGui::Render() ↓ Frame End ``` ### Key Files Reference | File | Purpose | Key Functions | |------|---------|---------------| | `imgui_node_editor.cpp` | Core editor implementation | `Begin()`, node/link creation | | `imgui_node_editor_render.cpp` | Rendering coordination | `End()`, `DrawNodes()`, `DrawLinks()` | | `imgui_node_editor_links.cpp` | Link interactions | `HandleControlPointDragging()`, waypoint management | | `imgui_node_editor_internal.h` | Internal declarations | `EditorContext` class, all helper declarations | | `utilities/pin_renderer.h/cpp` | Pin rendering | `ParameterPinRenderer`, `FlowPinRenderer` | | `blocks/block.cpp` | Standard blocks | `ParameterizedBlock::Render()` | | `blocks/parameter_node.cpp` | Parameter nodes | `ParameterNode::Render()` modes | | `app-render.cpp` | Rendering helpers | `GetPinPosition()`, `DrawPinIcon()` | ### Data Flow for Pin Positioning ``` 1. App calls pin renderer ParameterPinRenderer::Render() ↓ 2. Renderer calculates layout Layout layout = CalculateLayout(...) ↓ 3. Renderer stores position m_LastPivotPosition = layout.pivotPoint m_LastRenderBounds = layout.combinedRect ↓ 4. App stores in Pin struct pin.LastPivotPosition = renderer.GetPivotPosition() pin.LastRenderBounds = renderer.GetRenderBounds() pin.HasPositionData = true ↓ 5. Link system uses stored position GetPinPosition(pin) → returns pin.LastPivotPosition ↓ 6. Waypoints generated with exact positions CreateLink() → GenerateWaypoints() → pixel-perfect alignment ``` --- ## Summary The ImGui Node Editor architecture is built around: 1. **EditorContext** - Central coordinator managing all state 2. **Modular Files** - Focused modules for rendering, links, selection, etc. 3. **End() Method** - Main frame orchestrator split into focused helpers 4. **Pin Renderers** - Precise positioning system for perfect link alignment 5. **Block System** - Flexible node types (blocks, parameters, comments) 6. **Link System** - Auto and guided modes with waypoint editing 7. **Helper Functions** - Utilities in app-render.cpp for common operations This design provides a clean separation of concerns while maintaining the immediate-mode paradigm of Dear ImGui.