Skip to content

Instantly share code, notes, and snippets.

@MSylvia
Forked from ocornut/imgui_node_graph_test.cpp
Created August 31, 2022 03:21
Show Gist options
  • Select an option

  • Save MSylvia/202b90b6a0677dcf31ef3b2b23f35d29 to your computer and use it in GitHub Desktop.

Select an option

Save MSylvia/202b90b6a0677dcf31ef3b2b23f35d29 to your computer and use it in GitHub Desktop.

Revisions

  1. @ocornut ocornut revised this gist Mar 6, 2020. 1 changed file with 32 additions and 23 deletions.
    55 changes: 32 additions & 23 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,11 @@
    // Creating a node graph editor for ImGui
    // Quick demo, not production code! This is more of a demo of how to use ImGui to create custom stuff.
    // Better version by @daniel_collin here https://gist.github.com/emoon/b8ff4b4ce4f1b43e79f2
    // See https://github.com/ocornut/imgui/issues/306
    // v0.03: fixed grid offset issue, inverted sign of 'scrolling'
    // Animated gif: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif
    // Creating a node graph editor for Dear ImGui
    // Quick sample, not production code! This is more of a demo of how to use Dear ImGui to create custom stuff.
    // See https://github.com/ocornut/imgui/issues/306 for details
    // And more fancy node editors: https://github.com/ocornut/imgui/wiki#Useful-widgets--references

    // Changelog
    // - v0.04 (2020-03): minor tweaks
    // - v0.03 (2018-03): fixed grid offset issue, inverted sign of 'scrolling'

    #include <math.h> // fmodf

    @@ -12,11 +14,11 @@
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }

    // Really dumb data structure provided for the example.
    // Note that we storing links are INDICES (not ID) to make example code shorter, obviously a bad idea for any general purpose code.
    // Dummy data structure provided for the example.
    // Note that we storing links as indices (not ID) to make example code shorter.
    static void ShowExampleAppCustomNodeGraph(bool* opened)
    {
    ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiSetCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver);
    if (!ImGui::Begin("Example: Custom Node Graph", opened))
    {
    ImGui::End();
    @@ -33,7 +35,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImVec4 Color;
    int InputsCount, OutputsCount;

    Node(int id, const char* name, const ImVec2& pos, float value, const ImVec4& color, int inputs_count, int outputs_count) { ID = id; strncpy(Name, name, 31); Name[31] = 0; Pos = pos; Value = value; Color = color; InputsCount = inputs_count; OutputsCount = outputs_count; }
    Node(int id, const char* name, const ImVec2& pos, float value, const ImVec4& color, int inputs_count, int outputs_count) { ID = id; strcpy(Name, name); Pos = pos; Value = value; Color = color; InputsCount = inputs_count; OutputsCount = outputs_count; }

    ImVec2 GetInputSlotPos(int slot_no) const { return ImVec2(Pos.x, Pos.y + Size.y * ((float)slot_no + 1) / ((float)InputsCount + 1)); }
    ImVec2 GetOutputSlotPos(int slot_no) const { return ImVec2(Pos.x + Size.x, Pos.y + Size.y * ((float)slot_no + 1) / ((float)OutputsCount + 1)); }
    @@ -45,12 +47,16 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    NodeLink(int input_idx, int input_slot, int output_idx, int output_slot) { InputIdx = input_idx; InputSlot = input_slot; OutputIdx = output_idx; OutputSlot = output_slot; }
    };

    // State
    static ImVector<Node> nodes;
    static ImVector<NodeLink> links;
    static bool inited = false;
    static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
    static bool inited = false;
    static bool show_grid = true;
    static int node_selected = -1;

    // Initialization
    ImGuiIO& io = ImGui::GetIO();
    if (!inited)
    {
    nodes.push_back(Node(0, "MainTex", ImVec2(40, 50), 0.5f, ImColor(255, 100, 100), 1, 1));
    @@ -95,12 +101,14 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::Checkbox("Show grid", &show_grid);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, IM_COL32(60, 60, 70, 200));
    ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(60, 60, 70, 200));
    ImGui::BeginChild("scrolling_region", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove);
    ImGui::PopStyleVar(); // WindowPadding
    ImGui::PushItemWidth(120.0f);

    ImVec2 offset = ImGui::GetCursorScreenPos() + scrolling;
    const ImVec2 offset = ImGui::GetCursorScreenPos() + scrolling;
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    // Display grid
    if (show_grid)
    {
    @@ -161,8 +169,8 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    bool node_moving_active = ImGui::IsItemActive();
    if (node_widgets_active || node_moving_active)
    node_selected = node->ID;
    if (node_moving_active && ImGui::IsMouseDragging(0))
    node->Pos = node->Pos + ImGui::GetIO().MouseDelta;
    if (node_moving_active && ImGui::IsMouseDragging(ImGuiMouseButton_Left))
    node->Pos = node->Pos + io.MouseDelta;

    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? IM_COL32(75, 75, 75, 255) : IM_COL32(60, 60, 60, 255);
    draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
    @@ -177,11 +185,12 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    draw_list->ChannelsMerge();

    // Open context menu
    if (!ImGui::IsAnyItemHovered() && ImGui::IsMouseHoveringWindow() && ImGui::IsMouseClicked(1))
    {
    node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
    open_context_menu = true;
    }
    if (ImGui::IsMouseReleased(ImGuiMouseButton_Right))
    if (ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) || !ImGui::IsAnyItemHovered())
    {
    node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
    open_context_menu = true;
    }
    if (open_context_menu)
    {
    ImGui::OpenPopup("context_menu");
    @@ -215,13 +224,13 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::PopStyleVar();

    // Scrolling
    if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
    scrolling = scrolling + ImGui::GetIO().MouseDelta;
    if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.0f))
    scrolling = scrolling + io.MouseDelta;

    ImGui::PopItemWidth();
    ImGui::EndChild();
    ImGui::PopStyleColor();
    ImGui::PopStyleVar(2);
    ImGui::PopStyleVar();
    ImGui::EndGroup();

    ImGui::End();
  2. @ocornut ocornut revised this gist Mar 21, 2018. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -101,8 +101,6 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)

    ImVec2 offset = ImGui::GetCursorScreenPos() + scrolling;
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    draw_list->ChannelsSplit(2);

    // Display grid
    if (show_grid)
    {
    @@ -117,6 +115,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    }

    // Display links
    draw_list->ChannelsSplit(2);
    draw_list->ChannelsSetCurrent(0); // Background
    for (int link_idx = 0; link_idx < links.Size; link_idx++)
    {
  3. @ocornut ocornut revised this gist Mar 21, 2018. 1 changed file with 33 additions and 31 deletions.
    64 changes: 33 additions & 31 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -2,19 +2,21 @@
    // Quick demo, not production code! This is more of a demo of how to use ImGui to create custom stuff.
    // Better version by @daniel_collin here https://gist.github.com/emoon/b8ff4b4ce4f1b43e79f2
    // See https://github.com/ocornut/imgui/issues/306
    // v0.02
    // v0.03: fixed grid offset issue, inverted sign of 'scrolling'
    // Animated gif: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif

    #include <math.h> // fmodf

    // NB: You can use math functions/operators on ImVec2 if you #define IMGUI_DEFINE_MATH_OPERATORS and #include "imgui_internal.h"
    // Here we only declare simple +/- operators so others don't leak into the demo code.
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }

    // Really dumb data structure provided for the example.
    // Note that we storing links are INDICES (not ID) to make example code shorter, obviously a bad idea for any general purpose code.
    static void ShowExampleAppCustomNodeGraph(bool* opened)
    {
    ImGui::SetNextWindowSize(ImVec2(700,600), ImGuiSetCond_FirstUseEver);
    ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiSetCond_FirstUseEver);
    if (!ImGui::Begin("Example: Custom Node Graph", opened))
    {
    ImGui::End();
    @@ -33,8 +35,8 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)

    Node(int id, const char* name, const ImVec2& pos, float value, const ImVec4& color, int inputs_count, int outputs_count) { ID = id; strncpy(Name, name, 31); Name[31] = 0; Pos = pos; Value = value; Color = color; InputsCount = inputs_count; OutputsCount = outputs_count; }

    ImVec2 GetInputSlotPos(int slot_no) const { return ImVec2(Pos.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)InputsCount+1)); }
    ImVec2 GetOutputSlotPos(int slot_no) const { return ImVec2(Pos.x + Size.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)OutputsCount+1)); }
    ImVec2 GetInputSlotPos(int slot_no) const { return ImVec2(Pos.x, Pos.y + Size.y * ((float)slot_no + 1) / ((float)InputsCount + 1)); }
    ImVec2 GetOutputSlotPos(int slot_no) const { return ImVec2(Pos.x + Size.x, Pos.y + Size.y * ((float)slot_no + 1) / ((float)OutputsCount + 1)); }
    };
    struct NodeLink
    {
    @@ -51,9 +53,9 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    static int node_selected = -1;
    if (!inited)
    {
    nodes.push_back(Node(0, "MainTex", ImVec2(40,50), 0.5f, ImColor(255,100,100), 1, 1));
    nodes.push_back(Node(1, "BumpMap", ImVec2(40,150), 0.42f, ImColor(200,100,200), 1, 1));
    nodes.push_back(Node(2, "Combine", ImVec2(270,80), 1.0f, ImColor(0,200,100), 2, 2));
    nodes.push_back(Node(0, "MainTex", ImVec2(40, 50), 0.5f, ImColor(255, 100, 100), 1, 1));
    nodes.push_back(Node(1, "BumpMap", ImVec2(40, 150), 0.42f, ImColor(200, 100, 200), 1, 1));
    nodes.push_back(Node(2, "Combine", ImVec2(270, 80), 1.0f, ImColor(0, 200, 100), 2, 2));
    links.push_back(NodeLink(0, 0, 2, 0));
    links.push_back(NodeLink(1, 0, 2, 1));
    inited = true;
    @@ -63,7 +65,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    bool open_context_menu = false;
    int node_hovered_in_list = -1;
    int node_hovered_in_scene = -1;
    ImGui::BeginChild("node_list", ImVec2(100,0));
    ImGui::BeginChild("node_list", ImVec2(100, 0));
    ImGui::Text("Nodes");
    ImGui::Separator();
    for (int node_idx = 0; node_idx < nodes.Size; node_idx++)
    @@ -89,29 +91,29 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)

    // Create our child canvas
    ImGui::Text("Hold middle mouse button to scroll (%.2f,%.2f)", scrolling.x, scrolling.y);
    ImGui::SameLine(ImGui::GetWindowWidth()-100);
    ImGui::SameLine(ImGui::GetWindowWidth() - 100);
    ImGui::Checkbox("Show grid", &show_grid);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1,1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, IM_COL32(60,60,70,200));
    ImGui::BeginChild("scrolling_region", ImVec2(0,0), true, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, IM_COL32(60, 60, 70, 200));
    ImGui::BeginChild("scrolling_region", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove);
    ImGui::PushItemWidth(120.0f);

    ImVec2 offset = ImGui::GetCursorScreenPos() - scrolling;
    ImVec2 offset = ImGui::GetCursorScreenPos() + scrolling;
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    draw_list->ChannelsSplit(2);

    // Display grid
    if (show_grid)
    {
    ImU32 GRID_COLOR = IM_COL32(200,200,200,40);
    ImU32 GRID_COLOR = IM_COL32(200, 200, 200, 40);
    float GRID_SZ = 64.0f;
    ImVec2 win_pos = ImGui::GetCursorScreenPos();
    ImVec2 canvas_sz = ImGui::GetWindowSize();
    for (float x = fmodf(offset.x,GRID_SZ); x < canvas_sz.x; x += GRID_SZ)
    draw_list->AddLine(ImVec2(x,0.0f)+win_pos, ImVec2(x,canvas_sz.y)+win_pos, GRID_COLOR);
    for (float y = fmodf(offset.y,GRID_SZ); y < canvas_sz.y; y += GRID_SZ)
    draw_list->AddLine(ImVec2(0.0f,y)+win_pos, ImVec2(canvas_sz.x,y)+win_pos, GRID_COLOR);
    for (float x = fmodf(scrolling.x, GRID_SZ); x < canvas_sz.x; x += GRID_SZ)
    draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, GRID_COLOR);
    for (float y = fmodf(scrolling.y, GRID_SZ); y < canvas_sz.y; y += GRID_SZ)
    draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, GRID_COLOR);
    }

    // Display links
    @@ -122,8 +124,8 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    Node* node_inp = &nodes[link->InputIdx];
    Node* node_out = &nodes[link->OutputIdx];
    ImVec2 p1 = offset + node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezierCurve(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, IM_COL32(200,200,100,255), 3.0f);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezierCurve(p1, p1 + ImVec2(+50, 0), p2 + ImVec2(-50, 0), p2, IM_COL32(200, 200, 100, 255), 3.0f);
    }

    // Display nodes
    @@ -163,13 +165,13 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    if (node_moving_active && ImGui::IsMouseDragging(0))
    node->Pos = node->Pos + ImGui::GetIO().MouseDelta;

    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? IM_COL32(75,75,75,255) : IM_COL32(60,60,60,255);
    draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
    draw_list->AddRect(node_rect_min, node_rect_max, IM_COL32(100,100,100,255), 4.0f);
    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? IM_COL32(75, 75, 75, 255) : IM_COL32(60, 60, 60, 255);
    draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
    draw_list->AddRect(node_rect_min, node_rect_max, IM_COL32(100, 100, 100, 255), 4.0f);
    for (int slot_idx = 0; slot_idx < node->InputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetInputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150,150,150,150));
    draw_list->AddCircleFilled(offset + node->GetInputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150, 150, 150, 150));
    for (int slot_idx = 0; slot_idx < node->OutputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetOutputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150,150,150,150));
    draw_list->AddCircleFilled(offset + node->GetOutputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150, 150, 150, 150));

    ImGui::PopID();
    }
    @@ -191,7 +193,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    }

    // Draw context menu
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8,8));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 8));
    if (ImGui::BeginPopup("context_menu"))
    {
    Node* node = node_selected != -1 ? &nodes[node_selected] : NULL;
    @@ -206,7 +208,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    }
    else
    {
    if (ImGui::MenuItem("Add")) { nodes.push_back(Node(nodes.Size, "New node", scene_pos, 0.5f, ImColor(100,100,200), 2, 2)); }
    if (ImGui::MenuItem("Add")) { nodes.push_back(Node(nodes.Size, "New node", scene_pos, 0.5f, ImColor(100, 100, 200), 2, 2)); }
    if (ImGui::MenuItem("Paste", NULL, false, false)) {}
    }
    ImGui::EndPopup();
    @@ -215,7 +217,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)

    // Scrolling
    if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
    scrolling = scrolling - ImGui::GetIO().MouseDelta;
    scrolling = scrolling + ImGui::GetIO().MouseDelta;

    ImGui::PopItemWidth();
    ImGui::EndChild();
  4. @ocornut ocornut revised this gist Jan 11, 2018. 1 changed file with 7 additions and 7 deletions.
    14 changes: 7 additions & 7 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -93,7 +93,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::Checkbox("Show grid", &show_grid);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1,1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(60,60,70,200));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, IM_COL32(60,60,70,200));
    ImGui::BeginChild("scrolling_region", ImVec2(0,0), true, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove);
    ImGui::PushItemWidth(120.0f);

    @@ -104,7 +104,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    // Display grid
    if (show_grid)
    {
    ImU32 GRID_COLOR = ImColor(200,200,200,40);
    ImU32 GRID_COLOR = IM_COL32(200,200,200,40);
    float GRID_SZ = 64.0f;
    ImVec2 win_pos = ImGui::GetCursorScreenPos();
    ImVec2 canvas_sz = ImGui::GetWindowSize();
    @@ -123,7 +123,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    Node* node_out = &nodes[link->OutputIdx];
    ImVec2 p1 = offset + node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezierCurve(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,200,100), 3.0f);
    draw_list->AddBezierCurve(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, IM_COL32(200,200,100,255), 3.0f);
    }

    // Display nodes
    @@ -163,13 +163,13 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    if (node_moving_active && ImGui::IsMouseDragging(0))
    node->Pos = node->Pos + ImGui::GetIO().MouseDelta;

    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? ImColor(75,75,75) : ImColor(60,60,60);
    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? IM_COL32(75,75,75,255) : IM_COL32(60,60,60,255);
    draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
    draw_list->AddRect(node_rect_min, node_rect_max, ImColor(100,100,100), 4.0f);
    draw_list->AddRect(node_rect_min, node_rect_max, IM_COL32(100,100,100,255), 4.0f);
    for (int slot_idx = 0; slot_idx < node->InputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetInputSlotPos(slot_idx), NODE_SLOT_RADIUS, ImColor(150,150,150,150));
    draw_list->AddCircleFilled(offset + node->GetInputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150,150,150,150));
    for (int slot_idx = 0; slot_idx < node->OutputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetOutputSlotPos(slot_idx), NODE_SLOT_RADIUS, ImColor(150,150,150,150));
    draw_list->AddCircleFilled(offset + node->GetOutputSlotPos(slot_idx), NODE_SLOT_RADIUS, IM_COL32(150,150,150,150));

    ImGui::PopID();
    }
  5. @ocornut ocornut revised this gist Sep 11, 2015. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    // Creating a node graph editor for ImGui
    // Quick demo, not production code!
    // Quick demo, not production code! This is more of a demo of how to use ImGui to create custom stuff.
    // Better version by @daniel_collin here https://gist.github.com/emoon/b8ff4b4ce4f1b43e79f2
    // See https://github.com/ocornut/imgui/issues/306
    // v0.02
    // Animated gif: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif
  6. @ocornut ocornut revised this gist Aug 28, 2015. 1 changed file with 28 additions and 11 deletions.
    39 changes: 28 additions & 11 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -20,15 +20,17 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    return;
    }

    // Dummy
    struct Node
    {
    int ID;
    char Name[32];
    ImVec2 Pos, Size;
    float Value;
    ImVec4 Color;
    int InputsCount, OutputsCount;

    Node(int id, const char* name, const ImVec2& pos, float value, int inputs_count, int outputs_count) { ID = id; strncpy(Name, name, 31); Name[31] = 0; Pos = pos; Value = value; InputsCount = inputs_count; OutputsCount = outputs_count; }
    Node(int id, const char* name, const ImVec2& pos, float value, const ImVec4& color, int inputs_count, int outputs_count) { ID = id; strncpy(Name, name, 31); Name[31] = 0; Pos = pos; Value = value; Color = color; InputsCount = inputs_count; OutputsCount = outputs_count; }

    ImVec2 GetInputSlotPos(int slot_no) const { return ImVec2(Pos.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)InputsCount+1)); }
    ImVec2 GetOutputSlotPos(int slot_no) const { return ImVec2(Pos.x + Size.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)OutputsCount+1)); }
    @@ -44,12 +46,13 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    static ImVector<NodeLink> links;
    static bool inited = false;
    static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
    static bool show_grid = true;
    static int node_selected = -1;
    if (!inited)
    {
    nodes.push_back(Node(0, "MainTex", ImVec2(40,50), 0.5f, 1, 1));
    nodes.push_back(Node(1, "BumpMap", ImVec2(40,150), 0.42f, 1, 1));
    nodes.push_back(Node(2, "Combine", ImVec2(270,80), 1.0f, 2, 2));
    nodes.push_back(Node(0, "MainTex", ImVec2(40,50), 0.5f, ImColor(255,100,100), 1, 1));
    nodes.push_back(Node(1, "BumpMap", ImVec2(40,150), 0.42f, ImColor(200,100,200), 1, 1));
    nodes.push_back(Node(2, "Combine", ImVec2(270,80), 1.0f, ImColor(0,200,100), 2, 2));
    links.push_back(NodeLink(0, 0, 2, 0));
    links.push_back(NodeLink(1, 0, 2, 1));
    inited = true;
    @@ -85,15 +88,30 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)

    // Create our child canvas
    ImGui::Text("Hold middle mouse button to scroll (%.2f,%.2f)", scrolling.x, scrolling.y);
    ImGui::SameLine(ImGui::GetWindowWidth()-100);
    ImGui::Checkbox("Show grid", &show_grid);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1,1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(40,40,40,200));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(60,60,70,200));
    ImGui::BeginChild("scrolling_region", ImVec2(0,0), true, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove);
    ImGui::PushItemWidth(120.0f);

    ImVec2 offset = ImGui::GetCursorScreenPos() - scrolling;
    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    draw_list->ChannelsSplit(2);
    ImVec2 offset = ImGui::GetCursorScreenPos() - scrolling;

    // Display grid
    if (show_grid)
    {
    ImU32 GRID_COLOR = ImColor(200,200,200,40);
    float GRID_SZ = 64.0f;
    ImVec2 win_pos = ImGui::GetCursorScreenPos();
    ImVec2 canvas_sz = ImGui::GetWindowSize();
    for (float x = fmodf(offset.x,GRID_SZ); x < canvas_sz.x; x += GRID_SZ)
    draw_list->AddLine(ImVec2(x,0.0f)+win_pos, ImVec2(x,canvas_sz.y)+win_pos, GRID_COLOR);
    for (float y = fmodf(offset.y,GRID_SZ); y < canvas_sz.y; y += GRID_SZ)
    draw_list->AddLine(ImVec2(0.0f,y)+win_pos, ImVec2(canvas_sz.x,y)+win_pos, GRID_COLOR);
    }

    // Display links
    draw_list->ChannelsSetCurrent(0); // Background
    @@ -104,7 +122,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    Node* node_out = &nodes[link->OutputIdx];
    ImVec2 p1 = offset + node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezier(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,200,100), 3.0f);
    draw_list->AddBezierCurve(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,200,100), 3.0f);
    }

    // Display nodes
    @@ -121,8 +139,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::BeginGroup(); // Lock horizontal position
    ImGui::Text("%s", node->Name);
    ImGui::SliderFloat("##value", &node->Value, 0.0f, 1.0f, "Alpha %.2f");
    float dummy_color[3] = { node->Pos.x / ImGui::GetWindowWidth(), node->Pos.y / ImGui::GetWindowHeight(), fmodf((float)node->ID * 0.5f, 1.0f) };
    ImGui::ColorEdit3("##color", &dummy_color[0]);
    ImGui::ColorEdit3("##color", &node->Color.x);
    ImGui::EndGroup();

    // Save the size of what we have emitted and whether any of the widgets are being used
    @@ -188,7 +205,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    }
    else
    {
    if (ImGui::MenuItem("Add")) { nodes.push_back(Node(nodes.Size, "New node", scene_pos, 0.5f, 2, 2)); }
    if (ImGui::MenuItem("Add")) { nodes.push_back(Node(nodes.Size, "New node", scene_pos, 0.5f, ImColor(100,100,200), 2, 2)); }
    if (ImGui::MenuItem("Paste", NULL, false, false)) {}
    }
    ImGui::EndPopup();
    @@ -206,4 +223,4 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::EndGroup();

    ImGui::End();
    }
    }
  7. @ocornut ocornut revised this gist Aug 28, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    Node* node_out = &nodes[link->OutputIdx];
    ImVec2 p1 = offset + node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezier(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,100,100), 3.0f);
    draw_list->AddBezier(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,200,100), 3.0f);
    }

    // Display nodes
    @@ -125,7 +125,7 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    ImGui::ColorEdit3("##color", &dummy_color[0]);
    ImGui::EndGroup();

    // Save the size of what we have emitted and weither any of the widgets are being used
    // Save the size of what we have emitted and whether any of the widgets are being used
    bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive());
    node->Size = ImGui::GetItemRectSize() + NODE_WINDOW_PADDING + NODE_WINDOW_PADDING;
    ImVec2 node_rect_max = node_rect_min + node->Size;
  8. @ocornut ocornut revised this gist Aug 28, 2015. 1 changed file with 4 additions and 23 deletions.
    27 changes: 4 additions & 23 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    // Creating a node graph editor for ImGui
    // Quick demo, not production code!
    // See https://github.com/ocornut/imgui/issues/306
    // v0.01
    // v0.02
    // Animated gif: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif

    // NB: You can use math functions/operators on ImVec2 if you #define IMGUI_DEFINE_MATH_OPERATORS and #include "imgui_internal.h"
    @@ -102,28 +102,9 @@ static void ShowExampleAppCustomNodeGraph(bool* opened)
    NodeLink* link = &links[link_idx];
    Node* node_inp = &nodes[link->InputIdx];
    Node* node_out = &nodes[link->OutputIdx];

    #if 1
    // Hermite spline
    // TODO: move to ImDrawList path API
    ImVec2 p1 = offset+node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 t1 = ImVec2(+80.0f, 0.0f);
    ImVec2 p2 = offset+node_out->GetInputSlotPos(link->OutputSlot);
    ImVec2 t2 = ImVec2(+80.0f, 0.0f);
    const int STEPS = 12;
    for (int step = 0; step <= STEPS; step++)
    {
    float t = (float)step / (float)STEPS;
    float h1 = +2*t*t*t - 3*t*t + 1.0f;
    float h2 = -2*t*t*t + 3*t*t;
    float h3 = t*t*t - 2*t*t + t;
    float h4 = t*t*t - t*t;
    draw_list->PathLineTo(ImVec2(h1*p1.x + h2*p2.x + h3*t1.x + h4*t2.x, h1*p1.y + h2*p2.y + h3*t1.y + h4*t2.y));
    }
    draw_list->PathStroke(ImColor(200,200,100), false, 3.0f);
    #else
    draw_list->AddLine(offset+node_inp->GetOutputSlotPos(link->InputSlot), offset+node_out->GetInputSlotPos(link->OutputSlot), ImColor(200,200,100), 3.0f);
    #endif
    ImVec2 p1 = offset + node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 p2 = offset + node_out->GetInputSlotPos(link->OutputSlot);
    draw_list->AddBezier(p1, p1+ImVec2(+50,0), p2+ImVec2(-50,0), p2, ImColor(200,100,100), 3.0f);
    }

    // Display nodes
  9. @ocornut ocornut revised this gist Aug 25, 2015. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,13 @@
    // Animation: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif
    // Creating a node graph editor for ImGui
    // Quick demo, not production code!
    // See https://github.com/ocornut/imgui/issues/306
    // v0.01
    // Animated gif: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif

    // NB: You can use math functions/operators on ImVec2 if you #define IMGUI_DEFINE_MATH_OPERATORS and #include "imgui_internal.h"
    // Here we only declare simple +/- operators so others don't leak into the demo code.
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }

    // Really dumb data structure provided for the example.
    // Note that we storing links are INDICES (not ID) to make example code shorter, obviously a bad idea for any general purpose code.
  10. @ocornut ocornut revised this gist Aug 25, 2015. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions imgui_node_graph_test.cpp
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    // Animation: https://cloud.githubusercontent.com/assets/8225057/9472357/c0263c04-4b4c-11e5-9fdf-2cd4f33f6582.gif
    // See https://github.com/ocornut/imgui/issues/306
    // v0.01

  11. @ocornut ocornut renamed this gist Aug 25, 2015. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  12. @ocornut ocornut created this gist Aug 25, 2015.
    225 changes: 225 additions & 0 deletions node_graph_editor_test.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,225 @@
    // See https://github.com/ocornut/imgui/issues/306
    // v0.01

    // NB: You can use math functions/operators on ImVec2 if you #define IMGUI_DEFINE_MATH_OPERATORS and #include "imgui_internal.h"
    // Here we only declare simple +/- operators so others don't leak into the demo code.
    static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x+rhs.x, lhs.y+rhs.y); }
    static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x-rhs.x, lhs.y-rhs.y); }

    // Really dumb data structure provided for the example.
    // Note that we storing links are INDICES (not ID) to make example code shorter, obviously a bad idea for any general purpose code.
    static void ShowExampleAppCustomNodeGraph(bool* opened)
    {
    ImGui::SetNextWindowSize(ImVec2(700,600), ImGuiSetCond_FirstUseEver);
    if (!ImGui::Begin("Example: Custom Node Graph", opened))
    {
    ImGui::End();
    return;
    }

    struct Node
    {
    int ID;
    char Name[32];
    ImVec2 Pos, Size;
    float Value;
    int InputsCount, OutputsCount;

    Node(int id, const char* name, const ImVec2& pos, float value, int inputs_count, int outputs_count) { ID = id; strncpy(Name, name, 31); Name[31] = 0; Pos = pos; Value = value; InputsCount = inputs_count; OutputsCount = outputs_count; }

    ImVec2 GetInputSlotPos(int slot_no) const { return ImVec2(Pos.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)InputsCount+1)); }
    ImVec2 GetOutputSlotPos(int slot_no) const { return ImVec2(Pos.x + Size.x, Pos.y + Size.y * ((float)slot_no+1) / ((float)OutputsCount+1)); }
    };
    struct NodeLink
    {
    int InputIdx, InputSlot, OutputIdx, OutputSlot;

    NodeLink(int input_idx, int input_slot, int output_idx, int output_slot) { InputIdx = input_idx; InputSlot = input_slot; OutputIdx = output_idx; OutputSlot = output_slot; }
    };

    static ImVector<Node> nodes;
    static ImVector<NodeLink> links;
    static bool inited = false;
    static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
    static int node_selected = -1;
    if (!inited)
    {
    nodes.push_back(Node(0, "MainTex", ImVec2(40,50), 0.5f, 1, 1));
    nodes.push_back(Node(1, "BumpMap", ImVec2(40,150), 0.42f, 1, 1));
    nodes.push_back(Node(2, "Combine", ImVec2(270,80), 1.0f, 2, 2));
    links.push_back(NodeLink(0, 0, 2, 0));
    links.push_back(NodeLink(1, 0, 2, 1));
    inited = true;
    }

    // Draw a list of nodes on the left side
    bool open_context_menu = false;
    int node_hovered_in_list = -1;
    int node_hovered_in_scene = -1;
    ImGui::BeginChild("node_list", ImVec2(100,0));
    ImGui::Text("Nodes");
    ImGui::Separator();
    for (int node_idx = 0; node_idx < nodes.Size; node_idx++)
    {
    Node* node = &nodes[node_idx];
    ImGui::PushID(node->ID);
    if (ImGui::Selectable(node->Name, node->ID == node_selected))
    node_selected = node->ID;
    if (ImGui::IsItemHovered())
    {
    node_hovered_in_list = node->ID;
    open_context_menu |= ImGui::IsMouseClicked(1);
    }
    ImGui::PopID();
    }
    ImGui::EndChild();

    ImGui::SameLine();
    ImGui::BeginGroup();

    const float NODE_SLOT_RADIUS = 4.0f;
    const ImVec2 NODE_WINDOW_PADDING(8.0f, 8.0f);

    // Create our child canvas
    ImGui::Text("Hold middle mouse button to scroll (%.2f,%.2f)", scrolling.x, scrolling.y);
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1,1));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,0));
    ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, ImColor(40,40,40,200));
    ImGui::BeginChild("scrolling_region", ImVec2(0,0), true, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove);
    ImGui::PushItemWidth(120.0f);

    ImDrawList* draw_list = ImGui::GetWindowDrawList();
    draw_list->ChannelsSplit(2);
    ImVec2 offset = ImGui::GetCursorScreenPos() - scrolling;

    // Display links
    draw_list->ChannelsSetCurrent(0); // Background
    for (int link_idx = 0; link_idx < links.Size; link_idx++)
    {
    NodeLink* link = &links[link_idx];
    Node* node_inp = &nodes[link->InputIdx];
    Node* node_out = &nodes[link->OutputIdx];

    #if 1
    // Hermite spline
    // TODO: move to ImDrawList path API
    ImVec2 p1 = offset+node_inp->GetOutputSlotPos(link->InputSlot);
    ImVec2 t1 = ImVec2(+80.0f, 0.0f);
    ImVec2 p2 = offset+node_out->GetInputSlotPos(link->OutputSlot);
    ImVec2 t2 = ImVec2(+80.0f, 0.0f);
    const int STEPS = 12;
    for (int step = 0; step <= STEPS; step++)
    {
    float t = (float)step / (float)STEPS;
    float h1 = +2*t*t*t - 3*t*t + 1.0f;
    float h2 = -2*t*t*t + 3*t*t;
    float h3 = t*t*t - 2*t*t + t;
    float h4 = t*t*t - t*t;
    draw_list->PathLineTo(ImVec2(h1*p1.x + h2*p2.x + h3*t1.x + h4*t2.x, h1*p1.y + h2*p2.y + h3*t1.y + h4*t2.y));
    }
    draw_list->PathStroke(ImColor(200,200,100), false, 3.0f);
    #else
    draw_list->AddLine(offset+node_inp->GetOutputSlotPos(link->InputSlot), offset+node_out->GetInputSlotPos(link->OutputSlot), ImColor(200,200,100), 3.0f);
    #endif
    }

    // Display nodes
    for (int node_idx = 0; node_idx < nodes.Size; node_idx++)
    {
    Node* node = &nodes[node_idx];
    ImGui::PushID(node->ID);
    ImVec2 node_rect_min = offset + node->Pos;

    // Display node contents first
    draw_list->ChannelsSetCurrent(1); // Foreground
    bool old_any_active = ImGui::IsAnyItemActive();
    ImGui::SetCursorScreenPos(node_rect_min + NODE_WINDOW_PADDING);
    ImGui::BeginGroup(); // Lock horizontal position
    ImGui::Text("%s", node->Name);
    ImGui::SliderFloat("##value", &node->Value, 0.0f, 1.0f, "Alpha %.2f");
    float dummy_color[3] = { node->Pos.x / ImGui::GetWindowWidth(), node->Pos.y / ImGui::GetWindowHeight(), fmodf((float)node->ID * 0.5f, 1.0f) };
    ImGui::ColorEdit3("##color", &dummy_color[0]);
    ImGui::EndGroup();

    // Save the size of what we have emitted and weither any of the widgets are being used
    bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive());
    node->Size = ImGui::GetItemRectSize() + NODE_WINDOW_PADDING + NODE_WINDOW_PADDING;
    ImVec2 node_rect_max = node_rect_min + node->Size;

    // Display node box
    draw_list->ChannelsSetCurrent(0); // Background
    ImGui::SetCursorScreenPos(node_rect_min);
    ImGui::InvisibleButton("node", node->Size);
    if (ImGui::IsItemHovered())
    {
    node_hovered_in_scene = node->ID;
    open_context_menu |= ImGui::IsMouseClicked(1);
    }
    bool node_moving_active = ImGui::IsItemActive();
    if (node_widgets_active || node_moving_active)
    node_selected = node->ID;
    if (node_moving_active && ImGui::IsMouseDragging(0))
    node->Pos = node->Pos + ImGui::GetIO().MouseDelta;

    ImU32 node_bg_color = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID)) ? ImColor(75,75,75) : ImColor(60,60,60);
    draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
    draw_list->AddRect(node_rect_min, node_rect_max, ImColor(100,100,100), 4.0f);
    for (int slot_idx = 0; slot_idx < node->InputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetInputSlotPos(slot_idx), NODE_SLOT_RADIUS, ImColor(150,150,150,150));
    for (int slot_idx = 0; slot_idx < node->OutputsCount; slot_idx++)
    draw_list->AddCircleFilled(offset + node->GetOutputSlotPos(slot_idx), NODE_SLOT_RADIUS, ImColor(150,150,150,150));

    ImGui::PopID();
    }
    draw_list->ChannelsMerge();

    // Open context menu
    if (!ImGui::IsAnyItemHovered() && ImGui::IsMouseHoveringWindow() && ImGui::IsMouseClicked(1))
    {
    node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
    open_context_menu = true;
    }
    if (open_context_menu)
    {
    ImGui::OpenPopup("context_menu");
    if (node_hovered_in_list != -1)
    node_selected = node_hovered_in_list;
    if (node_hovered_in_scene != -1)
    node_selected = node_hovered_in_scene;
    }

    // Draw context menu
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8,8));
    if (ImGui::BeginPopup("context_menu"))
    {
    Node* node = node_selected != -1 ? &nodes[node_selected] : NULL;
    ImVec2 scene_pos = ImGui::GetMousePosOnOpeningCurrentPopup() - offset;
    if (node)
    {
    ImGui::Text("Node '%s'", node->Name);
    ImGui::Separator();
    if (ImGui::MenuItem("Rename..", NULL, false, false)) {}
    if (ImGui::MenuItem("Delete", NULL, false, false)) {}
    if (ImGui::MenuItem("Copy", NULL, false, false)) {}
    }
    else
    {
    if (ImGui::MenuItem("Add")) { nodes.push_back(Node(nodes.Size, "New node", scene_pos, 0.5f, 2, 2)); }
    if (ImGui::MenuItem("Paste", NULL, false, false)) {}
    }
    ImGui::EndPopup();
    }
    ImGui::PopStyleVar();

    // Scrolling
    if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
    scrolling = scrolling - ImGui::GetIO().MouseDelta;

    ImGui::PopItemWidth();
    ImGui::EndChild();
    ImGui::PopStyleColor();
    ImGui::PopStyleVar(2);
    ImGui::EndGroup();

    ImGui::End();
    }