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.

Revisions

  1. Flix01 revised this gist Apr 22, 2017. 1 changed file with 10 additions and 9 deletions.
    19 changes: 10 additions & 9 deletions imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -143,15 +143,15 @@ class CustomEnumEditorNode : public Node {
    inline static const ThisClass* Cast(const Node* n) {return Node::Cast<ThisClass>(n,TYPE);}

    };
    class ColorEnumUserNode : public Node
    class CustomEnumUserNode : public Node
    #ifndef NO_DYNAMIC_CAST
    , public virtual ITestEnum
    #endif //NO_DYNAMIC_CAST
    {
    protected:
    typedef Node Base; //Base Class
    typedef ColorEnumUserNode ThisClass;
    ColorEnumUserNode() : Base() {}
    typedef CustomEnumUserNode ThisClass;
    CustomEnumUserNode() : Base() {}
    static const int TYPE = TNT_CUSTOM_ENUM_USER_NODE;

    int selectedEnumIndex; // field
    @@ -228,11 +228,11 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    ImGui::CustomEnumUserNode& n = *ImGui::CustomEnumUserNode::Cast(nodes[i]);
    int& selectedIndexEnum = n.getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    ImGui::ColorEnumUserNode* copiedNode = ImGui::ColorEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    ImGui::CustomEnumUserNode* copiedNode = ImGui::CustomEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    @@ -278,12 +278,12 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    ImGui::CustomEnumUserNode& n = *ImGui::CustomEnumUserNode::Cast(nodes[i]);
    int& selectedIndexEnum = n.getSelectedItem();
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    ImGui::ColorEnumUserNode* copiedNode = ImGui::ColorEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    ImGui::CustomEnumUserNode* copiedNode = ImGui::CustomEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    @@ -304,10 +304,10 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    return nodeEdited;
    }

    static Node* TestNodeFactory(int nt,const ImVec2& pos) {
    static Node* TestNodeFactory(int nt,const ImVec2& pos,const NodeGraphEditor& /*nge*/) {
    switch (nt) {
    case TNT_CUSTOM_ENUM_EDITOR_NODE: return CustomEnumEditorNode::Create(pos);
    case TNT_CUSTOM_ENUM_USER_NODE: return ColorEnumUserNode::Create(pos);
    case TNT_CUSTOM_ENUM_USER_NODE: return CustomEnumUserNode::Create(pos);
    default:
    IM_ASSERT(true); // Missing node type creation
    return NULL;
    @@ -450,3 +450,4 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,
    return 0;
    }


  2. Flix01 revised this gist Apr 18, 2017. 1 changed file with 26 additions and 2 deletions.
    28 changes: 26 additions & 2 deletions imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,8 @@
    //
    // Added also some code to serialize/deserialize the enum names ("TestEnumNames") together
    // with the Node Graph Editor itself (to the same file).

    //
    // Fixed a possible copy/paste bug

    #include <imgui.h> // intellisense only
    #include <addons/imguinodegrapheditor/imguinodegrapheditor.h> // intellisense only
    @@ -208,7 +209,7 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    selectedEnumIndex = itemIndex;
    nodeEdited = true;
    //Now we must correct all the "selectedItem>=itemPlacement" in all the NodeGraphEditor
    ImGui::NodeGraphEditor& nge = getNodeGraphEditor();
    ImGui::NodeGraphEditor& nge = getNodeGraphEditor(); // Actually if we use more than one Node Graph Editor with the same node types, the Dynamic Enum is the same, so we should process other editors as well...
    # ifndef NO_DYNAMIC_CAST
    for (int i=0,iSz=nge.getNumNodes();i<iSz;i++) {
    ITestEnum* n = dynamic_cast<ITestEnum*>(nge.getNode(i));
    @@ -217,6 +218,11 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    }
    ITestEnum* copiedNode = dynamic_cast<ITestEnum*>(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # else //NO_DYNAMIC_CAST
    ImVector<ImGui::Node*> nodes;
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    @@ -226,7 +232,13 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    int& selectedIndexEnum = n.getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    ImGui::ColorEnumUserNode* copiedNode = ImGui::ColorEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # endif //NO_DYNAMIC_CAST

    mustFocusInputText=true;
    }
    }
    @@ -255,6 +267,12 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    }
    ITestEnum* copiedNode = dynamic_cast<ITestEnum*>(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # else //NO_DYNAMIC_CAST
    ImVector<ImGui::Node*> nodes;
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    @@ -265,6 +283,12 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    ImGui::ColorEnumUserNode* copiedNode = ImGui::ColorEnumUserNode::Cast(nge.getCopiedNode()); // This is the internal node kept for copy/paste operations: we can't forget to process it
    if (copiedNode) {
    int& selectedIndexEnum = copiedNode->getSelectedItem();
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # endif //NO_DYNAMIC_CAST
    if (--selectedEnumIndex<0) selectedEnumIndex=0;
    }
  3. Flix01 revised this gist Apr 17, 2017. 1 changed file with 83 additions and 49 deletions.
    132 changes: 83 additions & 49 deletions imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -7,10 +7,8 @@
    // And if you can use dynamic_cast<>() making new Node types that use it is easier (non-intrusive)
    // Otherwise you must modify the code of CustomEnumEditorNode::render(...) for every new user class you add.
    //
    // Added also some code to serialize/deserialize the enum names ("TestEnumNames").
    // Serialization/deserialization of the Node Graph Editor is not shown here, but it's trivial to add
    // and it's already shown in the default Node Graph Editor Demo.

    // Added also some code to serialize/deserialize the enum names ("TestEnumNames") together
    // with the Node Graph Editor itself (to the same file).


    #include <imgui.h> // intellisense only
    @@ -88,15 +86,16 @@ class CustomEnumEditorNode : public Node {

    int selectedEnumIndex; // field
    char buf[MAX_ENUM_NAME_LENGTH];
    bool mustFocusInputText;

    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 {
    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) {
    @@ -110,16 +109,19 @@ class CustomEnumEditorNode : public Node {

    // 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);
    // Please node that in the render(...) method we assume that the dynamic enum FieldInfo is the first one (at node->fields[0]).

    // 4) set (or load) field values
    node->selectedEnumIndex = 0;
    node->buf[0]='\0';
    node->selectedEnumIndex = 0; // field value
    node->buf[0]='\0'; // other (non-field) variables
    node->mustFocusInputText = false;

    return node;
    }

    protected:
    virtual bool render(float nodeWidth);
    virtual bool canBeCopied() const {return false;}

    public:

    @@ -142,7 +144,7 @@ class CustomEnumEditorNode : public Node {
    };
    class ColorEnumUserNode : public Node
    #ifndef NO_DYNAMIC_CAST
    , public ITestEnum
    , public virtual ITestEnum
    #endif //NO_DYNAMIC_CAST
    {
    protected:
    @@ -162,8 +164,7 @@ class ColorEnumUserNode : public Node

    public:

    int& getSelectedItem() {return selectedEnumIndex;} // ITestEnum
    friend class CustomEnumEditorNode;
    int& getSelectedItem() {return selectedEnumIndex;} // ITestEnum if available, but used also if not available (to expose a private variable)

    // create:
    static ThisClass* Create(const ImVec2& pos) {
    @@ -179,7 +180,7 @@ class ColorEnumUserNode : public Node
    node->fields.addFieldEnum(&node->selectedEnumIndex,&CustomEnumEditorNode::GetNumEnumItems,&CustomEnumEditorNode::GetTextFromEnumIndex,"Selection","select your favourite",&TestEnumNames);

    // 4) set (or load) field values
    node->selectedEnumIndex = 0;
    node->selectedEnumIndex = -1;

    return node;
    }
    @@ -193,11 +194,13 @@ class ColorEnumUserNode : public Node
    };



    bool CustomEnumEditorNode::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 (mustFocusInputText) ImGui::SetKeyboardFocusHere(0);
    if (ImGui::InputText("New item",buf,MAX_ENUM_NAME_LENGTH,ImGuiInputTextFlags_EnterReturnsTrue /*| ImGuiInputTextFlags_AutoSelectAll*/)) {
    mustFocusInputText=false;
    if (strlen(buf)>0) {
    const int itemIndex=TestEnumNamesInsert(buf);
    if (itemIndex>=0) {
    @@ -211,7 +214,7 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    ITestEnum* n = dynamic_cast<ITestEnum*>(nge.getNode(i));
    if (n) {
    int& selectedIndexEnum = n->getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum;
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    }
    # else //NO_DYNAMIC_CAST
    @@ -220,17 +223,26 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    if (n.selectedEnumIndex>=itemIndex) ++n.selectedEnumIndex;
    int& selectedIndexEnum = n.getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # endif //NO_DYNAMIC_CAST
    mustFocusInputText=true;
    }
    }
    }
    fields[0].render(nodeWidth);
    else mustFocusInputText=false;
    if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s","insert a new item");

    // Here we render field 0 (without marking the "nodeEdited" flag)
    if (fields[0].render(nodeWidth)) mustFocusInputText=true; // We use "mustFocusInputText" to keep the InputText focused while switching enum
    if (ImGui::IsItemActive()) mustFocusInputText=true; // This seems to cover the case when we switch to the same item

    if (TestEnumNames.size()>0) {
    ImGui::SameLine();
    if (ImGui::SmallButton("x") && TestEnumNamesDelete(selectedEnumIndex)) {
    nodeEdited = true;
    mustFocusInputText=true;

    //Now we must correct all the "selectedItem>=selectedEnumIndex" in all the NodeGraphEditor
    ImGui::NodeGraphEditor& nge = getNodeGraphEditor();
    @@ -239,7 +251,8 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    ITestEnum* n = dynamic_cast<ITestEnum*>(nge.getNode(i));
    if (n) {
    int& selectedIndexEnum = n->getSelectedItem();
    if (selectedIndexEnum>=selectedEnumIndex) --selectedIndexEnum;
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    }
    # else //NO_DYNAMIC_CAST
    @@ -248,12 +261,22 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    if (n.selectedEnumIndex>=selectedEnumIndex) --n.selectedEnumIndex;
    int& selectedIndexEnum = n.getSelectedItem();
    if (selectedIndexEnum==selectedEnumIndex) selectedIndexEnum=-1; // we're deleting the selected item of node n
    if (selectedIndexEnum>selectedEnumIndex) --selectedIndexEnum; // otherwise node n selected index gets wrong
    }
    # endif //NO_DYNAMIC_CAST
    if (--selectedEnumIndex<0) selectedEnumIndex=0;
    }
    if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s","delete");
    }

    // Now we can draw the other fields normally (no one in this demo)
    for (int i=1,isz=fields.size();i<isz;i++) {
    FieldInfo& f = fields[i];
    nodeEdited|=f.render(nodeWidth);
    }

    return nodeEdited;
    }

    @@ -289,7 +312,11 @@ inline bool TestEnumNamesSave(const char* filename) {
    #ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD
    static bool TestEnumNamesTypeLoadCallback(ImGuiHelper::FieldType ft,int numArrayElements,void* pValue,const char* name,void* userPtr) {
    TestEnumNamesType* vec = (TestEnumNamesType*) userPtr;
    if (strcmp(name,"num_text_line_items")==0) {vec->resize(*((int*)pValue));for (int i=0;i<vec->size();i++) (*vec)[i][0]='\0';}
    if (strcmp(name,"num_text_line_items")==0) {
    vec->resize(*((int*)pValue));
    for (int i=0;i<vec->size();i++) (*vec)[i][0]='\0';
    if (vec->size()==0) return true;
    }
    else if (ft==ImGui::FT_TEXTLINE && strcmp(name,"text_line_items")==0) {
    IM_ASSERT(numArrayElements<vec->size());
    strcpy(&(*vec)[numArrayElements][0],(char*)pValue);
    @@ -310,54 +337,61 @@ bool TestEnumNamesLoad(const char* filename) {
    #endif //NO_IMGUIHELPER_SERIALIZATION_LOAD
    #endif //NO_IMGUIHELPER_SERIALIZATION

    const char* TestEnumNamesSavePath = "testEnumNames.txt";
    const char* NodeGraphEditorSavePath = "testEnumNamesNodeGraphEditor.nge";

    ImGui::NodeGraphEditor nge;
    // Mandatory methods
    void InitGL() {
    if (nge.isInited()) {
    // We should load "TestEnumNames" from a file here. Instead we do:
    if (nge.isInited()) {
    // 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)
    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");
    //--------------------------------------------------------------------------------

    // Here we load "TestEnumNames" + the saved node graph editor from a singlefile.
    bool nodeGraphEditorSavePathLoaded = false;
    # if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION) && !defined(NO_IMGUIHELPER_SERIALIZATION_LOAD))
    TestEnumNamesLoad(TestEnumNamesSavePath);
    {
    ImGuiHelper::Deserializer d(NodeGraphEditorSavePath);
    nodeGraphEditorSavePathLoaded = d.isValid();
    const char* offset = 0; // Basically offset advances at each loading step
    TestEnumNamesLoad(d,&offset); // TestEnumNames
    nge.load(d,&offset); // nge
    }
    # endif
    if (TestEnumNames.size()==0) {
    if (!nodeGraphEditorSavePathLoaded) {
    // Starting items (sorted alphabetically)
    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):-----------
    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");
    //--------------------------------------------------------------------------------
    // Optional: starting nodes and links (load from file instead):-----------
    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);
    }
    }

    }
    void ResizeGL(int,int) {}
    void DestroyGL() {
    // We should save "TestEnumNames" to a file here
    // We save "TestEnumNames" + the node graph editor together to a single file here
    # if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION) && !defined(NO_IMGUIHELPER_SERIALIZATION_SAVE))
    TestEnumNamesSave(TestEnumNamesSavePath);
    ImGuiHelper::Serializer s(NodeGraphEditorSavePath);
    TestEnumNamesSave(s); // TestEnumNames
    nge.save(s); // nge
    # endif
    }
    void DrawGL()
  4. Flix01 revised this gist Apr 15, 2017. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -253,7 +253,6 @@ bool CustomEnumEditorNode::render(float nodeWidth) // should return "true" if th
    # endif //NO_DYNAMIC_CAST
    if (--selectedEnumIndex<0) selectedEnumIndex=0;
    }
    ImGui::SetTooltip("delete item");
    }
    return nodeEdited;
    }
  5. Flix01 revised this gist Apr 15, 2017. 1 changed file with 181 additions and 48 deletions.
    229 changes: 181 additions & 48 deletions imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -3,15 +3,31 @@

    // 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.
    // Dynamic enum works!
    // And if you can use dynamic_cast<>() making new Node types that use it is easier (non-intrusive)
    // Otherwise you must modify the code of CustomEnumEditorNode::render(...) for every new user class you add.
    //
    // Added also some code to serialize/deserialize the enum names ("TestEnumNames").
    // Serialization/deserialization of the Node Graph Editor is not shown here, but it's trivial to add
    // and it's already shown in the default Node Graph Editor Demo.



    #include <imgui.h> // intellisense only
    #include <addons/imguinodegrapheditor/imguinodegrapheditor.h> // intellisense only
    #include <string.h> //strcpy


    //#define NO_DYNAMIC_CAST // More portable, but makes code more intrusive
    #ifndef NO_DYNAMIC_CAST
    class ITestEnum {
    public:
    virtual int& getSelectedItem()=0;
    virtual ~ITestEnum() {}
    };
    #endif //NO_DYNAMIC_CAST


    #ifndef IM_PLACEMENT_NEW
    struct ImPlacementNewDummy {};
    inline void* operator new(size_t, ImPlacementNewDummy, void* ptr) { return ptr; }
    @@ -24,6 +40,35 @@ inline void operator delete(void*, ImPlacementNewDummy, void*) {}
    #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;
    int TestEnumNamesInsert(const char* name) {
    if (!name) return -1;
    const int len = strlen(name);
    if (len<=0 || len+1>=MAX_ENUM_NAME_LENGTH) return -1;

    // We want to add the item in a sorted way. First we must calculate "itemPlacement"
    int itemPlacement = 0,comp = 0;
    for (int i=0,iSz=TestEnumNames.size();i<iSz;i++) {
    comp = strcmp(name,&TestEnumNames[i][0]);
    if (comp>0) ++itemPlacement;
    else if (comp==0) return -1; // already present
    }

    // Here we insert "name" at "itemPlacement"
    TestEnumNames.resize(TestEnumNames.size()+1);
    for (int i=TestEnumNames.size()-1;i>itemPlacement;--i) strcpy(&TestEnumNames[i][0],&TestEnumNames[i-1][0]);
    strcpy(&TestEnumNames[itemPlacement][0],name);

    return itemPlacement;
    }
    bool TestEnumNamesDelete(int itemIndex) {
    // We must delete the item
    int size = TestEnumNames.size();
    for (int i=itemIndex;i<size-1;i++) {
    strcpy(&TestEnumNames[i][0],&TestEnumNames[i+1][0]);
    }
    --size;TestEnumNames.resize(size);
    return true;
    }


    // NODE DEFINITIONS ================================================================
    @@ -74,42 +119,7 @@ class CustomEnumEditorNode : public 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;
    }
    virtual bool render(float nodeWidth);

    public:

    @@ -128,8 +138,13 @@ class CustomEnumEditorNode : public 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);}

    };
    class ColorEnumUserNode : public Node {
    class ColorEnumUserNode : public Node
    #ifndef NO_DYNAMIC_CAST
    , public ITestEnum
    #endif //NO_DYNAMIC_CAST
    {
    protected:
    typedef Node Base; //Base Class
    typedef ColorEnumUserNode ThisClass;
    @@ -147,6 +162,9 @@ class ColorEnumUserNode : public Node {

    public:

    int& getSelectedItem() {return selectedEnumIndex;} // ITestEnum
    friend class CustomEnumEditorNode;

    // create:
    static ThisClass* Create(const ImVec2& pos) {
    // 1) allocation
    @@ -170,8 +188,76 @@ class ColorEnumUserNode : public 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);}


    };



    bool CustomEnumEditorNode::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) {
    const int itemIndex=TestEnumNamesInsert(buf);
    if (itemIndex>=0) {
    buf[0]='\0';
    selectedEnumIndex = itemIndex;
    nodeEdited = true;
    //Now we must correct all the "selectedItem>=itemPlacement" in all the NodeGraphEditor
    ImGui::NodeGraphEditor& nge = getNodeGraphEditor();
    # ifndef NO_DYNAMIC_CAST
    for (int i=0,iSz=nge.getNumNodes();i<iSz;i++) {
    ITestEnum* n = dynamic_cast<ITestEnum*>(nge.getNode(i));
    if (n) {
    int& selectedIndexEnum = n->getSelectedItem();
    if (selectedIndexEnum>=itemIndex) ++selectedIndexEnum;
    }
    }
    # else //NO_DYNAMIC_CAST
    ImVector<ImGui::Node*> nodes;
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    if (n.selectedEnumIndex>=itemIndex) ++n.selectedEnumIndex;
    }
    # endif //NO_DYNAMIC_CAST
    }
    }
    }
    fields[0].render(nodeWidth);
    if (TestEnumNames.size()>0) {
    ImGui::SameLine();
    if (ImGui::SmallButton("x") && TestEnumNamesDelete(selectedEnumIndex)) {
    nodeEdited = true;

    //Now we must correct all the "selectedItem>=selectedEnumIndex" in all the NodeGraphEditor
    ImGui::NodeGraphEditor& nge = getNodeGraphEditor();
    # ifndef NO_DYNAMIC_CAST
    for (int i=0,iSz=nge.getNumNodes();i<iSz;i++) {
    ITestEnum* n = dynamic_cast<ITestEnum*>(nge.getNode(i));
    if (n) {
    int& selectedIndexEnum = n->getSelectedItem();
    if (selectedIndexEnum>=selectedEnumIndex) --selectedIndexEnum;
    }
    }
    # else //NO_DYNAMIC_CAST
    ImVector<ImGui::Node*> nodes;
    // Similiar lines must be repeated for every new Node class definition that uses the dynamic enum
    nge.getAllNodesOfType(ImGui::TNT_CUSTOM_ENUM_USER_NODE,&nodes);
    for (int i=0,iSz=nodes.size();i<iSz;i++) {
    ImGui::ColorEnumUserNode& n = *ImGui::ColorEnumUserNode::Cast(nodes[i]);
    if (n.selectedEnumIndex>=selectedEnumIndex) --n.selectedEnumIndex;
    }
    # endif //NO_DYNAMIC_CAST
    if (--selectedEnumIndex<0) selectedEnumIndex=0;
    }
    ImGui::SetTooltip("delete item");
    }
    return nodeEdited;
    }

    static Node* TestNodeFactory(int nt,const ImVec2& pos) {
    switch (nt) {
    case TNT_CUSTOM_ENUM_EDITOR_NODE: return CustomEnumEditorNode::Create(pos);
    @@ -187,22 +273,68 @@ static Node* TestNodeFactory(int nt,const ImVec2& pos) {
    // END NODE DEFINITIONS ============================================================


    // Optional methods to load/save "TestEnumNames"
    #if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION))
    #ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE
    bool TestEnumNamesSave(ImGuiHelper::Serializer& s) {
    const int size = TestEnumNames.size();
    s.save(&size,"num_text_line_items");
    s.saveTextLines(size,ImGui::CustomEnumEditorNode::GetTextFromEnumIndex,(void*)&TestEnumNames,"text_line_items");
    return true;
    }
    inline bool TestEnumNamesSave(const char* filename) {
    ImGuiHelper::Serializer s(filename);
    return TestEnumNamesSave(s);
    }
    #endif //NO_IMGUIHELPER_SERIALIZATION_SAVE
    #ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD
    static bool TestEnumNamesTypeLoadCallback(ImGuiHelper::FieldType ft,int numArrayElements,void* pValue,const char* name,void* userPtr) {
    TestEnumNamesType* vec = (TestEnumNamesType*) userPtr;
    if (strcmp(name,"num_text_line_items")==0) {vec->resize(*((int*)pValue));for (int i=0;i<vec->size();i++) (*vec)[i][0]='\0';}
    else if (ft==ImGui::FT_TEXTLINE && strcmp(name,"text_line_items")==0) {
    IM_ASSERT(numArrayElements<vec->size());
    strcpy(&(*vec)[numArrayElements][0],(char*)pValue);
    if (numArrayElements==vec->size()-1) return true;
    }
    return false;
    }
    bool TestEnumNamesLoad(ImGuiHelper::Deserializer& d, const char ** pOptionalBufferStart=NULL) {
    const char* amount = pOptionalBufferStart ? (*pOptionalBufferStart) : 0;
    d.parse(TestEnumNamesTypeLoadCallback,(void*)&TestEnumNames,amount);
    if (pOptionalBufferStart) *pOptionalBufferStart=amount;
    return true;
    }
    bool TestEnumNamesLoad(const char* filename) {
    ImGuiHelper::Deserializer d(filename);
    return TestEnumNamesLoad(d);
    }
    #endif //NO_IMGUIHELPER_SERIALIZATION_LOAD
    #endif //NO_IMGUIHELPER_SERIALIZATION

    const char* TestEnumNamesSavePath = "testEnumNames.txt";

    ImGui::NodeGraphEditor nge;
    // Mandatory methods
    void InitGL() {
    if (nge.isInited()) {
    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");
    # if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION) && !defined(NO_IMGUIHELPER_SERIALIZATION_LOAD))
    TestEnumNamesLoad(TestEnumNamesSavePath);
    # endif
    if (TestEnumNames.size()==0) {
    // Starting items (sorted alphabetically)
    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));
    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));
    @@ -225,6 +357,9 @@ void InitGL() {
    void ResizeGL(int,int) {}
    void DestroyGL() {
    // We should save "TestEnumNames" to a file here
    # if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION) && !defined(NO_IMGUIHELPER_SERIALIZATION_SAVE))
    TestEnumNamesSave(TestEnumNamesSavePath);
    # endif
    }
    void DrawGL()
    {
    @@ -258,5 +393,3 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,
    return 0;
    }



  6. Flix01 created this gist Apr 15, 2017.
    262 changes: 262 additions & 0 deletions imguinodegrapheditor_demo2.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,262 @@
    // 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;
    }