Repository: Fattorino/ImNodeFlow
Branch: master
Commit: 5c93f4822869
Files: 18
Total size: 150.7 KB
Directory structure:
gitextract_elfg6xo2/
├── .gitignore
├── LICENSE.txt
├── documentation.md
├── example/
│ ├── CMakeLists.txt
│ ├── cmake/
│ │ ├── desktop.cmake
│ │ └── emscripten.cmake
│ ├── example.cpp
│ ├── example.hpp
│ └── html/
│ └── shell_min.html
├── include/
│ └── ImNodeFlow.h
├── readme.md
└── src/
├── ImNodeFlow.cpp
├── ImNodeFlow.inl
├── context_wrapper.h
├── imgui_bezier_math.h
├── imgui_bezier_math.inl
├── imgui_extra_math.h
└── imgui_extra_math.inl
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
example/build
example/includes
build
settings.json
================================================
FILE: LICENSE.txt
================================================
MIT License
Copyright (c) 2024 Gabriele Torelli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: documentation.md
================================================
# ImNodeFlow Documentation
***
## Index
- [NODES](#nodes)
- [Definition](#definition)
- [Body content](#body-content)
- [Static pins](#static-pins)
- [Dynamic pins](#dynamic-pins)
- [Lambda Defined Nodes](#lambda-defined-nodes)
- [Styling system](#styling-system)
- [PINS](#pins)
- [UID system](#uid-system)
- [Connection filters](#connection-filters)
- [Output pins](#output-pins)
- [Input pins](#input-pins)
- [Styling system](#styling-system-1)
- [Custom rendering](#custom-rendering)
- [HANDLER](#handler)
- [Creation](#creation)
- [Main loop](#main-loop)
- [Adding nodes](#adding-nodes)
- [Pop-ups](#pop-ups)
- [Customization](#customization)
***
## NODES
### Definition
Custom nodes **must** derive from the class BaseNode. This shows at a glance the requirements for the class and serves as a good starting point.
```c++
class CustomNode : public ImFlow::BaseNode
{
explicit CustomNode() { /* omitted */ }
};
```
You are free to add additional arguments to support custom behavior.
Any additional arguments will be forwarded during the node creation (see [Adding nodes](#adding-nodes)).
```c++
explicit MultiSumNode(int numInputs) { /* omitted */ }
```
Inside the constructor is then possible to define the node's structure and appearance.
_NB: every method used inside the constructor can perfectly be used anywhere else._
```c++
class CustomNode : public ImFlow::BaseNode
{
explicit CustomNode()
{
setTitle("I'm custom");
setStyle(NodeStyle::brown());
addIN("I'm input", 0, 0, PinStyle::red());
}
};
```
_This is just an example and every method used will be explained in full details._
### Body content
In addition to input and output pins, each node can have a body that supports all the ImGui widgets.
```c++
class CustomNode : public ImFlow::BaseNode
{
explicit CustomNode() { /* omitted */ }
void draw() override
{
/* Node's body with ImGui */
}
};
```
### Static pins
Static pins are Input or Output pins that can be added to the node and be deleted manually.
```c++
addIN(pin_name, default_value, filter, style);
addOUT(pin_name, filter)->behaviour( /* omitted */ );
```
_The **necessary** `behaviour()` method for Output pins is explained at [Output pins](#output-pins)._
The following example adds to the node an input pin named `"Input A"` for `int` values,
with a default return value of 0 (value returned when the pin is not connected), a filter,
and the default pin style (cyan).
```c++
addIN("Input A", 0, ConnectionFilter::SameType());
```
_For a detailed explanation of pins see [PINS](#pins)._
### Dynamic pins
Dynamic pins are Input or Output pins that, just like any other ImGui widget, exist only as long as they are called each frame.
Dynamic inputs take the same parameters as static inputs, And they return each frame either the default or the connected value.
```c++
T val = showIN(pin_name, default_value, filter, style);
showOUT(pin_name, behaviour, filter, style);
```
_As mentioned in Static pins, `behaviour` is explained at [Output pins](#output-pins)._
### Lambda Defined Nodes
When logic is straightforward enough or for quick prototyping this might be sufficient.
This avoids all the general hassle that comes with writing classes by using dynamic pins.
```c++
ImFlow::ImNodeFlow INF;
INF.addLambda([](ImFlow::BaseNode* self){
ImGui::Text("lambda");
self->showIN("INPUT", 0.0, ImFlow::ConnectionFilter::SameType());
self->showOUT("OUTPUT", [self](){ return self->getInVal("INPUT"); });
}, {0,0});
```
### Styling system
The node's style can be fully customized. Use `setStyle()` to change the style at any time.
The default style is cyan, and the available pre-built styles are: cyan, green, red, and brown .
It is alo possible to create custom styles either from scratch or starting prom a pre-built one.
```c++
// Most common
auto custom1 = NodeStyle::brown();
custom1->radius = 10.f;
// Less used
auto custom2 std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f);
```
Other than the visual appearance of the node (colors and sizes), it is also possible to set and/or change the node's title at any time using `setTitle()`.
***
## PINS
### UID system
Each pin can be identified by a UID, in some cases the UID can coincide with the display name, or it can be a custom UID of any type.
```c++
addIN(pin_name, 0, filter);
addIN_uid(0, pin_name, 0, filter);
```
The UID can be used to get a reference to the pin, and in case of an input pin, its value.
Searching for an UID that doesn't exist will throw an error.
### Connection filters
Filters are useful to avoid unwanted connection between pins.
_TODO: Update this section_
### Output pins
Output pins are in charge of processing the output and, as per the name, outputting it to the connected link.
What is outputted is defined by the pin behaviour. _(See [Static pins](#static-pins) and/or [Dynamic pins](#dynamic-pins) to set the behaviour)._
In particular, the behaviour is a function or a lambda expression that returns the same type of the pin.
```c++
addOUT(pin_name)
->behaviour([this](){ return 0; });
```
In this simple example, a static pin is added, such pin will always output a value of 0 to the connected link.
```c++
addOUT_uid(uid, pin_name)
->behaviour([this](){ /* omitted */ });
```
In this other example, another static pi is added, a custom UID is used and the behaviour is some custom, more complex, logic.
_Dynamic pins also exist, see [Dynamic pins](#dynamic-pins)._
### Input pins
Input pins are in charge of getting the value from the connected link.
If no link is connected to the pin, the default value is returned. (See)
The method `getInVal(uid)` can be used to retrieve an input value.
```c++
addIN(pin_name, default_value, filter, style);
addIN_uid(uid, pin_name, default_value, filter, style);
```
_The method `addIN` adds a static pin where the name is also used as its UID._
### Styling system
When creating either a Static or Dynamic pin, it is possible to pass as an argument a style setting.
The default style is cyan, and the available pre-built styles are: cyan, green, blue, brown, red, and white.
It is alo possible to create custom styles either from scratch or starting prom a pre-built one.
```c++
// Most common
auto custom1 = PinStyle::green();
custom1->socket_radius = 10.f;
// Less used
auto custom2 std::make_shared(PinStyle(IM_COL32(87,155,185,255), 0, 4.f, 4.67f, 3.7f, 1.f));
```
_When creating a style from scratch, keep in mind that it must be a `smart_pointer` and not a simple instance._
### Custom rendering
Pin rendering is handled internally. But for extra customization, a custom renderer can be assigned at each pin.
The custom renderer is a function or a lambda expression containing the new logic to draw the pin.
Some helpers are provided: `drawSocket()` and `drawDecoration()`.
```c++
addIN("Custom", 0, 0)->renderer([](Pin* p) {
auto pp = dynamic_cast*>(p);
ImGui::Text("%s: %.3f", pp->getName().c_str(), pp->val());
p->drawSocket();
p->drawDecoration();
});
```
In this example the pin is rendered with the same socket and hover background, thanks to the two helpers.
The content of the pin is a custom `ImGui::Text` with the name and the value of the pin.
All the logic related to links is still handled internally as well as hover events.
***
## HANDLER
### Creation
The handler, as the name suggests, handles the grid. It's responsible for all the evens and the rendering.
It is possible to create an unlimited number of grid editors.
The default constructor creates a new editor named `"FlowGrid{i}"` where `{i}` is an increment counter.
Otherwise it is possible to specify a custom name.
### Main loop
Each frame the handler must be updated. On each update the events will be processed and nodes and links are drawn.
```c++
// Inside Dear ImGui window
myGrid.update(); // Update logic and render
// . . .
```
_This will only render the node editor, so it must be called inside a Dear ImGui window. The editor will auto-fit the available space by default.
(See [Customization](#customization) for more options)._
### Adding nodes
The handler has ownership over the nodes. ALl the nodes are stored in a list.
Three methods are provided to add nodes.
```c++
myGrid.addNode(pos, ...);
```
Adds a node at the given grid coordinates.
The `...` represents the extra optional parameters that may be required by the custom node.
```c++
myGrid.placeNode(...);
```
Adds a node at the mouse position _(screen coordinates)_.
The `...` represents the extra optional parameters that may be required by the custom node.
```c++
myGrid.placeNodeAt(pos, ...);
```
Adds a node at the given screen coordinates.
The `...` represents the extra optional parameters that may be required by the custom node.
### Pop-ups
The handler also provides pop-up events for right-click and dropped-link events.
The dropped-link even is triggered when the user is dragging a link and _drops it_ on an empty point on the grid.
**Right-click pup-up:**
```c++
myGrid.rightClickPopUpContent([this](BaseNode* node){
/* omitted */
});
```
Takes a function or a lambda expression (like in the example) with the content of the pop-up and the subsequent logic.
The pointer `node` points to the right-clicked node. Can be `nullptr` if the right-click happened on an empty point.
**Dropped-link pup-up:**
```c++
myGrid.droppedLinkPopUpContent([this](Pin* dragged){
/* omitted */
}, key);
```
The first parameter is a function or a lambda expression (like in the example) with the content of the pop-up and the subsequent logic.
Additionally, an optional key can be specified. In this case the pop-up will trigger only if the given key is being held down at the moment of the _drop_.
The pointer `dragged` points to the pin the dropped link is attached to.
### Customization
The handler is fully customizable. A custom fixed size can be specified using `.setSize()`, and the visual appearance can be accessed using `.getStyle()`.
All the remaining configuration parameters can be accessed via `.getGrid().config()`.
***
_Also consult the [examples folder]() for hands-on practical examples **(coming soon)**_.
_In case of problems or questions, consider opening an issue._
_Please refer to the doxygen documentation for a list of public methods and their details._
================================================
FILE: example/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.14)
project(example VERSION 1.0 LANGUAGES CXX)
option(USE_SYSTEM_IMGUI "Use system Imgui instead of automatic download" OFF)
set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
list(APPEND imnode_flow_sources
${IMNODEFLOW_DIR}/src/ImNodeFlow.cpp)
if(USE_SYSTEM_IMGUI)
# Make sure you have the Findimgui.cmake scripts
# available to CMake using correct path
find_package(imgui)
# Make sure the target imgui::imgui is setup in your scripts
add_executable(example example.cpp ${imnode_flow_sources})
target_link_libraries(ImNodeFlow imgui::imgui)
else()
# Location to download Imgui sources
set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui)
include(FetchContent)
FetchContent_Declare(
imgui
GIT_REPOSITORY "https://github.com/ocornut/imgui.git"
GIT_TAG "v1.91.6" # Update with future minimum compatibility
SOURCE_DIR ${IMGUI_DIR}
GIT_SHALLOW TRUE # Limit history to download
)
FetchContent_MakeAvailable(imgui)
list(APPEND imgui_sources
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/misc/cpp/imgui_stdlib.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp)
add_executable(example example.cpp ${imgui_sources} ${imnode_flow_sources})
target_include_directories(example PRIVATE ${IMGUI_DIR} ${IMGUI_DIR}/backends)
endif()
# Common definitions
target_include_directories(example PRIVATE ${IMNODEFLOW_DIR}/include)
set_property(TARGET example PROPERTY CXX_STANDARD 17)
target_compile_definitions(example PRIVATE IMGUI_DEFINE_MATH_OPERATORS)
if(CMAKE_SYSTEM_NAME MATCHES Emscripten)
include(cmake/emscripten.cmake)
else()
include(cmake/desktop.cmake)
endif()
================================================
FILE: example/cmake/desktop.cmake
================================================
find_package(OpenGL REQUIRED)
find_package(SDL2 REQUIRED)
if (UNIX)
if (NOT APPLE)
find_package(Threads REQUIRED)
find_package(X11 REQUIRED)
target_link_libraries(example PRIVATE
${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
endif()
endif()
target_link_libraries(example PUBLIC OpenGL::GL SDL2::SDL2)
================================================
FILE: example/cmake/emscripten.cmake
================================================
message("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
message("^^^^^^^^^^ Enabling emscripten compile ^^^^^^^^^^^")
message("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
set(CMAKE_EXECUTABLE_SUFFIX ".html")
target_compile_options(example PUBLIC -sUSE_SDL=2 -fwasm-exceptions)
target_compile_definitions(example PUBLIC "-DIMGUI_DISABLE_FILE_FUNCTIONS -Wall -Wformat -Os")
target_link_options(example PUBLIC -sUSE_SDL=2 -fwasm-exceptions -sWASM=1 -sALLOW_MEMORY_GROWTH=1
-sNO_EXIT_RUNTIME=0 -sASSERTIONS=1 -sNO_FILESYSTEM=1
--no-heap-copy --shell-file ${CMAKE_SOURCE_DIR}/html/shell_min.html
--llvm-lto -O2 -Oz -s ELIMINATE_DUPLICATE_FUNCTIONS=1)
target_include_directories(example PRIVATE ${IMGUI_DIR}/examples/libs)
================================================
FILE: example/example.cpp
================================================
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_opengl3.h"
#include
#include
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include
#else
#include
#endif
#ifdef __EMSCRIPTEN__
#include "../libs/emscripten/emscripten_mainloop_stub.h"
#endif
#include "example.hpp"
// Main code
int main(int, char**)
{
// Setup SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
{
printf("Error: %s\n", SDL_GetError());
return -1;
}
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char* glsl_version = "#version 100";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
// GL 3.2 Core + GLSL 150
const char* glsl_version = "#version 150";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif
// From 2.0.18: Enable native IME.
#ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
// Create window with graphics context
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Window* window = SDL_CreateWindow("ImNodeFlow example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
if (window == nullptr)
{
printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
return -1;
}
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Create a node editor with width and height
NodeEditor* neditor = new(NodeEditor)(500, 500);
// Main loop
bool done = false;
#ifdef __EMSCRIPTEN__
io.IniFilename = nullptr;
EMSCRIPTEN_MAINLOOP_BEGIN
#else
while (!done)
#endif
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT)
done = true;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
done = true;
}
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
{
SDL_Delay(10);
continue;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
const auto window_size = io.DisplaySize - ImVec2(1, 1);
const auto window_pos = ImVec2(1, 1);
const auto node_editor_size = window_size - ImVec2(16, 16);
ImGui::SetNextWindowSize(window_size);
ImGui::SetNextWindowPos(window_pos);
ImGui::Begin("Node Editor", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
neditor->set_size(node_editor_size);
neditor->draw();
ImGui::End();
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
SDL_GL_SwapWindow(window);
}
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_MAINLOOP_END;
#endif
delete neditor;
neditor = nullptr;
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_GL_DeleteContext(gl_context);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
================================================
FILE: example/example.hpp
================================================
#pragma once
#include "ImNodeFlow.h"
class SimpleSum : public ImFlow::BaseNode {
public:
SimpleSum() {
setTitle("Simple sum");
setStyle(ImFlow::NodeStyle::green());
ImFlow::BaseNode::addIN("In", 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("In") + m_valB; });
}
void draw() override {
ImGui::SetNextItemWidth(100.f);
ImGui::InputInt("##ValB", &m_valB);
}
private:
int m_valB = 0;
};
class CollapsingNode : public ImFlow::BaseNode {
public:
CollapsingNode() {
setTitle("Collapsing node");
setStyle(ImFlow::NodeStyle::red());
ImFlow::BaseNode::addIN("A", 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addIN("B", 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("A") + getInVal("B"); });
}
void draw() override {
if(ImFlow::BaseNode::isSelected()) {
ImGui::SetNextItemWidth(100.f);
ImGui::Text("You can only see me when the node is selected!");
}
}
};
class ResultNode : public ImFlow::BaseNode {
public:
ResultNode() {
setTitle("Result node");
setStyle(ImFlow::NodeStyle::brown());
ImFlow::BaseNode::addIN("A", 0, ImFlow::ConnectionFilter::SameType());
ImFlow::BaseNode::addIN("B", 0, ImFlow::ConnectionFilter::SameType());
}
void draw() override {
ImGui::Text("Result: %d", getInVal("A") + getInVal("B"));
}
};
/* Node editor that sets up the grid to place nodes */
struct NodeEditor : ImFlow::BaseNode {
ImFlow::ImNodeFlow mINF;
NodeEditor(float d, std::size_t r) : BaseNode() {
mINF.setSize({d, d});
if (r > 0) {
auto n1 = mINF.addNode({40, 40});
auto n2 = mINF.addNode({40, 150});
auto result = mINF.addNode({250, 80});
// Add links between nodes
n1->outPin("Out")->createLink(result->inPin("A"));
n2->outPin("Out")->createLink(result->inPin("B"));
// Add a collapsing node
auto collapsingNode = mINF.addNode({300, 300});
}
}
void set_size(ImVec2 d) {
mINF.setSize(d);
}
void draw() override {
mINF.update();
}
};
================================================
FILE: example/html/shell_min.html
================================================
Emscripten-Generated Code
emscripten
Downloading...
Resize canvas
Lock/hide mouse pointer
{{{ SCRIPT }}}
================================================
FILE: include/ImNodeFlow.h
================================================
#ifndef IM_NODE_FLOW
#define IM_NODE_FLOW
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../src/imgui_bezier_math.h"
#include "../src/context_wrapper.h"
//#define ConnectionFilter_None [](ImFlow::Pin* out, ImFlow::Pin* in){ return true; }
//#define ConnectionFilter_SameType [](ImFlow::Pin* out, ImFlow::Pin* in){ return out->getDataType() == in->getDataType(); }
//#define ConnectionFilter_Numbers [](ImFlow::Pin* out, ImFlow::Pin* in){ return out->getDataType() == typeid(double) || out->getDataType() == typeid(float) || out->getDataType() == typeid(int); }
namespace ImFlow
{
// -----------------------------------------------------------------------------------------------------------------
// HELPERS
/**
* @brief Draw a sensible bezier between two points
* @param p1 Starting point
* @param p2 Ending point
* @param color Color of the curve
* @param thickness Thickness of the curve
*/
inline static void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness);
/**
* @brief Collider checker for smart_bezier
* @details Projects the point "p" orthogonally onto the bezier curve and
* checks if the distance is less than the given radius.
* @param p Point to be tested
* @param p1 Starting point of smart_bezier
* @param p2 Ending point of smart_bezier
* @param radius Lateral width of the hit box
* @return [TRUE] if "p" is inside the collider
*
* Intended to be used in union with smart_bezier();
*/
inline static bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius);
// -----------------------------------------------------------------------------------------------------------------
// CLASSES PRE-DEFINITIONS
template class InPin;
template class OutPin;
class Pin; class BaseNode;
class ImNodeFlow; class ConnectionFilter;
// -----------------------------------------------------------------------------------------------------------------
// PIN'S PROPERTIES
typedef unsigned long long int PinUID;
/**
* @brief Extra pin's style setting
*/
struct PinStyleExtras
{
/// @brief Top and bottom spacing
ImVec2 padding = ImVec2(3.f, 1.f);
/// @brief Border and background corner rounding
float bg_radius = 8.f;
/// @brief Border thickness
float border_thickness = 1.f;
/// @brief Background color
ImU32 bg_color = IM_COL32(23, 16, 16, 0);
/// @brief Background color when hovered
ImU32 bg_hover_color = IM_COL32(100, 100, 255, 70);
/// @brief Border color
ImU32 border_color = IM_COL32(255, 255, 255, 0);
/// @brief Link thickness
float link_thickness = 2.6f;
/// @brief Link thickness when dragged
float link_dragged_thickness = 2.2f;
/// @brief Link thickness when hovered
float link_hovered_thickness = 3.5f;
/// @brief Thickness of the outline of a selected link
float link_selected_outline_thickness = 0.5f;
/// @brief Color of the outline of a selected link
ImU32 outline_color = IM_COL32(80, 20, 255, 200);
/// @brief Spacing between pin content and socket
float socket_padding = 6.6f;
};
/**
* @brief Defines the visual appearance of a pin
*/
class PinStyle
{
public:
PinStyle(ImU32 color, int socket_shape, float socket_radius, float socket_hovered_radius, float socket_connected_radius, float socket_thickness)
:color(color), socket_shape(socket_shape), socket_radius(socket_radius), socket_hovered_radius(socket_hovered_radius), socket_connected_radius(socket_connected_radius), socket_thickness(socket_thickness) {}
/// @brief Socket and link color
ImU32 color;
/// @brief Socket shape ID
int socket_shape;
/// @brief Socket radius
float socket_radius;
/// @brief Socket radius when hovered
float socket_hovered_radius;
/// @brief Socket radius when connected
float socket_connected_radius;
/// @brief Socket outline thickness when empty
float socket_thickness;
/// @brief List of less common properties
PinStyleExtras extra;
public:
/// @brief Default cyan style
static std::shared_ptr cyan() { return std::make_shared(PinStyle(IM_COL32(87,155,185,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
/// @brief Default green style
static std::shared_ptr green() { return std::make_shared(PinStyle(IM_COL32(90,191,93,255), 4, 4.f, 4.67f, 4.2f, 1.3f)); }
/// @brief Default blue style
static std::shared_ptr blue() { return std::make_shared(PinStyle(IM_COL32(90,117,191,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
/// @brief Default brown style
static std::shared_ptr brown() { return std::make_shared(PinStyle(IM_COL32(191,134,90,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
/// @brief Default red style
static std::shared_ptr red() { return std::make_shared(PinStyle(IM_COL32(191,90,90,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
/// @brief Default white style
static std::shared_ptr white() { return std::make_shared(PinStyle(IM_COL32(255,255,255,255), 5, 4.f, 4.67f, 4.2f, 1.f)); }
};
// -----------------------------------------------------------------------------------------------------------------
// NODE'S PROPERTIES
typedef uintptr_t NodeUID;
/**
* @brief Defines the visual appearance of a node
*/
class NodeStyle
{
public:
NodeStyle(ImU32 header_bg, ImColor header_title_color, float radius) :header_bg(header_bg), header_title_color(header_title_color), radius(radius) {}
/// @brief Body's background color
ImU32 bg = IM_COL32(55,64,75,255);
/// @brief Header's background color
ImU32 header_bg;
/// @brief Header title color
ImColor header_title_color;
/// @brief Border color
ImU32 border_color = IM_COL32(30,38,41,140);
/// @brief Border color when selected
ImU32 border_selected_color = IM_COL32(170, 190, 205, 230);
/// @brief Body's content padding (Left Top Right Bottom)
ImVec4 padding = ImVec4(13.7f, 6.f, 13.7f, 2.f);
/// @brief Edges rounding
float radius;
/// @brief Border thickness
float border_thickness = -1.35f;
/// @brief Border thickness when selected
float border_selected_thickness = 2.f;
public:
/// @brief Default cyan style
static std::shared_ptr cyan() { return std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f); }
/// @brief Default green style
static std::shared_ptr green() { return std::make_shared(IM_COL32(90,191,93,255), ImColor(233,241,244,255), 3.5f); }
/// @brief Default red style
static std::shared_ptr red() { return std::make_shared(IM_COL32(191,90,90,255), ImColor(233,241,244,255), 11.f); }
/// @brief Default brown style
static std::shared_ptr brown() { return std::make_shared(IM_COL32(191,134,90,255), ImColor(233,241,244,255), 6.5f); }
};
// -----------------------------------------------------------------------------------------------------------------
// LINK
/**
* @brief Link between two Pins of two different Nodes
*/
class Link
{
public:
/**
* @brief Construct a link
* @param left Pointer to the output Pin of the Link
* @param right Pointer to the input Pin of the Link
* @param inf Pointer to the Handler that contains the Link
*/
explicit Link(Pin* left, Pin* right, ImNodeFlow* inf) : m_left(left), m_right(right), m_inf(inf) {}
/**
* @brief Destruction of a link
* @details Deletes references of this links form connected pins
*/
~Link();
/**
* @brief Looping function to update the Link
* @details Draws the Link and updates Hovering and Selected status.
*/
void update();
/**
* @brief Get Left pin of the link
* @return Pointer to the Pin
*/
[[nodiscard]] Pin* left() const { return m_left; }
/**
* @brief Get Right pin of the link
* @return Pointer to the Pin
*/
[[nodiscard]] Pin* right() const { return m_right; }
/**
* @brief Get hovering status
* @return [TRUE] If the link is hovered in the current frame
*/
[[nodiscard]] bool isHovered() const { return m_hovered; }
/**
* @brief Get selected status
* @return [TRUE] If the link is selected in the current frame
*/
[[nodiscard]] bool isSelected() const { return m_selected; }
private:
Pin* m_left;
Pin* m_right;
ImNodeFlow* m_inf;
bool m_hovered = false;
bool m_selected = false;
};
// -----------------------------------------------------------------------------------------------------------------
// HANDLER
/**
* @brief Grid's the color parameters
*/
struct InfColors
{
/// @brief Background of the grid
ImU32 background = IM_COL32(33,41,45,255);
/// @brief Main lines of the grid
ImU32 grid = IM_COL32(200, 200, 200, 40);
/// @brief Secondary lines
ImU32 subGrid = IM_COL32(200, 200, 200, 10);
};
/**
* @brief ALl the grid's appearance parameters. Sizes + Colors
*/
struct InfStyler
{
/// @brief Size of main grid
float grid_size = 50.f;
/// @brief Sub-grid divisions for Node snapping
float grid_subdivisions = 5.f;
/// @brief ImNodeFlow colors
InfColors colors;
};
/**
* @brief Main node editor
* @details Handles the infinite grid, nodes and links. Also handles all the logic.
*/
class ImNodeFlow
{
private:
static int m_instances;
public:
/**
* @brief Instantiate a new editor with default name.
* Editor name will be "FlowGrid + the number of editors"
*/
ImNodeFlow() : ImNodeFlow("FlowGrid" + std::to_string(m_instances)) {}
/**
* @brief Instantiate a new editor with given name
* @details Creates a new Node Editor with the given name.
* @param name Name of the editor
*/
explicit ImNodeFlow(std::string name) :m_name(std::move(name))
{
m_instances++;
m_context.config().extra_window_wrapper = true;
m_context.config().color = m_style.colors.background;
}
/**
* @brief Handler loop
* @details Main update function. Refreshes all the logic and draws everything. Must be called every frame.
*/
void update();
/**
* @brief Add a node to the grid
* @tparam T Derived class of to be added
* @tparam Params types of optional args to forward to derived class ctor
* @param pos Position of the Node in grid coordinates
* @param args Optional arguments to be forwarded to derived class ctor
* @return Shared pointer of the pushed type to the newly added node
*
* Inheritance is checked at compile time, \ MUST be derived from BaseNode.
*/
template
std::shared_ptr addNode(const ImVec2& pos, Params&&... args);
private:
/**
* @brief Helper struct for creating a node struct from a lambda
* @sa addLambdaNode which wraps creating one of these
* @tparam L the type of the lambda
* @tparam B always BaseNode, tparam because BaseNode is incomplete here
*/
template
struct NodeWrapper : public B
{
L mLambda;
NodeWrapper(L&& l): BaseNode(), mLambda(std::forward(l)) {}
void draw() { mLambda(this); }
};
public:
/**
* @brief Add a node whos operation can be defined within a lambda.
* @tparam L the type of the lambda
* @param lambda the lambda that defines the nodes operation
* @param pos the position at which to place the node
*/
template
std::shared_ptr> addLambdaNode(L&& lambda, const ImVec2& pos)
{
return addNode>(pos, std::forward(lambda));
}
/**
* @brief Add a node to the grid
* @tparam T Derived class of to be added
* @tparam Params types of optional args to forward to derived class ctor
* @param pos Position of the Node in screen coordinates
* @param args Optional arguments to be forwarded to derived class ctor
* @return Shared pointer of the pushed type to the newly added node
*
* Inheritance is checked at compile time, \ MUST be derived from BaseNode.
*/
template
std::shared_ptr placeNodeAt(const ImVec2& pos, Params&&... args);
/**
* @brief Add a node to the grid using mouse position
* @tparam T Derived class of to be added
* @tparam Params types of optional args to forward to derived class ctor
* @param args Optional arguments to be forwarded to derived class ctor
* @return Shared pointer of the pushed type to the newly added node
*
* Inheritance is checked at compile time, \ MUST be derived from BaseNode.
*/
template
std::shared_ptr placeNode(Params&&... args);
/**
* @brief Add link to the handler internal list
* @param link Reference to the link
*/
void addLink(std::shared_ptr& link);
/**
* @brief Pop-up when link is "dropped"
* @details Sets the content of a pop-up that can be displayed when dragging a link in the open instead of onto another pin.
* @details If "key = ImGuiKey_None" the pop-up will always open when a link is dropped.
* @param content Function or Lambda containing only the contents of the pop-up and the subsequent logic
* @param key Optional key required in order to open the pop-up
*/
void droppedLinkPopUpContent(std::function content, ImGuiKey key = ImGuiKey_None) { m_droppedLinkPopUp = std::move(content); m_droppedLinkPupUpComboKey = key; }
/**
* @brief Pop-up when right-clicking
* @details Sets the content of a pop-up that can be displayed when right-clicking on the grid.
* @param content Function or Lambda containing only the contents of the pop-up and the subsequent logic
*/
void rightClickPopUpContent(std::function content) { m_rightClickPopUp = std::move(content); }
/**
* @brief Get mouse clicking status
* @return [TRUE] if mouse is clicked and click hasn't been consumed
*/
[[nodiscard]] bool getSingleUseClick() const { return m_singleUseClick; }
/**
* @brief Consume the click for the given frame
*/
void consumeSingleUseClick() { m_singleUseClick = false; }
/**
* @brief Get editor's name
* @return Const reference to editor's name
*/
const std::string& getName() { return m_name; }
/**
* @brief Get editor's position
* @return Const reference to editor's position in screen coordinates
*/
const ImVec2& getPos() { return m_context.origin(); }
/**
* @brief Get editor's grid scroll
* @details Scroll is the offset from the origin of the grid, changes while navigating the grid.
* @return Const reference to editor's grid scroll
*/
const ImVec2& getScroll() { return m_context.scroll(); }
/**
* @brief Get the scale adjusted screen space mouse delta, needed for dragging
*
* @return scale adjusted mouse delta.
*/
ImVec2 getScreenSpaceDelta(){return m_context.getScreenDelta(); }
/**
* @brief Get editor's list of nodes
* @return Const reference to editor's internal nodes list
*/
std::unordered_map>& getNodes() { return m_nodes; }
/**
* @brief Get nodes count
* @return Number of nodes present in the editor
*/
uint32_t getNodesCount() { return (uint32_t)m_nodes.size(); }
/**
* @brief Get editor's list of links
* @return Const reference to editor's internal links list
*/
const std::vector>& getLinks() { return m_links; }
/**
* @brief Get zooming viewport
* @return Const reference to editor's internal viewport for zoom support
*/
ContainedContext& getGrid() { return m_context; }
/**
* @brief Get dragging status
* @return [TRUE] if a Node is being dragged around the grid
*/
[[nodiscard]] bool isNodeDragged() const { return m_draggingNode; }
/**
* @brief Get current style
* @return Reference to style variables
*/
InfStyler& getStyle() { return m_style; }
/**
* @brief Set editor's size
* @param size Editor's size. Set to (0, 0) to auto-fit.
*/
void setSize(const ImVec2& size) { m_context.config().size = size; }
/**
* @brief Set dragging status
* @param state New dragging state
*
* The new state will only be updated one at the start of each frame.
*/
void draggingNode(bool state) { m_draggingNodeNext = state; }
/**
* @brief Set what pin is being hovered
* @param hovering Pointer to the hovered pin
*/
void hovering(Pin* hovering) { m_hovering = hovering; }
/**
* @brief Set what node is being hovered
* @param hovering Pointer to the hovered node
*/
void hoveredNode(BaseNode* hovering) { m_hoveredNode = hovering; }
/**
* @brief Convert coordinates from screen to grid
* @param p Point in screen coordinates to be converted
* @return Point in grid's coordinates
*/
ImVec2 screen2grid(const ImVec2& p);
/**
* @brief Convert coordinates from grid to screen
* @param p Point in grid's coordinates to be converted
* @return Point in screen coordinates
*/
ImVec2 grid2screen(const ImVec2 &p);
/**
* @brief Check if mouse is on selected node
* @return [TRUE] if the mouse is hovering a selected node
*/
bool on_selected_node();
/**
* @brief Check if mouse is on a free point on the grid
* @return [TRUE] if the mouse is not hovering a node or a link
*/
bool on_free_space();
/**
* @brief Get recursion blacklist for nodes
* @return Reference to blacklist
*/
std::vector& get_recursion_blacklist() { return m_pinRecursionBlacklist; }
private:
std::string m_name;
ContainedContext m_context;
bool m_singleUseClick = false;
std::unordered_map> m_nodes;
std::vector m_pinRecursionBlacklist;
std::vector> m_links;
std::function m_droppedLinkPopUp;
ImGuiKey m_droppedLinkPupUpComboKey = ImGuiKey_None;
Pin* m_droppedLinkLeft = nullptr;
std::function m_rightClickPopUp;
BaseNode* m_hoveredNodeAux = nullptr;
BaseNode* m_hoveredNode = nullptr;
bool m_draggingNode = false, m_draggingNodeNext = false;
Pin* m_hovering = nullptr;
Pin* m_dragOut = nullptr;
InfStyler m_style;
};
// -----------------------------------------------------------------------------------------------------------------
// BASE NODE
/**
* @brief Parent class for custom nodes
* @details Main class from which custom nodes can be created. All interactions with the main grid are handled internally.
*/
class BaseNode
{
public:
virtual ~BaseNode() = default;
BaseNode() = default;
/**
* @brief Main loop of the node
* @details Updates position, hovering and selected status, and renders the node. Must be called each frame.
*/
void update();
/**
* @brief Content of the node
* @details Function to be implemented by derived custom nodes.
* Must contain the body of the node. If left empty the node will only have input and output pins.
*/
virtual void draw() {}
/**
* @brief Add an Input to the node
* @details Will add an Input pin to the node with the given name and data type.
*
In this case the name of the pin will also be its UID.
*
The UID must be unique only in the context of the current node's inputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param defReturn Default return value when the pin is not connected
* @param filter Connection filter
* @param style Style of the pin
* @return Shared pointer to the newly added pin
*/
template
std::shared_ptr> addIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
/**
* @brief Add an Input to the node
* @details Will add an Input pin to the node with the given name and data type.
*
The UID must be unique only in the context of the current node's inputs.
* @tparam T Type of the data the pin will handle
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @param name Name of the pin
* @param defReturn Default return value when the pin is not connected
* @param filter Connection filter
* @param style Style of the pin
* @return Shared pointer to the newly added pin
*/
template
std::shared_ptr> addIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
/**
* @brief Remove input pin
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
*/
template
void dropIN(const U& uid);
/**
* @brief Remove input pin
* @param uid Unique identifier of the pin
*/
void dropIN(const char* uid);
/**
* @brief Show a temporary input pin
* @details Will show an input pin with the given name.
* The pin is created the first time showIN is called and kept alive as long as showIN is called each frame.
*
In this case the name of the pin will also be its UID.
*
The UID must be unique only in the context of the current node's inputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param defReturn Default return value when the pin is not connected
* @param filter Connection filter
* @param style Style of the pin
* @return Const reference to the value of the connected link for the current frame of defReturn
*/
template
const T& showIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
/**
* @brief Show a temporary input pin
* @details Will show an input pin with the given name and UID.
* The pin is created the first time showIN_uid is called and kept alive as long as showIN_uid is called each frame.
*
The UID must be unique only in the context of the current node's inputs.
* @tparam T Type of the data the pin will handle
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @param name Name of the pin
* @param defReturn Default return value when the pin is not connected
* @param filter Connection filter
* @param style Style of the pin
* @return Const reference to the value of the connected link for the current frame of defReturn
*/
template
const T& showIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
/**
* @brief Add an Output to the node
* @details Must be called in the node constructor. WIll add an Output pin to the node with the given name and data type.
*
In this case the name of the pin will also be its UID.
*
The UID must be unique only in the context of the current node's outputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param filter Connection filter
* @param style Style of the pin
* @return Shared pointer to the newly added pin. Must be used to set the behaviour
*/
template
[[nodiscard]] std::shared_ptr> addOUT(const std::string& name, std::shared_ptr style = nullptr);
/**
* @brief Add an Output to the node
* @details Must be called in the node constructor. WIll add an Output pin to the node with the given name and data type.
*
The UID must be unique only in the context of the current node's outputs.
* @tparam T Type of the data the pin will handle
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @param name Name of the pin
* @param filter Connection filter
* @param style Style of the pin
* @return Shared pointer to the newly added pin. Must be used to set the behaviour
*/
template
[[nodiscard]] std::shared_ptr> addOUT_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr);
/**
* @brief Remove output pin
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
*/
template
void dropOUT(const U& uid);
/**
* @brief Remove output pin
* @param uid Unique identifier of the pin
*/
void dropOUT(const char* uid);
/**
* @brief Show a temporary output pin
* @details Will show an output pin with the given name.
* The pin is created the first time showOUT is called and kept alive as long as showOUT is called each frame.
*
In this case the name of the pin will also be its UID.
*
The UID must be unique only in the context of the current node's outputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param behaviour Function or lambda expression used to calculate output value
* @param filter Connection filter
* @param style Style of the pin
*/
template
void showOUT(const std::string& name, std::function behaviour, std::shared_ptr style = nullptr);
/**
* @brief Show a temporary output pin
* @details Will show an output pin with the given name.
* The pin is created the first time showOUT_uid is called and kept alive as long as showOUT_uid is called each frame.
*
The UID must be unique only in the context of the current node's outputs.
* @tparam T Type of the data the pin will handle
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @param name Name of the pin
* @param behaviour Function or lambda expression used to calculate output value
* @param filter Connection filter
* @param style Style of the pin
*/
template
void showOUT_uid(const U& uid, const std::string& name, std::function behaviour, std::shared_ptr style = nullptr);
/**
* @brief Get Input value from an InPin
* @details Get a reference to the value of an input pin, the value is stored in the output pin at the other end of the link.
* @tparam T Data type
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @return Const reference to the value
*/
template
const T& getInVal(const U& uid);
/**
* @brief Get Input value from an InPin
* @details Get a reference to the value of an input pin, the value is stored in the output pin at the other end of the link.
* @tparam T Data type
* @param uid Unique identifier of the pin
* @return Const reference to the value
*/
template
const T& getInVal(const char* uid);
/**
* @brief Get generic reference to input pin
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @return Generic pointer to the pin
*/
template
Pin* inPin(const U& uid);
/**
* @brief Get generic reference to input pin
* @param uid Unique identifier of the pin
* @return Generic pointer to the pin
*/
Pin* inPin(const char* uid);
/**
* @brief Get generic reference to output pin
* @tparam U Type of the UID
* @param uid Unique identifier of the pin
* @return Generic pointer to the pin
*/
template
Pin* outPin(const U& uid);
/**
* @brief Get generic reference to output pin
* @param uid Unique identifier of the pin
* @return Generic pointer to the pin
*/
Pin* outPin(const char* uid);
/**
* @brief Get internal input pins list
* @return Const reference to node's internal list
*/
const std::vector>& getIns() { return m_ins; }
/**
* @brief Get internal output pins list
* @return Const reference to node's internal list
*/
const std::vector>& getOuts() { return m_outs; }
/**
* @brief Delete itself
*/
void destroy() { m_destroyed = true; }
/*
* @brief Get if node must be deleted
*/
[[nodiscard]] bool toDestroy() const { return m_destroyed; }
/**
* @brief Get hovered status
* @return [TRUE] if the mouse is hovering the node
*/
bool isHovered();
/**
* @brief Get node's UID
* @return Node's unique identifier
*/
[[nodiscard]] NodeUID getUID() const { return m_uid; }
/**
* @brief Get node name
* @return Const reference to the node's name
*/
const std::string& getName() { return m_title; }
/**
* @brief Get node size
* @return Const reference to the node's size
*/
const ImVec2& getSize() { return m_size; }
/**
* @brief Get node size
* @return Const reference to the node's size
*/
const ImVec2& getFullSize() { return m_fullSize; }
/**
* @brief Get node position
* @return Const reference to the node's position
*/
const ImVec2& getPos() { return m_pos; }
/**
* @brief Get grid handler bound to node
* @return Pointer to the handler
*/
ImNodeFlow* getHandler() { return m_inf; }
/**
* @brief Get node's style
* @return Shared pointer to the node's style
*/
const std::shared_ptr& getStyle() { return m_style; }
/**
* @brief Get selected status
* @return [TRUE] if the node is selected
*/
[[nodiscard]] bool isSelected() const { return m_selected; }
/**
* @brief Get dragged status
* @return [TRUE] if the node is being dragged
*/
[[nodiscard]] bool isDragged() const { return m_dragged; }
/**
* @brief Set node's uid
* @param uid Node's unique identifier
*/
BaseNode* setUID(NodeUID uid) { m_uid = uid; return this; }
/**
* @brief Set node's name
* @param name New title
*/
BaseNode* setTitle(const std::string& title) { m_title = title; return this; }
/**
* @brief Set node's position
* @param pos Position in grid coordinates
*/
BaseNode* setPos(const ImVec2& pos) { m_pos = pos; m_posTarget = pos; return this; }
/**
* @brief Set ImNodeFlow handler
* @param inf Grid handler for the node
*/
BaseNode* setHandler(ImNodeFlow* inf) { m_inf = inf; return this; }
/**
* @brief Set node's style
* @param style New style
*/
BaseNode* setStyle(std::shared_ptr style) { m_style = std::move(style); return this; }
/**
* @brief Set selected status
* @param state New selected state
*
* Status only updates when updatePublicStatus() is called
*/
BaseNode* selected(bool state) { m_selectedNext = state; return this; }
/**
* @brief Update the isSelected status of the node
*/
void updatePublicStatus() { m_selected = m_selectedNext; }
private:
NodeUID m_uid = 0;
std::string m_title;
ImVec2 m_pos, m_posTarget;
ImVec2 m_size;
ImVec2 m_fullSize;
ImNodeFlow* m_inf = nullptr;
std::shared_ptr m_style;
bool m_selected = false, m_selectedNext = false;
bool m_dragged = false;
bool m_destroyed = false;
std::vector> m_ins;
std::vector>> m_dynamicIns;
std::vector> m_outs;
std::vector>> m_dynamicOuts;
};
// -----------------------------------------------------------------------------------------------------------------
// PINS
/**
* @brief Pins type identifier
*/
enum PinType
{
PinType_Input,
PinType_Output
};
/**
* @brief Generic base class for pins
*/
class Pin
{
public:
/**
* @brief Generic pin constructor
* @param name Name of the pin
* @param filter Connection filter
* @param kind Specifies Input or Output
* @param parent Pointer to the Node containing the pin
* @param inf Pointer to the Grid Handler the pin is in (same as parent)
* @param style Style of the pin
*/
explicit Pin(PinUID uid, std::string name, std::shared_ptr style, PinType kind, BaseNode* parent, ImNodeFlow** inf)
:m_uid(uid), m_name(std::move(name)), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style))
{
if(!m_style)
m_style = PinStyle::cyan();
}
virtual ~Pin() = default;
/**
* @brief Main loop of the pin
* @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame.
*/
void update();
/**
* @brief Draw default pin's socket
*/
void drawSocket();
/**
* @brief Draw default pin's decoration (border, bg, and hover overlay)
*/
void drawDecoration();
/**
* @brief Used by output pins to calculate their values
*/
virtual void resolve() {}
/**
* @brief Custom render function to override Pin appearance
* @param r Function or lambda expression with new ImGui rendering
*/
Pin* renderer(std::function r) { m_renderer = std::move(r); return this; }
/**
* @brief Create link between pins
* @param other Pointer to the other pin
*/
virtual void createLink(Pin* other) = 0;
/**
* @brief Set the reference to a link
* @param link Smart pointer to the link
*/
virtual void setLink(std::shared_ptr& link) {}
/**
* @brief Delete link reference
*/
virtual void deleteLink() = 0;
/**
* @brief Get connected status
* @return [TRUE] if the pin is connected
*/
virtual bool isConnected() = 0;
/**
* @brief Get pin's link
* @return Weak_ptr reference to pin's link
*/
virtual std::weak_ptr getLink() { return std::weak_ptr{}; }
/**
* @brief Get pin's UID
* @return Unique identifier of the pin
*/
[[nodiscard]] PinUID getUid() const { return m_uid; }
/**
* @brief Get pin's name
* @return Const reference to pin's name
*/
const std::string& getName() { return m_name; }
/**
* @brief Get pin's position
* @return Const reference to pin's position in grid coordinates
*/
[[nodiscard]] const ImVec2& getPos() { return m_pos; }
/**
* @brief Get pin's hit-box size
* @return Const reference to pin's hit-box size
*/
[[nodiscard]] const ImVec2& getSize() { return m_size; }
/**
* @brief Get pin's parent node
* @return Generic type pointer to pin's parent node. (Node that contains it)
*/
BaseNode* getParent() { return m_parent; }
/**
* @brief Get pin's type
* @return The pin type. Either Input or Output
*/
PinType getType() { return m_type; }
/**
* @brief Get pin's data type (aka: \)
* @return String containing unique information identifying the data type
*/
[[nodiscard]] virtual const std::type_info& getDataType() const = 0;
/**
* @brief Get pin's style
* @return Smart pointer to pin's style
*/
std::shared_ptr& getStyle() { return m_style; }
/**
* @brief Get pin's link attachment point (socket)
* @return Grid coordinates to the attachment point between the link and the pin's socket
*/
virtual ImVec2 pinPoint() = 0;
/**
* @brief Calculate pin's width pre-rendering
* @return The with of the pin once it will be rendered
*/
float calcWidth() { return ImGui::CalcTextSize(m_name.c_str()).x; }
/**
* @brief Set pin's position
* @param pos Position in screen coordinates
*/
void setPos(ImVec2 pos) { m_pos = pos; }
protected:
PinUID m_uid;
std::string m_name;
ImVec2 m_pos = ImVec2(0.f, 0.f);
ImVec2 m_size = ImVec2(0.f, 0.f);
PinType m_type;
BaseNode* m_parent = nullptr;
ImNodeFlow** m_inf;
std::shared_ptr m_style;
std::function m_renderer;
};
/**
* @brief Collection of Pin's collection filters
*/
class ConnectionFilter
{
public:
static std::function None() { return [](Pin* out, Pin* in){ return true; }; }
static std::function SameType() { return [](Pin* out, Pin* in) { return out->getDataType() == in->getDataType(); }; }
static std::function Numbers() { return [](Pin* out, Pin* in){ return out->getDataType() == typeid(double) || out->getDataType() == typeid(float) || out->getDataType() == typeid(int); }; }
};
/**
* @brief Input specific pin
* @details Derived from the generic class Pin. The input pin owns the link pointer.
* @tparam T Data type handled by the pin
*/
template class InPin : public Pin
{
public:
/**
* @brief Input pin constructor
* @param name Name of the pin
* @param filter Connection filter
* @param parent Pointer to the Node containing the pin
* @param defReturn Default return value when the pin is not connected
* @param inf Pointer to the Grid Handler the pin is in (same as parent)
* @param style Style of the pin
*/
explicit InPin(PinUID uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf)
: Pin(uid, name, style, PinType_Input, parent, inf), m_emptyVal(defReturn), m_filter(std::move(filter)) {}
/**
* @brief Create link between pins
* @param other Pointer to the other pin
*/
void createLink(Pin* other) override;
/**
* @brief Delete the link connected to the pin
*/
void deleteLink() override { m_link.reset(); }
/**
* @brief Specify if connections from an output on the same node are allowed
* @param state New state of the flag
*/
void allowSameNodeConnections(bool state) { m_allowSelfConnection = state; }
/**
* @brief Get connected status
* @return [TRUE] is pin is connected to a link
*/
bool isConnected() override { return m_link != nullptr; }
/**
* @brief Get pin's link
* @return Weak_ptr reference to the link connected to the pin
*/
std::weak_ptr getLink() override { return m_link; }
/**
* @brief Get InPin's connection filter
* @return InPin's connection filter configuration
*/
[[nodiscard]] const std::function& getFilter() const { return m_filter; }
/**
* @brief Get pin's data type (aka: \)
* @return String containing unique information identifying the data type
*/
[[nodiscard]] const std::type_info& getDataType() const override { return typeid(T); };
/**
* @brief Get pin's link attachment point (socket)
* @return Grid coordinates to the attachment point between the link and the pin's socket
*/
ImVec2 pinPoint() override { return m_pos + ImVec2(-m_style->extra.socket_padding, m_size.y / 2); }
/**
* @brief Get value carried by the connected link
* @return Reference to the value of the connected OutPin. Or the default value if not connected
*/
const T& val();
private:
std::shared_ptr m_link;
T m_emptyVal;
std::function m_filter;
bool m_allowSelfConnection = false;
};
/**
* @brief Output specific pin
* @details Derived from the generic class Pin. The output pin handles the logic.
* @tparam T Data type handled by the pin
*/
template class OutPin : public Pin
{
public:
/**
* @brief Output pin constructor
* @param name Name of the pin
* @param filter Connection filter
* @param parent Pointer to the Node containing the pin
* @param inf Pointer to the Grid Handler the pin is in (same as parent)
* @param style Style of the pin
*/
explicit OutPin(PinUID uid, const std::string& name, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf)
:Pin(uid, name, style, PinType_Output, parent, inf) {}
/**
* @brief When parent gets deleted, remove the links
*/
~OutPin() override {
std::vector> links = std::move(m_links);
for (auto &l: links) if (!l.expired()) l.lock()->right()->deleteLink();
}
/**
* @brief Create link between pins
* @param other Pointer to the other pin
*/
void createLink(Pin* other) override;
/**
* @brief Add a connected link to the internal list
* @param link Pointer to the link
*/
void setLink(std::shared_ptr& link) override;
/**
* @brief Delete any expired weak pointers to a (now deleted) link
*/
void deleteLink() override;
/**
* @brief Get connected status
* @return [TRUE] is pin is connected to one or more links
*/
bool isConnected() override { return !m_links.empty(); }
/**
* @brief Get pin's link attachment point (socket)
* @return Grid coordinates to the attachment point between the link and the pin's socket
*/
ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_style->extra.socket_padding, m_size.y / 2); }
/**
* @brief Get output value
* @return Const reference to the internal value of the pin
*/
const T& val();
/**
* @brief Set logic to calculate output value
* @details Used to define the pin behaviour. This is what gets the data from the parent's inputs, and applies the needed logic.
* @param func Function or lambda expression used to calculate output value
*/
OutPin* behaviour(std::function func) { m_behaviour = std::move(func); return this; }
/**
* @brief Get pin's data type (aka: \)
* @return String containing unique information identifying the data type
*/
[[nodiscard]] const std::type_info& getDataType() const override { return typeid(T); };
private:
std::vector> m_links;
std::function m_behaviour;
T m_val;
};
}
#include "../src/ImNodeFlow.inl"
#endif
================================================
FILE: readme.md
================================================
# ImNodeFlow
**Node-based editor/blueprints for ImGui**
Create your custom nodes and their logic... ImNodeFlow will handle connections, editor logic, and rendering.

## Features
- Support for Zoom
- Built-in Input and Output logic
- Built-in links handling
- Customizable filters for different connections
- Built-in customizable pop-up events
- Appearance 100% customizable
## Example using SDL2 + OpenGL3 (CMake project)
A simple example using SDL2 + OpenGL3 backend is provided in the /example folder. The CMakeLists.txt file downloads the necessary sources automatically and populates. Simply copy the contents of example folder, configure and build. You can use this example as your starting point to build your code.
```
> cd example
> mkdir build
> cd build
> cmake ..
> ./example
```
To use system installed Imgui pass the `-DUSE_SYSTEM_IMGUI=ON` option while configuring CMake. Note that you should have the Findimgui.cmake script to find the Imgui libs and headers (to use `find_package(imgui)`).
### Simple Node example
```c++
class SimpleSum : public BaseNode
{
public:
SimpleSum()
{
setTitle("Simple sum");
setStyle(NodeStyle::green());
addIN("IN_VAL", 0, ConnectionFilter::SameType());
addOUT("OUT_VAL", ConnectionFilter::SameType())
->behaviour([this](){ return getInVal("IN_VAL") + m_valB; });
}
void draw() override
{
ImGui::SetNextItemWidth(100.f);
ImGui::InputInt("##ValB", &m_valB);
}
private:
int m_valB = 0;
};
```

## CMake for custom targets
Shown below is a simple CMake script to setup your own program to compile using Imgui and ImNodeFlow. You can adapt this to your needs.
```cmake
set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui)
set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow)
include(FetchContent)
FetchContent_Declare(ImNodeFlow
GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git"
GIT_TAG "master"
SOURCE_DIR ${IMNODEFLOW_DIR}
)
FetchContent_GetProperties(ImNodeFlow)
if(NOT imnodeflow_POPULATED)
FetchContent_Populate(ImNodeFlow)
endif()
FetchContent_Declare(imgui
GIT_REPOSITORY "https://github.com/ocornut/imgui.git"
GIT_TAG "origin/master"
SOURCE_DIR ${IMGUI_DIR}
)
FetchContent_GetProperties(imgui)
if(NOT imgui_POPULATED)
FetchContent_Populate(imgui)
endif()
list(APPEND imgui_sources
${IMGUI_DIR}/imgui.cpp
${IMGUI_DIR}/misc/cpp/imgui_stdlib.cpp
${IMGUI_DIR}/imgui_draw.cpp
${IMGUI_DIR}/imgui_tables.cpp
${IMGUI_DIR}/imgui_widgets.cpp
${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp
${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp)
list(APPEND imnode_flow_sources
${IMNODEFLOW_DIR}/src/ImNodeFlow.cpp)
add_executable(custom_exe custom_exe.cpp ${imgui_sources} ${imnode_flow_sources})
set_property(TARGET custom_exe PROPERTY CXX_STANDARD 17)
target_include_directories(custom_exe PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends)
target_compile_definitions(custom_exe PRIVATE IMGUI_DEFINE_MATH_OPERATORS)
```
Depending on the backend you choose for Imgui you can set `target_link_libraries` to the correct sets. For example SDL2+OpenGL requires the following defintitions,
```cmake
find_package(OpenGL REQUIRED)
find_package(SDL2 REQUIRED)
if (UNIX)
if (NOT APPLE)
find_package(Threads REQUIRED)
find_package(X11 REQUIRED)
target_link_libraries(custom_exe PRIVATE
${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
endif()
endif()
target_link_libraries(custom_exe PUBLIC OpenGL::GL SDL2::SDL2)
```
## Full documentation
For a more detailed explanation please refer to the [documentation](documentation.md)
***
### Special credits
- [ocornut](https://github.com/ocornut) for Dear ImGui
- [thedmd](https://github.com/thedmd) for _imgui_bezier_math.h_
- [nem0](https://github.com/nem0) for helping with Zoom support
================================================
FILE: src/ImNodeFlow.cpp
================================================
#include "ImNodeFlow.h"
namespace ImFlow {
// -----------------------------------------------------------------------------------------------------------------
// LINK
void Link::update() {
ImVec2 start = m_left->pinPoint();
ImVec2 end = m_right->pinPoint();
float thickness = m_left->getStyle()->extra.link_thickness;
bool mouseClickState = m_inf->getSingleUseClick();
if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
m_selected = false;
if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) {
m_hovered = true;
thickness = m_left->getStyle()->extra.link_hovered_thickness;
if (mouseClickState) {
m_inf->consumeSingleUseClick();
m_selected = true;
}
} else { m_hovered = false; }
if (m_selected)
smart_bezier(start, end, m_left->getStyle()->extra.outline_color,
thickness + m_left->getStyle()->extra.link_selected_outline_thickness);
smart_bezier(start, end, m_left->getStyle()->color, thickness);
if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false))
m_right->deleteLink();
}
Link::~Link() {
if (!m_left) return;
m_left->deleteLink();
}
// -----------------------------------------------------------------------------------------------------------------
// BASE NODE
bool BaseNode::isHovered() {
ImVec2 paddingTL = {m_style->padding.x, m_style->padding.y};
ImVec2 paddingBR = {m_style->padding.z, m_style->padding.w};
return ImGui::IsMouseHoveringRect(m_inf->grid2screen(m_pos - paddingTL),
m_inf->grid2screen(m_pos + m_size + paddingBR));
}
void BaseNode::update() {
ImDrawList *draw_list = ImGui::GetWindowDrawList();
ImGui::PushID(this);
bool mouseClickState = m_inf->getSingleUseClick();
ImVec2 offset = m_inf->grid2screen({0.f, 0.f});
ImVec2 paddingTL = {m_style->padding.x, m_style->padding.y};
ImVec2 paddingBR = {m_style->padding.z, m_style->padding.w};
draw_list->ChannelsSetCurrent(1); // Foreground
ImGui::SetCursorScreenPos(offset + m_pos);
ImGui::BeginGroup();
// Header
ImGui::BeginGroup();
ImGui::TextColored(m_style->header_title_color, "%s", m_title.c_str());
ImGui::Spacing();
ImGui::EndGroup();
float headerH = ImGui::GetItemRectSize().y;
float titleW = ImGui::GetItemRectSize().x;
// Inputs
if (!m_ins.empty() || !m_dynamicIns.empty()) {
ImGui::BeginGroup();
for (auto &p: m_ins) {
p->setPos(ImGui::GetCursorPos());
p->update();
}
for (auto &p: m_dynamicIns) {
if (p.first == 1) {
p.second->setPos(ImGui::GetCursorPos());
p.second->update();
p.first = 0;
}
}
ImGui::EndGroup();
ImGui::SameLine();
}
// Content
ImGui::BeginGroup();
draw();
ImGui::Dummy(ImVec2(0.f, 0.f));
ImGui::EndGroup();
ImGui::SameLine();
// Outputs
float maxW = 0.0f;
for (auto &p: m_outs) {
float w = p->calcWidth();
if (w > maxW)
maxW = w;
}
for (auto &p: m_dynamicOuts) {
float w = p.second->calcWidth();
if (w > maxW)
maxW = w;
}
ImGui::BeginGroup();
for (auto &p: m_outs) {
// FIXME: This looks horrible
if ((m_pos + ImVec2(titleW, 0) + m_inf->getGrid().scroll()).x <
ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW)
p->setPos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f));
else
p->setPos(ImVec2((m_pos + ImVec2(titleW - p->calcWidth(), 0) + m_inf->getGrid().scroll()).x,
ImGui::GetCursorPos().y + ImGui::GetWindowPos().y));
p->update();
}
for (auto &p: m_dynamicOuts) {
// FIXME: This looks horrible
if ((m_pos + ImVec2(titleW, 0) + m_inf->getGrid().scroll()).x <
ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW)
p.second->setPos(
ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f));
else
p.second->setPos(
ImVec2((m_pos + ImVec2(titleW - p.second->calcWidth(), 0) + m_inf->getGrid().scroll()).x,
ImGui::GetCursorPos().y + ImGui::GetWindowPos().y));
p.second->update();
p.first -= 1;
}
ImGui::EndGroup();
ImGui::EndGroup();
m_size = ImGui::GetItemRectSize();
ImVec2 headerSize = ImVec2(m_size.x + paddingBR.x, headerH);
// Background
draw_list->ChannelsSetCurrent(0);
draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + m_size + paddingBR, m_style->bg,
m_style->radius);
draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + headerSize, m_style->header_bg,
m_style->radius, ImDrawFlags_RoundCornersTop);
m_fullSize = m_size + paddingTL + paddingBR;
ImU32 col = m_style->border_color;
float thickness = m_style->border_thickness;
ImVec2 ptl = paddingTL;
ImVec2 pbr = paddingBR;
if (m_selected) {
col = m_style->border_selected_color;
thickness = m_style->border_selected_thickness;
}
if (thickness < 0.f) {
ptl.x -= thickness / 2;
ptl.y -= thickness / 2;
pbr.x -= thickness / 2;
pbr.y -= thickness / 2;
thickness *= -1.f;
}
draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, m_style->radius, 0, thickness);
if (ImGui::IsWindowHovered() && !ImGui::IsKeyDown(ImGuiKey_LeftCtrl) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node())
selected(false);
if (isHovered()) {
m_inf->hoveredNode(this);
if (mouseClickState) {
selected(true);
m_inf->consumeSingleUseClick();
}
}
if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected())
destroy();
bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize);
if (onHeader && mouseClickState) {
m_inf->consumeSingleUseClick();
m_dragged = true;
m_inf->draggingNode(true);
}
if (m_dragged || (m_selected && m_inf->isNodeDragged())) {
float step = m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions;
m_posTarget += m_inf->getScreenSpaceDelta();
// "Slam" The position
m_pos.x = round(m_posTarget.x / step) * step;
m_pos.y = round(m_posTarget.y / step) * step;
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
m_dragged = false;
m_inf->draggingNode(false);
m_posTarget = m_pos;
}
}
ImGui::PopID();
// Deleting dead pins
m_dynamicIns.erase(std::remove_if(m_dynamicIns.begin(), m_dynamicIns.end(),
[](const std::pair> &p) { return p.first == 0; }),
m_dynamicIns.end());
m_dynamicOuts.erase(std::remove_if(m_dynamicOuts.begin(), m_dynamicOuts.end(),
[](const std::pair> &p) { return p.first == 0; }),
m_dynamicOuts.end());
}
// -----------------------------------------------------------------------------------------------------------------
// HANDLER
int ImNodeFlow::m_instances = 0;
bool ImNodeFlow::on_selected_node() {
return std::any_of(m_nodes.begin(), m_nodes.end(),
[](const auto &n) { return n.second->isSelected() && n.second->isHovered(); });
}
bool ImNodeFlow::on_free_space() {
return std::all_of(m_nodes.begin(), m_nodes.end(),
[](const auto &n) { return !n.second->isHovered(); })
&& std::all_of(m_links.begin(), m_links.end(),
[](const auto &l) { return !l.lock()->isHovered(); });
}
ImVec2 ImNodeFlow::screen2grid( const ImVec2 & p )
{
if ( ImGui::GetCurrentContext() == m_context.getRawContext() )
return p - m_context.scroll();
return ( p - m_context.origin() ) / m_context.scale() - m_context.scroll();
}
ImVec2 ImNodeFlow::grid2screen( const ImVec2 & p )
{
if ( ImGui::GetCurrentContext() == m_context.getRawContext() )
return p + m_context.scroll();
return ( p + m_context.scroll() ) * m_context.scale() + m_context.origin();
}
void ImNodeFlow::addLink(std::shared_ptr &link) {
m_links.push_back(link);
}
void ImNodeFlow::update() {
// Updating looping stuff
m_hovering = nullptr;
m_hoveredNode = nullptr;
m_draggingNode = m_draggingNodeNext;
m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
// Create child canvas
m_context.begin();
ImGui::GetIO().IniFilename = nullptr;
ImDrawList *draw_list = ImGui::GetWindowDrawList();
// Display grid
ImVec2 gridSize = ImGui::GetWindowSize();
float subGridStep = m_style.grid_size / m_style.grid_subdivisions;
for (float x = fmodf(m_context.scroll().x, m_style.grid_size); x < gridSize.x; x += m_style.grid_size)
draw_list->AddLine(ImVec2(x, 0.0f), ImVec2(x, gridSize.y), m_style.colors.grid);
for (float y = fmodf(m_context.scroll().y, m_style.grid_size); y < gridSize.y; y += m_style.grid_size)
draw_list->AddLine(ImVec2(0.0f, y), ImVec2(gridSize.x, y), m_style.colors.grid);
if (m_context.scale() > 0.7f) {
for (float x = fmodf(m_context.scroll().x, subGridStep); x < gridSize.x; x += subGridStep)
draw_list->AddLine(ImVec2(x, 0.0f), ImVec2(x, gridSize.y), m_style.colors.subGrid);
for (float y = fmodf(m_context.scroll().y, subGridStep); y < gridSize.y; y += subGridStep)
draw_list->AddLine(ImVec2(0.0f, y), ImVec2(gridSize.x, y), m_style.colors.subGrid);
}
// Update and draw nodes
// TODO: I don't like this
draw_list->ChannelsSplit(2);
for (auto &node: m_nodes) { node.second->update(); }
// Remove "toDelete" nodes
for (auto iter = m_nodes.begin(); iter != m_nodes.end();) {
if (iter->second->toDestroy())
iter = m_nodes.erase(iter);
else
++iter;
}
draw_list->ChannelsMerge();
for (auto &node: m_nodes) { node.second->updatePublicStatus(); }
// Update and draw links
for (auto &l: m_links) { if (!l.expired()) l.lock()->update(); }
// Links drop-off
if (m_dragOut && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
if (!m_hovering) {
if (on_free_space() && m_droppedLinkPopUp) {
if (m_droppedLinkPupUpComboKey == ImGuiKey_None || ImGui::IsKeyDown(m_droppedLinkPupUpComboKey)) {
m_droppedLinkLeft = m_dragOut;
ImGui::OpenPopup("DroppedLinkPopUp");
}
}
} else
m_dragOut->createLink(m_hovering);
}
// Links drag-out
if (!m_draggingNode && m_hovering && !m_dragOut && ImGui::IsMouseClicked(ImGuiMouseButton_Left))
m_dragOut = m_hovering;
if (m_dragOut) {
if (m_dragOut->getType() == PinType_Output)
smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_dragOut->getStyle()->color,
m_dragOut->getStyle()->extra.link_dragged_thickness);
else
smart_bezier(ImGui::GetMousePos(), m_dragOut->pinPoint(), m_dragOut->getStyle()->color,
m_dragOut->getStyle()->extra.link_dragged_thickness);
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
m_dragOut = nullptr;
}
// Right-click PopUp
if (m_rightClickPopUp && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered()) {
m_hoveredNodeAux = m_hoveredNode;
ImGui::OpenPopup("RightClickPopUp");
}
if (ImGui::BeginPopup("RightClickPopUp")) {
m_rightClickPopUp(m_hoveredNodeAux);
ImGui::EndPopup();
}
// Dropped Link PopUp
if (ImGui::BeginPopup("DroppedLinkPopUp")) {
m_droppedLinkPopUp(m_droppedLinkLeft);
ImGui::EndPopup();
}
// Removing dead Links
m_links.erase(std::remove_if(m_links.begin(), m_links.end(),
[](const std::weak_ptr &l) { return l.expired(); }), m_links.end());
// Clearing recursion blacklist
m_pinRecursionBlacklist.clear();
m_context.end();
}
}
================================================
FILE: src/ImNodeFlow.inl
================================================
#pragma once
#include "ImNodeFlow.h"
namespace ImFlow
{
inline void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness)
{
ImDrawList* dl = ImGui::GetWindowDrawList();
float distance = sqrt(pow((p2.x - p1.x), 2.f) + pow((p2.y - p1.y), 2.f));
float delta = distance * 0.45f;
if (p2.x < p1.x) delta += 0.2f * (p1.x - p2.x);
// float vert = (p2.x < p1.x - 20.f) ? 0.062f * distance * (p2.y - p1.y) * 0.005f : 0.f;
float vert = 0.f;
ImVec2 p22 = p2 - ImVec2(delta, vert);
if (p2.x < p1.x - 50.f) delta *= -1.f;
ImVec2 p11 = p1 + ImVec2(delta, vert);
dl->AddBezierCubic(p1, p11, p22, p2, color, thickness);
}
inline bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius)
{
float distance = sqrt(pow((p2.x - p1.x), 2.f) + pow((p2.y - p1.y), 2.f));
float delta = distance * 0.45f;
if (p2.x < p1.x) delta += 0.2f * (p1.x - p2.x);
// float vert = (p2.x < p1.x - 20.f) ? 0.062f * distance * (p2.y - p1.y) * 0.005f : 0.f;
float vert = 0.f;
ImVec2 p22 = p2 - ImVec2(delta, vert);
if (p2.x < p1.x - 50.f) delta *= -1.f;
ImVec2 p11 = p1 + ImVec2(delta, vert);
return ImProjectOnCubicBezier(p, p1, p11, p22, p2).Distance < radius;
}
// -----------------------------------------------------------------------------------------------------------------
// HANDLER
template
std::shared_ptr ImNodeFlow::addNode(const ImVec2& pos, Params&&... args)
{
static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!");
std::shared_ptr n = std::make_shared(std::forward(args)...);
n->setPos(pos);
n->setHandler(this);
if (!n->getStyle())
n->setStyle(NodeStyle::cyan());
auto uid = reinterpret_cast(n.get());
n->setUID(uid);
m_nodes[uid] = n;
return n;
}
template
std::shared_ptr ImNodeFlow::placeNodeAt(const ImVec2& pos, Params&&... args)
{
return addNode(screen2grid(pos), std::forward(args)...);
}
template
std::shared_ptr ImNodeFlow::placeNode(Params&&... args)
{
return placeNodeAt(ImGui::GetMousePos(), std::forward(args)...);
}
// -----------------------------------------------------------------------------------------------------------------
// BASE NODE
template
std::shared_ptr> BaseNode::addIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style)
{
return addIN_uid(name, name, defReturn, std::move(filter), std::move(style));
}
template
std::shared_ptr> BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style)
{
PinUID h = std::hash{}(uid);
auto p = std::make_shared>(h, name, defReturn, std::move(filter), std::move(style), this, &m_inf);
m_ins.emplace_back(p);
return p;
}
template
void BaseNode::dropIN(const U& uid)
{
PinUID h = std::hash{}(uid);
for (auto it = m_ins.begin(); it != m_ins.end(); it++)
{
if (it->get()->getUid() == h)
{
m_ins.erase(it);
return;
}
}
}
inline void BaseNode::dropIN(const char* uid)
{
dropIN(uid);
}
template
const T& BaseNode::showIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style)
{
return showIN_uid(name, name, defReturn, std::move(filter), std::move(style));
}
template
const T& BaseNode::showIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style)
{
PinUID h = std::hash{}(uid);
for (std::pair>& p : m_dynamicIns)
{
if (p.second->getUid() == h)
{
p.first = 1;
return static_cast*>(p.second.get())->val();
}
}
m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, defReturn, std::move(filter), std::move(style), this, &m_inf)));
return static_cast*>(m_dynamicIns.back().second.get())->val();
}
template
std::shared_ptr> BaseNode::addOUT(const std::string& name, std::shared_ptr style)
{
return addOUT_uid(name, name, std::move(style));
}
template
std::shared_ptr> BaseNode::addOUT_uid(const U& uid, const std::string& name, std::shared_ptr style)
{
PinUID h = std::hash{}(uid);
auto p = std::make_shared>(h, name, std::move(style), this, &m_inf);
m_outs.emplace_back(p);
return p;
}
template
void BaseNode::dropOUT(const U& uid)
{
PinUID h = std::hash{}(uid);
for (auto it = m_outs.begin(); it != m_outs.end(); it++)
{
if (it->get()->getUid() == h)
{
m_outs.erase(it);
return;
}
}
}
inline void BaseNode::dropOUT(const char* uid)
{
dropOUT(uid);
}
template
void BaseNode::showOUT(const std::string& name, std::function behaviour, std::shared_ptr style)
{
showOUT_uid(name, name, std::move(behaviour), std::move(style));
}
template
void BaseNode::showOUT_uid(const U& uid, const std::string& name, std::function behaviour, std::shared_ptr style)
{
PinUID h = std::hash{}(uid);
for (std::pair>& p : m_dynamicOuts)
{
if (p.second->getUid() == h)
{
p.first = 2;
static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour));
return;
}
}
m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, std::move(style), this, &m_inf)));
static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour));
}
template
const T& BaseNode::getInVal(const U& uid)
{
PinUID h = std::hash{}(uid);
auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p)
{ return p->getUid() == h; });
assert(it != m_ins.end() && "Pin UID not found!");
return static_cast*>(it->get())->val();
}
template
const T& BaseNode::getInVal(const char* uid)
{
return getInVal(uid);
}
template
Pin* BaseNode::inPin(const U& uid)
{
PinUID h = std::hash{}(uid);
auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p)
{ return p->getUid() == h; });
assert(it != m_ins.end() && "Pin UID not found!");
return it->get();
}
inline Pin* BaseNode::inPin(const char* uid)
{
return inPin(uid);
}
template
Pin* BaseNode::outPin(const U& uid)
{
PinUID h = std::hash{}(uid);
auto it = std::find_if(m_outs.begin(), m_outs.end(), [&h](std::shared_ptr& p)
{ return p->getUid() == h; });
assert(it != m_outs.end() && "Pin UID not found!");
return it->get();
}
inline Pin* BaseNode::outPin(const char* uid)
{
return outPin(uid);
}
// -----------------------------------------------------------------------------------------------------------------
// PIN
inline void Pin::drawSocket()
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 tl = pinPoint() - ImVec2(m_style->socket_radius, m_style->socket_radius);
ImVec2 br = pinPoint() + ImVec2(m_style->socket_radius, m_style->socket_radius);
if (isConnected())
draw_list->AddCircleFilled(pinPoint(), m_style->socket_connected_radius, m_style->color, m_style->socket_shape);
else
{
if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br))
draw_list->AddCircle(pinPoint(), m_style->socket_hovered_radius, m_style->color, m_style->socket_shape, m_style->socket_thickness);
else
draw_list->AddCircle(pinPoint(), m_style->socket_radius, m_style->color, m_style->socket_shape, m_style->socket_thickness);
}
if (ImGui::IsMouseHoveringRect(tl, br))
(*m_inf)->hovering(this);
}
inline void Pin::drawDecoration()
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
if (ImGui::IsItemHovered())
draw_list->AddRectFilled(m_pos - m_style->extra.padding, m_pos + m_size + m_style->extra.padding, m_style->extra.bg_hover_color, m_style->extra.bg_radius);
else
draw_list->AddRectFilled(m_pos - m_style->extra.padding, m_pos + m_size + m_style->extra.padding, m_style->extra.bg_color, m_style->extra.bg_radius);
draw_list->AddRect(m_pos - m_style->extra.padding, m_pos + m_size + m_style->extra.padding, m_style->extra.border_color, m_style->extra.bg_radius, 0, m_style->extra.border_thickness);
}
inline void Pin::update()
{
// Custom rendering
if (m_renderer)
{
ImGui::BeginGroup();
m_renderer(this);
ImGui::EndGroup();
m_size = ImGui::GetItemRectSize();
if (ImGui::IsItemHovered())
(*m_inf)->hovering(this);
return;
}
ImGui::SetCursorPos(m_pos);
ImGui::Text("%s", m_name.c_str());
m_size = ImGui::GetItemRectSize();
drawDecoration();
drawSocket();
if (ImGui::IsItemHovered())
(*m_inf)->hovering(this);
}
// -----------------------------------------------------------------------------------------------------------------
// IN PIN
template
const T& InPin::val()
{
if(!m_link)
return m_emptyVal;
return reinterpret_cast*>(m_link->left())->val();
}
template
void InPin::createLink(Pin *other)
{
if (other == this || other->getType() == PinType_Input)
return;
if (m_parent == other->getParent() && !m_allowSelfConnection)
return;
if (m_link && m_link->left() == other)
{
m_link.reset();
return;
}
if (!m_filter(other, this)) // Check Filter
return;
m_link = std::make_shared