Git Product home page Git Product logo

imnodeflow's People

Contributors

avlec avatar biglari avatar fattorino avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

imnodeflow's Issues

CPP20

Hello,

please add in your documentation than your lib is requiring c++20 at least.

and maybe other minimal requirements

Forwarding Arguments to Derived Node Constructor

It would be useful to have the option to forward constructor arguments to the derived class when performing addNode to get more reusable behaviour out of user defined node types without having to pass them in through some alternate mechanism.

[Feature Request] Use std::function as ConnectionFilter rather than just using an enum

Thanks for this amazing library.
ImNodeFlow is very easy for me to use.

I have some ideas regarding the enum "ConnectionFilter".
I need various types of nodes for complex pipeline design.
My nodes might have nested relationships (parent/children).
I hope the ConnectionFilter can be a std::function that determines whether the nodes are connectable or not (based on port type or parent type), rather than just using an enum.

Not sure if anyone else has any other ideas?

Possible to rearrange Pins?

Hi and first of all: thanks for the awesome project! I am currently looking for a node editor for a small project of mine. In this project I would need to connect one output pin of one node (let's call it node 1) to the input pin of another node (node 2) and than feed the outcome of the second node back into the first node. Would it be possible to move the pins around inside a node like in the attached sketch ("I": input pin, "O": output pin) to not clutter the diagram with crossing links?

Screenshot 2024-08-02 at 12 58 02

Is it possible to have 2 outputs from the same node linked to a node that has 2 inputs?

I think this part of the code is why the second out pin is not calling m_val = m_behaviour()

 template<class T>
    const T &OutPin<T>::val()
    {
        if (std::find((*m_inf)->get_recursion_blacklist().begin(), (*m_inf)->get_recursion_blacklist().end(), m_parent->getUID()) == (*m_inf)->get_recursion_blacklist().end())
        {
            (*m_inf)->get_recursion_blacklist().emplace_back(m_parent->getUID());
            m_val = m_behaviour();
        }

        return m_val;
    }

grafik

Dynamically Adding and Removing Pins

From preliminary testing despite the documentation on addIN and addOUT I seem to be able to dynamically add pins at runtime outside of the constructor. It seems like this just works and maybe there's some warrant to verifying this with a more extensive test as I just tested adding a single in/out to a node that was already "wired" up to other nodes.

I understand that probably the more conceptually correct way to do this would be by replacing the node with a new one with the right pin configurations. But there is no way to remove nodes from the view. And if that was added another problem would be that the connections on the existing node have to be re-routed to the new node. Which is why I think fully supporting adding and removing of IN/OUT pins outside of the constructor would be a preferred approach.

Would be interested to hear your thoughts about this.

[MSVC compiler] Cleanup crash when having one OutPin connected with multiple InPin's

Version: master branch
ImGui: 1.90.5-docking

Issue

Connecting a OutPin with 2 or more InPin's and then close the program. The deconstructor from ImFlow::Link produces a "Access violation on reading location ...".

image

One fix that I tried, was to replace m_left with m_right but that seems to break other things, like the node deletion logic.

Link::~Link() {
-    m_left->deleteLink();
+    m_right->deleteLink();
}

Code

I minimized my code to be still functional and replicate the issue here. It is not the most good looking code, but should be readable enough.

Also, I added all c++ code in the spoilers following.

main.cpp

#include "Window.h"

int main() {
    CWindow window{};
    window.run();
    return 0;
}

Window.h

#ifndef BOOLEANTABLE_WINDOW_H
#define BOOLEANTABLE_WINDOW_H

#include <stdexcept>


#include <SDL3/SDL.h>
#include <glad/gl.h>
#include <imgui.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_opengl3.h>
#include <ImNodeFlow.h>

const int cWinHeight = 400;
const int cWinWidth = 600;

class CWindow {
    SDL_Window* window;
    SDL_GLContext context;

    bool running = true;

    std::shared_ptr<ImFlow::ImNodeFlow> imNodeFlow;
    std::vector<std::string> variables;

public:
    explicit CWindow();
    ~CWindow();

    void run();
};


#endif //BOOLEANTABLE_WINDOW_H

Window.cpp

#include "Window.h"
#include "nodes/AndNode.h"

CWindow::CWindow() : imNodeFlow(std::make_shared<ImFlow::ImNodeFlow>("Main Grid")) {
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMEPAD) != 0) {
        throw std::runtime_error("Could not initialize SDL3");
    }

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    window = SDL_CreateWindow("Boolean Tables", cWinWidth, cWinHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
    if (window == nullptr) {
        throw std::runtime_error("Could not create a SDL3 Window");
    }

    context = SDL_GL_CreateContext(window);
    if (context == nullptr) {
        throw std::runtime_error("Could not create a OpenGL Context");
    }
    SDL_GL_MakeCurrent(window, context);

    SDL_GL_SetSwapInterval(1);

    int gl_version = gladLoadGL(SDL_GL_GetProcAddress);
    if (gl_version == 0) {
        throw std::runtime_error("Could not initialize OpenGL context");
    }

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;

    ImGui::StyleColorsDark();

    // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        style.WindowRounding = 0.0f;
        style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }

    // Setup Platform/Renderer backends
    ImGui_ImplSDL3_InitForOpenGL(window,context);
    ImGui_ImplOpenGL3_Init();



    imNodeFlow->addNode<CAndNode>(ImVec2{2, 2});

    imNodeFlow->droppedLinkPopUpContent([this](ImFlow::Pin *pin) {

        if (ImGui::Button("And")) {
            auto node = this->imNodeFlow->placeNode<CAndNode>();
            pin->createLink(node->first.get());
        }
    });
}


CWindow::~CWindow() {
    imNodeFlow.reset();

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();

    SDL_GL_DeleteContext(context);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

void CWindow::run() {
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    while(running) {
        for(SDL_Event event; SDL_PollEvent(&event);) {
            ImGui_ImplSDL3_ProcessEvent(&event);
            switch (event.type) {
                case SDL_EVENT_QUIT:
                    running = false;
                    return;
                case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
                    if (event.window.windowID == SDL_GetWindowID(window)) {
                        running = false;
                        return;
                    }
                    break;
                default:
                    break;
            }

        }

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplSDL3_NewFrame();
        ImGui::NewFrame();

        ImGui::Begin("Boolean Editor");
            imNodeFlow->update();
        ImGui::End();


        ImGui::Render();
        glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
        glClearColor(0.f, 0.f, 0.f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
        {
            SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
            SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
            ImGui::UpdatePlatformWindows();
            ImGui::RenderPlatformWindowsDefault();
            SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
        }

        SDL_GL_SwapWindow(window);
    }
}

AndNode.h

#ifndef BOOLEANTABLE_ANDNODE_H
#define BOOLEANTABLE_ANDNODE_H

#include <ImNodeFlow.h>

class CAndNode : public ImFlow::BaseNode {
public:
    std::shared_ptr<ImFlow::InPin<bool>> first;
    std::shared_ptr<ImFlow::InPin<bool>> second;
    CAndNode();

};


#endif //BOOLEANTABLE_ANDNODE_H

AndNode.cpp

#ifndef BOOLEANTABLE_ANDNODE_H
#define BOOLEANTABLE_ANDNODE_H

#include <ImNodeFlow.h>

class CAndNode : public ImFlow::BaseNode {
public:
    std::shared_ptr<ImFlow::InPin<bool>> first;
    std::shared_ptr<ImFlow::InPin<bool>> second;
    CAndNode();

};


#endif //BOOLEANTABLE_ANDNODE_H

Mouse click on other window causes node selection to be cancelled

I am not sure if it is the expected behavior for everyone or just in my usage scenarios.

By the design of my game engine, the user first selects some nodes in the graph, then the inspector window shows all properties of the selected nodes.

But when the user wants to click a button on the inspector window, suddenly the nodes are not selected anymore, and the inspector window does not show any properties of the nodes anymore (because the selection is canceled).

I added the IsWindowHovered() condition when selecting the node in "void BaseNode::update()", which fixes the problem:

// Orig:
if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ...)
    selected(false);

// My check:
if (ImGui::IsWindowHovered() && !ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ...)
    selected(false);

And for the destroy(), I added the IsWindowFocused() condition

// Orig:
if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected())
    destroy();

// My check:
if (IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected())
    destroy();

But I am not sure if it is a bug or the expected behavior, so I am just reporting it here.

Dynamic Pins Crash

Was trying out the following examples and they seem to crash when connecting pins to other objects with a Pin UID not found error.

ImNodeFlow.inl:189: const T& ImFlow::BaseNode::getInVal(const U&) [with T = double; U = std::__cxx11::basic_string<char>]: Assertion `it != m_ins.end() && "Pin UID not found!"' failed.

Using the following Node definition

struct Test : ImFlow::BaseNode
{
  void draw()
  {
    ImGui::Text("test");
    showIN<double>("INPUT", 0.0, ImFlow::ConnectionFilter::SameType());
    showOUT<double>("OUTPUT", [this](){ return getInVal<double>("INPUT"); });
  }
};

I noticed this while also trying out the notion of wrapping lambdas up as nodes, which I would also imagine should work. And it does render but has the same crash.

INF.addLambda([](ImFlow::BaseNode* self){
  ImGui::Text("lambda");
  self->showIN<double>("INPUT", 0.0, ImFlow::ConnectionFilter::SameType());
  self->showOUT<double>("OUTPUT", [self](){ return self->getInVal<double>("INPUT"); });
}, {0,0});

// with this added to ImNodeFlow class
template <typename L, typename B = BaseNode>
struct NodeWrapper : B, L
{
    NodeWrapper(L&& l): BaseNode(), L(std::forward<L>(l)) {}
    void draw() { L::operator()(this); }
};

template<typename L>
std::shared_ptr<NodeWrapper<L>> addLambda(L&& lambda, const ImVec2& pos)
{
    return addNode<NodeWrapper<L>>(pos, std::forward<L>(lambda));
}

License?

Is this MIT or what license do you want to use?

Feedback Connections

Allow an Input Pin from a node to be able to be fed from an Output Pin of the same node. This would allow modeling of things like PID which take output signals as feedback for control modulation.

ImFlow::ImFlowNode::update() causing window not to serialize to imgui.ini

When calling ImFlow::ImNodeFlow::update() inside an ImGui window, that window no longer updates its position and size within imgui.ini

Minimal reproduction

static ImFlow::ImNodeFlow myGrid;
if (ImGui::Begin("Test Window"))
{
    myGrid.update();
}
ImGui::End();

Reposition/resize the "Test Window" and restart the application. The windows' new position/size will not be retained between executions.

If the call to update() is removed, the retention of the windows position/size behaves normally again.

ImGui Info
Version 1.90 WIP
Num 18993

Need help with the window that the grid within

I am new to ImGUI, but it has been pretty easy to figure out. What I am not figuring out is changing the size of the window that the grid is rendered in. Or in setting its position. Before calling the ImNodeFlow::update(), I have the following to maximize the window:

const ImGuiViewport* MainViewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(MainViewport->Size, ImGuiCond_FirstUseEver);

But the ImGUI window for ImNodeFlow never matches the size of main viewport. What am I doing wrong? Thank you.

image

Removing Nodes

Ability to add/remove nodes from the Node editor via a removeNode interface.

I was playing around with how easy it would be. I've got it just searching, finding, and erasing the node from the ImNodeFlow context. Works fine if there is no connections but runs into some UB when the next update loop comes through and uses the deleted Pin in the Link. Haven't gotten further than a quick debugger to take a quick peek at what is going wrong. I'll put up a branch and continue off it when my cold goes away.

no input apart zooming in-out

tested your simple example but i cannot move nodes around or make connections I can just zoom in and any other input is ignored.

the fact that zooming works makes me guess my imgui implementation is ok as it also works correctly if I make normal imgui widgets.

I probably I forgot to initialize something... does it ring some bells?

this is the nodeflow related code just as in the example

#pragma once
#include <ImNodeFlow.h>

namespace Node_Editor {

    using namespace ImFlow;

    ImNodeFlow* nodeFlow;

    class SimpleSum : public BaseNode
    {
    public:
        SimpleSum()
        {
            setTitle("Simple sum");
            setStyle(NodeStyle::green());
            addIN<int>("IN_VAL", 0, ConnectionFilter_Int);
            addOUT<int>("OUT_VAL", ConnectionFilter_Int)
                ->behaviour([this]() { return getInVal<int>("IN_VAL") + m_valB; });
        }

        void draw() override
        {
            ImGui::SetNextItemWidth(100.f);
            ImGui::InputInt("##ValB", &m_valB);
        }
    private:
        int m_valB = 0;
    };

	void init() {
        nodeFlow = new ImNodeFlow("node Editor");
        nodeFlow->addNode<SimpleSum>(ImVec2(0,0));
        nodeFlow->addNode<SimpleSum>(ImVec2(100, 100));
	}

	void shutdown() {
        delete nodeFlow;
	}

	void Exec() {
        nodeFlow->update();
	}
}

The deletion of a node cause a crash

Hello,

thanks for this good lib.

btw when you delete a node by using the key "delete", cause a crash.

its because you destroy a node from him internally while iterated from a loop.

m_inf->getNodes().erase(m_uid)

Destroying a node internally via a global pointer is a dangerous behavior, and here cause the issue.
after that line, m_inf point on a release memory and cause the crash for next calls after line 186 of ImFlowNode.cpp

for me,
you need to mark a node as "to delete"
and delete it from the editor after nodes iteration

i have fixed it here (but maybe not the way you like :) )

ps : a demo app branch can be cool for see how to use it and test it

Too many vertices in ImDrawList

First, thank you for this awesome project!

The problem is that I want to deal with many vertices. This is why I get the following assertion error.

As the comment suggests, it is a good idea to skip drawing nodes that are outside of the editor view.

I am not quite familiar with DearImgui, in particular with this project -- I just started using it. But I think that it is possible to skip calls here: before update, it can check whether a node inside the render window and draw it only if it is in inside.

If this edit does not breaks imgui/ImNodeFlow architecture/invariants, I even can try to implement it myself and create a PR.

Thank you!

Minimal example?

Can someone please give a minimal working sample on how to use this library. Readme has a code on how to create a custom node, what about something that just works? I've searched codebase here with no success.

Nodes executing in random order?

I noticed that my nodes are being executed in completely random order even tho they are created in the exact same way every time:

std::shared_ptr<PathTracerOutputNode> inputNode = m_Handler->addNode<PathTracerOutputNode>({ 0, 400 }, &m_NodeQueue, m_InputImage);
std::shared_ptr<BloomNode> bloomNode = m_Handler->addNode<BloomNode>({ 170, 400 }, &m_NodeQueue, VkExtent2D{ 900, 900 });
std::shared_ptr<TonemapNode> tonemapNode = m_Handler->addNode<TonemapNode>({ 370, 400 }, &m_NodeQueue, VkExtent2D{ 900, 900 });
std::shared_ptr<OutputNode> outputNode = m_Handler->addNode<OutputNode>({ 500, 400 }, &m_NodeQueue, &m_OutputImage);

bloomNode->inPin("Input")->createLink(inputNode->outPin("Output"));
tonemapNode->inPin("Input")->createLink(bloomNode->outPin("Output"));
outputNode->inPin("Window Output")->createLink(tonemapNode->outPin("Output"));

Each node type has draw function and I need them to execute in the correct order, so from left to right, I first need output from path tracer to then be able to tonemap it, but sometimes for whatever reason tonemapping is executed first.

What I'm doing rn inside those draw functions is:

void draw() override
{
        // ImGui Stuff
	...

	auto func = [this]() {
		// Rendering Stuff, Get handle and run compute shaders
		VkImage handle = getInVal<Image*>("Input")->GetImage();
		...
	};

	m_Queue->PushTask(func); // Push rendering stuff into queue that is executed later on
}

I have to push rendering stuff into queue and not just execute it right away because vulkan doesn't allow that.

In documentation I read that What is outputted is defined by the pin behaviour, So I thought that maybe pushing func into a queue inside behaviour() instead of draw() would solve the problem but it didn't. Nodes are still executed in random order:

addOUT<Image*>("Output")->behaviour(
	[this]() 
	{
		// Makes sure to evaluate prev node?
		getInVal<Image*>("Input");

		auto func = [this]() {
			// Rendering Stuff, Get handle and run compute shaders
			VkImage handle = getInVal<Image*>("Input")->GetImage();
			...
		};

		m_Queue->PushTask(func); // Push rendering stuff into queue that is executed later on

		return &m_OutputImage;
	}
);

If I just print whenever I call PushTask() in these nodes I get different output each time I run the app:
( Correct output is Bloom -> Tonemap -> Output)
1st run:
[15:48:57] Pushed Viewport Output
[15:48:57] Pushed Bloom
[15:48:57] Pushed Tonemapping

2nd run:
[15:49:38] Pushed Bloom
[15:49:38] Pushed Tonemapping
[15:49:38] Pushed Viewport Output

3rd run:
[15:50:11] Pushed Bloom
[15:50:11] Pushed Viewport Output
[15:50:11] Pushed Tonemapping

So, am I doing something wrong, or nodes are just supposed to run in random order (which wouldn't make any sense)? What am I supposed to do to first evaluate all input nodes? I just need things Pushed into m_Queue in correct order, I would really appreciate any help.

Documentation/sample about linking nodes programmatically

Some things seems to be missing from the actual documentation like the possibility to add links programmatically to nodes.
I need to serialize and deserialize nodegraphs so I need to recreate connections programmatically.

It would be great to have a more complex example that show off the entire api some kind of imgui demo but for imNodeFlow.

Problem if 2 nodes have the same out pin name

On line 490 in the header file this should be changed to

std::vector<PinUID> m_nodeRecursionBlacklist;

Also if there are nodes with the same out pin name this will not work.
Maybe do something like using the parent uid and the out uid:

template<class T>
    const T &OutPin<T>::val()
    {
          PinUID po_uid = m_parent->getUID() + m_uid;   // po -> parent + out pin uid
          if (std::find((*m_inf)->get_recursion_blacklist().begin(), (*m_inf)->get_recursion_blacklist().end(), po_uid) == (*m_inf)->get_recursion_blacklist().end())
        {
            (*m_inf)->get_recursion_blacklist().emplace_back(po_uid);
            m_val = m_behaviour();
        }
        return m_val;
    }

Recursive Node Editors

I was pleasantly surprised that node editors within nodes worked pretty well.

There are a few quirks though listed below, and possibly others. I will refer to the node editor within a node as the "inner node editor" and the editor that contains that node the "outer node editor"

  • The inner node editor can render its grid and nodes outside of the outer node editor itself and the ImGui window containing it.
    oob
  • Dragging from a pin in the outer node editor to the inner node editor highlights the pin dragged to. Thankfully it doesn't connect when released. It should probably not highlight indicating a connection is possible if it cannot connect. However, going the other way doesn't do any highlighting or connecting which is good.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.