Last active
June 30, 2022 05:41
-
-
Save Flix01/a7873b73f0ffb00c87260e5bf13a18d4 to your computer and use it in GitHub Desktop.
NodeGraphEditor with dynamic enum test (https://github.com/Flix01/imgui/issues/15)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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; | |
| } | |
Author
Flix01
commented
Apr 15, 2017

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