deargui-vpl/docs/overview.md

766 lines
23 KiB
Markdown

# 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<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):
```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<ImVec2> 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<ImVec2> 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.