Skip to content

Instantly share code, notes, and snippets.

@Flix01
Last active June 30, 2022 05:41
Show Gist options
  • Select an option

  • Save Flix01/a7873b73f0ffb00c87260e5bf13a18d4 to your computer and use it in GitHub Desktop.

Select an option

Save Flix01/a7873b73f0ffb00c87260e5bf13a18d4 to your computer and use it in GitHub Desktop.
NodeGraphEditor with dynamic enum test (https://github.com/Flix01/imgui/issues/15)
// On Ubuntu, I can compile it with the following command line (provided that imgui.h is two folders up, and that I want to use glfw):
// gcc -o basicExample mainBasic.cpp -I"../../" ../../imgui.cpp ../../imgui_draw.cpp -D"IMGUI_INCLUDE_IMGUI_USER_H" -D"IMGUI_INCLUDE_IMGUI_USER_INL" -I"/usr/include/GLFW" -D"IMGUI_USE_GLFW_BINDING" -L"/usr/lib/x86_64-linux-gnu" -lglfw -lX11 -lm -lGL -lstdc++ -s
// This file is intended to test/answer to https://github.com/Flix01/imgui/issues/15
// RESULT:
// As far as you append new enum items everything goes smooth,
// but if you try to delete one, or to insert a new item in the middle of others
// very difficult problems arise, since all users must update their selected item.
#include <imgui.h> // intellisense only
#include <addons/imguinodegrapheditor/imguinodegrapheditor.h> // intellisense only
#include <string.h> //strcpy
#ifndef IM_PLACEMENT_NEW
struct ImPlacementNewDummy {};
inline void* operator new(size_t, ImPlacementNewDummy, void* ptr) { return ptr; }
inline void operator delete(void*, ImPlacementNewDummy, void*) {}
#define IM_PLACEMENT_NEW(_PTR) new(ImPlacementNewDummy(), _PTR)
#endif //IM_PLACEMENT_NEW
// MY DATA STRUCTURE ===============================================================
#define MAX_ENUM_NAME_LENGTH 84 // in bytes
typedef ImVector<char [MAX_ENUM_NAME_LENGTH]> TestEnumNamesType; // so that it works without STL (std::vector<std::string> will be easier to implement)
TestEnumNamesType TestEnumNames;
// NODE DEFINITIONS ================================================================
namespace ImGui {
enum TestNodeTypes {
TNT_CUSTOM_ENUM_EDITOR_NODE = 0,
TNT_CUSTOM_ENUM_USER_NODE,
TNT_COUNT
};
static const char* TestNodeTypeNames[TNT_COUNT] = {"Custom Enum Editor","Custom Enum User"};
class CustomEnumEditorNode : public Node {
protected:
typedef Node Base; //Base Class
typedef CustomEnumEditorNode ThisClass;
CustomEnumEditorNode() : Base() {}
static const int TYPE = TNT_CUSTOM_ENUM_EDITOR_NODE;
int selectedEnumIndex; // field
char buf[MAX_ENUM_NAME_LENGTH];
public:
virtual const char* getTooltip() const {return "CustomEnumEditorNode tooltip.";}
virtual const char* getInfo() const {return "CustomEnumEditorNode info.\n\nThis is supposed to display some info about this node.";}
/*virtual void getDefaultTitleBarColors(ImU32& defaultTitleTextColorOut,ImU32& defaultTitleBgColorOut,float& defaultTitleBgColorGradientOut) const {
// [Optional Override] customize Node Title Colors [default values: 0,0,-1.f => do not override == use default values from the Style()]
defaultTitleTextColorOut = IM_COL32(220,220,220,255);defaultTitleBgColorOut = IM_COL32(0,75,0,255);defaultTitleBgColorGradientOut = -1.f;
}*/
// create:
static ThisClass* Create(const ImVec2& pos) {
// 1) allocation
// MANDATORY (NodeGraphEditor::~NodeGraphEditor() will delete these with ImGui::MemFree(...))
// MANDATORY even with blank ctrs. Reason: ImVector does not call ctrs/dctrs on items.
ThisClass* node = (ThisClass*) ImGui::MemAlloc(sizeof(ThisClass));IM_PLACEMENT_NEW (node) ThisClass();
// 2) main init
node->init("CustomEnumEditorNode",pos,"","",TYPE);
// 3) init fields ( this uses the node->fields variable; otherwise we should have overridden other virtual methods (to render and serialize) )
node->fields.addFieldEnum(&node->selectedEnumIndex,&CustomEnumEditorNode::GetNumEnumItems,&CustomEnumEditorNode::GetTextFromEnumIndex,"###CustomEnumEditor",NULL,&TestEnumNames);
// 4) set (or load) field values
node->selectedEnumIndex = 0;
node->buf[0]='\0';
return node;
}
protected:
virtual bool render(float nodeWidth) // should return "true" if the node has been edited and its values modified (to fire "edited callbacks")
{
bool nodeEdited = false;
if (ImGui::InputText("New item",buf,MAX_ENUM_NAME_LENGTH,ImGuiInputTextFlags_EnterReturnsTrue)) {
if (strlen(buf)>0) {
// Well, here we should: 1) check if the enum already exists to skip it
// and 2) we should insert the new item at the right place (sorted alphabetically) [*]
// we don't:
const int size = TestEnumNames.size();
TestEnumNames.resize(size+1);
strcpy(&TestEnumNames[size][0],buf);
buf[0]='\0';
selectedEnumIndex = size;
nodeEdited = true;
}
}
fields[0].render(nodeWidth);
if (TestEnumNames.size()>0) {
ImGui::SameLine();
if (ImGui::SmallButton("x")) {
// We must delete the item
int size = TestEnumNames.size();
for (int i=selectedEnumIndex;i<size-1;i++) {
strcpy(&TestEnumNames[i][0],&TestEnumNames[i+1][0]);
}
--size;TestEnumNames.resize(size);
if (--selectedEnumIndex<0) selectedEnumIndex=0;
nodeEdited = true;
// Easy... not at all: what happens to all the other nodes of type ColorEnumUserNode ?
// Their selected item can be WRONG! And we don't fix this issue...
// The same BIG problem happens when we implement [2] above: all the selectedEnumIndex will be wrong!
ImGui::SetTooltip("delete item");
}
}
return nodeEdited;
}
public:
static bool GetTextFromEnumIndex(void* data,int value,const char** pTxt) {
if (!pTxt || !data) return false;
const TestEnumNamesType& vec = *((const TestEnumNamesType*) data);
*pTxt = (value>=0 && value<vec.size()) ? vec[value] : "UNKNOWN";
return true;
}
static int GetNumEnumItems(void* data) {
if (!data) return 0;
const TestEnumNamesType& vec = *((const TestEnumNamesType*) data);
return vec.size();
}
// casts:
inline static ThisClass* Cast(Node* n) {return Node::Cast<ThisClass>(n,TYPE);}
inline static const ThisClass* Cast(const Node* n) {return Node::Cast<ThisClass>(n,TYPE);}
};
class ColorEnumUserNode : public Node {
protected:
typedef Node Base; //Base Class
typedef ColorEnumUserNode ThisClass;
ColorEnumUserNode() : Base() {}
static const int TYPE = TNT_CUSTOM_ENUM_USER_NODE;
int selectedEnumIndex; // field
virtual const char* getTooltip() const {return "ColorEnumUserNode tooltip.";}
virtual const char* getInfo() const {return "ColorEnumUserNode info.\n\nThis is supposed to display some info about this node.";}
/*virtual void getDefaultTitleBarColors(ImU32& defaultTitleTextColorOut,ImU32& defaultTitleBgColorOut,float& defaultTitleBgColorGradientOut) const {
// [Optional Override] customize Node Title Colors [default values: 0,0,-1.f => do not override == use default values from the Style()]
defaultTitleTextColorOut = IM_COL32(220,220,220,255);defaultTitleBgColorOut = IM_COL32(0,75,0,255);defaultTitleBgColorGradientOut = -1.f;
}*/
public:
// create:
static ThisClass* Create(const ImVec2& pos) {
// 1) allocation
// MANDATORY (NodeGraphEditor::~NodeGraphEditor() will delete these with ImGui::MemFree(...))
// MANDATORY even with blank ctrs. Reason: ImVector does not call ctrs/dctrs on items.
ThisClass* node = (ThisClass*) ImGui::MemAlloc(sizeof(ThisClass));IM_PLACEMENT_NEW (node) ThisClass();
// 2) main init
node->init("ColorEnumUserNode",pos,"in_a;in_b","out_a;out_b",TYPE);
// 3) init fields ( this uses the node->fields variable; otherwise we should have overridden other virtual methods (to render and serialize) )
node->fields.addFieldEnum(&node->selectedEnumIndex,&CustomEnumEditorNode::GetNumEnumItems,&CustomEnumEditorNode::GetTextFromEnumIndex,"Selection","select your favourite",&TestEnumNames);
// 4) set (or load) field values
node->selectedEnumIndex = 0;
return node;
}
// casts:
inline static ThisClass* Cast(Node* n) {return Node::Cast<ThisClass>(n,TYPE);}
inline static const ThisClass* Cast(const Node* n) {return Node::Cast<ThisClass>(n,TYPE);}
};
static Node* TestNodeFactory(int nt,const ImVec2& pos) {
switch (nt) {
case TNT_CUSTOM_ENUM_EDITOR_NODE: return CustomEnumEditorNode::Create(pos);
case TNT_CUSTOM_ENUM_USER_NODE: return ColorEnumUserNode::Create(pos);
default:
IM_ASSERT(true); // Missing node type creation
return NULL;
}
return NULL;
}
} // namespace ImGui
// END NODE DEFINITIONS ============================================================
ImGui::NodeGraphEditor nge;
// Mandatory methods
void InitGL() {
if (nge.isInited()) {
// We should load "TestEnumNames" from a file here. Instead we do:
TestEnumNames.resize(3);
strcpy(&TestEnumNames[0][0],"APPLE");
strcpy(&TestEnumNames[1][0],"LEMON");
strcpy(&TestEnumNames[2][0],"ORANGE");
// This adds entries to the "add node" context menu
nge.registerNodeTypes(ImGui::TestNodeTypeNames,ImGui::TNT_COUNT,ImGui::TestNodeFactory,NULL,-1); // last 2 args can be used to add only a subset of nodes (or to sort their order inside the context menu)
nge.registerNodeTypeMaxAllowedInstances(ImGui::TNT_CUSTOM_ENUM_EDITOR_NODE,1); // Here we set the max number of allowed instances of the node (1)
// Optional: starting nodes and links (load from file instead):-----------
ImGui::Node* customEnumEditorNode = nge.addNode(ImGui::TNT_CUSTOM_ENUM_EDITOR_NODE,ImVec2(40,50));
ImGui::Node* colorEnumUserNode1 = nge.addNode(ImGui::TNT_CUSTOM_ENUM_USER_NODE,ImVec2(40,180));
ImGui::Node* colorEnumUserNode2 = nge.addNode(ImGui::TNT_CUSTOM_ENUM_USER_NODE,ImVec2(300,180)); // optionally use e.g.: ImGui::ColorEnumUserNode::Cast(colorEnumUserNode1)->...;
ImGui::Node* colorEnumUserNode3 = nge.addNode(ImGui::TNT_CUSTOM_ENUM_USER_NODE,ImVec2(550,180));
nge.addLink(colorEnumUserNode1, 0, colorEnumUserNode2, 0);
nge.addLink(colorEnumUserNode1, 1, colorEnumUserNode2, 1);
nge.addLink(colorEnumUserNode2, 0, colorEnumUserNode3, 0);
nge.addLink(colorEnumUserNode2, 1, colorEnumUserNode3, 1);
//-------------------------------------------------------------------------------
//nge.load("nodeGraphEditor.nge"); // Please note than if the saved graph has nodes out of our active subset, they will be displayed as usual (it's not clear what should be done in this case: hope that's good enough, it's a user's mistake).
//-------------------------------------------------------------------------------
nge.show_style_editor = true;
nge.show_load_save_buttons = true;
// optional load the style (for all the editors: better call it in InitGL()):
//NodeGraphEditor::Style::Load(NodeGraphEditor::GetStyle(),"nodeGraphEditor.style");
//--------------------------------------------------------------------------------
}
}
void ResizeGL(int,int) {}
void DestroyGL() {
// We should save "TestEnumNames" to a file here
}
void DrawGL()
{
ImImpl_ClearColorBuffer(ImVec4(0.6f, 0.6f, 0.6f, 1.0f)); // Warning: it does not clear the depth buffer
static bool open = true;
if (ImGui::Begin("Node Graph Editor", &open, ImVec2(1190,710),0.85f,ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings)) {
nge.render();
}
ImGui::End();
}
#ifndef IMGUI_USE_AUTO_BINDING_WINDOWS // IMGUI_USE_AUTO_ definitions get defined automatically (e.g. do NOT touch them!)
int main(int argc, char** argv)
#else //IMGUI_USE_AUTO_BINDING_WINDOWS
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int iCmdShow) {
#endif //IMGUI_USE_AUTO_BINDING_WINDOWS
{
ImImpl_InitParams* pInitParams = NULL;
# ifndef IMGUI_USE_AUTO_BINDING_WINDOWS
ImImpl_Main(pInitParams,argc,argv);
# else //IMGUI_USE_AUTO_BINDING_WINDOWS
ImImpl_WinMain(pInitParams,hInstance,hPrevInstance,lpCmdLine,iCmdShow);
# endif //IMGUI_USE_AUTO_BINDING_WINDOWS
return 0;
}
@Flix01
Copy link
Author

Flix01 commented Apr 15, 2017

dynamicenum

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