Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Examples/Documentation: Save/Load and integration #259

Open
dgm3333 opened this issue Sep 24, 2023 · 1 comment
Open

Comments

@dgm3333
Copy link

dgm3333 commented Sep 24, 2023

If you're making examples then showing how the example can be integrated into a pre-existing imgui app would be good - turns out to be only a handful of lines of code but it took me hours to figure out what they were.

The following uses the blueprint example and layouts (which seems to be a drop-in replacement for ImGui
My existing app was built on the imgui docking branch so although there appears to be absolutely no explanation of what each of the branches are for I picked this one (because it mentioned docking...)
https://github.com/thedmd/imgui/tree/feature/docking-layout-external

Adding to an existing Imgui app

//forward declare this somewhere appropriate
void BlueprintNE_Main(bool& showStyleEditor, bool& showFlow, bool& stop);
// Put this in your normal ImGui rendering loop

    if (ImGui::Begin("nodeEditor")) {
        static bool showStyleEditor = false;
        if (ImGui::Button("Style Editor"))                 // show the style panel (I'm using docking so this appears as a separate viewport)
            showStyleEditor = !showStyleEditor;

        ImGui::SameLine();
        static bool showFlow = false;
        if (ImGui::Button("Show Flow"))                // show flow dots for direction of link (this isn't working for me yet)
            showFlow = !showFlow;

        ImGui::SameLine();
        static bool stopBlueprint = false;
        if (ImGui::Button("Stop Editor"))                 // this triggers destruction of the editor ready for a fresh start
            stopBlueprint = !stopBlueprint;

        BlueprintNE_Main(showStyleEditor, showFlow, stopBlueprint);

    }
    ImGui::End();

This should replace the main() function in the blueprint example
(admittedly flow isn't yet working but I think that is a bigger issue as it doesn't trigger with the z key either)


void BlueprintNE_Main(bool& showStyleEditor, bool& showFlow, bool& stop)
{

    static Example blueprintNodeEditor("Blueprints");    // 'Example' is the name of the struct. This is annoying because of conflicts with other examples but is what it is. It should be called something unique (eg BlueprintEditorStructV1)

    static bool needsStart = true;
    if (needsStart && stop)          // if called with stop then don't progress if onStop has already been called.
        return;

    if (needsStart) {
        needsStart = false;
        blueprintNodeEditor.OnStart();            // Initialize editor. This loads the config and spawns hardcoded nodes. Ideally these should be loaded from file as a separate step
    }

    if (stop) {
        blueprintNodeEditor.OnStop();           // Release textures and destroy editor
        needsStart = true;                         // will need to be restarted if we want to use the editor again
        return;
    }

    if (!showStyleEditor)
        ImGui::SetCursorPos({ 5, 75 });
    else
        ImGui::SetCursorPos({ 250, 75 });
    auto& io = ImGui::GetIO();
    blueprintNodeEditor.OnFrame(io.DeltaTime); // Update the view each frame

    if (showStyleEditor) {
        blueprintNodeEditor.ShowStyleEditor(&showStyleEditor);
        ImGui::SetCursorPos({ 5, 75 });
        // blueprintNodeEditor.ShowLeftPane(350);      // this crashes with this basic example due to icon issue (tries to set zero size)
        // If you switch the invisible button to a normal one it won't crash
        //if (ImGui::InvisibleButton("save", ImVec2((float)saveIconWidth, (float)saveIconHeight)))
        // to this
        //if (ImGui::Button("save"))
    }

    if (showFlow) {
//        placeholder. Flow should trigger when pressing z-key, although this is only active if leftPane is running (and it crashes for me), so something else is not working when that's fixed this placeholder can be revisited
//        for (auto& link : m_Links)
//            ed::Flow(link.ID);
    }

    return;

}

Enabling the Left Pane and fixing Save/Restore

Using my current code the left pane will crash (because there are no icons loaded)
Changing to simple buttons fixes this with no loss of function.
NB:
The editor state (ie zoom and scroll) is saved between sessions.
However blueprint hardcodes the node positions and these are not saved

Also NB the buttons are saving/restoring a temporary state - not saving to a file

To allow the leftpane to show you will need to alter the save code (approx line 800)
to remove the dependence on icons and return to plain buttons.
you may also have to add an adjustment to position it correctly (for me the buttons were drawn off the pane)

            float DGM_Adjust = 30.0f;
            auto iconPanelPos = start + ImVec2(
                paneWidth - ImGui::GetStyle().FramePadding.x - ImGui::GetStyle().IndentSpacing - saveIconWidth - restoreIconWidth - ImGui::GetStyle().ItemInnerSpacing.x * 1 - DGM_Adjust,
                (ImGui::GetTextLineHeight() - saveIconHeight) / 2);

//...

if (node.SavedState.empty())
{
	if (ImGui::Button("save"))
		node.SavedState = node.State;
}
else
{
	ImGui::Dummy(ImVec2((float)saveIconWidth, (float)saveIconHeight));
}

ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SetNextItemAllowOverlap();
if (!node.SavedState.empty())
{
	if (ImGui::Button("restore"))
	{
		node.State = node.SavedState;
		ed::RestoreNodeState(node.ID);
		node.SavedState.clear();
	}
}
else
{
	ImGui::Dummy(ImVec2((float)restoreIconWidth, (float)restoreIconHeight));
}

Saving / Loading to/from file

The code automatically saves very basic information about nodes and state to a json file in the working directory called "Blueprint.json". However I've no idea how to trigger this manually nor how to add other details (eg user edits of contents of nodes) to this file.
commenting out the hardcoded positioning (approx line 530) does mean the hardcoded nodes are restored to their previous locations, but nodes previously added by the user are lost.

This is thus another vote for implementing examples of the state and node/link save/load functions into the side panel.
(or at an absolute minimum documenting what the code is doing - admittedly this is my biggest frustration throughout this entire library not just about save/load).

I spent ages digging around the backend code trying to figure this out. But it definitely doesn't seem to be easy to figure out and having traced my way around what I think it does it seems to be neither intuitive nor comprehensive.

Perhaps blueprint2 could also be merged with main as the following posts suggests this has a better strategy?
This was the most useful post I've found on the topic (but 3 years old there appears to have been no progress since then)
#58

Incidentally this is a very basic initial load routine for importing a mermaid flow chart


bool LoadMermaidChart(const std::string& filepath, nodePlot& nP) {

    std::cout << "Loading: " << filepath << std::endl;


    std::ifstream iFile(filepath);
    if (!iFile.is_open()) {
        return false;
    }

    std::string line;
    int nodeId = 0, linkId = 0;
    std::map<std::string, int> nodeMap; // to map node name to its ID

    // Default color
    ImColor defaultColor(255, 255, 255);

    while (std::getline(iFile, line)) {

        std::cout << line << std::endl;
        if (line.find("style") == 0) {
            // Parsing the style for nodes
            std::string nodeName = line.substr(6, line.find(' ') - 6); // getting the node name
            std::string colorHex = line.substr(line.find('#') + 1);
            int r = std::stoi(colorHex.substr(0, 2), nullptr, 16);
            int g = std::stoi(colorHex.substr(2, 2), nullptr, 16);
            int b = std::stoi(colorHex.substr(4, 2), nullptr, 16);
            ImColor nodeColor(r, g, b);

            // Assuming the node may not exist yet, so update or create node
            if (!nodeMap.contains(nodeName)) {
                nodeMap[nodeName] = nodeId++;
                nP.m_Nodes.emplace_back(nodeMap[nodeName], nodeName.c_str(), nodeColor);
            }
            else {
                for (auto& node : nP.m_Nodes) {
                    if (node.ID == (ed::NodeId)nodeMap[nodeName]) {
                        node.Color = nodeColor;
                        break;
                    }
                }
            }
        }
        else if (line.find("-->") != std::string::npos) {
            // It's a link
            size_t startBracketPos = line.find('[');
            size_t endBracketPos = line.find(']');

            std::string startNodeId = line.substr(0, startBracketPos);
            trim(startNodeId);
            std::string startNodeLabel = line.substr(startBracketPos + 1, endBracketPos - startBracketPos - 1);

            size_t arrowPos = line.find("-->");
            std::string arrow = line.substr(arrowPos, 3);

            int arrowPlus3 = arrowPos + 3;
            size_t startBracketPos2 = line.find('[', arrowPlus3);
            size_t endBracketPos2 = line.find(']', arrowPlus3);

            std::string endNodeId = line.substr(arrowPlus3, startBracketPos2 - arrowPlus3);
            trim(endNodeId);
            std::string endNodeLabel = line.substr(startBracketPos2 + 1, endBracketPos2 - startBracketPos2 - 1);

            // Generate unique identifiers based on the labels

            int startNodeNum = nodeId++;
            int endNodeNum = nodeId++;

            if (!nodeMap.contains(startNodeId)) {
                nodeMap[startNodeId] = startNodeNum;
                nP.m_Nodes.emplace_back(startNodeNum, startNodeLabel.c_str(), defaultColor);
                nP.m_Nodes.back().Type = NodeType::mermaid;
            }
            else {
                startNodeNum = nodeMap[startNodeLabel]; // Use existing ID
            }

            if (!nodeMap.contains(endNodeId)) {
                nodeMap[endNodeId] = endNodeNum;
                nP.m_Nodes.emplace_back(endNodeNum, endNodeLabel.c_str(), defaultColor);
                nP.m_Nodes.back().Type = NodeType::mermaid;
            }
            else {
                endNodeNum = nodeMap[endNodeLabel]; // Use existing ID
            }

            // Create the link
            nP.m_Links.emplace_back(linkId++, startNodeNum, endNodeNum);
        }
    }

    return true;
}
@Gellert5225
Copy link

This library provides you a canvas to draw ImGui nodes, it doesn't teach you how to serialize/deserialize data. You should implement that on your own, because everyone's node/graph structure is different and thus impossible to come up with a generic solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants