DAGs and ImGui node graphs research
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# Node Graph UI with DearImGui — Research
|
||||
|
||||
## Overview
|
||||
|
||||
Three main approaches exist for building a node graph UI with DearImGui:
|
||||
1. Use a standalone library like **ImNodes** or **imgui-node-editor**
|
||||
2. Build from scratch using ImGui's draw list API
|
||||
3. Use a hybrid — custom canvas with ImGui widgets
|
||||
|
||||
---
|
||||
|
||||
## 1. Existing Libraries
|
||||
|
||||
### 1.1 imnodes (Nelarius)
|
||||
|
||||
- **Stars**: ~2.4k | **License**: MIT | **Status**: Active (last push 2024)
|
||||
- **Files**: `imnodes.h`, `imnodes_internal.h`, `imnodes.cpp` — drop-in, no deps beyond ImGui
|
||||
- **API style**: Immediate-mode, mirrors ImGui idioms
|
||||
|
||||
```cpp
|
||||
imnodes::BeginNodeEditor();
|
||||
imnodes::BeginNode(node_id);
|
||||
imnodes::BeginNodeTitleBar();
|
||||
ImGui::Text("Node Name");
|
||||
imnodes::EndNodeTitleBar();
|
||||
imnodes::BeginInputAttribute(pin_id);
|
||||
ImGui::Text("input");
|
||||
imnodes::EndAttribute();
|
||||
imnodes::BeginOutputAttribute(pin_id);
|
||||
ImGui::Text("output");
|
||||
imnodes::EndAttribute();
|
||||
imnodes::EndNode();
|
||||
imnodes::EndNodeEditor();
|
||||
```
|
||||
|
||||
**Strengths:**
|
||||
- Minimal, dependency-free, easy to vendor
|
||||
- True immediate-mode — user owns all state
|
||||
- Pins auto-align with embedded ImGui widgets
|
||||
- Simple `ImNodes::Link(id, from, to)` API
|
||||
|
||||
**Weaknesses:**
|
||||
- Less feature-rich (no built-in minimap, no grouping, limited theming)
|
||||
- No built-in serialization of layout
|
||||
- Slower development velocity
|
||||
|
||||
**Under the hood:**
|
||||
- Uses `ImDrawList::ChannelsSplit()` to layer node backgrounds behind UI
|
||||
- Pins are detected via `ImGui::BeginGroup` bounding box capture
|
||||
- Link picking uses hierarchical bezier subdivision
|
||||
|
||||
### 1.2 imgui-node-editor (thedmd / Michal Cichon)
|
||||
|
||||
- **Stars**: ~4.4k | **License**: MIT | **Status**: Active
|
||||
- **Files**: `imgui_node_editor.h/.cpp` + `imgui_canvas.h/.cpp` — also drop-in
|
||||
- **API style**: retained-state editor context, user draws content
|
||||
|
||||
```cpp
|
||||
ax::NodeEditor::Begin("Editor");
|
||||
ax::NodeEditor::BeginNode(node_id);
|
||||
ax::NodeEditor::BeginPin(pin_id, ax::NodeEditor::PinKind::Input);
|
||||
ImGui::Text("input");
|
||||
ax::NodeEditor::EndPin();
|
||||
ax::NodeEditor::EndNode();
|
||||
ax::NodeEditor::End();
|
||||
```
|
||||
|
||||
**Strengths:**
|
||||
- Rich feature set: zoom/pan, minimap, selection, context menus, copy/paste
|
||||
- Blueprint-UE4-inspired default theme
|
||||
- Bézier curve links with flow animation
|
||||
- Configurable zoom levels, drag/navigate/select button mapping
|
||||
- `ImGuiEx::Canvas` can be used independently for custom infinite-workspace UIs
|
||||
- Built-in serialization callbacks (`SaveSettings`/`LoadSettings`)
|
||||
|
||||
**Weaknesses:**
|
||||
- Heavier than imnodes — more code, larger API surface
|
||||
- Editor context is a retained object (less "pure" immediate mode)
|
||||
- Can conflict with ImGui's own ID stack during complex widget embedding
|
||||
|
||||
**Key API patterns:**
|
||||
|
||||
| Concern | API |
|
||||
|---|---|
|
||||
| Create link | `BeginCreate()` / `QueryNewLink()` / `AcceptNewItem()` / `EndCreate()` |
|
||||
| Delete | `BeginDelete()` / `QueryDeletedLink()` / `AcceptDeletedItem()` / `EndDelete()` |
|
||||
| Suspend for popups | `Suspend()` / `Resume()` — pops out of canvas coordinate space |
|
||||
| Styling | `PushStyleColor()` / `PushStyleVar()` — 20+ style variables |
|
||||
|
||||
### 1.3 ImNodeFlow (Fattorino)
|
||||
|
||||
- **Stars**: Newer (2024–2025) | **License**: MIT
|
||||
- Even more feature-packed: node categories, commenting, layout algorithms
|
||||
- Still maturing; less battle-tested than the two above
|
||||
|
||||
> **Recommendation for Prism**: start with **imgui-node-editor** if we want a polished editor quickly, or **imnodes** if we want minimal deps and full state control. The custom approach (next section) is best if we have very specific rendering needs.
|
||||
|
||||
---
|
||||
|
||||
## 2. Custom Node Graph from Scratch
|
||||
|
||||
Building a node graph manually using `ImDrawList` gives maximum control but requires handling:
|
||||
|
||||
### 2.1 Canvas / Coordinate System
|
||||
|
||||
An infinite-zoom canvas requires:
|
||||
- An offset (`ImVec2`) and scale (`float`) transform
|
||||
- Conversion functions between screen ↔ canvas space
|
||||
- Clipping to the parent ImGui window
|
||||
|
||||
The `imgui-node-editor` library includes an `ImGuiEx::Canvas` utility that handles this standalone — it can be extracted and reused.
|
||||
|
||||
### 2.2 Rendering Nodes (DrawList)
|
||||
|
||||
Nodes are typically rendered in layers:
|
||||
1. **Background layer**: grid dots/lines, selection rectangle
|
||||
2. **Node bodies**: rounded rectangles (`AddRectFilled`)
|
||||
3. **Node borders**: rect strokes, optionally thicker on hover/select
|
||||
4. **Pins**: small circles or squares on left/right edges
|
||||
5. **Links**: cubic Bézier curves between pin centers
|
||||
6. **UI overlay**: selection handles, context menus
|
||||
|
||||
Use `ImDrawList::ChannelsSplit()` for correct z-ordering when mixing drawn shapes with ImGui widgets.
|
||||
|
||||
### 2.3 Interaction Handling
|
||||
|
||||
| Interaction | Implementation |
|
||||
|---|---|
|
||||
| Pan | Track middle-mouse drag → modify canvas offset |
|
||||
| Zoom | Mouse wheel → modify scale (clamp to range, center on cursor) |
|
||||
| Drag node | Hit-test node bodies (invis buttons or rect test), track delta → update node position |
|
||||
| Select | Rectangular marquee — track shift+drag → compute selection rect → test intersection with node rects |
|
||||
| Create link | Detect drag from pin, draw preview bezier, test against other pins on release |
|
||||
| Delete | Keyboard shortcut, query selection, or context menu |
|
||||
|
||||
### 2.4 Link-Picking (Bezier Hit Test)
|
||||
|
||||
Cubic Bézier curves require a hierarchical hit test:
|
||||
1. Subdivide curve into N segments
|
||||
2. Find segment closest to mouse cursor
|
||||
3. Recursively subdivide that segment
|
||||
4. Return hit if distance < threshold
|
||||
|
||||
## 3. Architecture Patterns
|
||||
|
||||
### 3.1 Data Model vs. View Separation
|
||||
|
||||
From Guillaume Boissé's RogueEngine post:
|
||||
- Define a **data model** independent of UI: `PrNode`, `PrGraph`, `PrPin`
|
||||
- The data model is used both by the runtime (graph evaluation) and the editor (UI rendering)
|
||||
- This enables easy serialization, undo/redo, and multi-context editing
|
||||
|
||||
### 3.2 Immediate-Mode Node Rendering Loop
|
||||
|
||||
```
|
||||
For each node in graph:
|
||||
BeginNode(node.id)
|
||||
Render node title bar (colored rect + text)
|
||||
For each input pin:
|
||||
BeginInputPin(pin.id)
|
||||
Render ImGui widget (e.g. DragFloat, ColorEdit)
|
||||
EndInputPin()
|
||||
For each output pin:
|
||||
BeginOutputPin(pin.id)
|
||||
Render ImGui widget
|
||||
EndOutputPin()
|
||||
EndNode()
|
||||
|
||||
For each link in graph:
|
||||
DrawBezierLink(from_pos, to_pos, color, thickness)
|
||||
```
|
||||
|
||||
Positions are stored per-node in user state and updated on drag.
|
||||
|
||||
### 3.3 DrawList Channels (Z-Order)
|
||||
|
||||
```cpp
|
||||
draw_list->ChannelsSplit(3);
|
||||
draw_list->ChannelsSetCurrent(0); // Background: grid, selection rect
|
||||
// ... draw nodes, pins, links
|
||||
draw_list->ChannelsSetCurrent(1); // UI: ImGui widgets inside nodes
|
||||
// ... BeginNode/EndNode calls
|
||||
draw_list->ChannelsSetCurrent(2); // Foreground: tooltips, drag previews
|
||||
draw_list->ChannelsMerge();
|
||||
```
|
||||
|
||||
### 3.4 Undo/Redo
|
||||
|
||||
Simplest viable approach (from RogueEngine / @voxagonlabs): serialize the entire project state on every change. Store snapshots in an undo stack. Works well for small-to-medium projects (node graphs are typically small data).
|
||||
|
||||
## 4. Summary Comparison
|
||||
|
||||
| Criteria | imnodes | imgui-node-editor | Custom |
|
||||
|---|---|---|---|
|
||||
| Integration effort | Copy 3 files | Copy 6-8 files | Full implementation |
|
||||
| Feature depth | Basic | Rich (minimap, flow, groups, copy/paste) | Whatever you build |
|
||||
| Immediate mode | Yes | Partial (retained context) | Yes |
|
||||
| Performance | High | High | Depends on impl |
|
||||
| Styling control | Minimal | Extensive | Full control |
|
||||
| Serialization | None | Built-in callbacks | Build your own |
|
||||
| Community / maturity | Mature, stable | Mature, active | N/A |
|
||||
|
||||
## 5. References
|
||||
|
||||
- **imnodes**: https://github.com/Nelarius/imnodes
|
||||
- **imgui-node-editor**: https://github.com/thedmd/imgui-node-editor
|
||||
- **ImNodeFlow**: https://github.com/Fattorino/ImNodeFlow
|
||||
- **Blog post — Visual node graph with ImGui**: https://gboisse.github.io/posts/node-graph/
|
||||
- **Blog post — Writing imnodes**: https://nelari.us/post/imnodes
|
||||
- **ImGui issue #306** (node editor discussion): https://github.com/ocornut/imgui/issues/306
|
||||
- **ImGui useful extensions wiki**: https://github.com/ocornut/imgui/wiki/Useful-Extensions
|
||||
- **Voxagon undo/redo**: https://blog.voxagon.se/2018/07/10/undo-for-lazy-programmers.html
|
||||
Reference in New Issue
Block a user