deargui-vpl/docs/overview.md

23 KiB

ImGui Node Editor - Architecture Overview

Table of Contents

  1. Introduction
  2. Core Architecture
  3. Editor Context
  4. Rendering System
  5. Pin Rendering
  6. Block System
  7. Link System
  8. 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

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.

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
  • 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:

// 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:

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

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

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:

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.

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:

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:

class ParameterizedBlock : public BlockBase
{
    std::vector<Pin> InputParameters;   // Data inputs (top area)
    std::vector<Pin> OutputParameters;  // Data outputs (top area)
    Pin FlowInputPin;                   // Execution input (left edge)
    Pin FlowOutputPin;                  // Execution output (right edge)
};

Rendering Pattern (lines 25-220):

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):

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();
}

struct Link
{
    ed::LinkId ID;
    ed::PinId StartPinID;  // Output pin
    ed::PinId EndPinID;    // Input pin
    ImColor Color;
    
    // Guided link waypoints
    std::vector<ImVec2> ControlPoints;  // User-defined waypoints
    bool IsGuided = false;              // Guided vs auto-routed
};

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

Links are drawn in DrawLinks() (imgui_node_editor_render.cpp:129-170):

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):

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):

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

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 is determined by pin type and position:

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)

// 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)

// 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 };
// 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<ImVec2> GetLinkCurve(const Link& link);

Interaction Helpers (lines 1450-1610)

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