Adds a minimal node editor implementation

Implements a basic node editor using ImGui for creating
and manipulating nodes and links.

Introduces node creation, deletion, selection, dragging,
zooming, panning, and linking functionalities.

Adds basic pin typing for color and compatibility, including
Any, Float, Vector, Texture, and Exec types.

Also includes saving and loading node graphs to/from strings.
This commit is contained in:
2025-10-15 09:56:27 -05:00
parent 9426ee0061
commit 724783bbaa
5 changed files with 625 additions and 719 deletions

Binary file not shown.

View File

@@ -32,3 +32,8 @@
62 3579 7822428527763633 CMakeFiles/ImNodeEditor.dir/src/ImNodeEditor.cpp.obj 966712c70001063c
3581 4167 7822428562950218 libImNodeEditor.a 456ddb6c40baee90
4168 4647 7822428568807417 example.exe 24050db2cb3fbf91
25 2053 7822433126382984 CMakeFiles/example.dir/examples/example_main.cpp.obj c467c4ef9fc3e04b
15 3089 7822433126235298 CMakeFiles/ImNodeEditor.dir/src/ImNodeEditor.cpp.obj 966712c70001063c
3090 3339 7822433156978395 libImNodeEditor.a 456ddb6c40baee90
78 2061 7822433307832734 CMakeFiles/example.dir/examples/example_main.cpp.obj c467c4ef9fc3e04b
2063 2448 7822433327670595 example.exe 24050db2cb3fbf91

View File

@@ -30,16 +30,16 @@ int main() {
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
// ---- GLEW init (must be after context creation) ----
// ---- GLEW init ----
glewExperimental = GL_TRUE;
GLenum err = glewInit();
if (err != GLEW_OK) {
std::fprintf(stderr, "GLEW init error: %s\n", glewGetErrorString(err));
return 3;
}
glGetError(); // clear benign error
glGetError();
// ---- ImGui ----
// ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
@@ -49,23 +49,30 @@ int main() {
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330 core");
ImNodeEditor::Editor editor;
using namespace ImNodeEditor;
// Seed example nodes/links
const int n1 = editor.AddNode("Texture", ImVec2(100, 120));
const int n1_out = editor.AddPin(n1, ImNodeEditor::PinKind::Output);
Editor editor;
// Seed example nodes/links with types
const int n1 = editor.AddNode("Number", ImVec2(100, 120));
const int n1_out = editor.AddPin(n1, PinKind::Output, PinType::Float);
const int n2 = editor.AddNode("Multiply", ImVec2(380, 160));
const int n2_inA = editor.AddPin(n2, ImNodeEditor::PinKind::Input);
const int n2_inB = editor.AddPin(n2, ImNodeEditor::PinKind::Input);
const int n2_out = editor.AddPin(n2, ImNodeEditor::PinKind::Output);
const int n2_inA = editor.AddPin(n2, PinKind::Input, PinType::Float);
const int n2_inB = editor.AddPin(n2, PinKind::Input, PinType::Float);
const int n2_out = editor.AddPin(n2, PinKind::Output, PinType::Float);
const int n3 = editor.AddNode("Output", ImVec2(700, 200));
const int n3_in = editor.AddPin(n3, ImNodeEditor::PinKind::Input);
const int n3_in = editor.AddPin(n3, PinKind::Input, PinType::Float);
// Texture node to showcase color/type (not connected by default)
const int n4 = editor.AddNode("Texture", ImVec2(120, 300));
const int n4_out = editor.AddPin(n4, PinKind::Output, PinType::Texture);
editor.AddLink(n1_out, n2_inA);
editor.AddLink(n2_out, n3_in);
bool show_demo_window = false;
bool show_editor_window = true;
while (!glfwWindowShouldClose(window)) {
@@ -104,21 +111,25 @@ int main() {
const char* text =
"NODE 1 100 100 A\n"
"NODE 2 300 160 B\n"
"PIN 1 1 1 0\n"
"PIN 2 2 0 0\n"
"PIN 1 1 1 0 1\n"
"PIN 2 2 0 0 1\n"
"LINK 1 1 2\n";
editor.LoadFromString(text);
}
if (ImGui::MenuItem("Quit")) glfwSetWindowShouldClose(window, GLFW_TRUE);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
ImGui::MenuItem("Show ImGui Demo", nullptr, &show_demo_window);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::End();
}
if (show_editor_window) {
ImGui::Begin("Node Editor", &show_editor_window, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar);
ImGui::Begin("Node Editor", nullptr, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar);
ImVec2 avail = ImGui::GetContentRegionAvail();
editor.Draw("MainCanvas", avail);
ImGui::End();

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
// ImNodeEditor.h
// Minimal, standalone node editor for Dear ImGui.
// MIT License
#pragma once
@@ -17,6 +19,10 @@ namespace ImNodeEditor {
enum class PinKind : int { Input = 0, Output = 1 };
// Basic pin typing for color + compatibility.
// Any = wild-card (connects to anything).
enum class PinType : int { Any = 0, Float = 1, Vector = 2, Texture = 3, Exec = 4 };
struct ColorTheme {
ImU32 gridMinor = IM_COL32(50, 50, 50, 255);
ImU32 gridMajor = IM_COL32(70, 70, 70, 255);
@@ -24,13 +30,19 @@ struct ColorTheme {
ImU32 nodeHeader= IM_COL32(60, 60, 80, 255);
ImU32 nodeBorder= IM_COL32(110,110,140,255);
ImU32 nodeSelected = IM_COL32(180,180,220,255);
ImU32 pinFill = IM_COL32(180,180,200,255);
ImU32 pinFill = IM_COL32(180,180,200,255); // used for Any
ImU32 pinHover = IM_COL32(255,220,150,255);
ImU32 link = IM_COL32(180,180,200,255);
ImU32 linkHover = IM_COL32(255,240,170,255);
ImU32 linkSel = IM_COL32(200,200,255,255);
ImU32 selectionRect = IM_COL32(80,140,255,60);
ImU32 selectionRectBorder = IM_COL32(80,140,255,180);
// Pin type colors (override defaults)
ImU32 typeFloat = IM_COL32(90, 200, 100, 255);
ImU32 typeVector = IM_COL32(90, 180, 240, 255);
ImU32 typeTexture = IM_COL32(240, 180, 80, 255);
ImU32 typeExec = IM_COL32(220, 80, 80, 255);
};
struct EditorConfig {
@@ -56,6 +68,7 @@ struct Pin {
int node = 0;
PinKind kind = PinKind::Input;
int order = 0;
PinType type = PinType::Any;
ImVec2 localPos{};
ImRect rect{};
};
@@ -72,8 +85,8 @@ struct Node {
struct Link {
int id = 0;
int aPin = 0;
int bPin = 0;
int aPin = 0; // output
int bPin = 0; // input
bool selected = false;
};
@@ -91,19 +104,19 @@ public:
// Creation API
int AddNode(const std::string& title, ImVec2 canvasPos);
int AddPin(int nodeId, PinKind kind, int order = -1);
int AddPin(int nodeId, PinKind kind, int order = -1); // legacy: Any type
int AddPin(int nodeId, PinKind kind, PinType type, int order = -1); // typed
int AddLink(int pinA, int pinB);
void RemoveNode(int nodeId);
void RemoveLink(int linkId);
// Access
Node* GetNode(int nodeId);
Pin* GetPin(int pinId);
Link* GetLink(int linkId);
Node* GetNode(int nodeId);
Pin* GetPin(int pinId);
Link* GetLink(int linkId);
const Node* GetNode(int nodeId) const;
const Pin* GetPin(int pinId) const;
const Link* GetLink(int linkId) const;
const Pin* GetPin(int pinId) const;
const Link* GetLink(int linkId) const;
// Frame draw
void Draw(const char* label, ImVec2 size = ImVec2(0,0));
@@ -137,7 +150,6 @@ private:
void UpdatePinRects(Node& n);
HoverInfo HitTest(const ImVec2& mouseCanvas) const;
bool LinkDistanceToPoint(const Link& L, const ImVec2& P, float& outMin) const;
ImVec2 PinScreenPos(const Pin& p) const;
// Interaction
void HandleCanvasInput(const ImRect& canvas);
@@ -146,8 +158,9 @@ private:
void HandleKeyboard();
// Utilities
static float CubicBezierYForX(float x);
ImVec2 EvalLinkTangent(const ImVec2& a, const ImVec2& b) const;
static float CubicBezierYForX(float x);
ImU32 PinTypeColor(PinType t) const;
private:
EditorConfig m_cfg{};