Skip to content

Instantly share code, notes, and snippets.

@emoon
Created September 9, 2015 17:02
Show Gist options
  • Star 35 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save emoon/b8ff4b4ce4f1b43e79f2 to your computer and use it in GitHub Desktop.
Save emoon/b8ff4b4ce4f1b43e79f2 to your computer and use it in GitHub Desktop.
// ImGui - standalone example application for Glfw + OpenGL 3, using programmable pipeline
#include <imgui.h>
#include "imgui_impl_glfw_gl3.h"
#include <stdio.h>
#include <GL/gl3w.h>
#include <GLFW/glfw3.h>
#include <math.h>
#include <string.h>
#include <vector>
#include <algorithm>
#include <stdint.h>
//#include <jansson.h>
//#include "dialogs.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define sizeof_array(t) (sizeof(t) / sizeof(t[0]))
const float NODE_SLOT_RADIUS = 5.0f;
const ImVec2 NODE_WINDOW_PADDING(8.0f, 8.0f);
#define MAX_CONNECTION_COUNT 32
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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 void error_callback(int error, const char* description)
{
fprintf(stderr, "Error %d: %s\n", error, description);
}
static uint32_t s_id = 0;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
enum ConnectionType
{
ConnectionType_Color,
ConnectionType_Vec3,
ConnectionType_Float,
ConnectionType_Int,
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct ConnectionDesc
{
const char* name;
ConnectionType type;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct NodeType
{
const char* name;
ConnectionDesc inputConnections[MAX_CONNECTION_COUNT];
ConnectionDesc outputConnections[MAX_CONNECTION_COUNT];
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct Connection
{
ImVec2 pos;
ConnectionDesc desc;
inline Connection()
{
pos.x = pos.y = 0.0f;
input = 0;
}
union {
float v3[3];
float v;
int i;
};
struct Connection* input;
std::vector<Connection*> output;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Node types
static struct NodeType s_nodeTypes[] =
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Math
{
"Multiply",
// Input connections
{
{ "Input1", ConnectionType_Float },
{ "Input2", ConnectionType_Float },
},
// Output
{
{ "Out", ConnectionType_Float },
},
},
{
"Add",
// Input connections
{
{ "Input1", ConnectionType_Float },
{ "Input2", ConnectionType_Float },
},
// Output
{
{ "Out", ConnectionType_Float },
},
},
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct Node
{
ImVec2 pos;
ImVec2 size;
int id;
const char* name;
std::vector<Connection*> inputConnections;
std::vector<Connection*> outputConnections;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void setupConnections(std::vector<Connection*>& connections, ConnectionDesc* connectionDescs)
{
for (int i = 0; i < MAX_CONNECTION_COUNT; ++i)
{
const ConnectionDesc& desc = connectionDescs[i];
if (!desc.name)
break;
Connection* con = new Connection;
con->desc = desc;
connections.push_back(con);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static Node* createNodeFromType(ImVec2 pos, NodeType* nodeType)
{
Node* node = new Node;
node->id = s_id++;
node->name = nodeType->name;
ImVec2 titleSize = ImGui::CalcTextSize(node->name);
titleSize.y *= 3;
setupConnections(node->inputConnections, nodeType->inputConnections);
setupConnections(node->outputConnections, nodeType->outputConnections);
// Calculate the size needed for the whole box
ImVec2 inputTextSize(0.0f, 0.0f);
ImVec2 outputText(0.0f, 0.0f);
for (Connection* c : node->inputConnections)
{
ImVec2 textSize = ImGui::CalcTextSize(c->desc.name);
inputTextSize.x = std::max<float>(textSize.x, inputTextSize.x);
c->pos = ImVec2(0.0f, titleSize.y + inputTextSize.y + textSize.y / 2.0f);
inputTextSize.y += textSize.y;
inputTextSize.y += 4.0f; // size between text entries
}
inputTextSize.x += 40.0f;
// max text size + 40 pixels in between
float xStart = inputTextSize.x;
// Calculate for the outputs
for (Connection* c : node->outputConnections)
{
ImVec2 textSize = ImGui::CalcTextSize(c->desc.name);
inputTextSize.x = std::max<float>(xStart + textSize.x, inputTextSize.x);
}
node->pos = pos;
node->size.x = inputTextSize.x;
node->size.y = inputTextSize.y + titleSize.y;
inputTextSize.y = 0.0f;
// set the positions for the output nodes when we know where the place them
for (Connection* c : node->outputConnections)
{
ImVec2 textSize = ImGui::CalcTextSize(c->desc.name);
c->pos = ImVec2(node->size.x, titleSize.y + inputTextSize.y + textSize.y / 2.0f);
inputTextSize.y += textSize.y;
inputTextSize.y += 4.0f; // size between text entries
}
// calculate the size of the node depending on nuber of connections
return node;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Node* createNodeFromName(ImVec2 pos, const char* name)
{
for (int i = 0; i < (int)sizeof_array(s_nodeTypes); ++i)
{
if (!strcmp(s_nodeTypes[i].name, name))
return createNodeFromType(pos, &s_nodeTypes[i]);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
enum DragState
{
DragState_Default,
DragState_Hover,
DragState_BeginDrag,
DragState_Draging,
DragState_Connect,
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct DragNode
{
ImVec2 pos;
Connection* con;
};
static DragNode s_dragNode;
static DragState s_dragState = DragState_Default;
static std::vector<Node*> s_nodes;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
static void saveNodes(const char* filename)
{
json_t* root = json_object();
json_t* nodes = json_array();
for (Node* node : s_nodes)
{
json_t* item = json_object();
json_object_set_new(item, "type", json_string(node->name));
json_object_set_new(item, "id", json_integer(node->id));
json_object_set_new(item, "pos", json_pack("{s:f, s:f}", "x", node->pos.x, "y", node->pos.y));
json_array_append_new(nodes, item);
}
// save the nodes
json_object_set_new(root, "nodes", nodes);
if (json_dump_file(root, filename, JSON_INDENT(4) | JSON_PRESERVE_ORDER) != 0)
printf("JSON: Unable to open %s for write\n", filename);
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void drawHermite(ImDrawList* drawList, ImVec2 p1, ImVec2 p2, int STEPS)
{
ImVec2 t1 = ImVec2(+80.0f, 0.0f);
ImVec2 t2 = ImVec2(+80.0f, 0.0f);
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;
drawList->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));
}
drawList->PathStroke(ImColor(200,200,100), false, 3.0f);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static bool isConnectorHovered(Connection* c, ImVec2 offset)
{
ImVec2 mousePos = ImGui::GetIO().MousePos;
ImVec2 conPos = offset + c->pos;
float xd = mousePos.x - conPos.x;
float yd = mousePos.y - conPos.y;
return ((xd * xd) + (yd *yd)) < (NODE_SLOT_RADIUS * NODE_SLOT_RADIUS);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static Connection* getHoverCon(ImVec2 offset, ImVec2* pos)
{
for (Node* node : s_nodes)
{
ImVec2 nodePos = node->pos + offset;
for (Connection* con : node->inputConnections)
{
if (isConnectorHovered(con, nodePos))
{
*pos = nodePos + con->pos;
return con;
}
}
for (Connection* con : node->outputConnections)
{
if (isConnectorHovered(con, nodePos))
{
*pos = nodePos + con->pos;
return con;
}
}
}
s_dragNode.con = 0;
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void updateDraging(ImVec2 offset)
{
switch (s_dragState)
{
case DragState_Default:
{
ImVec2 pos;
Connection* con = getHoverCon(offset, &pos);
if (con)
{
s_dragNode.con = con;
s_dragNode.pos = pos;
s_dragState = DragState_Hover;
return;
}
break;
}
case DragState_Hover:
{
ImVec2 pos;
Connection* con = getHoverCon(offset, &pos);
// Make sure we are still hovering the same node
if (con != s_dragNode.con)
{
s_dragNode.con = 0;
s_dragState = DragState_Default;
return;
}
if (ImGui::IsMouseClicked(0) && s_dragNode.con)
s_dragState = DragState_Draging;
break;
}
case DragState_BeginDrag:
{
break;
}
case DragState_Draging:
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->ChannelsSetCurrent(0); // Background
drawHermite(drawList, s_dragNode.pos, ImGui::GetIO().MousePos, 12);
if (!ImGui::IsMouseDown(0))
{
ImVec2 pos;
Connection* con = getHoverCon(offset, &pos);
// Make sure we are still hovering the same node
if (con == s_dragNode.con)
{
s_dragNode.con = 0;
s_dragState = DragState_Default;
return;
}
// Lets connect the nodes.
// TODO: Make sure we connect stuff in the correct way!
con->input = s_dragNode.con;
s_dragNode.con = 0;
s_dragState = DragState_Default;
}
break;
}
case DragState_Connect:
{
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void displayNode(ImDrawList* drawList, ImVec2 offset, Node* node, int& node_selected)
{
int node_hovered_in_scene = -1;
bool open_context_menu = false;
ImGui::PushID(node->id);
ImVec2 node_rect_min = offset + node->pos;
// Display node contents first
drawList->ChannelsSetCurrent(1); // Foreground
bool old_any_active = ImGui::IsAnyItemActive();
// Draw title in center
ImVec2 textSize = ImGui::CalcTextSize(node->name);
ImVec2 pos = node_rect_min + NODE_WINDOW_PADDING;
pos.x = node_rect_min.x + (node->size.x / 2) - textSize.x / 2;
ImGui::SetCursorScreenPos(pos);
//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]);
// 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
drawList->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 = false;
if (ImGui::IsItemActive() && !s_dragNode.con)
node_moving_active = true;
ImU32 node_bg_color = node_hovered_in_scene == node->id ? ImColor(75,75,75) : ImColor(60,60,60);
drawList->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
ImVec2 titleArea = node_rect_max;
titleArea.y = node_rect_min.y + 30.0f;
// Draw text bg area
drawList->AddRectFilled(node_rect_min + ImVec2(1,1), titleArea, ImColor(100,0,0), 4.0f);
drawList->AddRect(node_rect_min, node_rect_max, ImColor(100,100,100), 4.0f);
ImVec2 off;
offset.y += 40.0f;
offset = offset + node_rect_min;
off.x = node_rect_min.x;
off.y = node_rect_min.y;
for (Connection* con : node->inputConnections)
{
ImGui::SetCursorScreenPos(offset + ImVec2(10.0f, 0));
ImGui::Text("%s", con->desc.name);
ImColor conColor = ImColor(150, 150, 150);
if (isConnectorHovered(con, node_rect_min))
conColor = ImColor(200, 200, 200);
drawList->AddCircleFilled(node_rect_min + con->pos, NODE_SLOT_RADIUS, conColor);
offset.y += textSize.y + 2.0f;
}
offset = node_rect_min;
offset.y += 40.0f;
for (Connection* con : node->outputConnections)
{
textSize = ImGui::CalcTextSize(con->desc.name);
ImGui::SetCursorScreenPos(offset + ImVec2(con->pos.x - (textSize.x + 10.0f), 0));
ImGui::Text("%s", con->desc.name);
ImColor conColor = ImColor(150, 150, 150);
if (isConnectorHovered(con, node_rect_min))
conColor = ImColor(200, 200, 200);
drawList->AddCircleFilled(node_rect_min + con->pos, NODE_SLOT_RADIUS, conColor);
offset.y += textSize.y + 2.0f;
}
//for (int i = 0; i < node->outputConnections.size(); ++i)
// drawList->AddCircleFilled(offset + node->outputSlotPos(i), NODE_SLOT_RADIUS, ImColor(150,150,150,150));
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;
//ImGui::EndGroup();
ImGui::PopID();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: Ugly fix: me
Node* findNodeByCon(Connection* findCon)
{
for (Node* node : s_nodes)
{
for (Connection* con : node->inputConnections)
{
if (con == findCon)
return node;
}
for (Connection* con : node->outputConnections)
{
if (con == findCon)
return node;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void renderLines(ImDrawList* drawList, ImVec2 offset)
{
for (Node* node : s_nodes)
{
for (Connection* con : node->inputConnections)
{
if (!con->input)
continue;
Node* targetNode = findNodeByCon(con->input);
if (!targetNode)
continue;
drawHermite(drawList,
offset + targetNode->pos + con->input->pos,
offset + node->pos + con->pos,
12);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void ShowExampleAppCustomNodeGraph(bool* opened)
{
ImGui::SetNextWindowSize(ImVec2(700,600), ImGuiSetCond_FirstUseEver);
if (!ImGui::Begin("Example: Custom Node Graph", opened))
{
ImGui::End();
return;
}
bool open_context_menu = false;
int node_hovered_in_list = -1;
int node_hovered_in_scene = -1;
static int node_selected = -1;
static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
#if 0
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
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();
#endif
ImGui::SameLine();
ImGui::BeginGroup();
// 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;
//displayNode(draw_list, scrolling, s_emittable, node_selected);
//displayNode(draw_list, scrolling, s_emitter, node_selected);
for (Node* node : s_nodes)
displayNode(draw_list, scrolling, node, node_selected);
updateDraging(scrolling);
renderLines(draw_list, scrolling);
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"))
{
if (ImGui::MenuItem("Load graph..."))
{
/*
char path[1024];
if (Dialog_open(path))
{
printf("file to load %s\n", path);
}
*/
}
if (ImGui::MenuItem("Save graph..."))
{
/*
char path[1024];
if (Dialog_save(path))
{
saveNodes(path);
}
*/
}
/*
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
for (int i = 0; i < (int)sizeof_array(s_nodeTypes); ++i)
{
if (ImGui::MenuItem(s_nodeTypes[i].name))
{
Node* node = createNodeFromType(ImGui::GetIO().MousePos, &s_nodeTypes[i]);
s_nodes.push_back(node);
}
}
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();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int main(int, char**)
{
// Setup window
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#if __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui OpenGL3 example", NULL, NULL);
glfwMakeContextCurrent(window);
gl3wInit();
// Setup ImGui binding
ImGui_ImplGlfwGL3_Init(window, true);
/*
s_emitter = createNodeFromName(ImVec2(500.0f, 100.0f), "Emitter");
s_emittable = createNodeFromName(ImVec2(500.0f, 300.0f), "Emittable");
s_quad = createNodeFromName(ImVec2(500.0f, 600.0f), "Quad");
*/
bool show_test_window = true;
//bool show_another_window = false;
ImVec4 clear_color = ImColor(114, 144, 154);
// Main loop
while (!glfwWindowShouldClose(window))
{
ImGuiIO& io = ImGui::GetIO();
glfwPollEvents();
ImGui_ImplGlfwGL3_NewFrame();
ShowExampleAppCustomNodeGraph(&show_test_window);
// Rendering
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui::Render();
glfwSwapBuffers(window);
}
// Cleanup
ImGui_ImplGlfwGL3_Shutdown();
glfwTerminate();
return 0;
}
#ifdef _WIN32
int CALLBACK WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow)
{
return main(0, 0);
}
#endif
@sphaero
Copy link

sphaero commented Apr 19, 2018

The example doesn't draw anything, setting #if 1 ends with NodeLink not being declared

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment