From 293f9496e0dbf7b0113605b6cb95710f936d5e9a Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:19:18 +0100 Subject: [PATCH 001/116] Update readme.md --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index ed6bf08..e68a396 100644 --- a/readme.md +++ b/readme.md @@ -1,13 +1,12 @@ -# ImNodeFLow +# ImNodeFlow **Node based editor/blueprints for ImGui** Create your custom nodes, and their logic. ImNodeFLow will handle connections, editor logic and rendering. -[INSERT IMAGE HERE] +https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26-037de8835f9d ## Features -- Object based nodes creation - Backed-in Input and Output logic - Backed-in links handling - Customizable filters for different connections @@ -47,7 +46,8 @@ private: }; ``` -[INSERT IMAGE HERE] +![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/4722b1e8-a52c-4ae2-b3f7-babfc713d8db) + ## Full documentation For a more detailed explanation please refer to the [full documentation](documentation.md) From fd7fce289326f1d51e625efff12e7a7f181fc1c8 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:08:31 +0100 Subject: [PATCH 002/116] Update readme.md Added credits --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index e68a396..6383c03 100644 --- a/readme.md +++ b/readme.md @@ -51,3 +51,6 @@ private: ## Full documentation For a more detailed explanation please refer to the [full documentation](documentation.md) +*** +### Special credits +- [thedmd](https://github.com/thedmd) for imgui_bezier_math.h From 6a294ffd1a0d7c005b8e3170ee5822c13e5a5e62 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Tue, 30 Jan 2024 02:37:35 +0100 Subject: [PATCH 003/116] Wrote the practical documentation --- documentation.md | 193 ++++++++++++++++++++++++++++++++++++++++++++++- readme.md | 2 +- 2 files changed, 193 insertions(+), 2 deletions(-) diff --git a/documentation.md b/documentation.md index bb78409..f8073b5 100644 --- a/documentation.md +++ b/documentation.md @@ -1,7 +1,198 @@ # ImNodeFlow Documentation +*** + +## Index +- [Getting started](#getting-started) +- [Custom Nodes 101](#creating-a-custom-node) + - [Inputs](#adding-input-pins) + - [Outputs](#adding-output-pins) + - [Body](#nodes-body) + - [Example](#all-together) + - [Adding it](#adding-nodes-to-the-grid) +- [Connection filters 101](#custom-filters) + - [Basic filters](#basic-filters) + - [Creating filters](#creating-more-filters) +- [Pop-ups 101](#custom-pop-ups) + - [Right click](#right-click-pop-up) + - [Dropped link](#dropped-link-pop-up) + +## Getting started +After having included the necessary files into the project. A few simple steps are necessary. +```c++ +#include // Include dependencies +``` +```c++ +ImNodeFlow INF; // Create an editor with default name +ImNodeFlow INF("Name"); // Create an editor with given name +``` +```c++ +// Inside Dear ImGui loop +INF.update(); // Update logic and render +// . . . +``` ## Creating a custom node +Custom nodes must be derived from the class BaseNode. +```c++ +class CustomNode : public BaseNode +{ +public: + . . . +private: + . . . +}; +``` + +### The constructor +```c++ +explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf); +``` +The constructor is standard and must **not** be changed. + +### Adding input pins +```c++ +explicit CustomNode(. . .) +{ + addIN("Pin name", 0, Connection filter); +} +``` +`addIN` will add an input pin to the node. Usually called in the node's constructor. +#### Getting the value +```c++ +int value = ins(0); +``` +Returns a read only reference to the value connected to the first input pin. + +### Adding output pins +```c++ +explicit CustomNode(. . .) +{ + addOUT("Pin name", Connection filter); +} +``` +`addOUT` will add an output pin to the node. Usually called in the node's constructor. +#### Defining logic +```c++ +behaviour([this](){ return 0; }); +``` +Node's logic for a specific pin. +
Takes either a Lambda of a function. The return value is the output of the pin. In this case, the pin will always output 0. +#### All together +```c++ +addOUT("Pin name", Connection filter) + ->behaviour([this](){ return 0; }); +``` +Creates a pin with given name and filter and sets its logic. This pin will be rather useless since it always returns 0 (also known as the author's IQ). + + +### Node's body +```c++ +void draw() override { . . . } +``` +Called each frame to draw ImGui widgets inside the node's body. +
Can be left empty if the nodes only needs inputs and outputs. + +### What have we learned? +To create a custom node, all it's needed is to define input pins, output pins + custom logic, and an optional body +
Everything else will be handled internally. +```c++ +class SimpleSum : public BaseNode +{ +public: + explicit SimpleSum(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) + { + addIN("IN_VAL", 0, ConnectionFilter_Int); + addOUT("OUT_VAL", ConnectionFilter_Int) + ->behaviour([this](){ return ins(0) + m_valB; }); + } + + void draw() override + { + ImGui::SetNextItemWidth(100.f); + ImGui::InputInt("##ValB", &m_valB); + } +private: + int m_valB = 0; +}; +``` +The example presented in the readme. +
SimpleDum had one input pin of type int called `IN_VAL` and one output pin called `OUT_VAL`. +Both have their filter set to `int`. +
The output pin returns the input + the slider's value. +
In the body the slider is rendered. + +### Adding nodes to the grid +It's now time to add our beautifully useless note to the grid. +```c++ +INF.addNode("Node's name", ImVec2(0, 0)); // Add node at canvas coordinates +INF.dropNode("Node's name", ImVec2(0, 0)); // Add node at screen coordinates +``` + +*** ## Custom filters +Filters can be used to block unwanted links between pins. +### Basic filters +- `ConnectionFilter_None`: The pin will allow any connection +- `ConnectionFilter_Int`: The pin will only allow other `int` connections +- `ConnectionFilter_Float`: The pin will only allow other `float` connections +- `ConnectionFilter_Double`: The pin will only allow other `double` connections +- `ConnectionFilter_String`: The pin will only allow other `string` connections +- `ConnectionFilter_Numbers`: The pin will only allow `int`, `float` and `double` connections + +### Creating more filters +It is possible to create more filters with the help of `ConnectionFilter_MakeCustom`. +```c++ +ConnectionFilter myFilter = ConnectionFilter_MakeCustom << 0; + +enum MyFilters +{ + FilterA = ConnectionFilter_MakeCustom << 1, + FilterB = ConnectionFilter_MakeCustom << 2, + FilterC = FilterA | FilterB +}; +``` +Demonstrates different approaches to creating filters. +
Note that `FilterC` will allow `FilterA`, `FilterB` and `FilterC` connections. + +*** + +## Custom pop-ups +ImNodeFlow supports two pop-up events. +
The pop-up is handled internally, so we only need to handle the content and out custom logic. + +### Right-click pop-up +Triggered when right-clicking on an empty point on the grid. +```c++ +INF.rightClickPopUpContent([]() { + if (ImGui::Selectable("Dummy example")) + { + // My very smart logic + } + // . . . +}); +``` +`rightClickPopUpContent` takes either a function or lambda expression. +
Said function must contain pop-up contents to be displayed and the logic. + +### Dropped link pop-up +Triggered when a link id _dropped_ in an empty point on the grid. And, if specified, the correct key is pressed. +
In this example the pop-up will only opened if the _Shift_ key is being pressed while _dropping_ the link. +```c++ +INF.droppedLinkPopUpContent([](Pin* dragged) { + if (ImGui::Selectable("Dummy example")) + { + // My very smart logic + } + // . . . +}, ImGuiKey_LeftShift); +``` +`droppedLinkPopUpContent` takes either a function or lambda expression. +
Said function must contain pop-up contents to be displayed and the logic. +
An optional key to press can also be specified. + +*** + +_Please refer to the doxygen documentation for a list of public methods and their details_ -## Custom pop-ups \ No newline at end of file +_In case of problems or questions, consider opening an issue._ \ No newline at end of file diff --git a/readme.md b/readme.md index 6383c03..8bc268f 100644 --- a/readme.md +++ b/readme.md @@ -29,7 +29,7 @@ Download the latest ImNodeFlow.zip containing only the necessary files and add t class SimpleSum : public BaseNode { public: - explicit Somma(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) + explicit SimpleSum(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) { addIN("IN_VAL", 0, ConnectionFilter_Int); addOUT("OUT_VAL", ConnectionFilter_Int) From fe1c27705a1fea62acdc995994bde603ac94a3b1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Tue, 30 Jan 2024 02:48:03 +0100 Subject: [PATCH 004/116] Documentation fix-up round --- documentation.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation.md b/documentation.md index f8073b5..938fca0 100644 --- a/documentation.md +++ b/documentation.md @@ -32,7 +32,7 @@ INF.update(); // Update logic and render ``` ## Creating a custom node -Custom nodes must be derived from the class BaseNode. +Custom nodes **must** be derived from the class BaseNode. ```c++ class CustomNode : public BaseNode { @@ -59,9 +59,9 @@ explicit CustomNode(. . .) `addIN` will add an input pin to the node. Usually called in the node's constructor. #### Getting the value ```c++ -int value = ins(0); +int value = ins(n); ``` -Returns a read only reference to the value connected to the first input pin. +Returns a read only reference to the value connected to the nth input pin. ### Adding output pins ```c++ @@ -73,17 +73,17 @@ explicit CustomNode(. . .) `addOUT` will add an output pin to the node. Usually called in the node's constructor. #### Defining logic ```c++ -behaviour([this](){ return 0; }); +behaviour([this](){ return . . .; }); ``` Node's logic for a specific pin. -
Takes either a Lambda of a function. The return value is the output of the pin. In this case, the pin will always output 0. +
Takes either a function or lambda expression. The return value is the output of the pin. #### All together ```c++ addOUT("Pin name", Connection filter) ->behaviour([this](){ return 0; }); ``` -Creates a pin with given name and filter and sets its logic. This pin will be rather useless since it always returns 0 (also known as the author's IQ). - +Creates a pin with given name and filter and sets its logic. +
This pin will be rather useless since it always returns 0 (also known as the author's IQ). ### Node's body ```c++ @@ -93,7 +93,7 @@ Called each frame to draw ImGui widgets inside the node's body.
Can be left empty if the nodes only needs inputs and outputs. ### What have we learned? -To create a custom node, all it's needed is to define input pins, output pins + custom logic, and an optional body +To create a custom node, all it's needed is to define input pins, output pins + custom logic, and an optional body.
Everything else will be handled internally. ```c++ class SimpleSum : public BaseNode @@ -116,13 +116,13 @@ private: }; ``` The example presented in the readme. -
SimpleDum had one input pin of type int called `IN_VAL` and one output pin called `OUT_VAL`. +
SimpleSum has one input pin of type int called `IN_VAL` and one output pin called `OUT_VAL`. Both have their filter set to `int`.
The output pin returns the input + the slider's value.
In the body the slider is rendered. ### Adding nodes to the grid -It's now time to add our beautifully useless note to the grid. +It's now time to add our beautifully useless node to the grid. ```c++ INF.addNode("Node's name", ImVec2(0, 0)); // Add node at canvas coordinates INF.dropNode("Node's name", ImVec2(0, 0)); // Add node at screen coordinates @@ -159,7 +159,7 @@ Demonstrates different approaches to creating filters. ## Custom pop-ups ImNodeFlow supports two pop-up events. -
The pop-up is handled internally, so we only need to handle the content and out custom logic. +
The pop-up state is handled internally, so we only need to handle the content and out custom logic. ### Right-click pop-up Triggered when right-clicking on an empty point on the grid. @@ -167,7 +167,7 @@ Triggered when right-clicking on an empty point on the grid. INF.rightClickPopUpContent([]() { if (ImGui::Selectable("Dummy example")) { - // My very smart logic + // Very smart logic } // . . . }); @@ -182,7 +182,7 @@ Triggered when a link id _dropped_ in an empty point on the grid. And, if specif INF.droppedLinkPopUpContent([](Pin* dragged) { if (ImGui::Selectable("Dummy example")) { - // My very smart logic + // Very smart logic } // . . . }, ImGuiKey_LeftShift); @@ -193,6 +193,6 @@ INF.droppedLinkPopUpContent([](Pin* dragged) { *** -_Please refer to the doxygen documentation for a list of public methods and their details_ +_Please refer to the doxygen documentation for a list of public methods and their details._ _In case of problems or questions, consider opening an issue._ \ No newline at end of file From 159d7d43fd3321c2309347319e6d7ef044d63474 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Tue, 30 Jan 2024 02:49:13 +0100 Subject: [PATCH 005/116] aaaaa another error --- documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation.md b/documentation.md index 938fca0..058a905 100644 --- a/documentation.md +++ b/documentation.md @@ -7,7 +7,7 @@ - [Inputs](#adding-input-pins) - [Outputs](#adding-output-pins) - [Body](#nodes-body) - - [Example](#all-together) + - [Example](#what-have-we-learned) - [Adding it](#adding-nodes-to-the-grid) - [Connection filters 101](#custom-filters) - [Basic filters](#basic-filters) From b197554a3fe95396ad7a37eea5c47bb1110acc29 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 1 Feb 2024 16:01:46 +0100 Subject: [PATCH 006/116] Reordered project structure Getting ready for first release. --- .gitignore | 3 - .idea/.gitignore | 8 - .idea/.name | 1 - .idea/ImNodeFlow.iml | 2 - .idea/misc.xml | 9 - .idea/modules.xml | 8 - .idea/vcs.xml | 7 - CMakeLists.txt | 12 +- conan_provider.cmake | 604 ------------------------------------------- conandata.yml | 7 - conanfile.py | 23 -- documentation.md | 1 + include/ImNodeFlow.h | 27 +- main.cpp | 178 ------------- readme.md | 23 +- src/ImNodeFlow.cpp | 30 +-- src/ImNodeFlow.inl | 31 ++- 17 files changed, 69 insertions(+), 905 deletions(-) delete mode 100644 .gitignore delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/ImNodeFlow.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 conan_provider.cmake delete mode 100644 conandata.yml delete mode 100644 conanfile.py delete mode 100644 main.cpp diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 724bfa2..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/ImGuiHandler/ -/cmake-build-debug/ -/cmake-build-release/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 728b856..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ImNodeFlow_dev \ No newline at end of file diff --git a/.idea/ImNodeFlow.iml b/.idea/ImNodeFlow.iml deleted file mode 100644 index f08604b..0000000 --- a/.idea/ImNodeFlow.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3b5d262..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4218af1..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index ea53a3c..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dc8f33..1f88aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,12 @@ set(CMAKE_CXX_STANDARD 17) # CREATE PROJECT project(ImNodeFlow) +add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) + # SET SOURCE FILES FOR PROJECT file(GLOB_RECURSE _HDRS "include/*.h") file(GLOB_RECURSE _SRCS "src/*.cpp" "src/*.h" "src/*.inl") -# PRE-PROCESSOR VARs -add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) - # CREATE LIBRARY FROM SOURCE_FILES add_library(ImNodeFlow ${_SRCS} ${_HDRS}) @@ -22,10 +21,3 @@ target_link_libraries(ImNodeFlow imgui::imgui) # PREP TO USE "#include <>" target_include_directories(ImNodeFlow PUBLIC include) - -#DEVELOPMENT PROJECT -project(ImNodeFlow_dev) -add_subdirectory(ImGuiHandler) -add_executable(ImNodeFlow_dev main.cpp) -target_link_libraries(ImNodeFlow_dev ImNodeFlow) -target_link_libraries(ImNodeFlow_dev ImGuiHandler) diff --git a/conan_provider.cmake b/conan_provider.cmake deleted file mode 100644 index e6084a4..0000000 --- a/conan_provider.cmake +++ /dev/null @@ -1,604 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -set(CONAN_MINIMUM_VERSION 2.0.5) - - -function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION) - # it could be cross compilation - message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}") - if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(${OS} Macos PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME STREQUAL "QNX") - set(${OS} Neutrino PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") - set(${OS} Windows PARENT_SCOPE) - set(${OS_SUBSYSTEM} cygwin PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME MATCHES "^MSYS") - set(${OS} Windows PARENT_SCOPE) - set(${OS_SUBSYSTEM} msys2 PARENT_SCOPE) - else() - set(${OS} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE) - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Android") - if(DEFINED ANDROID_PLATFORM) - string(REGEX MATCH "[0-9]+" _OS_API_LEVEL ${ANDROID_PLATFORM}) - elseif(DEFINED CMAKE_SYSTEM_VERSION) - set(_OS_API_LEVEL ${CMAKE_SYSTEM_VERSION}) - endif() - message(STATUS "CMake-Conan: android api level=${_OS_API_LEVEL}") - set(${OS_API_LEVEL} ${_OS_API_LEVEL} PARENT_SCOPE) - endif() - if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") - # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja - # generators, but just has the original input string for Xcode. - if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) - set(_OS_SDK ${CMAKE_OSX_SYSROOT}) - else() - if(CMAKE_OSX_SYSROOT MATCHES Simulator) - set(apple_platform_suffix simulator) - else() - set(apple_platform_suffix os) - endif() - if(CMAKE_OSX_SYSROOT MATCHES AppleTV) - set(_OS_SDK "appletv${apple_platform_suffix}") - elseif(CMAKE_OSX_SYSROOT MATCHES iPhone) - set(_OS_SDK "iphone${apple_platform_suffix}") - elseif(CMAKE_OSX_SYSROOT MATCHES Watch) - set(_OS_SDK "watch${apple_platform_suffix}") - endif() - endif() - if(DEFINED _OS_SDK) - message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}") - set(${OS_SDK} ${_OS_SDK} PARENT_SCOPE) - endif() - if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) - message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}") - set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - - -function(detect_arch ARCH) - # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one. - # Therefore this code only finds one. If the recipes support multiple architectures, the - # build will work. Otherwise, there will be a linker error for the missing architecture(s). - if(DEFINED CMAKE_OSX_ARCHITECTURES) - string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}") - list(LENGTH apple_arch_list apple_arch_count) - if(apple_arch_count GREATER 1) - message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") - endif() - endif() - if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") - set(host_arch ${CMAKE_OSX_ARCHITECTURES}) - elseif(MSVC) - set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) - else() - set(host_arch ${CMAKE_SYSTEM_PROCESSOR}) - endif() - if(host_arch MATCHES "aarch64|arm64|ARM64") - set(_ARCH armv8) - elseif(host_arch MATCHES "armv7|armv7-a|armv7l|ARMV7") - set(_ARCH armv7) - elseif(host_arch MATCHES armv7s) - set(_ARCH armv7s) - elseif(host_arch MATCHES "i686|i386|X86") - set(_ARCH x86) - elseif(host_arch MATCHES "AMD64|amd64|x86_64|x64") - set(_ARCH x86_64) - endif() - message(STATUS "CMake-Conan: cmake_system_processor=${_ARCH}") - set(${ARCH} ${_ARCH} PARENT_SCOPE) -endfunction() - - -function(detect_cxx_standard CXX_STANDARD) - set(${CXX_STANDARD} ${CMAKE_CXX_STANDARD} PARENT_SCOPE) - if(CMAKE_CXX_EXTENSIONS) - set(${CXX_STANDARD} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE) - endif() -endfunction() - - -macro(detect_gnu_libstdcxx) - # _CONAN_IS_GNU_LIBSTDCXX true if GNU libstdc++ - check_cxx_source_compiles(" - #include - #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__) - static_assert(false); - #endif - int main(){}" _CONAN_IS_GNU_LIBSTDCXX) - - # _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI true if C++11 ABI - check_cxx_source_compiles(" - #include - static_assert(sizeof(std::string) != sizeof(void*), \"using libstdc++\"); - int main () {}" _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) - - set(_CONAN_GNU_LIBSTDCXX_SUFFIX "") - if(_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) - set(_CONAN_GNU_LIBSTDCXX_SUFFIX "11") - endif() - unset (_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) -endmacro() - - -macro(detect_libcxx) - # _CONAN_IS_LIBCXX true if LLVM libc++ - check_cxx_source_compiles(" - #include - #if !defined(_LIBCPP_VERSION) - static_assert(false); - #endif - int main(){}" _CONAN_IS_LIBCXX) -endmacro() - - -function(detect_lib_cxx LIB_CXX) - if(CMAKE_SYSTEM_NAME STREQUAL "Android") - message(STATUS "CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}") - set(${LIB_CXX} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE) - return() - endif() - - include(CheckCXXSourceCompiles) - - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - detect_gnu_libstdcxx() - set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") - set(${LIB_CXX} "libc++" PARENT_SCOPE) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") - # Check for libc++ - detect_libcxx() - if(_CONAN_IS_LIBCXX) - set(${LIB_CXX} "libc++" PARENT_SCOPE) - return() - endif() - - # Check for libstdc++ - detect_gnu_libstdcxx() - if(_CONAN_IS_GNU_LIBSTDCXX) - set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) - return() - endif() - - # TODO: it would be an error if we reach this point - elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - # Do nothing - compiler.runtime and compiler.runtime_type - # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516 - return() - else() - # TODO: unable to determine, ask user to provide a full profile file instead - endif() -endfunction() - - -function(detect_compiler COMPILER COMPILER_VERSION COMPILER_RUNTIME COMPILER_RUNTIME_TYPE) - if(DEFINED CMAKE_CXX_COMPILER_ID) - set(_COMPILER ${CMAKE_CXX_COMPILER_ID}) - set(_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) - else() - if(NOT DEFINED CMAKE_C_COMPILER_ID) - message(FATAL_ERROR "C or C++ compiler not defined") - endif() - set(_COMPILER ${CMAKE_C_COMPILER_ID}) - set(_COMPILER_VERSION ${CMAKE_C_COMPILER_VERSION}) - endif() - - message(STATUS "CMake-Conan: CMake compiler=${_COMPILER}") - message(STATUS "CMake-Conan: CMake compiler version=${_COMPILER_VERSION}") - - if(_COMPILER MATCHES MSVC) - set(_COMPILER "msvc") - string(SUBSTRING ${MSVC_VERSION} 0 3 _COMPILER_VERSION) - # Configure compiler.runtime and compiler.runtime_type settings for MSVC - if(CMAKE_MSVC_RUNTIME_LIBRARY) - set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY}) - else() - set(_msvc_runtime_library MultiThreaded$<$:Debug>DLL) # default value documented by CMake - endif() - - set(_KNOWN_MSVC_RUNTIME_VALUES "") - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL) - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL) - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$:Debug> MultiThreaded$<$:Debug>DLL) - - # only accept the 6 possible values, otherwise we don't don't know to map this - if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES) - message(FATAL_ERROR "CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings") - endif() - - # Runtime is "dynamic" in all cases if it ends in DLL - if(_msvc_runtime_library MATCHES ".*DLL$") - set(_COMPILER_RUNTIME "dynamic") - else() - set(_COMPILER_RUNTIME "static") - endif() - message(STATUS "CMake-Conan: CMake compiler.runtime=${_COMPILER_RUNTIME}") - - # Only define compiler.runtime_type when explicitly requested - # If a generator expression is used, let Conan handle it conditional on build_type - if(NOT _msvc_runtime_library MATCHES ":Debug>") - if(_msvc_runtime_library MATCHES "Debug") - set(_COMPILER_RUNTIME_TYPE "Debug") - else() - set(_COMPILER_RUNTIME_TYPE "Release") - endif() - message(STATUS "CMake-Conan: CMake compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") - endif() - - unset(_KNOWN_MSVC_RUNTIME_VALUES) - - elseif(_COMPILER MATCHES AppleClang) - set(_COMPILER "apple-clang") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - elseif(_COMPILER MATCHES Clang) - set(_COMPILER "clang") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - elseif(_COMPILER MATCHES GNU) - set(_COMPILER "gcc") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - endif() - - message(STATUS "CMake-Conan: [settings] compiler=${_COMPILER}") - message(STATUS "CMake-Conan: [settings] compiler.version=${_COMPILER_VERSION}") - if (_COMPILER_RUNTIME) - message(STATUS "CMake-Conan: [settings] compiler.runtime=${_COMPILER_RUNTIME}") - endif() - if (_COMPILER_RUNTIME_TYPE) - message(STATUS "CMake-Conan: [settings] compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") - endif() - - set(${COMPILER} ${_COMPILER} PARENT_SCOPE) - set(${COMPILER_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) - set(${COMPILER_RUNTIME} ${_COMPILER_RUNTIME} PARENT_SCOPE) - set(${COMPILER_RUNTIME_TYPE} ${_COMPILER_RUNTIME_TYPE} PARENT_SCOPE) -endfunction() - - -function(detect_build_type BUILD_TYPE) - get_property(_MULTICONFIG_GENERATOR GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(NOT _MULTICONFIG_GENERATOR) - # Only set when we know we are in a single-configuration generator - # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined - set(${BUILD_TYPE} ${CMAKE_BUILD_TYPE} PARENT_SCOPE) - endif() -endfunction() - -macro(set_conan_compiler_if_appleclang lang command output_variable) - if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") - execute_process(COMMAND xcrun --find ${command} - OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_xcrun_out STREQUAL "${CMAKE_${lang}_COMPILER}") - set(${output_variable} "") - endif() - unset(_xcrun_out) - endif() -endmacro() - - -macro(append_compiler_executables_configuration) - set(_conan_c_compiler "") - set(_conan_cpp_compiler "") - if(CMAKE_C_COMPILER) - set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\",") - set_conan_compiler_if_appleclang(C cc _conan_c_compiler) - else() - message(WARNING "CMake-Conan: The C compiler is not defined. " - "Please define CMAKE_C_COMPILER or enable the C language.") - endif() - if(CMAKE_CXX_COMPILER) - set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") - set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) - else() - message(WARNING "CMake-Conan: The C++ compiler is not defined. " - "Please define CMAKE_CXX_COMPILER or enable the C++ language.") - endif() - - if(NOT "x${_conan_c_compiler}${_conan_cpp_compiler}" STREQUAL "x") - string(APPEND PROFILE "tools.build:compiler_executables={${_conan_c_compiler}${_conan_cpp_compiler}}\n") - endif() - unset(_conan_c_compiler) - unset(_conan_cpp_compiler) -endmacro() - - -function(detect_host_profile output_file) - detect_os(MYOS MYOS_API_LEVEL MYOS_SDK MYOS_SUBSYSTEM MYOS_VERSION) - detect_arch(MYARCH) - detect_compiler(MYCOMPILER MYCOMPILER_VERSION MYCOMPILER_RUNTIME MYCOMPILER_RUNTIME_TYPE) - detect_cxx_standard(MYCXX_STANDARD) - detect_lib_cxx(MYLIB_CXX) - detect_build_type(MYBUILD_TYPE) - - set(PROFILE "") - string(APPEND PROFILE "[settings]\n") - if(MYARCH) - string(APPEND PROFILE arch=${MYARCH} "\n") - endif() - if(MYOS) - string(APPEND PROFILE os=${MYOS} "\n") - endif() - if(MYOS_API_LEVEL) - string(APPEND PROFILE os.api_level=${MYOS_API_LEVEL} "\n") - endif() - if(MYOS_VERSION) - string(APPEND PROFILE os.version=${MYOS_VERSION} "\n") - endif() - if(MYOS_SDK) - string(APPEND PROFILE os.sdk=${MYOS_SDK} "\n") - endif() - if(MYOS_SUBSYSTEM) - string(APPEND PROFILE os.subsystem=${MYOS_SUBSYSTEM} "\n") - endif() - if(MYCOMPILER) - string(APPEND PROFILE compiler=${MYCOMPILER} "\n") - endif() - if(MYCOMPILER_VERSION) - string(APPEND PROFILE compiler.version=${MYCOMPILER_VERSION} "\n") - endif() - if(MYCOMPILER_RUNTIME) - string(APPEND PROFILE compiler.runtime=${MYCOMPILER_RUNTIME} "\n") - endif() - if(MYCOMPILER_RUNTIME_TYPE) - string(APPEND PROFILE compiler.runtime_type=${MYCOMPILER_RUNTIME_TYPE} "\n") - endif() - if(MYCXX_STANDARD) - string(APPEND PROFILE compiler.cppstd=${MYCXX_STANDARD} "\n") - endif() - if(MYLIB_CXX) - string(APPEND PROFILE compiler.libcxx=${MYLIB_CXX} "\n") - endif() - if(MYBUILD_TYPE) - string(APPEND PROFILE "build_type=${MYBUILD_TYPE}\n") - endif() - - if(NOT DEFINED output_file) - set(_FN "${CMAKE_BINARY_DIR}/profile") - else() - set(_FN ${output_file}) - endif() - - string(APPEND PROFILE "[conf]\n") - string(APPEND PROFILE "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n") - - # propagate compilers via profile - append_compiler_executables_configuration() - - if(MYOS STREQUAL "Android") - string(APPEND PROFILE "tools.android:ndk_path=${CMAKE_ANDROID_NDK}\n") - endif() - - message(STATUS "CMake-Conan: Creating profile ${_FN}") - file(WRITE ${_FN} ${PROFILE}) - message(STATUS "CMake-Conan: Profile: \n${PROFILE}") -endfunction() - - -function(conan_profile_detect_default) - message(STATUS "CMake-Conan: Checking if a default profile exists") - execute_process(COMMAND ${CONAN_COMMAND} profile path default - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - ECHO_OUTPUT_VARIABLE - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(NOT ${return_code} EQUAL "0") - message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") - execute_process(COMMAND ${CONAN_COMMAND} profile detect - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - ECHO_OUTPUT_VARIABLE - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - endif() -endfunction() - - -function(conan_install) - cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN}) - set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan) - # Invoke "conan install" with the provided arguments - set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER}) - message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") - execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN} --format=json - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(NOT "${return_code}" STREQUAL "0") - message(FATAL_ERROR "Conan install failed='${return_code}'") - else() - # the files are generated in a folder that depends on the layout used, if - # one is specified, but we don't know a priori where this is. - # TODO: this can be made more robust if Conan can provide this in the json output - string(JSON CONAN_GENERATORS_FOLDER GET ${conan_stdout} graph nodes 0 generators_folder) - cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) - # message("conan stdout: ${conan_stdout}") - message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") - set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") - # reconfigure on conanfile changes - string(JSON CONANFILE GET ${conan_stdout} graph nodes 0 label) - message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") - set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") - # success - set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) - endif() -endfunction() - - -function(conan_get_version conan_command conan_current_version) - execute_process( - COMMAND ${conan_command} --version - OUTPUT_VARIABLE conan_output - RESULT_VARIABLE conan_result - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(conan_result) - message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") - endif() - - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) - set(${conan_current_version} ${conan_version} PARENT_SCOPE) -endfunction() - - -function(conan_version_check) - set(options ) - set(oneValueArgs MINIMUM CURRENT) - set(multiValueArgs ) - cmake_parse_arguments(CONAN_VERSION_CHECK - "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT CONAN_VERSION_CHECK_MINIMUM) - message(FATAL_ERROR "CMake-Conan: Required parameter MINIMUM not set!") - endif() - if(NOT CONAN_VERSION_CHECK_CURRENT) - message(FATAL_ERROR "CMake-Conan: Required parameter CURRENT not set!") - endif() - - if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM) - message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") - endif() -endfunction() - - -macro(construct_profile_argument argument_variable profile_list) - set(${argument_variable} "") - if("${profile_list}" STREQUAL "CONAN_HOST_PROFILE") - set(_arg_flag "--profile:host=") - elseif("${profile_list}" STREQUAL "CONAN_BUILD_PROFILE") - set(_arg_flag "--profile:build=") - endif() - - set(_profile_list "${${profile_list}}") - list(TRANSFORM _profile_list REPLACE "auto-cmake" "${CMAKE_BINARY_DIR}/conan_host_profile") - list(TRANSFORM _profile_list PREPEND ${_arg_flag}) - set(${argument_variable} ${_profile_list}) - - unset(_arg_flag) - unset(_profile_list) -endmacro() - - -macro(conan_provide_dependency method package_name) - set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) - get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) - if(NOT _conan_install_success) - find_program(CONAN_COMMAND "conan" REQUIRED) - conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) - conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) - message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") - if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) - conan_profile_detect_default() - endif() - if("auto-cmake" IN_LIST CONAN_HOST_PROFILE) - detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile) - endif() - construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) - construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) - if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") - file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) - if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") - message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile") - endif() - set(generator "") - elseif (EXISTS "${CMAKE_SOURCE_DIR}/conanfile.txt") - file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) - if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") - message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " - "Please define the generator as it will be mandatory in the future") - endif() - set(generator "-g;CMakeDeps") - endif() - get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(NOT _multiconfig_generator) - message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") - conan_install(${_host_profile_flags} ${_build_profile_flags} --build=missing ${generator}) - else() - message(STATUS "CMake-Conan: Installing both Debug and Release") - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release --build=missing ${generator}) - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug --build=missing ${generator}) - endif() - unset(_host_profile_flags) - unset(_build_profile_flags) - unset(_multiconfig_generator) - unset(_conan_install_success) - else() - message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran") - unset(_conan_install_success) - endif() - - get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER) - - # Ensure that we consider Conan-provided packages ahead of any other, - # irrespective of other settings that modify the search order or search paths - # This follows the guidelines from the find_package documentation - # (https://cmake.org/cmake/help/latest/command/find_package.html): - # find_package ( PATHS paths... NO_DEFAULT_PATH) - # find_package () - - # Filter out `REQUIRED` from the argument list, as the first call may fail - set(_find_args_${package_name} "${ARGN}") - list(REMOVE_ITEM _find_args_${package_name} "REQUIRED") - if(NOT "MODULE" IN_LIST _find_args_${package_name}) - find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS "${_conan_generators_folder}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) - unset(_find_args_${package_name}) - endif() - - # Invoke find_package a second time - if the first call succeeded, - # this will simply reuse the result. If not, fall back to CMake default search - # behaviour, also allowing modules to be searched. - if(NOT ${package_name}_FOUND) - list(FIND CMAKE_MODULE_PATH "${_conan_generators_folder}" _index) - if(_index EQUAL -1) - list(PREPEND CMAKE_MODULE_PATH "${_conan_generators_folder}") - endif() - unset(_index) - find_package(${package_name} ${ARGN} BYPASS_PROVIDER) - list(REMOVE_ITEM CMAKE_MODULE_PATH "${_conan_generators_folder}") - endif() -endmacro() - - -cmake_language( - SET_DEPENDENCY_PROVIDER conan_provide_dependency - SUPPORTED_METHODS FIND_PACKAGE -) - - -macro(conan_provide_dependency_check) - set(_CONAN_PROVIDE_DEPENDENCY_INVOKED FALSE) - get_property(_CONAN_PROVIDE_DEPENDENCY_INVOKED GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) - if(NOT _CONAN_PROVIDE_DEPENDENCY_INVOKED) - message(WARNING "Conan is correctly configured as dependency provider, " - "but Conan has not been invoked. Please add at least one " - "call to `find_package()`.") - if(DEFINED CONAN_COMMAND) - # supress warning in case `CONAN_COMMAND` was specified but unused. - set(_CONAN_COMMAND ${CONAN_COMMAND}) - unset(_CONAN_COMMAND) - endif() - endif() - unset(_CONAN_PROVIDE_DEPENDENCY_INVOKED) -endmacro() - - -# Add a deferred call at the end of processing the top-level directory -# to check if the dependency provider was invoked at all. -cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) - -# Configurable variables for Conan profiles -set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") -set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") diff --git a/conandata.yml b/conandata.yml deleted file mode 100644 index bbbe0d8..0000000 --- a/conandata.yml +++ /dev/null @@ -1,7 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -requirements: - - "glfw/3.3.8" - - "opengl/system" - - "imgui/1.90" \ No newline at end of file diff --git a/conanfile.py b/conanfile.py deleted file mode 100644 index b89a080..0000000 --- a/conanfile.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -from conan import ConanFile -from conan.tools.cmake import cmake_layout, CMakeToolchain - -class ConanApplication(ConanFile): - package_type = "application" - settings = "os", "compiler", "build_type", "arch" - generators = "CMakeDeps" - - def layout(self): - cmake_layout(self) - - def generate(self): - tc = CMakeToolchain(self) - tc.user_presets_path = False - tc.generate() - - def requirements(self): - requirements = self.conan_data.get('requirements', []) - for requirement in requirements: - self.requires(requirement) \ No newline at end of file diff --git a/documentation.md b/documentation.md index 058a905..a18d0c0 100644 --- a/documentation.md +++ b/documentation.md @@ -20,6 +20,7 @@ After having included the necessary files into the project. A few simple steps are necessary. ```c++ #include // Include dependencies +using namespace ImFlow; ``` ```c++ ImNodeFlow INF; // Create an editor with default name diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7c798be..2692b65 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -12,6 +12,9 @@ #include #include "../src/imgui_bezier_math.h" +// TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping +// TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) + namespace ImFlow { // ----------------------------------------------------------------------------------------------------------------- @@ -240,14 +243,8 @@ namespace ImFlow */ int nodesCount() { return (int)m_nodes.size(); } - /** - * @brief Creates a link between two pins - * @details Creates a link. Will check for same node connections, IN to IN or OUT to OUT connections and evaluate the filters. - * Then the link will be created and the input pin of the two will own the link. - * @param start Pointer to the start pin to be connected - * @param end Pointer to the end pin to be connected - */ - void createLink(Pin* start, Pin* end); + + void addLink(std::shared_ptr& link); /** * @brief Pop-up when link is "dropped" @@ -562,9 +559,9 @@ namespace ImFlow /** * @brief Create link between pins - * @param left Pointer to the other pin + * @param other Pointer to the other pin */ - virtual void createLink(Pin* left) {} + virtual void createLink(Pin* other) = 0; /** * @brief Sets the reference to a link @@ -673,9 +670,9 @@ namespace ImFlow /** * @brief Create link between pins - * @param left Pointer to the other pin + * @param other Pointer to the other pin */ - void createLink(Pin* left) override; + void createLink(Pin* other) override; /** * @brief Deletes the link from pin @@ -733,6 +730,12 @@ namespace ImFlow */ void update() override; + /** + * @brief Create link between pins + * @param other Pointer to the other pin + */ + void createLink(Pin* other) override; + /** * @brief Sets the reference to a link * @param link Pointer to the link diff --git a/main.cpp b/main.cpp deleted file mode 100644 index c5116cf..0000000 --- a/main.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace ImFlow; - -ImNodeFlow INF; - -class AB : public BaseNode -{ -public: - explicit AB(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("intouno", 0, ConnectionFilter_Int); - addOUT("int", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_slider; }); - addOUT("dummy", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_slider * 2; }); - } - - void draw() override - { - ImGui::Text("ASDDJHGFDSA"); - ImGui::SetNextItemWidth(120.0f); - ImGui::SliderInt("##SLSLSL", &m_slider, 0, 200); - } -private: - int m_slider = 0; -}; - -class Somma : public BaseNode -{ -public: - explicit Somma(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("A", 0, ConnectionFilter_Int); - addIN("B", 0, ConnectionFilter_Int); - addOUT("C", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + ins(1); }); - } - - void draw() override - { - ImGui::Text("A + B = C"); - } -private: -}; - -class CD : public BaseNode -{ -public: - explicit CD(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addOUT("str_out", ConnectionFilter_String) - ->behaviour([this](){ return m_ss; }); - } - - void draw() override - { - ImGui::Text("String Spitter"); - ImGui::PushItemWidth(120.0f); - ImGui::InputText("##ToSpit", &m_ss); - } -private: - std::string m_ss; -}; - -class Pri : public BaseNode -{ -public: - explicit Pri(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("INT", 0, ConnectionFilter_Int); - } - - void draw() override - { - ImGui::Text("%d", ins(0)); - } -private: -}; - -class StrPri : public BaseNode -{ -public: - explicit StrPri(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("Str", "Not Connected", ConnectionFilter_String); - } - - void draw() override - { - ImGui::Text("%s", ins(0).c_str()); - } -private: -}; - -class DemoWindow : public appLayer -{ -public: - void update() override - { - ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver); - ImGui::Begin("FlowGrid Test"); - INF.update(); - ImGui::End(); - } -}; - -// TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping -// TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) - -void foo(Pin* dragged) -{ - if (dragged->kind() == PinKind_Output) - { - if (ImGui::Selectable("Sommatore")) - { - auto n = INF.dropNode("Sommatore", ImGui::GetWindowPos()); - INF.createLink(dragged, n->ins(0)); - } - } - else - { - if (ImGui::Selectable("Sommatore")) - { - auto n = INF.dropNode("Sommatore", ImGui::GetWindowPos()); - INF.createLink(n->outs(0), dragged); - } - if (ImGui::Selectable("AB thingy")) - { - auto n = INF.dropNode("AB", ImGui::GetWindowPos()); - INF.createLink(n->outs(0), dragged); - } - } -} - -int main() -{ - if (!IGH.init("Example", 1300, 800)) - return 1; - - ImGui::GetIO().IniFilename = nullptr; - - IGH.pushLayer(); - IGH.setActiveWin(0); - - INF.addNode("AA", ImVec2(0, 0)); - INF.addNode("BB", ImVec2(0, 100)); - INF.addNode("Printer ONE", ImVec2(500, 0)); - INF.addNode("Printer TWO", ImVec2(500, 100)); - INF.addNode("Printer THREE", ImVec2(500, 200)); - - INF.addNode("Sommatore", ImVec2(100, 100)); - - INF.addNode("CC", ImVec2(0, 100)); - INF.addNode("String Printer", ImVec2(500, 300)); - - INF.rightClickPopUpContent([]() { - if (ImGui::Selectable("AB")) - { - printf_s("AOOOO!\n"); - } - }); - INF.droppedLinkPopUpContent(foo, ImGuiKey_LeftShift); - - bool done = false; - while (!done) - { - IGH.loop(&done); - } - - IGH.end(); - return 0; -} diff --git a/readme.md b/readme.md index 8bc268f..ab9103f 100644 --- a/readme.md +++ b/readme.md @@ -14,15 +14,32 @@ https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26- - Appearance 100% customizable ## Implementation (CMake project) +### CMake `FetchContent` +1. Add the following lines to your CMakeLists.txt: + ``` + include(FetchContent) + FetchContent_Declare(ImNodeFlow + GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" + GIT_TAG "origin/master" + SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" + ) + FetchContent_MakeAvailable(ImNodeFlow) + ``` + ``` + target_link_libraries(YourProject ImNodeFlow) + ``` +2. Make sure you have the following dependencies available for `find_package()`: + - [Dear ImGui](https://github.com/ocornut/imgui) + +### Manually 1. Download and copy, or clone the repo inside your project 2. Add the following lines to your CMakeLists.txt: ``` add_subdirectory(path/to/ImNodeFlow) target_link_libraries(YourProject ImNodeFlow) ``` - -### Alternative -Download the latest ImNodeFlow.zip containing only the necessary files and add them manually. +3. Make sure you have the following dependencies available for `find_package()`: + - [Dear ImGui](https://github.com/ocornut/imgui) ## Simple Node example ```c++ diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 87ea176..7e926d5 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -177,33 +177,9 @@ namespace ImFlow return p - m_pos - m_scroll; } - void ImNodeFlow::createLink(Pin* start, Pin* end) + void ImNodeFlow::addLink(std::shared_ptr& link) { - if (start->parent() == end->parent()) - return; - if (!((start->filter() & end->filter()) != 0 || start->filter() == ConnectionFilter_None || end->filter() == ConnectionFilter_None)) // Check Filter - return; - - if (start->kind() == PinKind_Output && end->kind() == PinKind_Input) // OUT to IN - { - if (end->getLink().expired() || end->getLink().lock()->left() != start) - { - end->createLink(start); - m_links.emplace_back(end->getLink()); - } - else - end->deleteLink(); - } - if (start->kind() == PinKind_Input && end->kind() == PinKind_Output) // IN to OUT - { - if (start->getLink().expired() || start->getLink().lock()->left() != end) - { - start->createLink(end); - m_links.emplace_back(start->getLink()); - } - else - start->deleteLink(); - } + m_links.push_back(link); } void ImNodeFlow::update() @@ -261,7 +237,7 @@ namespace ImFlow } } else - createLink(m_dragOut, m_hovering); + m_dragOut->createLink(m_hovering); } // Links drag-out diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 514d09e..55aca17 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -99,10 +99,26 @@ namespace ImFlow } template - void InPin::createLink(Pin *left) + void InPin::createLink(Pin *other) { - m_link = std::make_shared(left, this, m_inf); - left->setLink(m_link); + if (other == this || m_parent == other->parent()) + return; + + if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter + return; + + if (other->kind() == PinKind_Input) + return; + + if (m_link && m_link->left() == other) + { + m_link.reset(); + return; + } + + m_link = std::make_shared(other, this, m_inf); + other->setLink(m_link); + m_inf->addLink(m_link); } // ----------------------------------------------------------------------------------------------------------------- @@ -130,4 +146,13 @@ namespace ImFlow if (ImGui::IsItemHovered()) m_inf->hovering(this); } + + template + void OutPin::createLink(ImFlow::Pin *other) + { + if (other == this) + return; + + other->createLink(this); + } } From 738d3ff83b0540b3aff31f3f473ff7f940d7cf35 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 1 Feb 2024 16:01:46 +0100 Subject: [PATCH 007/116] Reordered project structure Getting ready for first release. --- .gitignore | 3 - .idea/.gitignore | 8 - .idea/.name | 1 - .idea/ImNodeFlow.iml | 2 - .idea/misc.xml | 9 - .idea/modules.xml | 8 - .idea/vcs.xml | 7 - CMakeLists.txt | 12 +- conan_provider.cmake | 604 ------------------------------------------- conandata.yml | 7 - conanfile.py | 23 -- documentation.md | 1 + include/ImNodeFlow.h | 27 +- main.cpp | 178 ------------- readme.md | 28 +- src/ImNodeFlow.cpp | 30 +-- src/ImNodeFlow.inl | 31 ++- 17 files changed, 73 insertions(+), 906 deletions(-) delete mode 100644 .gitignore delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/ImNodeFlow.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 conan_provider.cmake delete mode 100644 conandata.yml delete mode 100644 conanfile.py delete mode 100644 main.cpp diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 724bfa2..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/ImGuiHandler/ -/cmake-build-debug/ -/cmake-build-release/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 728b856..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ImNodeFlow_dev \ No newline at end of file diff --git a/.idea/ImNodeFlow.iml b/.idea/ImNodeFlow.iml deleted file mode 100644 index f08604b..0000000 --- a/.idea/ImNodeFlow.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 3b5d262..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4218af1..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index ea53a3c..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dc8f33..1f88aba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,13 +4,12 @@ set(CMAKE_CXX_STANDARD 17) # CREATE PROJECT project(ImNodeFlow) +add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) + # SET SOURCE FILES FOR PROJECT file(GLOB_RECURSE _HDRS "include/*.h") file(GLOB_RECURSE _SRCS "src/*.cpp" "src/*.h" "src/*.inl") -# PRE-PROCESSOR VARs -add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) - # CREATE LIBRARY FROM SOURCE_FILES add_library(ImNodeFlow ${_SRCS} ${_HDRS}) @@ -22,10 +21,3 @@ target_link_libraries(ImNodeFlow imgui::imgui) # PREP TO USE "#include <>" target_include_directories(ImNodeFlow PUBLIC include) - -#DEVELOPMENT PROJECT -project(ImNodeFlow_dev) -add_subdirectory(ImGuiHandler) -add_executable(ImNodeFlow_dev main.cpp) -target_link_libraries(ImNodeFlow_dev ImNodeFlow) -target_link_libraries(ImNodeFlow_dev ImGuiHandler) diff --git a/conan_provider.cmake b/conan_provider.cmake deleted file mode 100644 index e6084a4..0000000 --- a/conan_provider.cmake +++ /dev/null @@ -1,604 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -set(CONAN_MINIMUM_VERSION 2.0.5) - - -function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION) - # it could be cross compilation - message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}") - if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(${OS} Macos PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME STREQUAL "QNX") - set(${OS} Neutrino PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") - set(${OS} Windows PARENT_SCOPE) - set(${OS_SUBSYSTEM} cygwin PARENT_SCOPE) - elseif(CMAKE_SYSTEM_NAME MATCHES "^MSYS") - set(${OS} Windows PARENT_SCOPE) - set(${OS_SUBSYSTEM} msys2 PARENT_SCOPE) - else() - set(${OS} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE) - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Android") - if(DEFINED ANDROID_PLATFORM) - string(REGEX MATCH "[0-9]+" _OS_API_LEVEL ${ANDROID_PLATFORM}) - elseif(DEFINED CMAKE_SYSTEM_VERSION) - set(_OS_API_LEVEL ${CMAKE_SYSTEM_VERSION}) - endif() - message(STATUS "CMake-Conan: android api level=${_OS_API_LEVEL}") - set(${OS_API_LEVEL} ${_OS_API_LEVEL} PARENT_SCOPE) - endif() - if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") - # CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja - # generators, but just has the original input string for Xcode. - if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT}) - set(_OS_SDK ${CMAKE_OSX_SYSROOT}) - else() - if(CMAKE_OSX_SYSROOT MATCHES Simulator) - set(apple_platform_suffix simulator) - else() - set(apple_platform_suffix os) - endif() - if(CMAKE_OSX_SYSROOT MATCHES AppleTV) - set(_OS_SDK "appletv${apple_platform_suffix}") - elseif(CMAKE_OSX_SYSROOT MATCHES iPhone) - set(_OS_SDK "iphone${apple_platform_suffix}") - elseif(CMAKE_OSX_SYSROOT MATCHES Watch) - set(_OS_SDK "watch${apple_platform_suffix}") - endif() - endif() - if(DEFINED _OS_SDK) - message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}") - set(${OS_SDK} ${_OS_SDK} PARENT_SCOPE) - endif() - if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) - message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}") - set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE) - endif() - endif() - endif() -endfunction() - - -function(detect_arch ARCH) - # CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one. - # Therefore this code only finds one. If the recipes support multiple architectures, the - # build will work. Otherwise, there will be a linker error for the missing architecture(s). - if(DEFINED CMAKE_OSX_ARCHITECTURES) - string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}") - list(LENGTH apple_arch_list apple_arch_count) - if(apple_arch_count GREATER 1) - message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") - endif() - endif() - if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") - set(host_arch ${CMAKE_OSX_ARCHITECTURES}) - elseif(MSVC) - set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) - else() - set(host_arch ${CMAKE_SYSTEM_PROCESSOR}) - endif() - if(host_arch MATCHES "aarch64|arm64|ARM64") - set(_ARCH armv8) - elseif(host_arch MATCHES "armv7|armv7-a|armv7l|ARMV7") - set(_ARCH armv7) - elseif(host_arch MATCHES armv7s) - set(_ARCH armv7s) - elseif(host_arch MATCHES "i686|i386|X86") - set(_ARCH x86) - elseif(host_arch MATCHES "AMD64|amd64|x86_64|x64") - set(_ARCH x86_64) - endif() - message(STATUS "CMake-Conan: cmake_system_processor=${_ARCH}") - set(${ARCH} ${_ARCH} PARENT_SCOPE) -endfunction() - - -function(detect_cxx_standard CXX_STANDARD) - set(${CXX_STANDARD} ${CMAKE_CXX_STANDARD} PARENT_SCOPE) - if(CMAKE_CXX_EXTENSIONS) - set(${CXX_STANDARD} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE) - endif() -endfunction() - - -macro(detect_gnu_libstdcxx) - # _CONAN_IS_GNU_LIBSTDCXX true if GNU libstdc++ - check_cxx_source_compiles(" - #include - #if !defined(__GLIBCXX__) && !defined(__GLIBCPP__) - static_assert(false); - #endif - int main(){}" _CONAN_IS_GNU_LIBSTDCXX) - - # _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI true if C++11 ABI - check_cxx_source_compiles(" - #include - static_assert(sizeof(std::string) != sizeof(void*), \"using libstdc++\"); - int main () {}" _CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) - - set(_CONAN_GNU_LIBSTDCXX_SUFFIX "") - if(_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) - set(_CONAN_GNU_LIBSTDCXX_SUFFIX "11") - endif() - unset (_CONAN_GNU_LIBSTDCXX_IS_CXX11_ABI) -endmacro() - - -macro(detect_libcxx) - # _CONAN_IS_LIBCXX true if LLVM libc++ - check_cxx_source_compiles(" - #include - #if !defined(_LIBCPP_VERSION) - static_assert(false); - #endif - int main(){}" _CONAN_IS_LIBCXX) -endmacro() - - -function(detect_lib_cxx LIB_CXX) - if(CMAKE_SYSTEM_NAME STREQUAL "Android") - message(STATUS "CMake-Conan: android_stl=${CMAKE_ANDROID_STL_TYPE}") - set(${LIB_CXX} ${CMAKE_ANDROID_STL_TYPE} PARENT_SCOPE) - return() - endif() - - include(CheckCXXSourceCompiles) - - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - detect_gnu_libstdcxx() - set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") - set(${LIB_CXX} "libc++" PARENT_SCOPE) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") - # Check for libc++ - detect_libcxx() - if(_CONAN_IS_LIBCXX) - set(${LIB_CXX} "libc++" PARENT_SCOPE) - return() - endif() - - # Check for libstdc++ - detect_gnu_libstdcxx() - if(_CONAN_IS_GNU_LIBSTDCXX) - set(${LIB_CXX} "libstdc++${_CONAN_GNU_LIBSTDCXX_SUFFIX}" PARENT_SCOPE) - return() - endif() - - # TODO: it would be an error if we reach this point - elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - # Do nothing - compiler.runtime and compiler.runtime_type - # should be handled separately: https://github.com/conan-io/cmake-conan/pull/516 - return() - else() - # TODO: unable to determine, ask user to provide a full profile file instead - endif() -endfunction() - - -function(detect_compiler COMPILER COMPILER_VERSION COMPILER_RUNTIME COMPILER_RUNTIME_TYPE) - if(DEFINED CMAKE_CXX_COMPILER_ID) - set(_COMPILER ${CMAKE_CXX_COMPILER_ID}) - set(_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) - else() - if(NOT DEFINED CMAKE_C_COMPILER_ID) - message(FATAL_ERROR "C or C++ compiler not defined") - endif() - set(_COMPILER ${CMAKE_C_COMPILER_ID}) - set(_COMPILER_VERSION ${CMAKE_C_COMPILER_VERSION}) - endif() - - message(STATUS "CMake-Conan: CMake compiler=${_COMPILER}") - message(STATUS "CMake-Conan: CMake compiler version=${_COMPILER_VERSION}") - - if(_COMPILER MATCHES MSVC) - set(_COMPILER "msvc") - string(SUBSTRING ${MSVC_VERSION} 0 3 _COMPILER_VERSION) - # Configure compiler.runtime and compiler.runtime_type settings for MSVC - if(CMAKE_MSVC_RUNTIME_LIBRARY) - set(_msvc_runtime_library ${CMAKE_MSVC_RUNTIME_LIBRARY}) - else() - set(_msvc_runtime_library MultiThreaded$<$:Debug>DLL) # default value documented by CMake - endif() - - set(_KNOWN_MSVC_RUNTIME_VALUES "") - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded MultiThreadedDLL) - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreadedDebug MultiThreadedDebugDLL) - list(APPEND _KNOWN_MSVC_RUNTIME_VALUES MultiThreaded$<$:Debug> MultiThreaded$<$:Debug>DLL) - - # only accept the 6 possible values, otherwise we don't don't know to map this - if(NOT _msvc_runtime_library IN_LIST _KNOWN_MSVC_RUNTIME_VALUES) - message(FATAL_ERROR "CMake-Conan: unable to map MSVC runtime: ${_msvc_runtime_library} to Conan settings") - endif() - - # Runtime is "dynamic" in all cases if it ends in DLL - if(_msvc_runtime_library MATCHES ".*DLL$") - set(_COMPILER_RUNTIME "dynamic") - else() - set(_COMPILER_RUNTIME "static") - endif() - message(STATUS "CMake-Conan: CMake compiler.runtime=${_COMPILER_RUNTIME}") - - # Only define compiler.runtime_type when explicitly requested - # If a generator expression is used, let Conan handle it conditional on build_type - if(NOT _msvc_runtime_library MATCHES ":Debug>") - if(_msvc_runtime_library MATCHES "Debug") - set(_COMPILER_RUNTIME_TYPE "Debug") - else() - set(_COMPILER_RUNTIME_TYPE "Release") - endif() - message(STATUS "CMake-Conan: CMake compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") - endif() - - unset(_KNOWN_MSVC_RUNTIME_VALUES) - - elseif(_COMPILER MATCHES AppleClang) - set(_COMPILER "apple-clang") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - elseif(_COMPILER MATCHES Clang) - set(_COMPILER "clang") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - elseif(_COMPILER MATCHES GNU) - set(_COMPILER "gcc") - string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION}) - list(GET VERSION_LIST 0 _COMPILER_VERSION) - endif() - - message(STATUS "CMake-Conan: [settings] compiler=${_COMPILER}") - message(STATUS "CMake-Conan: [settings] compiler.version=${_COMPILER_VERSION}") - if (_COMPILER_RUNTIME) - message(STATUS "CMake-Conan: [settings] compiler.runtime=${_COMPILER_RUNTIME}") - endif() - if (_COMPILER_RUNTIME_TYPE) - message(STATUS "CMake-Conan: [settings] compiler.runtime_type=${_COMPILER_RUNTIME_TYPE}") - endif() - - set(${COMPILER} ${_COMPILER} PARENT_SCOPE) - set(${COMPILER_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) - set(${COMPILER_RUNTIME} ${_COMPILER_RUNTIME} PARENT_SCOPE) - set(${COMPILER_RUNTIME_TYPE} ${_COMPILER_RUNTIME_TYPE} PARENT_SCOPE) -endfunction() - - -function(detect_build_type BUILD_TYPE) - get_property(_MULTICONFIG_GENERATOR GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(NOT _MULTICONFIG_GENERATOR) - # Only set when we know we are in a single-configuration generator - # Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined - set(${BUILD_TYPE} ${CMAKE_BUILD_TYPE} PARENT_SCOPE) - endif() -endfunction() - -macro(set_conan_compiler_if_appleclang lang command output_variable) - if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") - execute_process(COMMAND xcrun --find ${command} - OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_xcrun_out STREQUAL "${CMAKE_${lang}_COMPILER}") - set(${output_variable} "") - endif() - unset(_xcrun_out) - endif() -endmacro() - - -macro(append_compiler_executables_configuration) - set(_conan_c_compiler "") - set(_conan_cpp_compiler "") - if(CMAKE_C_COMPILER) - set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\",") - set_conan_compiler_if_appleclang(C cc _conan_c_compiler) - else() - message(WARNING "CMake-Conan: The C compiler is not defined. " - "Please define CMAKE_C_COMPILER or enable the C language.") - endif() - if(CMAKE_CXX_COMPILER) - set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") - set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) - else() - message(WARNING "CMake-Conan: The C++ compiler is not defined. " - "Please define CMAKE_CXX_COMPILER or enable the C++ language.") - endif() - - if(NOT "x${_conan_c_compiler}${_conan_cpp_compiler}" STREQUAL "x") - string(APPEND PROFILE "tools.build:compiler_executables={${_conan_c_compiler}${_conan_cpp_compiler}}\n") - endif() - unset(_conan_c_compiler) - unset(_conan_cpp_compiler) -endmacro() - - -function(detect_host_profile output_file) - detect_os(MYOS MYOS_API_LEVEL MYOS_SDK MYOS_SUBSYSTEM MYOS_VERSION) - detect_arch(MYARCH) - detect_compiler(MYCOMPILER MYCOMPILER_VERSION MYCOMPILER_RUNTIME MYCOMPILER_RUNTIME_TYPE) - detect_cxx_standard(MYCXX_STANDARD) - detect_lib_cxx(MYLIB_CXX) - detect_build_type(MYBUILD_TYPE) - - set(PROFILE "") - string(APPEND PROFILE "[settings]\n") - if(MYARCH) - string(APPEND PROFILE arch=${MYARCH} "\n") - endif() - if(MYOS) - string(APPEND PROFILE os=${MYOS} "\n") - endif() - if(MYOS_API_LEVEL) - string(APPEND PROFILE os.api_level=${MYOS_API_LEVEL} "\n") - endif() - if(MYOS_VERSION) - string(APPEND PROFILE os.version=${MYOS_VERSION} "\n") - endif() - if(MYOS_SDK) - string(APPEND PROFILE os.sdk=${MYOS_SDK} "\n") - endif() - if(MYOS_SUBSYSTEM) - string(APPEND PROFILE os.subsystem=${MYOS_SUBSYSTEM} "\n") - endif() - if(MYCOMPILER) - string(APPEND PROFILE compiler=${MYCOMPILER} "\n") - endif() - if(MYCOMPILER_VERSION) - string(APPEND PROFILE compiler.version=${MYCOMPILER_VERSION} "\n") - endif() - if(MYCOMPILER_RUNTIME) - string(APPEND PROFILE compiler.runtime=${MYCOMPILER_RUNTIME} "\n") - endif() - if(MYCOMPILER_RUNTIME_TYPE) - string(APPEND PROFILE compiler.runtime_type=${MYCOMPILER_RUNTIME_TYPE} "\n") - endif() - if(MYCXX_STANDARD) - string(APPEND PROFILE compiler.cppstd=${MYCXX_STANDARD} "\n") - endif() - if(MYLIB_CXX) - string(APPEND PROFILE compiler.libcxx=${MYLIB_CXX} "\n") - endif() - if(MYBUILD_TYPE) - string(APPEND PROFILE "build_type=${MYBUILD_TYPE}\n") - endif() - - if(NOT DEFINED output_file) - set(_FN "${CMAKE_BINARY_DIR}/profile") - else() - set(_FN ${output_file}) - endif() - - string(APPEND PROFILE "[conf]\n") - string(APPEND PROFILE "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n") - - # propagate compilers via profile - append_compiler_executables_configuration() - - if(MYOS STREQUAL "Android") - string(APPEND PROFILE "tools.android:ndk_path=${CMAKE_ANDROID_NDK}\n") - endif() - - message(STATUS "CMake-Conan: Creating profile ${_FN}") - file(WRITE ${_FN} ${PROFILE}) - message(STATUS "CMake-Conan: Profile: \n${PROFILE}") -endfunction() - - -function(conan_profile_detect_default) - message(STATUS "CMake-Conan: Checking if a default profile exists") - execute_process(COMMAND ${CONAN_COMMAND} profile path default - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - ECHO_OUTPUT_VARIABLE - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(NOT ${return_code} EQUAL "0") - message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") - execute_process(COMMAND ${CONAN_COMMAND} profile detect - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - ECHO_OUTPUT_VARIABLE - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - endif() -endfunction() - - -function(conan_install) - cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN}) - set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan) - # Invoke "conan install" with the provided arguments - set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER}) - message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") - execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN} --format=json - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - if(NOT "${return_code}" STREQUAL "0") - message(FATAL_ERROR "Conan install failed='${return_code}'") - else() - # the files are generated in a folder that depends on the layout used, if - # one is specified, but we don't know a priori where this is. - # TODO: this can be made more robust if Conan can provide this in the json output - string(JSON CONAN_GENERATORS_FOLDER GET ${conan_stdout} graph nodes 0 generators_folder) - cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) - # message("conan stdout: ${conan_stdout}") - message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") - set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") - # reconfigure on conanfile changes - string(JSON CONANFILE GET ${conan_stdout} graph nodes 0 label) - message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") - set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") - # success - set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) - endif() -endfunction() - - -function(conan_get_version conan_command conan_current_version) - execute_process( - COMMAND ${conan_command} --version - OUTPUT_VARIABLE conan_output - RESULT_VARIABLE conan_result - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(conan_result) - message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") - endif() - - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) - set(${conan_current_version} ${conan_version} PARENT_SCOPE) -endfunction() - - -function(conan_version_check) - set(options ) - set(oneValueArgs MINIMUM CURRENT) - set(multiValueArgs ) - cmake_parse_arguments(CONAN_VERSION_CHECK - "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT CONAN_VERSION_CHECK_MINIMUM) - message(FATAL_ERROR "CMake-Conan: Required parameter MINIMUM not set!") - endif() - if(NOT CONAN_VERSION_CHECK_CURRENT) - message(FATAL_ERROR "CMake-Conan: Required parameter CURRENT not set!") - endif() - - if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM) - message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") - endif() -endfunction() - - -macro(construct_profile_argument argument_variable profile_list) - set(${argument_variable} "") - if("${profile_list}" STREQUAL "CONAN_HOST_PROFILE") - set(_arg_flag "--profile:host=") - elseif("${profile_list}" STREQUAL "CONAN_BUILD_PROFILE") - set(_arg_flag "--profile:build=") - endif() - - set(_profile_list "${${profile_list}}") - list(TRANSFORM _profile_list REPLACE "auto-cmake" "${CMAKE_BINARY_DIR}/conan_host_profile") - list(TRANSFORM _profile_list PREPEND ${_arg_flag}) - set(${argument_variable} ${_profile_list}) - - unset(_arg_flag) - unset(_profile_list) -endmacro() - - -macro(conan_provide_dependency method package_name) - set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) - get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) - if(NOT _conan_install_success) - find_program(CONAN_COMMAND "conan" REQUIRED) - conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) - conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) - message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") - if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) - conan_profile_detect_default() - endif() - if("auto-cmake" IN_LIST CONAN_HOST_PROFILE) - detect_host_profile(${CMAKE_BINARY_DIR}/conan_host_profile) - endif() - construct_profile_argument(_host_profile_flags CONAN_HOST_PROFILE) - construct_profile_argument(_build_profile_flags CONAN_BUILD_PROFILE) - if(EXISTS "${CMAKE_SOURCE_DIR}/conanfile.py") - file(READ "${CMAKE_SOURCE_DIR}/conanfile.py" outfile) - if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") - message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile") - endif() - set(generator "") - elseif (EXISTS "${CMAKE_SOURCE_DIR}/conanfile.txt") - file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) - if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") - message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " - "Please define the generator as it will be mandatory in the future") - endif() - set(generator "-g;CMakeDeps") - endif() - get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) - if(NOT _multiconfig_generator) - message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") - conan_install(${_host_profile_flags} ${_build_profile_flags} --build=missing ${generator}) - else() - message(STATUS "CMake-Conan: Installing both Debug and Release") - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release --build=missing ${generator}) - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug --build=missing ${generator}) - endif() - unset(_host_profile_flags) - unset(_build_profile_flags) - unset(_multiconfig_generator) - unset(_conan_install_success) - else() - message(STATUS "CMake-Conan: find_package(${ARGV1}) found, 'conan install' already ran") - unset(_conan_install_success) - endif() - - get_property(_conan_generators_folder GLOBAL PROPERTY CONAN_GENERATORS_FOLDER) - - # Ensure that we consider Conan-provided packages ahead of any other, - # irrespective of other settings that modify the search order or search paths - # This follows the guidelines from the find_package documentation - # (https://cmake.org/cmake/help/latest/command/find_package.html): - # find_package ( PATHS paths... NO_DEFAULT_PATH) - # find_package () - - # Filter out `REQUIRED` from the argument list, as the first call may fail - set(_find_args_${package_name} "${ARGN}") - list(REMOVE_ITEM _find_args_${package_name} "REQUIRED") - if(NOT "MODULE" IN_LIST _find_args_${package_name}) - find_package(${package_name} ${_find_args_${package_name}} BYPASS_PROVIDER PATHS "${_conan_generators_folder}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) - unset(_find_args_${package_name}) - endif() - - # Invoke find_package a second time - if the first call succeeded, - # this will simply reuse the result. If not, fall back to CMake default search - # behaviour, also allowing modules to be searched. - if(NOT ${package_name}_FOUND) - list(FIND CMAKE_MODULE_PATH "${_conan_generators_folder}" _index) - if(_index EQUAL -1) - list(PREPEND CMAKE_MODULE_PATH "${_conan_generators_folder}") - endif() - unset(_index) - find_package(${package_name} ${ARGN} BYPASS_PROVIDER) - list(REMOVE_ITEM CMAKE_MODULE_PATH "${_conan_generators_folder}") - endif() -endmacro() - - -cmake_language( - SET_DEPENDENCY_PROVIDER conan_provide_dependency - SUPPORTED_METHODS FIND_PACKAGE -) - - -macro(conan_provide_dependency_check) - set(_CONAN_PROVIDE_DEPENDENCY_INVOKED FALSE) - get_property(_CONAN_PROVIDE_DEPENDENCY_INVOKED GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) - if(NOT _CONAN_PROVIDE_DEPENDENCY_INVOKED) - message(WARNING "Conan is correctly configured as dependency provider, " - "but Conan has not been invoked. Please add at least one " - "call to `find_package()`.") - if(DEFINED CONAN_COMMAND) - # supress warning in case `CONAN_COMMAND` was specified but unused. - set(_CONAN_COMMAND ${CONAN_COMMAND}) - unset(_CONAN_COMMAND) - endif() - endif() - unset(_CONAN_PROVIDE_DEPENDENCY_INVOKED) -endmacro() - - -# Add a deferred call at the end of processing the top-level directory -# to check if the dependency provider was invoked at all. -cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) - -# Configurable variables for Conan profiles -set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") -set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") diff --git a/conandata.yml b/conandata.yml deleted file mode 100644 index bbbe0d8..0000000 --- a/conandata.yml +++ /dev/null @@ -1,7 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -requirements: - - "glfw/3.3.8" - - "opengl/system" - - "imgui/1.90" \ No newline at end of file diff --git a/conanfile.py b/conanfile.py deleted file mode 100644 index b89a080..0000000 --- a/conanfile.py +++ /dev/null @@ -1,23 +0,0 @@ -# This file is managed by Conan, contents will be overwritten. -# To keep your changes, remove these comment lines, but the plugin won't be able to modify your requirements - -from conan import ConanFile -from conan.tools.cmake import cmake_layout, CMakeToolchain - -class ConanApplication(ConanFile): - package_type = "application" - settings = "os", "compiler", "build_type", "arch" - generators = "CMakeDeps" - - def layout(self): - cmake_layout(self) - - def generate(self): - tc = CMakeToolchain(self) - tc.user_presets_path = False - tc.generate() - - def requirements(self): - requirements = self.conan_data.get('requirements', []) - for requirement in requirements: - self.requires(requirement) \ No newline at end of file diff --git a/documentation.md b/documentation.md index 058a905..a18d0c0 100644 --- a/documentation.md +++ b/documentation.md @@ -20,6 +20,7 @@ After having included the necessary files into the project. A few simple steps are necessary. ```c++ #include // Include dependencies +using namespace ImFlow; ``` ```c++ ImNodeFlow INF; // Create an editor with default name diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7c798be..2692b65 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -12,6 +12,9 @@ #include #include "../src/imgui_bezier_math.h" +// TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping +// TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) + namespace ImFlow { // ----------------------------------------------------------------------------------------------------------------- @@ -240,14 +243,8 @@ namespace ImFlow */ int nodesCount() { return (int)m_nodes.size(); } - /** - * @brief Creates a link between two pins - * @details Creates a link. Will check for same node connections, IN to IN or OUT to OUT connections and evaluate the filters. - * Then the link will be created and the input pin of the two will own the link. - * @param start Pointer to the start pin to be connected - * @param end Pointer to the end pin to be connected - */ - void createLink(Pin* start, Pin* end); + + void addLink(std::shared_ptr& link); /** * @brief Pop-up when link is "dropped" @@ -562,9 +559,9 @@ namespace ImFlow /** * @brief Create link between pins - * @param left Pointer to the other pin + * @param other Pointer to the other pin */ - virtual void createLink(Pin* left) {} + virtual void createLink(Pin* other) = 0; /** * @brief Sets the reference to a link @@ -673,9 +670,9 @@ namespace ImFlow /** * @brief Create link between pins - * @param left Pointer to the other pin + * @param other Pointer to the other pin */ - void createLink(Pin* left) override; + void createLink(Pin* other) override; /** * @brief Deletes the link from pin @@ -733,6 +730,12 @@ namespace ImFlow */ void update() override; + /** + * @brief Create link between pins + * @param other Pointer to the other pin + */ + void createLink(Pin* other) override; + /** * @brief Sets the reference to a link * @param link Pointer to the link diff --git a/main.cpp b/main.cpp deleted file mode 100644 index c5116cf..0000000 --- a/main.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include -#include -#include -#include -#include -#include - -using namespace ImFlow; - -ImNodeFlow INF; - -class AB : public BaseNode -{ -public: - explicit AB(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("intouno", 0, ConnectionFilter_Int); - addOUT("int", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_slider; }); - addOUT("dummy", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_slider * 2; }); - } - - void draw() override - { - ImGui::Text("ASDDJHGFDSA"); - ImGui::SetNextItemWidth(120.0f); - ImGui::SliderInt("##SLSLSL", &m_slider, 0, 200); - } -private: - int m_slider = 0; -}; - -class Somma : public BaseNode -{ -public: - explicit Somma(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("A", 0, ConnectionFilter_Int); - addIN("B", 0, ConnectionFilter_Int); - addOUT("C", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + ins(1); }); - } - - void draw() override - { - ImGui::Text("A + B = C"); - } -private: -}; - -class CD : public BaseNode -{ -public: - explicit CD(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addOUT("str_out", ConnectionFilter_String) - ->behaviour([this](){ return m_ss; }); - } - - void draw() override - { - ImGui::Text("String Spitter"); - ImGui::PushItemWidth(120.0f); - ImGui::InputText("##ToSpit", &m_ss); - } -private: - std::string m_ss; -}; - -class Pri : public BaseNode -{ -public: - explicit Pri(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("INT", 0, ConnectionFilter_Int); - } - - void draw() override - { - ImGui::Text("%d", ins(0)); - } -private: -}; - -class StrPri : public BaseNode -{ -public: - explicit StrPri(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("Str", "Not Connected", ConnectionFilter_String); - } - - void draw() override - { - ImGui::Text("%s", ins(0).c_str()); - } -private: -}; - -class DemoWindow : public appLayer -{ -public: - void update() override - { - ImGui::SetNextWindowSize(ImVec2(700, 600), ImGuiCond_FirstUseEver); - ImGui::Begin("FlowGrid Test"); - INF.update(); - ImGui::End(); - } -}; - -// TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping -// TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) - -void foo(Pin* dragged) -{ - if (dragged->kind() == PinKind_Output) - { - if (ImGui::Selectable("Sommatore")) - { - auto n = INF.dropNode("Sommatore", ImGui::GetWindowPos()); - INF.createLink(dragged, n->ins(0)); - } - } - else - { - if (ImGui::Selectable("Sommatore")) - { - auto n = INF.dropNode("Sommatore", ImGui::GetWindowPos()); - INF.createLink(n->outs(0), dragged); - } - if (ImGui::Selectable("AB thingy")) - { - auto n = INF.dropNode("AB", ImGui::GetWindowPos()); - INF.createLink(n->outs(0), dragged); - } - } -} - -int main() -{ - if (!IGH.init("Example", 1300, 800)) - return 1; - - ImGui::GetIO().IniFilename = nullptr; - - IGH.pushLayer(); - IGH.setActiveWin(0); - - INF.addNode("AA", ImVec2(0, 0)); - INF.addNode("BB", ImVec2(0, 100)); - INF.addNode("Printer ONE", ImVec2(500, 0)); - INF.addNode("Printer TWO", ImVec2(500, 100)); - INF.addNode("Printer THREE", ImVec2(500, 200)); - - INF.addNode("Sommatore", ImVec2(100, 100)); - - INF.addNode("CC", ImVec2(0, 100)); - INF.addNode("String Printer", ImVec2(500, 300)); - - INF.rightClickPopUpContent([]() { - if (ImGui::Selectable("AB")) - { - printf_s("AOOOO!\n"); - } - }); - INF.droppedLinkPopUpContent(foo, ImGuiKey_LeftShift); - - bool done = false; - while (!done) - { - IGH.loop(&done); - } - - IGH.end(); - return 0; -} diff --git a/readme.md b/readme.md index 8bc268f..c753e8f 100644 --- a/readme.md +++ b/readme.md @@ -14,15 +14,35 @@ https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26- - Appearance 100% customizable ## Implementation (CMake project) -1. Download and copy, or clone the repo inside your project +### CMake `FetchContent` +1. Add the following lines to your CMakeLists.txt: + ``` + include(FetchContent) + FetchContent_Declare(ImNodeFlow + GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" + GIT_TAG "origin/master" + SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" + ) + FetchContent_MakeAvailable(ImNodeFlow) + ``` + ``` + add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) + target_link_libraries(YourProject ImNodeFlow) + ``` +2. Make sure you have the following dependencies available for `find_package()`: + - [Dear ImGui](https://github.com/ocornut/imgui) + +### Manually +1. Download and copy, or clone the repo (or the latest release) inside your project 2. Add the following lines to your CMakeLists.txt: ``` add_subdirectory(path/to/ImNodeFlow) + . . . + add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) target_link_libraries(YourProject ImNodeFlow) ``` - -### Alternative -Download the latest ImNodeFlow.zip containing only the necessary files and add them manually. +3. Make sure you have the following dependencies available for `find_package()`: + - [Dear ImGui](https://github.com/ocornut/imgui) ## Simple Node example ```c++ diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 87ea176..7e926d5 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -177,33 +177,9 @@ namespace ImFlow return p - m_pos - m_scroll; } - void ImNodeFlow::createLink(Pin* start, Pin* end) + void ImNodeFlow::addLink(std::shared_ptr& link) { - if (start->parent() == end->parent()) - return; - if (!((start->filter() & end->filter()) != 0 || start->filter() == ConnectionFilter_None || end->filter() == ConnectionFilter_None)) // Check Filter - return; - - if (start->kind() == PinKind_Output && end->kind() == PinKind_Input) // OUT to IN - { - if (end->getLink().expired() || end->getLink().lock()->left() != start) - { - end->createLink(start); - m_links.emplace_back(end->getLink()); - } - else - end->deleteLink(); - } - if (start->kind() == PinKind_Input && end->kind() == PinKind_Output) // IN to OUT - { - if (start->getLink().expired() || start->getLink().lock()->left() != end) - { - start->createLink(end); - m_links.emplace_back(start->getLink()); - } - else - start->deleteLink(); - } + m_links.push_back(link); } void ImNodeFlow::update() @@ -261,7 +237,7 @@ namespace ImFlow } } else - createLink(m_dragOut, m_hovering); + m_dragOut->createLink(m_hovering); } // Links drag-out diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 514d09e..55aca17 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -99,10 +99,26 @@ namespace ImFlow } template - void InPin::createLink(Pin *left) + void InPin::createLink(Pin *other) { - m_link = std::make_shared(left, this, m_inf); - left->setLink(m_link); + if (other == this || m_parent == other->parent()) + return; + + if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter + return; + + if (other->kind() == PinKind_Input) + return; + + if (m_link && m_link->left() == other) + { + m_link.reset(); + return; + } + + m_link = std::make_shared(other, this, m_inf); + other->setLink(m_link); + m_inf->addLink(m_link); } // ----------------------------------------------------------------------------------------------------------------- @@ -130,4 +146,13 @@ namespace ImFlow if (ImGui::IsItemHovered()) m_inf->hovering(this); } + + template + void OutPin::createLink(ImFlow::Pin *other) + { + if (other == this) + return; + + other->createLink(this); + } } From 0c6d05f1248188bbeee241fd3e755310d4eb5122 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 1 Feb 2024 16:26:40 +0100 Subject: [PATCH 008/116] Minor touch-up on link creation --- src/ImNodeFlow.cpp | 1 - src/ImNodeFlow.inl | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 7e926d5..483f6eb 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -245,7 +245,6 @@ namespace ImFlow m_dragOut = m_hovering; if (m_dragOut) { - ImVec2 pinDot; if (m_dragOut->kind() == PinKind_Output) smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); else diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 55aca17..821b6f3 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -101,15 +101,12 @@ namespace ImFlow template void InPin::createLink(Pin *other) { - if (other == this || m_parent == other->parent()) + if (other == this || other->kind() == PinKind_Input || m_parent == other->parent()) return; if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter return; - if (other->kind() == PinKind_Input) - return; - if (m_link && m_link->left() == other) { m_link.reset(); @@ -150,7 +147,7 @@ namespace ImFlow template void OutPin::createLink(ImFlow::Pin *other) { - if (other == this) + if (other == this || other->kind() == PinKind_Output) return; other->createLink(this); From 9afce663bbaae572f019e2ad769b88fd6d3c854f Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 1 Feb 2024 16:36:10 +0100 Subject: [PATCH 009/116] Added missing doxygen comment --- include/ImNodeFlow.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 2692b65..bc4e8d1 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -243,7 +243,10 @@ namespace ImFlow */ int nodesCount() { return (int)m_nodes.size(); } - + /** + * @brief Add link to the handler internal list + * @param link Reference to the link + */ void addLink(std::shared_ptr& link); /** From b231b165dbc47da546ae7d4d90ab84219d87e119 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 2 Feb 2024 18:40:45 +0100 Subject: [PATCH 010/116] Fixed right click triggering out of the grid --- src/ImNodeFlow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 483f6eb..c4a67b2 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -262,7 +262,7 @@ namespace ImFlow } // Right-click PopUp - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && on_free_space()) + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered() && on_free_space()) { if (m_rightClickPopUp) ImGui::OpenPopup("RightClickPopUp"); @@ -285,7 +285,7 @@ namespace ImFlow [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); // Scrolling - if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.0f)) + if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.f)) m_scroll = m_scroll + ImGui::GetIO().MouseDelta; ImGui::EndChild(); From a6126fa01ab092de273ee7844a4eaad00c17e6c4 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 2 Feb 2024 22:49:11 +0100 Subject: [PATCH 011/116] Implemented ability to zoom --- include/ImNodeFlow.h | 4 ++ src/ImNodeFlow.cpp | 12 ++-- src/canvas_wrapper.h | 140 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 src/canvas_wrapper.h diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index bc4e8d1..fd5a78d 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -11,6 +11,7 @@ #include #include #include "../src/imgui_bezier_math.h" +#include "../src/canvas_wrapper.h" // TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping // TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) @@ -307,6 +308,8 @@ namespace ImFlow */ const std::vector>& links() { return m_links; } + const Canvas& canvas() { return m_canvas; } + /** * @brief Get dragging status * @return [TRUE] if a Node is being dragged around the grid @@ -362,6 +365,7 @@ namespace ImFlow std::string m_name; ImVec2 m_pos; ImVec2 m_scroll = ImVec2(0, 0); + Canvas m_canvas; bool m_singleUseClick = false; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index c4a67b2..19bf3e9 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -1,3 +1,4 @@ +#include #include "ImNodeFlow.h" namespace ImFlow @@ -188,15 +189,10 @@ namespace ImFlow m_hovering = nullptr; m_draggingNode = m_draggingNodeNext; m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + m_pos = ImGui::GetWindowPos(); // Create child canvas - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_ChildBg, m_style.colors.background); - ImGui::BeginChild(m_name.c_str(), ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse); - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(); - m_pos = ImGui::GetWindowPos(); + m_canvas.begin(m_style.colors.background); ImVec2 offset = ImGui::GetCursorScreenPos() + m_scroll; ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -288,6 +284,6 @@ namespace ImFlow if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.f)) m_scroll = m_scroll + ImGui::GetIO().MouseDelta; - ImGui::EndChild(); + m_canvas.end(); } } diff --git a/src/canvas_wrapper.h b/src/canvas_wrapper.h new file mode 100644 index 0000000..77775b5 --- /dev/null +++ b/src/canvas_wrapper.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +using namespace ImGui; + +#ifndef ASSERT +#ifdef LUMIX_DEBUG +#ifdef _WIN32 + #define LUMIX_DEBUG_BREAK() __debugbreak() + #else + #define LUMIX_DEBUG_BREAK() raise(SIGTRAP) + #endif + #define ASSERT(x) do { const volatile bool lumix_assert_b____ = !(x); if(lumix_assert_b____) LUMIX_DEBUG_BREAK(); } while (false) +#else +#if defined _MSC_VER && !defined __clang__ +#define ASSERT(x) __assume(x) +#else +#define ASSERT(x) { false ? (void)(x) : (void)0; } +#endif +#endif +#endif + +inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 origin, float scale) +{ + dst->InputEventsQueue = src->InputEventsTrail; + for (ImGuiInputEvent& e : dst->InputEventsQueue) { + if (e.Type == ImGuiInputEventType_MousePos) { + e.MousePos.PosX = (e.MousePos.PosX - origin.x) / scale; + e.MousePos.PosY = (e.MousePos.PosY - origin.y) / scale; + } + } +} + +inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) +{ + // TODO optimize if vtx_start == 0 || if idx_start == 0 + ImDrawList* dl = GetWindowDrawList(); + const int vtx_start = dl->VtxBuffer.size(); + const int idx_start = dl->IdxBuffer.size(); + dl->VtxBuffer.resize(dl->VtxBuffer.size() + src->VtxBuffer.size()); + dl->IdxBuffer.resize(dl->IdxBuffer.size() + src->IdxBuffer.size()); + dl->CmdBuffer.reserve(dl->CmdBuffer.size() + src->CmdBuffer.size()); + dl->_VtxWritePtr = dl->VtxBuffer.Data + vtx_start; + dl->_IdxWritePtr = dl->IdxBuffer.Data + idx_start; + const ImDrawVert* vtx_read = src->VtxBuffer.Data; + const ImDrawIdx* idx_read = src->IdxBuffer.Data; + for (int i = 0, c = src->VtxBuffer.size(); i < c; ++i) { + dl->_VtxWritePtr[i].uv = vtx_read[i].uv; + dl->_VtxWritePtr[i].col = vtx_read[i].col; + dl->_VtxWritePtr[i].pos = vtx_read[i].pos * scale + origin; + } + for (int i = 0, c = src->IdxBuffer.size(); i < c; ++i) { + dl->_IdxWritePtr[i] = idx_read[i] + vtx_start; + } + for (int i = 0, c = src->CmdBuffer.size(); i < c; ++i) { + ImDrawCmd cmd = src->CmdBuffer[i]; + cmd.IdxOffset += idx_start; + ASSERT(cmd.VtxOffset == 0); + cmd.ClipRect.x = cmd.ClipRect.x * scale + origin.x; + cmd.ClipRect.y = cmd.ClipRect.y * scale + origin.y; + cmd.ClipRect.z = cmd.ClipRect.z * scale + origin.x; + cmd.ClipRect.w = cmd.ClipRect.w * scale + origin.y; + dl->CmdBuffer.push_back(cmd); + } + + dl->_VtxCurrentIdx += src->VtxBuffer.size(); + dl->_VtxWritePtr = dl->VtxBuffer.Data + dl->VtxBuffer.size(); + dl->_IdxWritePtr = dl->IdxBuffer.Data + dl->IdxBuffer.size(); +} + +struct IMGUI_API Canvas +{ + ~Canvas(); + void begin(ImU32 color); + void end(); + [[nodiscard]] bool hovered() const { return m_hovered; } + + ImVec2 m_origin; + ImVec2 m_size = ImVec2(0, 0); + float m_scale = 1.f; + ImGuiContext* m_ctx = nullptr; + ImGuiContext* m_original_ctx = nullptr; + bool m_hovered = false; +}; + +inline Canvas::~Canvas() +{ + if (m_ctx) DestroyContext(m_ctx); +} + +inline void Canvas::begin(ImU32 color) +{ + m_size = GetContentRegionAvail(); + m_origin = GetCursorScreenPos(); + m_original_ctx = GetCurrentContext(); + const ImGuiStyle& orig_style = GetStyle(); + if (!m_ctx) m_ctx = CreateContext(GetIO().Fonts); + SetCurrentContext(m_ctx); + ImGuiStyle& new_style = GetStyle(); + new_style = orig_style; + + CopyIOEvents(m_original_ctx, m_ctx, m_origin, m_scale); + + GetIO().DisplaySize = m_size / m_scale; + GetIO().ConfigInputTrickleEventQueue = false; + NewFrame(); + + SetNextWindowPos(ImVec2(0, 0)); + SetNextWindowSize(m_size / m_scale); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, color); + Begin("canvas_wrapper", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse); + PopStyleVar(2); + PopStyleColor(); +} + +inline void Canvas::end() +{ + m_hovered = IsWindowHovered(); + End(); + Render(); + + ImDrawData* draw_data = GetDrawData(); + + SetCurrentContext(m_original_ctx); + m_original_ctx = nullptr; + + for (int i = 0; i < draw_data->CmdListsCount; ++i) + AppendDrawData(draw_data->CmdLists[i], m_origin, m_scale); + + if (m_hovered && GetIO().MouseWheel != 0.f) + { + m_scale += GetIO().MouseWheel / 16; + m_scale = m_scale < 0.3f ? 0.3f : m_scale; + m_scale = m_scale > 1.5f ? 1.5f : m_scale; + } +} From dc04be2906d28d15e4d32660a75857f548e465d1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 3 Feb 2024 10:40:03 +0100 Subject: [PATCH 012/116] Fixed coordinates conversion errors due to zoom --- include/ImNodeFlow.h | 18 ++++++++++++++++++ src/ImNodeFlow.cpp | 18 ++++++++++++++---- src/ImNodeFlow.inl | 2 +- src/canvas_wrapper.h | 16 ++++------------ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index fd5a78d..a60bec3 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -308,6 +308,10 @@ namespace ImFlow */ const std::vector>& links() { return m_links; } + /** + * @brief Get zooming viewport + * @return Const reference to editor's internal viewport for zoom support + */ const Canvas& canvas() { return m_canvas; } /** @@ -330,6 +334,13 @@ namespace ImFlow */ void hovering(Pin* hovering) { m_hovering = hovering; } + /** + * @brief Convert coordinates from grid to zooming viewport + * @param p Point in canvas coordinates to be converted + * @return Point in screen coordinates + */ + ImVec2 content2canvas(const ImVec2& p); + /** * @brief Convert coordinates from canvas to screen * @param p Point in canvas coordinates to be converted @@ -342,6 +353,13 @@ namespace ImFlow * @param p Point in screen coordinates to be converted * @return Point in canvas coordinates */ + ImVec2 screen2content(const ImVec2 &p); + + /** + * @brief Convert coordinates from screen to zooming viewport + * @param p Point in screen coordinates to be converted + * @return Point in canvas coordinates + */ ImVec2 screen2canvas(const ImVec2& p); /** diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 19bf3e9..7a3b28f 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -41,7 +41,7 @@ namespace ImFlow bool BaseNode::hovered() { - return ImGui::IsMouseHoveringRect(m_inf->canvas2screen(m_pos - m_paddingTL), m_inf->canvas2screen(m_pos + m_size + m_paddingBR)); + return ImGui::IsMouseHoveringRect(m_inf->content2canvas(m_pos - m_paddingTL), m_inf->content2canvas(m_pos + m_size + m_paddingBR)); } void BaseNode::update(ImVec2& offset) @@ -92,10 +92,10 @@ namespace ImFlow for (auto& p : m_outs) { // FIXME: This looks horrible - if (m_inf->canvas2screen(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) + if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f)); else - p->pos(ImVec2(m_inf->canvas2screen(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); p->update(); } ImGui::EndGroup(); @@ -168,11 +168,21 @@ namespace ImFlow [](auto& l) {return !l.lock()->hovered();}); } - ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p) + ImVec2 ImNodeFlow::content2canvas(const ImVec2& p) { return p + m_scroll + ImGui::GetWindowPos(); } + ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p) + { + return (p + m_scroll) * m_canvas.scale() + m_canvas.origin(); + } + + ImVec2 ImNodeFlow::screen2content(const ImVec2 &p) + { + return p - m_scroll; + } + ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p) { return p - m_pos - m_scroll; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 821b6f3..26c5c62 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -46,7 +46,7 @@ namespace ImFlow T* ImNodeFlow::dropNode(const std::string& name, const ImVec2& pos) { static_assert(std::is_base_of::value, "Pushed type is not subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, screen2canvas(pos), this)); + m_nodes.emplace_back(std::make_shared(name, screen2content(pos), this)); return static_cast(m_nodes.back().get()); } diff --git a/src/canvas_wrapper.h b/src/canvas_wrapper.h index 77775b5..b3a9d6c 100644 --- a/src/canvas_wrapper.h +++ b/src/canvas_wrapper.h @@ -6,21 +6,12 @@ using namespace ImGui; #ifndef ASSERT -#ifdef LUMIX_DEBUG -#ifdef _WIN32 - #define LUMIX_DEBUG_BREAK() __debugbreak() - #else - #define LUMIX_DEBUG_BREAK() raise(SIGTRAP) - #endif - #define ASSERT(x) do { const volatile bool lumix_assert_b____ = !(x); if(lumix_assert_b____) LUMIX_DEBUG_BREAK(); } while (false) -#else #if defined _MSC_VER && !defined __clang__ #define ASSERT(x) __assume(x) #else #define ASSERT(x) { false ? (void)(x) : (void)0; } #endif #endif -#endif inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 origin, float scale) { @@ -54,10 +45,9 @@ inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) for (int i = 0, c = src->IdxBuffer.size(); i < c; ++i) { dl->_IdxWritePtr[i] = idx_read[i] + vtx_start; } - for (int i = 0, c = src->CmdBuffer.size(); i < c; ++i) { - ImDrawCmd cmd = src->CmdBuffer[i]; + for (auto cmd : src->CmdBuffer) { cmd.IdxOffset += idx_start; - ASSERT(cmd.VtxOffset == 0); + //ASSERT(cmd.VtxOffset == 0) cmd.ClipRect.x = cmd.ClipRect.x * scale + origin.x; cmd.ClipRect.y = cmd.ClipRect.y * scale + origin.y; cmd.ClipRect.z = cmd.ClipRect.z * scale + origin.x; @@ -76,6 +66,8 @@ struct IMGUI_API Canvas void begin(ImU32 color); void end(); [[nodiscard]] bool hovered() const { return m_hovered; } + [[nodiscard]] float scale() const { return m_scale; } + [[nodiscard]] const ImVec2& origin() const { return m_origin; } ImVec2 m_origin; ImVec2 m_size = ImVec2(0, 0); From ca211f762e46fb96f81d02b9e232b9b4ef105a75 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Sat, 3 Feb 2024 11:03:56 +0100 Subject: [PATCH 013/116] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 8d8ca72..b53492f 100644 --- a/readme.md +++ b/readme.md @@ -7,6 +7,7 @@ ImNodeFLow will handle connections, editor logic and rendering. https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26-037de8835f9d ## Features +- Support for Zoom - Backed-in Input and Output logic - Backed-in links handling - Customizable filters for different connections From e483bfaec34c1eda35f6579437777c6aba4ecaa6 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 4 Feb 2024 21:43:08 +0100 Subject: [PATCH 014/116] Reworking zoom helper library viewport_wrapper.h will become a stand-alone helper for rendering multiple viewport with ease. --- include/ImNodeFlow.h | 6 +- src/{canvas_wrapper.h => viewport_wrapper.h} | 112 +++++++++++-------- 2 files changed, 68 insertions(+), 50 deletions(-) rename src/{canvas_wrapper.h => viewport_wrapper.h} (56%) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index a60bec3..c7effda 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -11,7 +11,7 @@ #include #include #include "../src/imgui_bezier_math.h" -#include "../src/canvas_wrapper.h" +#include "../src/viewport_wrapper.h" // TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping // TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) @@ -312,7 +312,7 @@ namespace ImFlow * @brief Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const Canvas& canvas() { return m_canvas; } + const ViewPort& canvas() { return m_canvas; } /** * @brief Get dragging status @@ -383,7 +383,7 @@ namespace ImFlow std::string m_name; ImVec2 m_pos; ImVec2 m_scroll = ImVec2(0, 0); - Canvas m_canvas; + ViewPort m_canvas; bool m_singleUseClick = false; diff --git a/src/canvas_wrapper.h b/src/viewport_wrapper.h similarity index 56% rename from src/canvas_wrapper.h rename to src/viewport_wrapper.h index b3a9d6c..af9b54d 100644 --- a/src/canvas_wrapper.h +++ b/src/viewport_wrapper.h @@ -3,16 +3,6 @@ #include #include -using namespace ImGui; - -#ifndef ASSERT -#if defined _MSC_VER && !defined __clang__ -#define ASSERT(x) __assume(x) -#else -#define ASSERT(x) { false ? (void)(x) : (void)0; } -#endif -#endif - inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 origin, float scale) { dst->InputEventsQueue = src->InputEventsTrail; @@ -27,7 +17,7 @@ inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 ori inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) { // TODO optimize if vtx_start == 0 || if idx_start == 0 - ImDrawList* dl = GetWindowDrawList(); + ImDrawList* dl = ImGui::GetWindowDrawList(); const int vtx_start = dl->VtxBuffer.size(); const int idx_start = dl->IdxBuffer.size(); dl->VtxBuffer.resize(dl->VtxBuffer.size() + src->VtxBuffer.size()); @@ -60,73 +50,101 @@ inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) dl->_IdxWritePtr = dl->IdxBuffer.Data + dl->IdxBuffer.size(); } -struct IMGUI_API Canvas +struct ViewPortConfig +{ + ImVec2 size = {0.f, 0.f}; + ImU32 color = IM_COL32_WHITE; + bool zoom_enabled = true; + float zoom_smoothness = 0.f; + float default_zoom = 1.f; + ImGuiKey reset_zoom_key = ImGuiKey_R; + ImGuiMouseButton scroll_button = ImGuiMouseButton_Middle; +}; + +class ViewPort { - ~Canvas(); +public: + ~ViewPort(); void begin(ImU32 color); void end(); - [[nodiscard]] bool hovered() const { return m_hovered; } [[nodiscard]] float scale() const { return m_scale; } [[nodiscard]] const ImVec2& origin() const { return m_origin; } + [[nodiscard]] bool hovered() const { return m_hovered; } + [[nodiscard]] const ImVec2& scroll() const { return m_scroll; } +private: + ViewPortConfig m_config; ImVec2 m_origin; - ImVec2 m_size = ImVec2(0, 0); - float m_scale = 1.f; ImGuiContext* m_ctx = nullptr; ImGuiContext* m_original_ctx = nullptr; + + bool m_anyWindowHovered = false; + bool m_anyItemActive = false; bool m_hovered = false; + + float m_scale = 2.f; + ImVec2 m_scroll = {0.f, 0.f}; }; -inline Canvas::~Canvas() +inline ViewPort::~ViewPort() { - if (m_ctx) DestroyContext(m_ctx); + if (m_ctx) ImGui::DestroyContext(m_ctx); } -inline void Canvas::begin(ImU32 color) +inline void ViewPort::begin(ImU32 color) { - m_size = GetContentRegionAvail(); - m_origin = GetCursorScreenPos(); - m_original_ctx = GetCurrentContext(); - const ImGuiStyle& orig_style = GetStyle(); - if (!m_ctx) m_ctx = CreateContext(GetIO().Fonts); - SetCurrentContext(m_ctx); - ImGuiStyle& new_style = GetStyle(); + ImGui::PushID(this); + ImGui::PushStyleColor(ImGuiCol_ChildBg, color); + ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); + ImGui::PopStyleColor(); + + ImVec2 size = ImGui::GetContentRegionAvail(); + m_origin = ImGui::GetCursorScreenPos(); + m_original_ctx = ImGui::GetCurrentContext(); + const ImGuiStyle& orig_style = ImGui::GetStyle(); + if (!m_ctx) m_ctx = ImGui::CreateContext(ImGui::GetIO().Fonts); + ImGui::SetCurrentContext(m_ctx); + ImGuiStyle& new_style = ImGui::GetStyle(); new_style = orig_style; CopyIOEvents(m_original_ctx, m_ctx, m_origin, m_scale); - GetIO().DisplaySize = m_size / m_scale; - GetIO().ConfigInputTrickleEventQueue = false; - NewFrame(); - - SetNextWindowPos(ImVec2(0, 0)); - SetNextWindowSize(m_size / m_scale); - PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, color); - Begin("canvas_wrapper", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse); - PopStyleVar(2); - PopStyleColor(); + ImGui::GetIO().DisplaySize = size / m_scale; + ImGui::GetIO().ConfigInputTrickleEventQueue = false; + ImGui::NewFrame(); } -inline void Canvas::end() +inline void ViewPort::end() { - m_hovered = IsWindowHovered(); - End(); - Render(); + m_anyWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow); + m_anyItemActive = ImGui::IsAnyItemActive(); + + ImGui::Render(); - ImDrawData* draw_data = GetDrawData(); + ImDrawData* draw_data = ImGui::GetDrawData(); - SetCurrentContext(m_original_ctx); + ImGui::SetCurrentContext(m_original_ctx); m_original_ctx = nullptr; for (int i = 0; i < draw_data->CmdListsCount; ++i) AppendDrawData(draw_data->CmdLists[i], m_origin, m_scale); - if (m_hovered && GetIO().MouseWheel != 0.f) + m_hovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !m_anyWindowHovered; + + // Zooming + if (m_hovered && ImGui::GetIO().MouseWheel != 0.f) { - m_scale += GetIO().MouseWheel / 16; + m_scale += ImGui::GetIO().MouseWheel / 16; m_scale = m_scale < 0.3f ? 0.3f : m_scale; - m_scale = m_scale > 1.5f ? 1.5f : m_scale; + m_scale = m_scale > 2.f ? 2.f : m_scale; } + + // Scrolling + if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.f)) + m_scroll = m_scroll + ImGui::GetIO().MouseDelta; + + + + ImGui::EndChild(); + ImGui::PopID(); } From d2741b7b448fc4d5a2793b4f070a545817909a77 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 4 Feb 2024 22:45:02 +0100 Subject: [PATCH 015/116] Implemented more configs --- src/viewport_wrapper.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/viewport_wrapper.h b/src/viewport_wrapper.h index af9b54d..ee93d2a 100644 --- a/src/viewport_wrapper.h +++ b/src/viewport_wrapper.h @@ -65,7 +65,7 @@ class ViewPort { public: ~ViewPort(); - void begin(ImU32 color); + void begin(); void end(); [[nodiscard]] float scale() const { return m_scale; } [[nodiscard]] const ImVec2& origin() const { return m_origin; } @@ -82,7 +82,7 @@ class ViewPort bool m_anyItemActive = false; bool m_hovered = false; - float m_scale = 2.f; + float m_scale = m_config.default_zoom; ImVec2 m_scroll = {0.f, 0.f}; }; @@ -91,10 +91,10 @@ inline ViewPort::~ViewPort() if (m_ctx) ImGui::DestroyContext(m_ctx); } -inline void ViewPort::begin(ImU32 color) +inline void ViewPort::begin() { ImGui::PushID(this); - ImGui::PushStyleColor(ImGuiCol_ChildBg, color); + ImGui::PushStyleColor(ImGuiCol_ChildBg, m_config.color); ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); ImGui::PopStyleColor(); @@ -132,15 +132,19 @@ inline void ViewPort::end() m_hovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !m_anyWindowHovered; // Zooming - if (m_hovered && ImGui::GetIO().MouseWheel != 0.f) + if (m_config.zoom_enabled && m_hovered && ImGui::GetIO().MouseWheel != 0.f) { m_scale += ImGui::GetIO().MouseWheel / 16; m_scale = m_scale < 0.3f ? 0.3f : m_scale; m_scale = m_scale > 2.f ? 2.f : m_scale; } + // Zoom reset + if (ImGui::IsKeyPressed(m_config.reset_zoom_key, false)) + m_scale = m_config.default_zoom; + // Scrolling - if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.f)) + if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) m_scroll = m_scroll + ImGui::GetIO().MouseDelta; From b5443dec8d87ca69882a7d6dcc1bf03ee3dec627 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 5 Feb 2024 17:48:18 +0100 Subject: [PATCH 016/116] Fully implemented new viewport helper --- include/ImNodeFlow.h | 33 ++++++++++++++++++++++++--------- src/ImNodeFlow.cpp | 27 +++++++++++---------------- src/viewport_wrapper.h | 37 ++++++++++++++++++++++++++++++++----- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index c7effda..4ed22a5 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -192,21 +192,37 @@ namespace ImFlow * @brief Instantiate a new editor * @details Empty constructor, creates a new Node Editor. Editor name will be "FlowGrid + the number of editors". */ - ImNodeFlow() { m_name = "FlowGrid" + std::to_string(m_instances); m_instances++; } + ImNodeFlow() + { + m_name = "FlowGrid" + std::to_string(m_instances); + m_instances++; + m_viewport.config().extra_window_wrapper = true; + m_viewport.config().color = m_style.colors.background; + } /** * @brief Instantiate a new editor * @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++; } + explicit ImNodeFlow(std::string name) :m_name(std::move(name)) + { + m_instances++; + m_viewport.config().extra_window_wrapper = true; + m_viewport.config().color = m_style.colors.background; + } /** * @brief Instantiate a new editor * @details Creates a new Node Editor with the given name. * @param name Name of the editor */ - explicit ImNodeFlow(const char* name) :m_name(name) { m_instances++; } + explicit ImNodeFlow(const char* name) :m_name(name) + { + m_instances++; + m_viewport.config().extra_window_wrapper = true; + m_viewport.config().color = m_style.colors.background; + } /** * @brief Handler loop @@ -287,14 +303,14 @@ namespace ImFlow * @brief Get editor's position * @return Const reference to editor's position in screen coordinates */ - const ImVec2& pos() { return m_pos; } + const ImVec2& pos() { return m_viewport.origin(); } /** * @brief Get editor's grid scroll * @details Scroll is the offset from the origin of the grid, changes while navigating the grid with the middle mouse. * @return Const reference to editor's grid scroll */ - const ImVec2& scroll() { return m_scroll; } + const ImVec2& scroll() { return m_viewport.scroll(); } /** * @brief Get editor's list of nodes @@ -312,7 +328,7 @@ namespace ImFlow * @brief Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const ViewPort& canvas() { return m_canvas; } + const ViewPort& viewport() { return m_viewport; } /** * @brief Get dragging status @@ -381,9 +397,8 @@ namespace ImFlow InfStyler& style() { return m_style; } private: std::string m_name; - ImVec2 m_pos; - ImVec2 m_scroll = ImVec2(0, 0); - ViewPort m_canvas; +// ImVec2 m_pos; + ViewPort m_viewport; bool m_singleUseClick = false; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 7a3b28f..c053d8b 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -170,22 +170,22 @@ namespace ImFlow ImVec2 ImNodeFlow::content2canvas(const ImVec2& p) { - return p + m_scroll + ImGui::GetWindowPos(); + return p + m_viewport.scroll() + ImGui::GetWindowPos(); } ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p) { - return (p + m_scroll) * m_canvas.scale() + m_canvas.origin(); + return (p + m_viewport.scroll()) * m_viewport.scale() + m_viewport.origin(); } ImVec2 ImNodeFlow::screen2content(const ImVec2 &p) { - return p - m_scroll; + return p - m_viewport.scroll(); } ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p) { - return p - m_pos - m_scroll; + return p - pos() - m_viewport.scroll(); } void ImNodeFlow::addLink(std::shared_ptr& link) @@ -199,24 +199,23 @@ namespace ImFlow m_hovering = nullptr; m_draggingNode = m_draggingNodeNext; m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); - m_pos = ImGui::GetWindowPos(); // Create child canvas - m_canvas.begin(m_style.colors.background); + m_viewport.begin(); - ImVec2 offset = ImGui::GetCursorScreenPos() + m_scroll; + ImVec2 offset = ImGui::GetCursorScreenPos() + m_viewport.scroll(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Display grid ImVec2 win_pos = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetWindowSize(); - for (float x = fmodf(m_scroll.x, m_style.grid_size); x < canvas_sz.x; x += m_style.grid_size) + for (float x = fmodf(m_viewport.scroll().x, m_style.grid_size); x < canvas_sz.x; x += m_style.grid_size) draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.grid); - for (float y = fmodf(m_scroll.y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) + for (float y = fmodf(m_viewport.scroll().y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.grid); - for (float x = fmodf(m_scroll.x, m_style.grid_size / m_style.grid_subdivisions); x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) + for (float x = fmodf(m_viewport.scroll().x, m_style.grid_size / m_style.grid_subdivisions); x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.subGrid); - for (float y = fmodf(m_scroll.y, m_style.grid_size / m_style.grid_subdivisions); y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) + for (float y = fmodf(m_viewport.scroll().y, m_style.grid_size / m_style.grid_subdivisions); y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.subGrid); // Update and draw nodes @@ -290,10 +289,6 @@ namespace ImFlow m_links.erase(std::remove_if(m_links.begin(), m_links.end(), [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); - // Scrolling - if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.f)) - m_scroll = m_scroll + ImGui::GetIO().MouseDelta; - - m_canvas.end(); + m_viewport.end(); } } diff --git a/src/viewport_wrapper.h b/src/viewport_wrapper.h index ee93d2a..227ba65 100644 --- a/src/viewport_wrapper.h +++ b/src/viewport_wrapper.h @@ -52,10 +52,11 @@ inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) struct ViewPortConfig { + bool extra_window_wrapper = false; ImVec2 size = {0.f, 0.f}; ImU32 color = IM_COL32_WHITE; bool zoom_enabled = true; - float zoom_smoothness = 0.f; + float zoom_smoothness = 5.f; float default_zoom = 1.f; ImGuiKey reset_zoom_key = ImGuiKey_R; ImGuiMouseButton scroll_button = ImGuiMouseButton_Middle; @@ -65,6 +66,7 @@ class ViewPort { public: ~ViewPort(); + ViewPortConfig& config() { return m_config; } void begin(); void end(); [[nodiscard]] float scale() const { return m_scale; } @@ -82,7 +84,7 @@ class ViewPort bool m_anyItemActive = false; bool m_hovered = false; - float m_scale = m_config.default_zoom; + float m_scale = m_config.default_zoom, m_scaleTarget = m_config.default_zoom; ImVec2 m_scroll = {0.f, 0.f}; }; @@ -112,13 +114,28 @@ inline void ViewPort::begin() ImGui::GetIO().DisplaySize = size / m_scale; ImGui::GetIO().ConfigInputTrickleEventQueue = false; ImGui::NewFrame(); + + if (!m_config.extra_window_wrapper) + return; + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Appearing); + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->WorkSize); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::Begin("viewport_container", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::PopStyleVar(); } inline void ViewPort::end() { m_anyWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow); + if (m_config.extra_window_wrapper && ImGui::IsWindowHovered()) + m_anyWindowHovered = false; + m_anyItemActive = ImGui::IsAnyItemActive(); + if (m_config.extra_window_wrapper) + ImGui::End(); + ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); @@ -134,9 +151,19 @@ inline void ViewPort::end() // Zooming if (m_config.zoom_enabled && m_hovered && ImGui::GetIO().MouseWheel != 0.f) { - m_scale += ImGui::GetIO().MouseWheel / 16; - m_scale = m_scale < 0.3f ? 0.3f : m_scale; - m_scale = m_scale > 2.f ? 2.f : m_scale; + m_scaleTarget += ImGui::GetIO().MouseWheel / 16; + m_scaleTarget = m_scaleTarget < 0.3f ? 0.3f : m_scaleTarget; + m_scaleTarget = m_scaleTarget > 2.f ? 2.f : m_scaleTarget; + if (m_config.zoom_smoothness == 0.f) + { + m_scale = m_scaleTarget; + } + } + if (abs(m_scaleTarget - m_scale) >= 0.015f / m_config.zoom_smoothness) + { + m_scale += (m_scaleTarget - m_scale) / m_config.zoom_smoothness; + if (abs(m_scaleTarget - m_scale) < 0.02f) + m_scale = m_scaleTarget; } // Zoom reset From 951df8e5adffbe9691e5ca7212029d8129f97802 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 5 Feb 2024 17:52:00 +0100 Subject: [PATCH 017/116] Updated credits --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index b53492f..8ca436e 100644 --- a/readme.md +++ b/readme.md @@ -76,3 +76,4 @@ For a more detailed explanation please refer to the [full documentation](documen ### 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 From bbade1d9c7016dda4a0f1076e0e205439ede486b Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 5 Feb 2024 22:20:48 +0100 Subject: [PATCH 018/116] Fixed bugs --- include/ImNodeFlow.h | 9 +++++---- src/ImNodeFlow.inl | 18 +++++++++++++++--- src/viewport_wrapper.h | 6 ++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 4ed22a5..8b06367 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -161,7 +161,8 @@ namespace ImFlow ImVec2 pin_padding = ImVec2(3.f, 1.f); // Padding between Pin border and content float pin_radius = 8.f; // Pin's edges rounding float pin_border_thickness = 1.f; // Thickness of the border drawn around the Pin - float pin_point_radius = 3.5f; // Radius of the circle in front of the Pin + float pin_point_radius = 3.5f; // Radius of the circle in front of the Pin when connected + float pin_point_empty_radius = 4.f; // Radius of the circle in front of the Pin when not connected float link_thickness = 2.6f; // Thickness of the drawn link float link_hovered_thickness = 3.5f; // Thickness of the drawn link when hovered @@ -762,7 +763,7 @@ namespace ImFlow /** * @brief When parent gets deleted, remove the link */ - ~OutPin() { if (!m_link.expired()) m_link.lock()->right()->deleteLink(); } + ~OutPin() { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } /** * @brief Main loop of the pin @@ -780,7 +781,7 @@ namespace ImFlow * @brief Sets the reference to a link * @param link Pointer to the link */ - void setLink(std::shared_ptr& link) override { m_link = link; } + void setLink(std::shared_ptr& link) override; /** * @brief Get pin's link attachment point @@ -801,7 +802,7 @@ namespace ImFlow */ void behaviour(std::function func) { m_behaviour = std::move(func); } private: - std::weak_ptr m_link; + std::vector> m_links; std::function m_behaviour; T m_val; }; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 26c5c62..29d94a3 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -92,7 +92,10 @@ namespace ImFlow else draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_bg, m_inf->style().pin_radius); draw_list->AddRect(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_border, m_inf->style().pin_radius, 0, m_inf->style().pin_border_thickness); - draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); + if (m_link) + draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); + else + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); if (ImGui::IsItemHovered()) m_inf->hovering(this); @@ -122,7 +125,7 @@ namespace ImFlow // OUT PIN template - const T &OutPin::val() { m_val = m_behaviour(); return m_val; } // TODO: Resolve ME somewhere else so it's not done every frame + const T &OutPin::val() { m_val = m_behaviour(); return m_val; } template void OutPin::update() @@ -138,7 +141,10 @@ namespace ImFlow else draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_bg, m_inf->style().pin_radius); draw_list->AddRect(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_border, m_inf->style().pin_radius, 0, m_inf->style().pin_border_thickness); - draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); + if (m_links.empty()) + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + else + draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); if (ImGui::IsItemHovered()) m_inf->hovering(this); @@ -152,4 +158,10 @@ namespace ImFlow other->createLink(this); } + + template + void OutPin::setLink(std::shared_ptr& link) + { + m_links.emplace_back(link); + } } diff --git a/src/viewport_wrapper.h b/src/viewport_wrapper.h index 227ba65..694e67b 100644 --- a/src/viewport_wrapper.h +++ b/src/viewport_wrapper.h @@ -168,13 +168,11 @@ inline void ViewPort::end() // Zoom reset if (ImGui::IsKeyPressed(m_config.reset_zoom_key, false)) - m_scale = m_config.default_zoom; + m_scaleTarget = m_config.default_zoom; // Scrolling if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) - m_scroll = m_scroll + ImGui::GetIO().MouseDelta; - - + m_scroll = m_scroll + ImGui::GetIO().MouseDelta / m_scale; ImGui::EndChild(); ImGui::PopID(); From c4e5c56f3bf6802f7e7c3f70b20397f2a362b6b1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 5 Feb 2024 22:29:24 +0100 Subject: [PATCH 019/116] Fixed typo --- include/ImNodeFlow.h | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 8b06367..f36e4fd 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -761,7 +761,7 @@ namespace ImFlow :Pin(name, filter, PinKind_Output, parent, inf) {} /** - * @brief When parent gets deleted, remove the link + * @brief When parent gets deleted, remove the links */ ~OutPin() { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } diff --git a/readme.md b/readme.md index 8ca436e..4fa40e0 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ **Node based editor/blueprints for ImGui** Create your custom nodes, and their logic. -ImNodeFLow will handle connections, editor logic and rendering. +ImNodeFlow will handle connections, editor logic and rendering. https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26-037de8835f9d From bc25e6a020e09e33e43c4efe20a1c0755f981bd8 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 7 Feb 2024 14:13:49 +0100 Subject: [PATCH 020/116] Zoom follows mouse position --- include/ImNodeFlow.h | 26 +++++++------- src/ImNodeFlow.cpp | 35 ++++++++++--------- src/{viewport_wrapper.h => context_wrapper.h} | 32 ++++++++++++----- 3 files changed, 54 insertions(+), 39 deletions(-) rename src/{viewport_wrapper.h => context_wrapper.h} (86%) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index f36e4fd..b48ab23 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -11,7 +11,7 @@ #include #include #include "../src/imgui_bezier_math.h" -#include "../src/viewport_wrapper.h" +#include "../src/context_wrapper.h" // TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping // TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) @@ -197,8 +197,8 @@ namespace ImFlow { m_name = "FlowGrid" + std::to_string(m_instances); m_instances++; - m_viewport.config().extra_window_wrapper = true; - m_viewport.config().color = m_style.colors.background; + m_context.config().extra_window_wrapper = true; + m_context.config().color = m_style.colors.background; } /** @@ -209,8 +209,8 @@ namespace ImFlow explicit ImNodeFlow(std::string name) :m_name(std::move(name)) { m_instances++; - m_viewport.config().extra_window_wrapper = true; - m_viewport.config().color = m_style.colors.background; + m_context.config().extra_window_wrapper = true; + m_context.config().color = m_style.colors.background; } /** @@ -221,8 +221,8 @@ namespace ImFlow explicit ImNodeFlow(const char* name) :m_name(name) { m_instances++; - m_viewport.config().extra_window_wrapper = true; - m_viewport.config().color = m_style.colors.background; + m_context.config().extra_window_wrapper = true; + m_context.config().color = m_style.colors.background; } /** @@ -304,14 +304,14 @@ namespace ImFlow * @brief Get editor's position * @return Const reference to editor's position in screen coordinates */ - const ImVec2& pos() { return m_viewport.origin(); } + const ImVec2& pos() { 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 with the middle mouse. * @return Const reference to editor's grid scroll */ - const ImVec2& scroll() { return m_viewport.scroll(); } + const ImVec2& scroll() { return m_context.scroll(); } /** * @brief Get editor's list of nodes @@ -329,7 +329,7 @@ namespace ImFlow * @brief Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const ViewPort& viewport() { return m_viewport; } + const ContainedContext& viewport() { return m_context; } /** * @brief Get dragging status @@ -398,8 +398,7 @@ namespace ImFlow InfStyler& style() { return m_style; } private: std::string m_name; -// ImVec2 m_pos; - ViewPort m_viewport; + ContainedContext m_context; bool m_singleUseClick = false; @@ -439,6 +438,7 @@ namespace ImFlow { m_paddingTL = {m_inf->style().node_padding.x, m_inf->style().node_padding.y}; m_paddingBR = {m_inf->style().node_padding.z, m_inf->style().node_padding.w}; + m_posTarget = m_pos; } /** @@ -551,7 +551,7 @@ namespace ImFlow void updatePublicStatus() { m_selected = m_selectedNext; } private: std::string m_name; - ImVec2 m_pos, m_posOld = m_pos; + ImVec2 m_pos, m_posTarget; ImVec2 m_size; ImNodeFlow* m_inf; bool m_selected = false, m_selectedNext = false; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index c053d8b..a703bdd 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -67,7 +67,7 @@ namespace ImFlow ImGui::BeginGroup(); for(auto& p : m_ins) { - p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos()); + p->pos(ImGui::GetCursorPos()); p->update(); } ImGui::EndGroup(); @@ -129,21 +129,20 @@ namespace ImFlow m_inf->consumeSingleUseClick(); m_dragged = true; m_inf->draggingNode(true); - m_posOld = m_pos; } if(m_dragged || (m_selected && m_inf->draggingNode())) { float step = m_inf->style().grid_size / m_inf->style().grid_subdivisions; - ImVec2 wantedPos = m_posOld + ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0.f); + m_posTarget += ImGui::GetIO().MouseDelta; // "Slam" The position - m_pos.x = step * (int)(wantedPos.x / step); - m_pos.y = step * (int)(wantedPos.y / step); + 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_posOld = m_pos; + m_posTarget = m_pos; } } ImGui::PopID(); @@ -170,22 +169,22 @@ namespace ImFlow ImVec2 ImNodeFlow::content2canvas(const ImVec2& p) { - return p + m_viewport.scroll() + ImGui::GetWindowPos(); + return p + m_context.scroll() + ImGui::GetWindowPos(); } ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p) { - return (p + m_viewport.scroll()) * m_viewport.scale() + m_viewport.origin(); + return (p + m_context.scroll()) * m_context.scale() + m_context.origin(); } ImVec2 ImNodeFlow::screen2content(const ImVec2 &p) { - return p - m_viewport.scroll(); + return p - m_context.scroll(); } ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p) { - return p - pos() - m_viewport.scroll(); + return p - pos() - m_context.scroll(); } void ImNodeFlow::addLink(std::shared_ptr& link) @@ -201,21 +200,23 @@ namespace ImFlow m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); // Create child canvas - m_viewport.begin(); + m_context.begin(); - ImVec2 offset = ImGui::GetCursorScreenPos() + m_viewport.scroll(); + ImVec2 offset = ImGui::GetCursorScreenPos() + m_context.scroll(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddCircleFilled(ImVec2(0,0) + m_context.scroll(), 5.f, IM_COL32_WHITE); + // Display grid ImVec2 win_pos = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetWindowSize(); - for (float x = fmodf(m_viewport.scroll().x, m_style.grid_size); x < canvas_sz.x; x += m_style.grid_size) + for (float x = fmodf(m_context.scroll().x, m_style.grid_size); x < canvas_sz.x; x += m_style.grid_size) draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.grid); - for (float y = fmodf(m_viewport.scroll().y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) + for (float y = fmodf(m_context.scroll().y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.grid); - for (float x = fmodf(m_viewport.scroll().x, m_style.grid_size / m_style.grid_subdivisions); x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) + for (float x = fmodf(m_context.scroll().x, m_style.grid_size / m_style.grid_subdivisions); x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.subGrid); - for (float y = fmodf(m_viewport.scroll().y, m_style.grid_size / m_style.grid_subdivisions); y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) + for (float y = fmodf(m_context.scroll().y, m_style.grid_size / m_style.grid_subdivisions); y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.subGrid); // Update and draw nodes @@ -289,6 +290,6 @@ namespace ImFlow m_links.erase(std::remove_if(m_links.begin(), m_links.end(), [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); - m_viewport.end(); + m_context.end(); } } diff --git a/src/viewport_wrapper.h b/src/context_wrapper.h similarity index 86% rename from src/viewport_wrapper.h rename to src/context_wrapper.h index 694e67b..1a186b7 100644 --- a/src/viewport_wrapper.h +++ b/src/context_wrapper.h @@ -37,7 +37,7 @@ inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) } for (auto cmd : src->CmdBuffer) { cmd.IdxOffset += idx_start; - //ASSERT(cmd.VtxOffset == 0) + IM_ASSERT(cmd.VtxOffset == 0); cmd.ClipRect.x = cmd.ClipRect.x * scale + origin.x; cmd.ClipRect.y = cmd.ClipRect.y * scale + origin.y; cmd.ClipRect.z = cmd.ClipRect.z * scale + origin.x; @@ -62,10 +62,10 @@ struct ViewPortConfig ImGuiMouseButton scroll_button = ImGuiMouseButton_Middle; }; -class ViewPort +class ContainedContext { public: - ~ViewPort(); + ~ContainedContext(); ViewPortConfig& config() { return m_config; } void begin(); void end(); @@ -77,6 +77,7 @@ class ViewPort ViewPortConfig m_config; ImVec2 m_origin; + ImVec2 m_pos, m_size; ImGuiContext* m_ctx = nullptr; ImGuiContext* m_original_ctx = nullptr; @@ -85,20 +86,22 @@ class ViewPort bool m_hovered = false; float m_scale = m_config.default_zoom, m_scaleTarget = m_config.default_zoom; - ImVec2 m_scroll = {0.f, 0.f}; + ImVec2 m_scroll = {0.f, 0.f}, m_scrollTarget = {0.f, 0.f}; }; -inline ViewPort::~ViewPort() +inline ContainedContext::~ContainedContext() { if (m_ctx) ImGui::DestroyContext(m_ctx); } -inline void ViewPort::begin() +inline void ContainedContext::begin() { ImGui::PushID(this); ImGui::PushStyleColor(ImGuiCol_ChildBg, m_config.color); ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); ImGui::PopStyleColor(); + m_size = ImGui::GetWindowSize(); + m_pos = ImGui::GetWindowPos(); ImVec2 size = ImGui::GetContentRegionAvail(); m_origin = ImGui::GetCursorScreenPos(); @@ -125,7 +128,7 @@ inline void ViewPort::begin() ImGui::PopStyleVar(); } -inline void ViewPort::end() +inline void ContainedContext::end() { m_anyWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow); if (m_config.extra_window_wrapper && ImGui::IsWindowHovered()) @@ -154,16 +157,24 @@ inline void ViewPort::end() m_scaleTarget += ImGui::GetIO().MouseWheel / 16; m_scaleTarget = m_scaleTarget < 0.3f ? 0.3f : m_scaleTarget; m_scaleTarget = m_scaleTarget > 2.f ? 2.f : m_scaleTarget; + if (m_config.zoom_smoothness == 0.f) { + m_scroll += (ImGui::GetMousePos() - m_pos) / m_scaleTarget - (ImGui::GetMousePos() - m_pos) / m_scale; m_scale = m_scaleTarget; } } if (abs(m_scaleTarget - m_scale) >= 0.015f / m_config.zoom_smoothness) { + float cs = (m_scaleTarget - m_scale) / m_config.zoom_smoothness; + m_scroll += (ImGui::GetMousePos() - m_pos) / (m_scale + cs) - (ImGui::GetMousePos() - m_pos) / m_scale; m_scale += (m_scaleTarget - m_scale) / m_config.zoom_smoothness; - if (abs(m_scaleTarget - m_scale) < 0.02f) + + if (abs(m_scaleTarget - m_scale) < 0.015f) + { + m_scroll += (ImGui::GetMousePos() - m_pos) / m_scaleTarget - (ImGui::GetMousePos() - m_pos) / m_scale; m_scale = m_scaleTarget; + } } // Zoom reset @@ -172,7 +183,10 @@ inline void ViewPort::end() // Scrolling if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) - m_scroll = m_scroll + ImGui::GetIO().MouseDelta / m_scale; + { + m_scroll += ImGui::GetIO().MouseDelta / m_scale; + m_scrollTarget = m_scroll; + } ImGui::EndChild(); ImGui::PopID(); From 9f4fee3974b82e586340f995aebad5bd297f33f0 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 8 Feb 2024 13:11:24 +0100 Subject: [PATCH 021/116] Major polish-up Refactored confusing method's names. Implemented generic type Pin UID system. Improved documentation. Fixed OutPin graphic bug. Added additional hit-box on pin's circle + little animation. --- documentation.md | 26 +++- include/ImNodeFlow.h | 293 ++++++++++++++++++++++++++++-------------- readme.md | 2 +- src/ImNodeFlow.cpp | 19 ++- src/ImNodeFlow.inl | 81 +++++++++--- src/context_wrapper.h | 4 +- 6 files changed, 297 insertions(+), 128 deletions(-) diff --git a/documentation.md b/documentation.md index a18d0c0..237b630 100644 --- a/documentation.md +++ b/documentation.md @@ -15,6 +15,9 @@ - [Pop-ups 101](#custom-pop-ups) - [Right click](#right-click-pop-up) - [Dropped link](#dropped-link-pop-up) +- [Custom styles 101](#custom-styles) + +*** ## Getting started After having included the necessary files into the project. A few simple steps are necessary. @@ -25,12 +28,17 @@ using namespace ImFlow; ```c++ ImNodeFlow INF; // Create an editor with default name ImNodeFlow INF("Name"); // Create an editor with given name +ImNodeFlow INF = ImNodeFlow("Name"); // Create an editor with given name ``` ```c++ // Inside Dear ImGui loop INF.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. +
A custom size can be specified using `.size(newSize)`. + +*** ## Creating a custom node Custom nodes **must** be derived from the class BaseNode. @@ -62,7 +70,7 @@ explicit CustomNode(. . .) ```c++ int value = ins(n); ``` -Returns a read only reference to the value connected to the nth input pin. +Returns a read only reference to the value associated with the _nth_ input pin. ### Adding output pins ```c++ @@ -125,8 +133,9 @@ Both have their filter set to `int`. ### Adding nodes to the grid It's now time to add our beautifully useless node to the grid. ```c++ -INF.addNode("Node's name", ImVec2(0, 0)); // Add node at canvas coordinates -INF.dropNode("Node's name", ImVec2(0, 0)); // Add node at screen coordinates +INF.addNode("Node's name", ImVec2(0, 0)); // Add node at given canvas coordinates +INF.placeNode("Node's name", ImVec2(0, 0)); // Add node at given screen coordinates +INF.placeNode("Node's name"); // Add node at Mouse position ``` *** @@ -194,6 +203,17 @@ INF.droppedLinkPopUpContent([](Pin* dragged) { *** +## Custom styles +It is possible to change every color and size used by the editor. +```c++ +INF.style() // Get access to all the sizes +INF.style().colors // Get access to all the colors +``` +Sizes and colors can be updated every frame. But it is not possible to change them mid-rendering. +_
It is not possible for example to have links of multiple colors or thickness._ + +*** + _Please refer to the doxygen documentation for a list of public methods and their details._ _In case of problems or questions, consider opening an issue._ \ No newline at end of file diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b48ab23..ea9c379 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,7 @@ namespace ImFlow * @param color Color of the curve * @param thickness Thickness of the curve */ - inline void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness); + inline static void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness); /** * @brief Collider checker for smart_bezier @@ -42,7 +43,7 @@ namespace ImFlow * * Intended to be used in union with smart_bezier(); */ - inline bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius); + inline static bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius); // ----------------------------------------------------------------------------------------------------------------- // CLASSES PRE-DEFINITIONS @@ -57,7 +58,7 @@ namespace ImFlow /** * @brief Basic filters - * @details List of, ready to use,basic filters. It's possible to create more filters with the help of "ConnectionFilter_MakeCustom". + * @details List of, ready to use, basic filters. It's possible to create more filters with the help of "ConnectionFilter_MakeCustom". */ enum ConnectionFilter_ { @@ -88,6 +89,12 @@ namespace ImFlow */ 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. @@ -133,24 +140,39 @@ namespace ImFlow */ struct InfColors { - ImU32 pin_bg = IM_COL32(23, 16, 16, 0); // Color of the background of the Pin - ImU32 pin_hovered = IM_COL32(100, 100, 255, 70); // Color of the overlay to be displayed when Pin is hovered - ImU32 pin_border = IM_COL32(255, 255, 255, 0); // Color of the border to be displayed around the Pin - ImU32 pin_point = IM_COL32(255, 255, 240, 230); // Color of the border to be displayed around the Pin - - ImU32 link = IM_COL32(230, 230, 200, 230); // Color of the link - ImU32 drag_out_link = IM_COL32(230, 230, 200, 230); // Color of the link while dragging - ImU32 link_selected_outline = IM_COL32(80, 20, 255, 200); // Color of the outline of a selected link - - ImU32 node_bg = IM_COL32(97, 103, 122, 100); // Color of the background of the node's body - ImU32 node_header = IM_COL32(23, 16, 16, 150); // Background of the node's header - ImColor node_header_title = ImColor(255, 246, 240, 255); // Text in the node's header - ImU32 node_border = IM_COL32(100, 100, 100, 255); // Node border - ImU32 node_selected_border = IM_COL32(170, 190, 205, 230); // Node border when it's selected - - ImU32 background = IM_COL32(44, 51, 51, 255); // Background of the grid - ImU32 grid = IM_COL32(200, 200, 200, 40); // Color of the main lines of the grid - ImU32 subGrid = IM_COL32(200, 200, 200, 10); // Color of the secondary lines + /// @brief Background of the Pin + ImU32 pin_bg = IM_COL32(23, 16, 16, 0); + /// @brief Overlay to be displayed when Pin is hovered + ImU32 pin_hovered = IM_COL32(100, 100, 255, 70); + /// @brief Border to be displayed around the Pin + ImU32 pin_border = IM_COL32(255, 255, 255, 0); + /// @brief Border to be displayed around the Pin + ImU32 pin_point = IM_COL32(255, 255, 240, 230); + + /// @brief Link + ImU32 link = IM_COL32(230, 230, 200, 230); + /// @brief Link while dragging + ImU32 drag_out_link = IM_COL32(230, 230, 200, 230); + /// @brief Outline of a selected link + ImU32 link_selected_outline = IM_COL32(80, 20, 255, 200); + + /// @brief Background of the node's body + ImU32 node_bg = IM_COL32(97, 103, 122, 100); + /// @brief Background of the node's header + ImU32 node_header = IM_COL32(23, 16, 16, 150); + /// @brief Text in the node's header + ImColor node_header_title = ImColor(255, 246, 240, 255); + /// @brief Node's border + ImU32 node_border = IM_COL32(100, 100, 100, 255); + /// @brief Node's border when it's selected + ImU32 node_selected_border = IM_COL32(170, 190, 205, 230); + + /// @brief Background of the grid + ImU32 background = IM_COL32(44, 51, 51, 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); }; /** @@ -158,26 +180,44 @@ namespace ImFlow */ struct InfStyler { - ImVec2 pin_padding = ImVec2(3.f, 1.f); // Padding between Pin border and content - float pin_radius = 8.f; // Pin's edges rounding - float pin_border_thickness = 1.f; // Thickness of the border drawn around the Pin - float pin_point_radius = 3.5f; // Radius of the circle in front of the Pin when connected - float pin_point_empty_radius = 4.f; // Radius of the circle in front of the Pin when not connected - - float link_thickness = 2.6f; // Thickness of the drawn link - float link_hovered_thickness = 3.5f; // Thickness of the drawn link when hovered - float link_selected_outline_thickness = 0.5f; // Thickness of the outline of a selected Link - float drag_out_link_thickness = 2.f; // Thickness of the dummy link while dragging - - ImVec4 node_padding = ImVec4(9.f, 6.f, 9.f, 2.f); // Padding of Node's content (Left Top Right Bottom) - float node_radius = 8.f; // Node's edges rounding - float node_border_thickness = 1.f; // Node's border thickness - float node_border_selected_thickness = 2.f; // Node's border thickness when selected - - float grid_size = 50.f; // Size of main grid - float grid_subdivisions = 5.f; // Sub-grid divisions for Node snapping - - InfColors colors; // ImNodeFlow colors + /// @brief Padding between Pin border and content + ImVec2 pin_padding = ImVec2(3.f, 1.f); + /// @brief Pin's edges rounding + float pin_radius = 8.f; + /// @brief Thickness of the border drawn around the Pin + float pin_border_thickness = 1.f; + /// @brief Radius of the circle in front of the Pin when connected + float pin_point_radius = 3.5f; + /// @brief Radius of the circle in front of the Pin when not connected + float pin_point_empty_radius = 4.f; + /// @brief Radius of the circle in front of the Pin when not connected and hovered + float pin_point_empty_hovered_radius = 4.67f; + + /// @brief Thickness of the drawn link + float link_thickness = 2.6f; + /// @brief Thickness of the drawn link 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 Thickness of the dummy link while dragging + float drag_out_link_thickness = 2.f; + + /// @brief Padding of Node's content (Left Top Right Bottom) + ImVec4 node_padding = ImVec4(9.f, 6.f, 9.f, 2.f); + /// @brief Node's edges rounding + float node_radius = 8.f; + /// @brief Node's border thickness + float node_border_thickness = 1.f; + /// @brief Node's border thickness when selected + float node_border_selected_thickness = 2.f; + + /// @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; }; /** @@ -190,19 +230,14 @@ namespace ImFlow static int m_instances; public: /** - * @brief Instantiate a new editor - * @details Empty constructor, creates a new Node Editor. Editor name will be "FlowGrid + the number of editors". + * @brief Instantiate a new editor with default name + * + *
Editor name will be "FlowGrid + the number of editors". */ - ImNodeFlow() - { - m_name = "FlowGrid" + std::to_string(m_instances); - m_instances++; - m_context.config().extra_window_wrapper = true; - m_context.config().color = m_style.colors.background; - } + ImNodeFlow() : ImNodeFlow("FlowGrid" + std::to_string(m_instances)) {} /** - * @brief Instantiate a new editor + * @brief Instantiate a new editor with given name * @details Creates a new Node Editor with the given name. * @param name Name of the editor */ @@ -213,18 +248,6 @@ namespace ImFlow m_context.config().color = m_style.colors.background; } - /** - * @brief Instantiate a new editor - * @details Creates a new Node Editor with the given name. - * @param name Name of the editor - */ - explicit ImNodeFlow(const char* name) :m_name(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. @@ -238,28 +261,33 @@ namespace ImFlow * @param pos Position of the Node in canvas coordinates * @return Pointer of the pushed type to the newly added Node * - * Inheritance is checked at compile time, MUST be derived from BaseNode. + * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template T* addNode(const std::string& name, const ImVec2& pos); /** - * @brief Adds a node to the editor + * @brief Adds a node to the editor using mouse position * @tparam T Derived class of to be added * @param name Name to be given to the Node - * @param pos Position of the Node in screen coordinates * @return Pointer of the pushed type to the newly added Node * - * Inheritance is checked at compile time, MUST be derived from BaseNode. + * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* dropNode(const std::string& name, const ImVec2& pos); + T* placeNode(const std::string& name); /** - * @brief Get nodes count - * @return Number of nodes present in the editor + * @brief Adds a node to the editor + * @tparam T Derived class of to be added + * @param name Name to be given to the Node + * @param pos Position of the Node in screen coordinates + * @return Pointer of the pushed type to the newly added Node + * + * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - int nodesCount() { return (int)m_nodes.size(); } + template + T* placeNode(const std::string& name, const ImVec2& pos); /** * @brief Add link to the handler internal list @@ -319,6 +347,12 @@ namespace ImFlow */ const std::vector>& nodes() { return m_nodes; } + /** + * @brief Get nodes count + * @return Number of nodes present in the editor + */ + uint32_t nodesCount() { return (uint32_t)m_nodes.size(); } + /** * @brief Get editor's list of links * @return Const reference to editor's internal links list @@ -329,7 +363,7 @@ namespace ImFlow * @brief Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const ContainedContext& viewport() { return m_context; } + const ContainedContext& context() { return m_context; } /** * @brief Get dragging status @@ -337,6 +371,18 @@ namespace ImFlow */ [[nodiscard]] bool draggingNode() const { return m_draggingNode; } + /** + * @brief Get current style + * @return Reference to style variables + */ + InfStyler& style() { return m_style; } + + /** + * @brief Set editor's size + * @param size Editor's size. Set to (0, 0) to auto-fit. + */ + void size(const ImVec2& size) { m_context.config().size = size; } + /** * @brief Set dragging status * @param state New dragging state @@ -390,12 +436,6 @@ namespace ImFlow * @return [TRUE] if the mouse is not hovering a node or a link */ bool on_free_space(); - - /** - * @brief Get current style - * @return Reference to style variables - */ - InfStyler& style() { return m_style; } private: std::string m_name; ContainedContext m_context; @@ -420,6 +460,8 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE + typedef long long int PinUID; + /** * @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. @@ -458,48 +500,94 @@ namespace ImFlow /** * @brief Add an Input to the node * @details Must be called in the node constructor. 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 his UID. * @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 */ template - void addIN(std::string name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + void addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + + /** + * @brief Add an Input to the node + * @details Must be called in the node constructor. 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. + * @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 + */ + template + void addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); /** * @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 his UID. + *

The UID must be unique only in the context of the current node. * @tparam T Type of the data the pin will handle * @param name Name of the pin * @param filter Connection filter * @return Pointer to the newly added pin. Must be used to set behaviour */ template - [[nodiscard]] OutPin* addOUT(std::string name, ConnectionFilter filter = ConnectionFilter_None); + [[nodiscard]] OutPin* addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None); /** - * @brief Get Input value - * @details Get a reference to the value, the value is stored in the output pin at the other end of the link. + * @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. + * @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 + * @return Pointer to the newly added pin. Must be used to set behaviour + */ + template + [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None); + + /** + * @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 i Index of the pin - * @return Reference to the value + * @tparam U Type of the UID + * @param uid Unique identifier of the pin + * @return Const reference to the value + */ + template + const T& getInVal(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& ins(int i); + const T& getInVal(const char* uid); /** * @brief Get generic reference to input pin - * @param i Index of the pin + * @tparam U Type of the UID + * @param uid Unique identifier of the pin * @return Generic pointer to the pin */ - Pin* ins(int i) { return m_ins[i].get(); } + template + Pin* inPin(U uid) { return m_ins.at(std::hash{}(uid)).get(); } /** * @brief Get generic reference to output pin - * @param i Index of the pin + * @tparam U Type of the UID + * @param uid Unique identifier of the pin * @return Generic pointer to the pin */ - Pin* outs(int i) { return m_outs[i].get(); } + template + Pin* outPin(U uid) { return m_outs.at(std::hash{}(uid)).get(); } /** * @brief Get hovered status @@ -559,8 +647,8 @@ namespace ImFlow ImVec2 m_paddingTL; ImVec2 m_paddingBR; - std::vector> m_ins; - std::vector> m_outs; + std::unordered_map> m_ins; + std::unordered_map> m_outs; }; // ----------------------------------------------------------------------------------------------------------------- @@ -569,10 +657,10 @@ namespace ImFlow /** * @brief Pins type identifier */ - enum PinKind + enum PinType { - PinKind_Input, - PinKind_Output + PinType_Input, + PinType_Output }; /** @@ -589,8 +677,8 @@ namespace ImFlow * @param parent Pointer to the Node containing the pin * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ - explicit Pin(std::string name, ConnectionFilter filter, PinKind kind, BaseNode* parent, ImNodeFlow* inf) - :m_name(std::move(name)), m_filter(filter), m_kind(kind), m_parent(parent), m_inf(inf) {} + explicit Pin(std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf) + : m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf) {} /** * @brief Main loop of the pin @@ -649,7 +737,7 @@ namespace ImFlow * @brief Get pin's type * @return The pin type. Either Input or Output */ - PinKind kind() { return m_kind; } + PinType type() { return m_type; } /** * @brief Get pin's connection filter @@ -678,7 +766,7 @@ namespace ImFlow std::string m_name; ImVec2 m_pos = ImVec2(0.f, 0.f); ImVec2 m_size = ImVec2(0.f, 0.f); - PinKind m_kind; + PinType m_type; ConnectionFilter m_filter; BaseNode* m_parent = nullptr; ImNodeFlow* m_inf; @@ -701,7 +789,7 @@ namespace ImFlow * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ explicit InPin(const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf) - :Pin(name, filter, PinKind_Input, parent, inf), m_emptyVal(defReturn) {} + : Pin(name, filter, PinType_Input, parent, inf), m_emptyVal(defReturn) {} /** * @brief Main loop of the pin @@ -758,7 +846,7 @@ namespace ImFlow * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ explicit OutPin(const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf) - :Pin(name, filter, PinKind_Output, parent, inf) {} + :Pin(name, filter, PinType_Output, parent, inf) {} /** * @brief When parent gets deleted, remove the links @@ -783,6 +871,11 @@ namespace ImFlow */ void setLink(std::shared_ptr& link) override; + /** + * @brief Deletes any expired pointer to a (now deleted) link + */ + void deleteLink() override; + /** * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin diff --git a/readme.md b/readme.md index 4fa40e0..6e0ca38 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26- include(FetchContent) FetchContent_Declare(ImNodeFlow GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" - GIT_TAG "origin/master" + GIT_TAG "v1.1.1" SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" ) FetchContent_MakeAvailable(ImNodeFlow) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index a703bdd..b0e0d8c 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -36,6 +36,11 @@ namespace ImFlow m_right->deleteLink(); } + Link::~Link() + { + m_left->deleteLink(); + } + // ----------------------------------------------------------------------------------------------------------------- // BASE NODE @@ -67,8 +72,8 @@ namespace ImFlow ImGui::BeginGroup(); for(auto& p : m_ins) { - p->pos(ImGui::GetCursorPos()); - p->update(); + p.second->pos(ImGui::GetCursorPos()); + p.second->update(); } ImGui::EndGroup(); ImGui::SameLine(); @@ -84,7 +89,7 @@ namespace ImFlow float maxW = 0.0f; for (auto& p : m_outs) { - float w = p->calcWidth(); + float w = p.second->calcWidth(); if (w > maxW) maxW = w; } @@ -93,10 +98,10 @@ namespace ImFlow { // FIXME: This looks horrible if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) - p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f)); + p.second->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f)); else - p->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); - p->update(); + p.second->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p.second->update(); } ImGui::EndGroup(); @@ -251,7 +256,7 @@ namespace ImFlow m_dragOut = m_hovering; if (m_dragOut) { - if (m_dragOut->kind() == PinKind_Output) + if (m_dragOut->type() == PinType_Output) smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); else smart_bezier(ImGui::GetMousePos(), m_dragOut->pinPoint(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 29d94a3..1055b35 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -37,15 +37,21 @@ namespace ImFlow template T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos) { - static_assert(std::is_base_of::value, "Pushed type is not subclass of BaseNode!"); + static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); m_nodes.emplace_back(std::make_shared(name, pos, this)); return static_cast(m_nodes.back().get()); } template - T* ImNodeFlow::dropNode(const std::string& name, const ImVec2& pos) + T* ImNodeFlow::placeNode(const std::string& name) { - static_assert(std::is_base_of::value, "Pushed type is not subclass of BaseNode!"); + return placeNode(name, ImGui::GetMousePos()); + } + + template + T* ImNodeFlow::placeNode(const std::string& name, const ImVec2& pos) + { + static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); m_nodes.emplace_back(std::make_shared(name, screen2content(pos), this)); return static_cast(m_nodes.back().get()); } @@ -54,20 +60,43 @@ namespace ImFlow // BASE NODE template - void BaseNode::addIN(const std::string name, T defReturn, ConnectionFilter filter) + void BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter) + { + addIN_uid(name, name, defReturn, filter); + } + + template + void BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) { - m_ins.emplace_back(std::make_shared>(name, filter, this, defReturn, m_inf)); + PinUID h = std::hash{}(uid); + m_ins.emplace(std::make_pair(h, std::make_shared>(name, filter, this, defReturn, m_inf))); } template - OutPin* BaseNode::addOUT(const std::string name, ConnectionFilter filter) + OutPin* BaseNode::addOUT(const std::string& name, ConnectionFilter filter) + { + return addOUT_uid(name, name, filter); + } + + template + OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter) + { + PinUID h = std::hash{}(uid); + m_outs.emplace(std::make_pair(h, std::make_shared>(name, filter, this, m_inf))); + return static_cast*>(m_outs.at(h).get()); + } + + template + const T& BaseNode::getInVal(U uid) { - m_outs.emplace_back(std::make_shared>(name, filter, this, m_inf)); - return static_cast*>(m_outs.back().get()); + return static_cast*>(m_ins.at(std::hash{}(uid)).get())->val(); } template - const T& BaseNode::ins(int i) { return static_cast*>(m_ins[i].get())->val(); } + const T& BaseNode::getInVal(const char* uid) + { + return static_cast*>(m_ins.at(std::hash{}(std::string(uid))).get())->val(); + } // ----------------------------------------------------------------------------------------------------------------- // IN PIN @@ -85,6 +114,9 @@ namespace ImFlow void InPin::update() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); + ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); + ImGui::Text(m_name.c_str()); m_size = ImGui::GetItemRectSize(); if (ImGui::IsItemHovered()) @@ -95,16 +127,21 @@ namespace ImFlow if (m_link) draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); else - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + { + if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_hovered_radius, m_inf->style().colors.pin_point); + else + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + } - if (ImGui::IsItemHovered()) + if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) m_inf->hovering(this); } template void InPin::createLink(Pin *other) { - if (other == this || other->kind() == PinKind_Input || m_parent == other->parent()) + if (other == this || other->type() == PinType_Input || m_parent == other->parent()) return; if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter @@ -131,6 +168,8 @@ namespace ImFlow void OutPin::update() { ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); + ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); ImGui::SetCursorScreenPos(m_pos); ImGui::Text(m_name.c_str()); @@ -142,18 +181,23 @@ namespace ImFlow draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_bg, m_inf->style().pin_radius); draw_list->AddRect(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_border, m_inf->style().pin_radius, 0, m_inf->style().pin_border_thickness); if (m_links.empty()) - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + { + if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_hovered_radius, m_inf->style().colors.pin_point); + else + draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + } else draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); - if (ImGui::IsItemHovered()) + if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) m_inf->hovering(this); } template void OutPin::createLink(ImFlow::Pin *other) { - if (other == this || other->kind() == PinKind_Output) + if (other == this || other->type() == PinType_Output) return; other->createLink(this); @@ -164,4 +208,11 @@ namespace ImFlow { m_links.emplace_back(link); } + + template + void OutPin::deleteLink() + { + m_links.erase(std::remove_if(m_links.begin(), m_links.end(), + [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); + } } diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 1a186b7..cbcecb7 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -77,7 +77,7 @@ class ContainedContext ViewPortConfig m_config; ImVec2 m_origin; - ImVec2 m_pos, m_size; + ImVec2 m_pos; ImGuiContext* m_ctx = nullptr; ImGuiContext* m_original_ctx = nullptr; @@ -100,7 +100,7 @@ inline void ContainedContext::begin() ImGui::PushStyleColor(ImGuiCol_ChildBg, m_config.color); ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); ImGui::PopStyleColor(); - m_size = ImGui::GetWindowSize(); +// m_size = ImGui::GetWindowSize(); m_pos = ImGui::GetWindowPos(); ImVec2 size = ImGui::GetContentRegionAvail(); From be0a335d00c5a8057d819ccd71f397f26730d2aa Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 8 Feb 2024 13:44:42 +0100 Subject: [PATCH 022/116] Corrected documentation to use new UID system --- documentation.md | 34 +++++++++++++++++++++++++++++----- readme.md | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/documentation.md b/documentation.md index 237b630..8613dd3 100644 --- a/documentation.md +++ b/documentation.md @@ -62,24 +62,41 @@ The constructor is standard and must **not** be changed. ```c++ explicit CustomNode(. . .) { - addIN("Pin name", 0, Connection filter); + addIN("Pin name", 0, Connection filter); // The name is also used as the UID + addIN(uid, "Pin name", 0, Connection filter); // Custom UID of generic type } ``` `addIN` will add an input pin to the node. Usually called in the node's constructor. +
UIDs must be unique but only in the context of the inputs of the current node +_(an output or an input of another node can have the same uid)_ +
The UID can be any of type and of different types between pins. #### Getting the value +BaseNode provides the following getter ```c++ -int value = ins(n); +int value = getInVal(uid); ``` -Returns a read only reference to the value associated with the _nth_ input pin. +Returns a read only reference to the value associated with the input pin identified with given uid. +
_Refer to the doxygen documentation for more details on the different use cases_ +#### Referencing the pin +BaseNode provides the following getter +```c++ +Pin* pin = inPin(uid); // From inside the node +Pin* pin = node.inPin(uid); // Elsewhere +``` +Returns a generic pin type pointer to the input pin identified with given uid. ### Adding output pins ```c++ explicit CustomNode(. . .) { - addOUT("Pin name", Connection filter); + addOUT("Pin name", Connection filter); // The name is also used as the UID + addOUT(uid, "Pin name", Connection filter); // Custom UID of generic type } ``` `addOUT` will add an output pin to the node. Usually called in the node's constructor. +
UIDs must be unique but only in the context of the inputs of the current node +_(an output or an input of another node can have the same uid)_ +
The UID can be any of type and of different types between pins. #### Defining logic ```c++ behaviour([this](){ return . . .; }); @@ -93,6 +110,13 @@ addOUT("Pin name", Connection filter) ``` Creates a pin with given name and filter and sets its logic.
This pin will be rather useless since it always returns 0 (also known as the author's IQ). +#### Referencing the pin +BaseNode provides the following getter +```c++ +Pin* pin = outPin(uid); // From inside the node +Pin* pin = node.outPin(uid); // Elsewhere +``` +Returns a generic pin type pointer to the output pin identified with given uid. ### Node's body ```c++ @@ -112,7 +136,7 @@ public: { addIN("IN_VAL", 0, ConnectionFilter_Int); addOUT("OUT_VAL", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_valB; }); + ->behaviour([this](){ return getInVal("IN_VAL") + m_valB; }); } void draw() override diff --git a/readme.md b/readme.md index 6e0ca38..c4e2edd 100644 --- a/readme.md +++ b/readme.md @@ -54,7 +54,7 @@ public: { addIN("IN_VAL", 0, ConnectionFilter_Int); addOUT("OUT_VAL", ConnectionFilter_Int) - ->behaviour([this](){ return ins(0) + m_valB; }); + ->behaviour([this](){ return getInVal("IN_VAL") + m_valB; }); } void draw() override From d1ae6f0f7dfc4c97a1af611a19a13c110a30bcab Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 8 Feb 2024 19:04:27 +0100 Subject: [PATCH 023/116] Fixed bug with string type UID --- include/ImNodeFlow.h | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index ea9c379..b8d2cc5 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -460,7 +460,7 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - typedef long long int PinUID; + typedef unsigned long long int PinUID; /** * @brief Parent class for custom nodes @@ -580,6 +580,13 @@ namespace ImFlow template Pin* inPin(U uid) { return m_ins.at(std::hash{}(uid)).get(); } + /** + * @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) { return m_ins.at(std::hash{}(std::string(uid))).get(); } + /** * @brief Get generic reference to output pin * @tparam U Type of the UID @@ -589,6 +596,13 @@ namespace ImFlow template Pin* outPin(U uid) { return m_outs.at(std::hash{}(uid)).get(); } + /** + * @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) { return m_outs.at(std::hash{}(std::string(uid))).get(); } + /** * @brief Get hovered status * @return [TRUE] if the mouse is hovering the node @@ -625,6 +639,12 @@ namespace ImFlow */ [[nodiscard]] bool dragged() const { return m_dragged; } + /** + * @brief Set node's name + * @param name New name + */ + void name(const std::string& name) { m_name = name; } + /** * @brief Set selected status * @param state New selected state From 590f09a0588d08a19e8390c5c971a112b914d107 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 11 Feb 2024 12:33:43 +0100 Subject: [PATCH 024/116] Added more customizable parameters --- src/context_wrapper.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index cbcecb7..49d1d71 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -56,6 +56,9 @@ struct ViewPortConfig ImVec2 size = {0.f, 0.f}; ImU32 color = IM_COL32_WHITE; bool zoom_enabled = true; + float zoom_min = 0.3f; + float zoom_max = 2.f; + float zoom_divisions = 10.f; float zoom_smoothness = 5.f; float default_zoom = 1.f; ImGuiKey reset_zoom_key = ImGuiKey_R; @@ -154,9 +157,9 @@ inline void ContainedContext::end() // Zooming if (m_config.zoom_enabled && m_hovered && ImGui::GetIO().MouseWheel != 0.f) { - m_scaleTarget += ImGui::GetIO().MouseWheel / 16; - m_scaleTarget = m_scaleTarget < 0.3f ? 0.3f : m_scaleTarget; - m_scaleTarget = m_scaleTarget > 2.f ? 2.f : m_scaleTarget; + m_scaleTarget += ImGui::GetIO().MouseWheel / m_config.zoom_divisions; + m_scaleTarget = m_scaleTarget < m_config.zoom_min ? m_config.zoom_min : m_scaleTarget; + m_scaleTarget = m_scaleTarget > m_config.zoom_max ? m_config.zoom_max : m_scaleTarget; if (m_config.zoom_smoothness == 0.f) { @@ -170,7 +173,7 @@ inline void ContainedContext::end() m_scroll += (ImGui::GetMousePos() - m_pos) / (m_scale + cs) - (ImGui::GetMousePos() - m_pos) / m_scale; m_scale += (m_scaleTarget - m_scale) / m_config.zoom_smoothness; - if (abs(m_scaleTarget - m_scale) < 0.015f) + if (abs(m_scaleTarget - m_scale) < 0.015f / m_config.zoom_smoothness) { m_scroll += (ImGui::GetMousePos() - m_pos) / m_scaleTarget - (ImGui::GetMousePos() - m_pos) / m_scale; m_scale = m_scaleTarget; From 904b355e15df19e64d9d9e7cf10a6e3cefd2e450 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 11 Feb 2024 12:34:14 +0100 Subject: [PATCH 025/116] Added optional custom renderer for Pins --- include/ImNodeFlow.h | 9 ++++++--- src/ImNodeFlow.cpp | 2 -- src/ImNodeFlow.inl | 31 ++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b8d2cc5..9992977 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -507,7 +507,7 @@ namespace ImFlow * @param filter Connection filter */ template - void addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + InPin* addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); /** * @brief Add an Input to the node @@ -521,7 +521,7 @@ namespace ImFlow * @param filter Connection filter */ template - void addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); /** * @brief Add an Output to the node @@ -706,6 +706,8 @@ namespace ImFlow */ virtual void update() = 0; + Pin* renderer(std::function r) { m_renderer = std::move(r); return this; } + /** * @brief Create link between pins * @param other Pointer to the other pin @@ -790,6 +792,7 @@ namespace ImFlow ConnectionFilter m_filter; BaseNode* m_parent = nullptr; ImNodeFlow* m_inf; + std::function m_renderer; }; /** @@ -913,7 +916,7 @@ namespace ImFlow * @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 to be called by val() */ - void behaviour(std::function func) { m_behaviour = std::move(func); } + OutPin* behaviour(std::function func) { m_behaviour = std::move(func); return this; } private: std::vector> m_links; std::function m_behaviour; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b0e0d8c..daab8c4 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -210,8 +210,6 @@ namespace ImFlow ImVec2 offset = ImGui::GetCursorScreenPos() + m_context.scroll(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddCircleFilled(ImVec2(0,0) + m_context.scroll(), 5.f, IM_COL32_WHITE); - // Display grid ImVec2 win_pos = ImGui::GetCursorScreenPos(); ImVec2 canvas_sz = ImGui::GetWindowSize(); diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 1055b35..f1bc3f1 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -60,16 +60,17 @@ namespace ImFlow // BASE NODE template - void BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter) + InPin* BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter) { - addIN_uid(name, name, defReturn, filter); + return addIN_uid(name, name, defReturn, filter); } template - void BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) + InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) { PinUID h = std::hash{}(uid); m_ins.emplace(std::make_pair(h, std::make_shared>(name, filter, this, defReturn, m_inf))); + return static_cast*>(m_ins.at(h).get()); } template @@ -113,6 +114,18 @@ namespace ImFlow template void InPin::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; + } + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); @@ -167,6 +180,18 @@ namespace ImFlow template void OutPin::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; + } + ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); From 8c2a3601272224aa32813fb7084c727cc7fefbda Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 11 Feb 2024 12:56:12 +0100 Subject: [PATCH 026/116] Reworked internal pin UID logic --- include/ImNodeFlow.h | 12 ++++++------ src/ImNodeFlow.inl | 46 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 9992977..d22e209 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -578,14 +578,14 @@ namespace ImFlow * @return Generic pointer to the pin */ template - Pin* inPin(U uid) { return m_ins.at(std::hash{}(uid)).get(); } + Pin* inPin(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) { return m_ins.at(std::hash{}(std::string(uid))).get(); } + Pin* inPin(const char* uid); /** * @brief Get generic reference to output pin @@ -594,14 +594,14 @@ namespace ImFlow * @return Generic pointer to the pin */ template - Pin* outPin(U uid) { return m_outs.at(std::hash{}(uid)).get(); } + Pin* outPin(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) { return m_outs.at(std::hash{}(std::string(uid))).get(); } + Pin* outPin(const char* uid); /** * @brief Get hovered status @@ -667,8 +667,8 @@ namespace ImFlow ImVec2 m_paddingTL; ImVec2 m_paddingBR; - std::unordered_map> m_ins; - std::unordered_map> m_outs; + std::vector>> m_ins; + std::vector>> m_outs; }; // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index f1bc3f1..a68a93b 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -69,8 +69,8 @@ namespace ImFlow InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) { PinUID h = std::hash{}(uid); - m_ins.emplace(std::make_pair(h, std::make_shared>(name, filter, this, defReturn, m_inf))); - return static_cast*>(m_ins.at(h).get()); + m_ins.emplace_back(std::make_pair(h, std::make_shared>(name, filter, this, defReturn, m_inf))); + return static_cast*>(m_ins.back().second.get()); } template @@ -83,20 +83,54 @@ namespace ImFlow OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter) { PinUID h = std::hash{}(uid); - m_outs.emplace(std::make_pair(h, std::make_shared>(name, filter, this, m_inf))); - return static_cast*>(m_outs.at(h).get()); + m_outs.emplace_back(std::make_pair(h, std::make_shared>(name, filter, this, m_inf))); + return static_cast*>(m_outs.back().second.get()); } template const T& BaseNode::getInVal(U uid) { - return static_cast*>(m_ins.at(std::hash{}(uid)).get())->val(); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(uid); }); + return static_cast*>(it->second.get())->val(); } template const T& BaseNode::getInVal(const char* uid) { - return static_cast*>(m_ins.at(std::hash{}(std::string(uid))).get())->val(); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(std::string(uid)); }); + return static_cast*>(it->second.get())->val(); + } + + template + Pin* BaseNode::inPin(U uid) + { + return std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(uid); }) + ->second.get(); + } + + inline Pin* BaseNode::inPin(const char* uid) + { + return std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(std::string(uid)); }) + ->second.get(); + } + + template + Pin* BaseNode::outPin(U uid) + { + return std::find_if(m_outs.begin(), m_outs.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(uid); }) + ->second.get(); + } + + inline Pin* BaseNode::outPin(const char* uid) + { + return std::find_if(m_outs.begin(), m_outs.end(), [&uid](std::pair>& p) + { return p.first == std::hash{}(std::string(uid)); }) + ->second.get(); } // ----------------------------------------------------------------------------------------------------------------- From dddcac0f0e8ea0d0e205abec48bb5d614dcff827 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 11 Feb 2024 13:29:27 +0100 Subject: [PATCH 027/116] Added ability to create feedback loops and same-node links --- include/ImNodeFlow.h | 30 +++++++++++++++++++++--------- src/ImNodeFlow.cpp | 4 ++++ src/ImNodeFlow.inl | 4 ++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index d22e209..e9f6cc5 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -14,9 +14,6 @@ #include "../src/imgui_bezier_math.h" #include "../src/context_wrapper.h" -// TODO: [POLISH] Collision solver to bring first node on foreground to avoid clipping -// TODO: [EXTRA] Custom renderers for Pins (with lambdas I think) - namespace ImFlow { // ----------------------------------------------------------------------------------------------------------------- @@ -62,12 +59,13 @@ namespace ImFlow */ enum ConnectionFilter_ { - ConnectionFilter_None = 0, - ConnectionFilter_Int = 1 << 1, - ConnectionFilter_Float = 1 << 2, - ConnectionFilter_Double = 1 << 3, - ConnectionFilter_String = 1 << 4, - ConnectionFilter_MakeCustom = 1 << 5, + ConnectionFilter_None = 0, + ConnectionFilter_SameNode = 1 << 1, + ConnectionFilter_Int = 1 << 2, + ConnectionFilter_Float = 1 << 3, + ConnectionFilter_Double = 1 << 4, + ConnectionFilter_String = 1 << 5, + ConnectionFilter_MakeCustom = 1 << 6, ConnectionFilter_Numbers = ConnectionFilter_Int | ConnectionFilter_Float | ConnectionFilter_Double }; typedef long ConnectionFilter; @@ -706,6 +704,15 @@ namespace ImFlow */ virtual void update() = 0; + /** + * @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; } /** @@ -882,6 +889,11 @@ namespace ImFlow */ void update() override; + /** + * @brief Calculate value based on set behaviour + */ + void resolve() override { m_val = m_behaviour(); } + /** * @brief Create link between pins * @param other Pointer to the other pin diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index daab8c4..fd27150 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -151,6 +151,10 @@ namespace ImFlow } } ImGui::PopID(); + + // Resolve output pins values + for (auto& p : m_outs) + p.second->resolve(); } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index a68a93b..8981b54 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -188,7 +188,7 @@ namespace ImFlow template void InPin::createLink(Pin *other) { - if (other == this || other->type() == PinType_Input || m_parent == other->parent()) + if (other == this || other->type() == PinType_Input || (m_parent == other->parent() && (m_filter & ConnectionFilter_SameNode) == 0)) return; if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter @@ -209,7 +209,7 @@ namespace ImFlow // OUT PIN template - const T &OutPin::val() { m_val = m_behaviour(); return m_val; } + const T &OutPin::val() { return m_val; } template void OutPin::update() From 3213c7d111dd71af2690297db2175ae8eb40a907 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Sun, 11 Feb 2024 11:37:14 -0800 Subject: [PATCH 028/116] BaseNode ctor to source file BaseNode ctor is non-constexpr/inline so it would violate ODR. Move it into the accompanying source file to fix. --- include/ImNodeFlow.h | 8 +------- src/ImNodeFlow.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b8d2cc5..b67a375 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -475,13 +475,7 @@ namespace ImFlow * @param pos Position in grid coordinates * @param inf Pointer to the Grid Handler the node is in */ - explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) - :m_name(std::move(name)), m_pos(pos), m_inf(inf) - { - m_paddingTL = {m_inf->style().node_padding.x, m_inf->style().node_padding.y}; - m_paddingBR = {m_inf->style().node_padding.z, m_inf->style().node_padding.w}; - m_posTarget = m_pos; - } + explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf); /** * @brief Main loop of the node diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b0e0d8c..e1d19cd 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -44,6 +44,14 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE + BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) + :m_name(std::move(name)), m_pos(pos), m_inf(inf) + { + m_paddingTL = {m_inf->style().node_padding.x, m_inf->style().node_padding.y}; + m_paddingBR = {m_inf->style().node_padding.z, m_inf->style().node_padding.w}; + m_posTarget = m_pos; + } + bool BaseNode::hovered() { return ImGui::IsMouseHoveringRect(m_inf->content2canvas(m_pos - m_paddingTL), m_inf->content2canvas(m_pos + m_size + m_paddingBR)); From d3f49dbabeac2114db840871341d6cd541b2aa83 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Sun, 11 Feb 2024 11:38:51 -0800 Subject: [PATCH 029/116] Forwarding additional params to ctor Use std::forward and further template addNode to allow variable arguments to be passed to the derived class constructor. Future improvement might be to put the definition in the header so it's a viable candidate for inlining. --- include/ImNodeFlow.h | 4 ++-- src/ImNodeFlow.inl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b67a375..7c20675 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -263,8 +263,8 @@ namespace ImFlow * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - template - T* addNode(const std::string& name, const ImVec2& pos); + template + T* addNode(const std::string& name, const ImVec2& pos, Params&&... args); /** * @brief Adds a node to the editor using mouse position diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 1055b35..a7121bc 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -34,11 +34,11 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // HANDLER - template - T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos) + template + T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, pos, this)); + m_nodes.emplace_back(std::make_shared(name, pos, this, std::forward(args)...)); return static_cast(m_nodes.back().get()); } From ae9c6a7f63c0d16d74e649cb7d0a02d1316aa449 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Sun, 11 Feb 2024 11:51:48 -0800 Subject: [PATCH 030/116] Documentation update for custom nodes --- documentation.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/documentation.md b/documentation.md index 8613dd3..14ed1f8 100644 --- a/documentation.md +++ b/documentation.md @@ -41,22 +41,26 @@ This will only render the node editor, so it must be called inside a Dear ImGui *** ## Creating a custom node -Custom nodes **must** be derived from the class BaseNode. +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. Descriptions for this required structure are in the subsequent sections. ```c++ -class CustomNode : public BaseNode +class CustomNode : public ImFlow::BaseNode { -public: - . . . -private: - . . . + explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) + : BaseNode(name, pos, inf) { /* omitted */} + void draw() override { /* omitted */ } }; ``` ### The constructor ```c++ -explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf); +explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) +: BaseNode(name, pos, inf) { /* omitted */ } +``` +The first 3 arguments to the constructor are standard and must **not** deviate. You are free to add additional arguments after the first 3 to support custom behavior. This is to ensure nodes construct correctly. +```c++ +explicit InputAveragingNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf, int numInputs) +: BaseNode(name, pos, inf) { /* omitted */ } ``` -The constructor is standard and must **not** be changed. ### Adding input pins ```c++ @@ -240,4 +244,4 @@ _
It is not possible for example to have links of multiple colors or thickne _Please refer to the doxygen documentation for a list of public methods and their details._ -_In case of problems or questions, consider opening an issue._ \ No newline at end of file +_In case of problems or questions, consider opening an issue._ From 8bb38e4c5a20873271fa768eed03639c5bed854f Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Sun, 11 Feb 2024 16:53:25 -0800 Subject: [PATCH 031/116] Update code docs and support rest of interface --- include/ImNodeFlow.h | 16 ++++++++++++---- src/ImNodeFlow.inl | 14 ++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7c20675..aaa7331 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -257,8 +257,11 @@ namespace ImFlow /** * @brief Adds a node to the editor * @tparam T Derived class of to be added + * @tparam Params types of optional args to forward to derived class ctor + * * @param name Name to be given to the Node * @param pos Position of the Node in canvas coordinates + * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. @@ -269,25 +272,30 @@ namespace ImFlow /** * @brief Adds a node to the editor using mouse position * @tparam T Derived class of to be added + * @tparam Params types of optional args to forward to derived class ctor + * * @param name Name to be given to the Node + * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - template - T* placeNode(const std::string& name); + template + T* placeNode(const std::string& name, Params&&... args); /** * @brief Adds a node to the editor * @tparam T Derived class of to be added + * @tparam Params types of optional args to forward to derived class ctor * @param name Name to be given to the Node * @param pos Position of the Node in screen coordinates + * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - template - T* placeNode(const std::string& name, const ImVec2& pos); + template + T* placeNode(const std::string& name, const ImVec2& pos, Params&&... args); /** * @brief Add link to the handler internal list diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index a7121bc..b923485 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -42,18 +42,16 @@ namespace ImFlow return static_cast(m_nodes.back().get()); } - template - T* ImNodeFlow::placeNode(const std::string& name) + template + T* ImNodeFlow::placeNode(const std::string& name, Params&&... args) { - return placeNode(name, ImGui::GetMousePos()); + return placeNode(name, ImGui::GetMousePos(), std::forward(args)...); } - template - T* ImNodeFlow::placeNode(const std::string& name, const ImVec2& pos) + template + T* ImNodeFlow::placeNode(const std::string& name, const ImVec2& pos, Params&&... args) { - static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, screen2content(pos), this)); - return static_cast(m_nodes.back().get()); + return addNode(name, screen2content(pos), std::forward(args)...); } // ----------------------------------------------------------------------------------------------------------------- From a6eef19d2d4d673e674587c5bc4b627181e1f0ad Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 12 Feb 2024 09:39:44 +0100 Subject: [PATCH 032/116] Dynamic InPin test and also added assertions to pin getters and reworked UID system. --- documentation.md | 19 ++++++++-- include/ImNodeFlow.h | 30 ++++++++++------ src/ImNodeFlow.cpp | 30 +++++++++++----- src/ImNodeFlow.inl | 83 +++++++++++++++++++++++++++++++------------- 4 files changed, 117 insertions(+), 45 deletions(-) diff --git a/documentation.md b/documentation.md index 14ed1f8..50302d2 100644 --- a/documentation.md +++ b/documentation.md @@ -9,6 +9,10 @@ - [Body](#nodes-body) - [Example](#what-have-we-learned) - [Adding it](#adding-nodes-to-the-grid) +- [Pins 101](#pins) + - [Static vs Dynamic](#static-vs-dynamic) + - [Custom rendering](#custom-rendering) +- [Editor Handling 101](#handling-the-editor) - [Connection filters 101](#custom-filters) - [Basic filters](#basic-filters) - [Creating filters](#creating-more-filters) @@ -67,7 +71,7 @@ explicit InputAveragingNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf explicit CustomNode(. . .) { addIN("Pin name", 0, Connection filter); // The name is also used as the UID - addIN(uid, "Pin name", 0, Connection filter); // Custom UID of generic type + addIN_uid(uid, "Pin name", 0, Connection filter); // Custom UID of generic type } ``` `addIN` will add an input pin to the node. Usually called in the node's constructor. @@ -94,7 +98,7 @@ Returns a generic pin type pointer to the input pin identified with given uid. explicit CustomNode(. . .) { addOUT("Pin name", Connection filter); // The name is also used as the UID - addOUT(uid, "Pin name", Connection filter); // Custom UID of generic type + addOUT_uid(uid, "Pin name", Connection filter); // Custom UID of generic type } ``` `addOUT` will add an output pin to the node. Usually called in the node's constructor. @@ -168,6 +172,17 @@ INF.placeNode("Node's name"); // Add node at Mouse position *** +## Pins +### Static vs Dynamic + +### Custom rendering + +*** + +## Handling the editor + +*** + ## Custom filters Filters can be used to block unwanted links between pins. ### Basic filters diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 1876b7b..5b69bec 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -3,9 +3,7 @@ #pragma once #include -#include #include -#include #include #include #include @@ -293,7 +291,7 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* placeNode(const std::string& name, const ImVec2& pos, Params&&... args); + T* placeNodeAt(const std::string& name, const ImVec2& pos, Params&&... args); /** * @brief Add link to the handler internal list @@ -523,6 +521,12 @@ namespace ImFlow template InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + template + const T& showIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + + template + const T& showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + /** * @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. @@ -667,8 +671,9 @@ namespace ImFlow ImVec2 m_paddingTL; ImVec2 m_paddingBR; - std::vector>> m_ins; - std::vector>> m_outs; + std::vector> m_ins; + std::vector>> m_dynamicIns; + std::vector> m_outs; }; // ----------------------------------------------------------------------------------------------------------------- @@ -697,8 +702,8 @@ namespace ImFlow * @param parent Pointer to the Node containing the pin * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ - explicit Pin(std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf) - : m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf) {} + explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf) + :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf) {} /** * @brief Main loop of the pin @@ -740,6 +745,8 @@ namespace ImFlow */ virtual std::weak_ptr getLink() { return std::weak_ptr{}; } + PinUID uid() { return m_uid; } + /** * @brief Get pin's name * @return Const reference to pin's name @@ -794,6 +801,7 @@ namespace ImFlow */ void pos(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); @@ -820,8 +828,8 @@ namespace ImFlow * @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) */ - explicit InPin(const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf) - : Pin(name, filter, PinType_Input, parent, inf), m_emptyVal(defReturn) {} + explicit InPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf) + : Pin(uid, name, filter, PinType_Input, parent, inf), m_emptyVal(defReturn) {} /** * @brief Main loop of the pin @@ -877,8 +885,8 @@ namespace ImFlow * @param parent Pointer to the Node containing the pin * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ - explicit OutPin(const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf) - :Pin(name, filter, PinType_Output, parent, inf) {} + explicit OutPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf) + :Pin(uid, name, filter, PinType_Output, parent, inf) {} /** * @brief When parent gets deleted, remove the links diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 924a510..4a159e5 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -78,10 +78,19 @@ namespace ImFlow // Inputs ImGui::BeginGroup(); - for(auto& p : m_ins) + for (auto& p : m_ins) { - p.second->pos(ImGui::GetCursorPos()); - p.second->update(); + p->pos(ImGui::GetCursorPos()); + p->update(); + } + for (auto& p : m_dynamicIns) + { + if (p.first == 1) + { + p.second->pos(ImGui::GetCursorPos()); + p.second->update(); + p.first = 0; + } } ImGui::EndGroup(); ImGui::SameLine(); @@ -97,7 +106,7 @@ namespace ImFlow float maxW = 0.0f; for (auto& p : m_outs) { - float w = p.second->calcWidth(); + float w = p->calcWidth(); if (w > maxW) maxW = w; } @@ -106,10 +115,10 @@ namespace ImFlow { // FIXME: This looks horrible if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) - p.second->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f)); + p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f)); else - p.second->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); - p.second->update(); + p->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p->update(); } ImGui::EndGroup(); @@ -162,7 +171,12 @@ namespace ImFlow // Resolve output pins values for (auto& p : m_outs) - p.second->resolve(); + p->resolve(); + + // 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()); } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 3a89385..8b14aeb 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -45,11 +45,11 @@ namespace ImFlow template T* ImNodeFlow::placeNode(const std::string& name, Params&&... args) { - return placeNode(name, ImGui::GetMousePos(), std::forward(args)...); + return placeNodeAt(name, ImGui::GetMousePos(), std::forward(args)...); } template - T* ImNodeFlow::placeNode(const std::string& name, const ImVec2& pos, Params&&... args) + T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, Params&&... args) { return addNode(name, screen2content(pos), std::forward(args)...); } @@ -67,8 +67,31 @@ namespace ImFlow InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) { PinUID h = std::hash{}(uid); - m_ins.emplace_back(std::make_pair(h, std::make_shared>(name, filter, this, defReturn, m_inf))); - return static_cast*>(m_ins.back().second.get()); + m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf)); + return static_cast*>(m_ins.back().get()); + } + + template + const T& BaseNode::showIN(const std::string& name, T defReturn, ConnectionFilter filter) + { + return showIN_uid(name, name, defReturn, filter); + } + + template + const T& BaseNode::showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) + { + PinUID h = std::hash{}(uid); + for (std::pair>& p : m_dynamicIns) + { + if (p.second->uid() == h) + { + p.first = 1; + return static_cast*>(p.second.get())->val(); + } + } + + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf))); + return static_cast*>(m_dynamicIns.back().second.get())->val(); } template @@ -81,54 +104,66 @@ namespace ImFlow OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter) { PinUID h = std::hash{}(uid); - m_outs.emplace_back(std::make_pair(h, std::make_shared>(name, filter, this, m_inf))); - return static_cast*>(m_outs.back().second.get()); + m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf)); + return static_cast*>(m_outs.back().get()); } template const T& BaseNode::getInVal(U uid) { - auto it = std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(uid); }); - return static_cast*>(it->second.get())->val(); + PinUID h = std::hash{}(uid); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_ins.end() && "Pin UID not found!"); + return static_cast*>(it->get())->val(); } template const T& BaseNode::getInVal(const char* uid) { - auto it = std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(std::string(uid)); }); - return static_cast*>(it->second.get())->val(); + PinUID h = std::hash{}(std::string(uid)); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_ins.end() && "Pin UID not found!"); + return static_cast*>(it->get())->val(); } template Pin* BaseNode::inPin(U uid) { - return std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(uid); }) - ->second.get(); + PinUID h = std::hash{}(uid); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_ins.end() && "Pin UID not found!"); + return it->get(); } inline Pin* BaseNode::inPin(const char* uid) { - return std::find_if(m_ins.begin(), m_ins.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(std::string(uid)); }) - ->second.get(); + PinUID h = std::hash{}(std::string(uid)); + auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_ins.end() && "Pin UID not found!"); + return it->get(); } template Pin* BaseNode::outPin(U uid) { - return std::find_if(m_outs.begin(), m_outs.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(uid); }) - ->second.get(); + PinUID h = std::hash{}(uid); + auto it = std::find_if(m_outs.begin(), m_outs.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_outs.end() && "Pin UID not found!"); + return it->get(); } inline Pin* BaseNode::outPin(const char* uid) { - return std::find_if(m_outs.begin(), m_outs.end(), [&uid](std::pair>& p) - { return p.first == std::hash{}(std::string(uid)); }) - ->second.get(); + PinUID h = std::hash{}(std::string(uid)); + auto it = std::find_if(m_outs.begin(), m_outs.end(), [&h](std::shared_ptr& p) + { return p->uid() == h; }); + assert(it != m_outs.end() && "Pin UID not found!"); + return it->get(); } // ----------------------------------------------------------------------------------------------------------------- From 22a4535693b721fcff2a64bfa83128099d2b32c1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 12 Feb 2024 10:09:26 +0100 Subject: [PATCH 033/116] Dynamic OutPin test --- include/ImNodeFlow.h | 7 +++++++ src/ImNodeFlow.cpp | 22 ++++++++++++++++++++++ src/ImNodeFlow.inl | 23 +++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 5b69bec..11eb738 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -554,6 +554,12 @@ namespace ImFlow template [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None); + template + void showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None); + + template + void showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None); + /** * @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. @@ -674,6 +680,7 @@ namespace ImFlow std::vector> m_ins; std::vector>> m_dynamicIns; std::vector> m_outs; + std::vector>> m_dynamicOuts; }; // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 4a159e5..2c281d9 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -110,6 +110,12 @@ namespace ImFlow 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) { @@ -120,6 +126,17 @@ namespace ImFlow p->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); p->update(); } + for (auto& p :m_dynamicOuts) + { + // FIXME: This looks horrible + if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) + p.second->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f)); + else + p.second->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p.second->update(); + p.first -= 1; + } + ImGui::EndGroup(); ImGui::EndGroup(); @@ -172,11 +189,16 @@ namespace ImFlow // Resolve output pins values for (auto& p : m_outs) p->resolve(); + for (auto& p :m_dynamicOuts) + p.second->resolve(); // 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()); } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 8b14aeb..d9c9510 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -108,6 +108,29 @@ namespace ImFlow return static_cast*>(m_outs.back().get()); } + template + void BaseNode::showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter) + { + showOUT_uid(name, name, std::move(behaviour), filter); + } + + template + void BaseNode::showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter) + { + PinUID h = std::hash{}(uid); + for (std::pair>& p : m_dynamicOuts) + { + if (p.second->uid() == h) + { + p.first = 2; + return; + } + } + + m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf))); + static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); + } + template const T& BaseNode::getInVal(U uid) { From 3557be613254e85474aad1c5aaa044be57d2bc61 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Tue, 13 Feb 2024 16:47:03 +0100 Subject: [PATCH 034/116] Renamed getters and setters --- include/ImNodeFlow.h | 58 ++++++++++++++++++++++---------------------- src/ImNodeFlow.cpp | 54 ++++++++++++++++++++--------------------- src/ImNodeFlow.inl | 54 ++++++++++++++++++++--------------------- 3 files changed, 83 insertions(+), 83 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 11eb738..e3a92cc 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -113,13 +113,13 @@ namespace ImFlow * @brief Get hovering status * @return [TRUE] If the link is hovered in the current frame */ - [[nodiscard]] bool hovered() const { return m_hovered; } + [[nodiscard]] bool isHovered() const { return m_hovered; } /** * @brief Get selected status * @return [TRUE] If the link is selected in the current frame */ - [[nodiscard]] bool selected() const { return m_selected; } + [[nodiscard]] bool isSelected() const { return m_selected; } private: ImNodeFlow* m_inf; Pin* m_left; @@ -330,62 +330,62 @@ namespace ImFlow * @brief Get editor's name * @return Const reference to editor's name */ - const std::string& name() { return m_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& pos() { return m_context.origin(); } + 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 with the middle mouse. * @return Const reference to editor's grid scroll */ - const ImVec2& scroll() { return m_context.scroll(); } + const ImVec2& getScroll() { return m_context.scroll(); } /** * @brief Get editor's list of nodes * @return Const reference to editor's internal nodes list */ - const std::vector>& nodes() { return m_nodes; } + const std::vector>& getNodes() { return m_nodes; } /** * @brief Get nodes count * @return Number of nodes present in the editor */ - uint32_t nodesCount() { return (uint32_t)m_nodes.size(); } + 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>& links() { return m_links; } + const std::vector>& getLinks() { return m_links; } /** * @brief Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const ContainedContext& context() { return m_context; } + const ContainedContext& getGrid() { return m_context; } /** * @brief Get dragging status * @return [TRUE] if a Node is being dragged around the grid */ - [[nodiscard]] bool draggingNode() const { return m_draggingNode; } + [[nodiscard]] bool isNodeDragged() const { return m_draggingNode; } /** * @brief Get current style * @return Reference to style variables */ - InfStyler& style() { return m_style; } + InfStyler& getStyle() { return m_style; } /** * @brief Set editor's size * @param size Editor's size. Set to (0, 0) to auto-fit. */ - void size(const ImVec2& size) { m_context.config().size = size; } + void setSize(const ImVec2& size) { m_context.config().size = size; } /** * @brief Set dragging status @@ -617,43 +617,43 @@ namespace ImFlow * @brief Get hovered status * @return [TRUE] if the mouse is hovering the node */ - bool hovered(); + bool isHovered(); /** * @brief Get node name * @return Const reference to the node's name */ - const std::string& name() { return m_name; } + const std::string& getName() { return m_name; } /** * @brief Get node size * @return Const reference to the node's size */ - const ImVec2& size() { return m_size; } + const ImVec2& getSize() { return m_size; } /** * @brief Get node position * @return Const reference to the node's position */ - const ImVec2& pos() { return m_pos; } + const ImVec2& getPos() { return m_pos; } /** * @brief Get selected status * @return [TRUE] if the node is selected */ - [[nodiscard]] bool selected() const { return m_selected; } + [[nodiscard]] bool isSelected() const { return m_selected; } /** * @brief Get dragged status * @return [TRUE] if the node is being dragged */ - [[nodiscard]] bool dragged() const { return m_dragged; } + [[nodiscard]] bool isDragged() const { return m_dragged; } /** * @brief Set node's name * @param name New name */ - void name(const std::string& name) { m_name = name; } + void setName(const std::string& name) { m_name = name; } /** * @brief Set selected status @@ -752,43 +752,43 @@ namespace ImFlow */ virtual std::weak_ptr getLink() { return std::weak_ptr{}; } - PinUID uid() { return m_uid; } + [[nodiscard]] PinUID getUid() const { return m_uid; } /** * @brief Get pin's name * @return Const reference to pin's name */ - const std::string& name() { return m_name; } + const std::string& getName() { return m_name; } /** * @brief Get pin's position * @return Const reference to pin's position in canvas coordinates */ - [[nodiscard]] const ImVec2& pos() { return m_pos; } + [[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& size() { return m_size; } + [[nodiscard]] const ImVec2& getSize() { return m_size; } /** * @brief Get pin's parent node * @return Const reference to pin's parent node. Node that contains it */ - BaseNode* parent() { return m_parent; } + BaseNode* getParent() { return m_parent; } /** * @brief Get pin's type * @return The pin type. Either Input or Output */ - PinType type() { return m_type; } + PinType getType() { return m_type; } /** * @brief Get pin's connection filter * @return Pin's connection filter configuration */ - [[nodiscard]] ConnectionFilter filter() const { return m_filter; } + [[nodiscard]] ConnectionFilter getFilter() const { return m_filter; } /** * @brief Get pin's link attachment point @@ -806,7 +806,7 @@ namespace ImFlow * @brief Set pin's position * @param pos Position in screen coordinates */ - void pos(ImVec2 pos) { m_pos = pos; } + void setPos(ImVec2 pos) { m_pos = pos; } protected: PinUID m_uid; std::string m_name; @@ -865,7 +865,7 @@ namespace ImFlow * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin */ - ImVec2 pinPoint() override { return m_pos + ImVec2(-m_inf->style().node_padding.z, m_size.y / 2); } + ImVec2 pinPoint() override { return m_pos + ImVec2(-m_inf->getStyle().node_padding.z, m_size.y / 2); } /** * @brief Get value carried by the link @@ -932,7 +932,7 @@ namespace ImFlow * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin */ - ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_inf->style().node_padding.z, m_size.y / 2); } + ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_inf->getStyle().node_padding.z, m_size.y / 2); } /** * @brief Calculate and get pin's value diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 2c281d9..c778786 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -10,7 +10,7 @@ namespace ImFlow { ImVec2 start = m_left->pinPoint(); ImVec2 end = m_right->pinPoint(); - float thickness = m_inf->style().link_thickness; + float thickness = m_inf->getStyle().link_thickness; bool mouseClickState = m_inf->getSingleUseClick(); if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) @@ -19,7 +19,7 @@ namespace ImFlow if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) { m_hovered = true; - thickness = m_inf->style().link_hovered_thickness; + thickness = m_inf->getStyle().link_hovered_thickness; if (mouseClickState) { m_inf->consumeSingleUseClick(); @@ -29,8 +29,8 @@ namespace ImFlow else { m_hovered = false; } if (m_selected) - smart_bezier(start, end, m_inf->style().colors.link_selected_outline, thickness + m_inf->style().link_selected_outline_thickness); - smart_bezier(start, end, m_inf->style().colors.link, thickness); + smart_bezier(start, end, m_inf->getStyle().colors.link_selected_outline, thickness + m_inf->getStyle().link_selected_outline_thickness); + smart_bezier(start, end, m_inf->getStyle().colors.link, thickness); if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false)) m_right->deleteLink(); @@ -47,12 +47,12 @@ namespace ImFlow BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) :m_name(std::move(name)), m_pos(pos), m_inf(inf) { - m_paddingTL = {m_inf->style().node_padding.x, m_inf->style().node_padding.y}; - m_paddingBR = {m_inf->style().node_padding.z, m_inf->style().node_padding.w}; + m_paddingTL = {m_inf->getStyle().node_padding.x, m_inf->getStyle().node_padding.y}; + m_paddingBR = {m_inf->getStyle().node_padding.z, m_inf->getStyle().node_padding.w}; m_posTarget = m_pos; } - bool BaseNode::hovered() + bool BaseNode::isHovered() { return ImGui::IsMouseHoveringRect(m_inf->content2canvas(m_pos - m_paddingTL), m_inf->content2canvas(m_pos + m_size + m_paddingBR)); } @@ -70,7 +70,7 @@ namespace ImFlow // Header ImGui::BeginGroup(); - ImGui::TextColored(m_inf->style().colors.node_header_title, m_name.c_str()); + ImGui::TextColored(m_inf->getStyle().colors.node_header_title, m_name.c_str()); ImGui::Spacing(); ImGui::EndGroup(); float headerH = ImGui::GetItemRectSize().y; @@ -80,14 +80,14 @@ namespace ImFlow ImGui::BeginGroup(); for (auto& p : m_ins) { - p->pos(ImGui::GetCursorPos()); + p->setPos(ImGui::GetCursorPos()); p->update(); } for (auto& p : m_dynamicIns) { if (p.first == 1) { - p.second->pos(ImGui::GetCursorPos()); + p.second->setPos(ImGui::GetCursorPos()); p.second->update(); p.first = 0; } @@ -121,18 +121,18 @@ namespace ImFlow { // FIXME: This looks horrible if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) - p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f)); + p->setPos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p->calcWidth(), 0.f)); else - p->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p->setPos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); p->update(); } for (auto& p :m_dynamicOuts) { // FIXME: This looks horrible if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) - p.second->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f)); + p.second->setPos(ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f)); else - p.second->pos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + p.second->setPos(ImVec2(m_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); p.second->update(); p.first -= 1; } @@ -145,18 +145,18 @@ namespace ImFlow // Background draw_list->ChannelsSetCurrent(0); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->style().colors.node_bg, m_inf->style().node_radius); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + headerSize, m_inf->style().colors.node_header, m_inf->style().node_radius, ImDrawFlags_RoundCornersTop); + draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_bg, m_inf->getStyle().node_radius); + draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + headerSize, m_inf->getStyle().colors.node_header, m_inf->getStyle().node_radius, ImDrawFlags_RoundCornersTop); if(m_selected) - draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->style().colors.node_selected_border, m_inf->style().node_radius, 0, m_inf->style().node_border_selected_thickness); + draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_selected_border, m_inf->getStyle().node_radius, 0, m_inf->getStyle().node_border_selected_thickness); else - draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->style().colors.node_border, m_inf->style().node_radius, 0, m_inf->style().node_border_thickness); + draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_border, m_inf->getStyle().node_radius, 0, m_inf->getStyle().node_border_thickness); if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) selected(false); - if (hovered() && mouseClickState) + if (isHovered() && mouseClickState) { selected(true); m_inf->consumeSingleUseClick(); @@ -169,9 +169,9 @@ namespace ImFlow m_dragged = true; m_inf->draggingNode(true); } - if(m_dragged || (m_selected && m_inf->draggingNode())) + if(m_dragged || (m_selected && m_inf->isNodeDragged())) { - float step = m_inf->style().grid_size / m_inf->style().grid_subdivisions; + float step = m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions; m_posTarget += ImGui::GetIO().MouseDelta; // "Slam" The position m_pos.x = round(m_posTarget.x / step) * step; @@ -209,15 +209,15 @@ namespace ImFlow bool ImNodeFlow::on_selected_node() { return std::any_of(m_nodes.begin(), m_nodes.end(), - [](auto& n) { return n->selected() && n->hovered();}); + [](auto& n) { return n->isSelected() && n->isHovered();}); } bool ImNodeFlow::on_free_space() { return std::all_of(m_nodes.begin(), m_nodes.end(), - [](auto& n) {return !n->hovered();}) + [](auto& n) {return !n->isHovered();}) && std::all_of(m_links.begin(), m_links.end(), - [](auto& l) {return !l.lock()->hovered();}); + [](auto& l) {return !l.lock()->isHovered();}); } ImVec2 ImNodeFlow::content2canvas(const ImVec2& p) @@ -237,7 +237,7 @@ namespace ImFlow ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p) { - return p - pos() - m_context.scroll(); + return p - getPos() - m_context.scroll(); } void ImNodeFlow::addLink(std::shared_ptr& link) @@ -302,7 +302,7 @@ namespace ImFlow m_dragOut = m_hovering; if (m_dragOut) { - if (m_dragOut->type() == PinType_Output) + if (m_dragOut->getType() == PinType_Output) smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); else smart_bezier(ImGui::GetMousePos(), m_dragOut->pinPoint(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); @@ -315,7 +315,7 @@ namespace ImFlow if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) { m_nodes.erase(std::remove_if(m_nodes.begin(), m_nodes.end(), - [](const std::shared_ptr& n) { return n->selected(); }), m_nodes.end()); + [](const std::shared_ptr& n) { return n->isSelected(); }), m_nodes.end()); } // Right-click PopUp diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index d9c9510..aa49992 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -83,7 +83,7 @@ namespace ImFlow PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicIns) { - if (p.second->uid() == h) + if (p.second->getUid() == h) { p.first = 1; return static_cast*>(p.second.get())->val(); @@ -120,7 +120,7 @@ namespace ImFlow PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicOuts) { - if (p.second->uid() == h) + if (p.second->getUid() == h) { p.first = 2; return; @@ -136,7 +136,7 @@ namespace ImFlow { PinUID h = std::hash{}(uid); auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_ins.end() && "Pin UID not found!"); return static_cast*>(it->get())->val(); } @@ -146,7 +146,7 @@ namespace ImFlow { PinUID h = std::hash{}(std::string(uid)); auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_ins.end() && "Pin UID not found!"); return static_cast*>(it->get())->val(); } @@ -156,7 +156,7 @@ namespace ImFlow { PinUID h = std::hash{}(uid); auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_ins.end() && "Pin UID not found!"); return it->get(); } @@ -165,7 +165,7 @@ namespace ImFlow { PinUID h = std::hash{}(std::string(uid)); auto it = std::find_if(m_ins.begin(), m_ins.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_ins.end() && "Pin UID not found!"); return it->get(); } @@ -175,7 +175,7 @@ namespace ImFlow { PinUID h = std::hash{}(uid); auto it = std::find_if(m_outs.begin(), m_outs.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_outs.end() && "Pin UID not found!"); return it->get(); } @@ -184,7 +184,7 @@ namespace ImFlow { PinUID h = std::hash{}(std::string(uid)); auto it = std::find_if(m_outs.begin(), m_outs.end(), [&h](std::shared_ptr& p) - { return p->uid() == h; }); + { return p->getUid() == h; }); assert(it != m_outs.end() && "Pin UID not found!"); return it->get(); } @@ -217,24 +217,24 @@ namespace ImFlow } ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); - ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); + ImVec2 tl = pinPoint() - ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); + ImVec2 br = pinPoint() + ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); ImGui::Text(m_name.c_str()); m_size = ImGui::GetItemRectSize(); if (ImGui::IsItemHovered()) - draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_hovered, m_inf->style().pin_radius); + draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_hovered, m_inf->getStyle().pin_radius); else - draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_bg, m_inf->style().pin_radius); - draw_list->AddRect(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_border, m_inf->style().pin_radius, 0, m_inf->style().pin_border_thickness); + draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_bg, m_inf->getStyle().pin_radius); + draw_list->AddRect(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_border, m_inf->getStyle().pin_radius, 0, m_inf->getStyle().pin_border_thickness); if (m_link) - draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); + draw_list->AddCircleFilled(pinPoint(), m_inf->getStyle().pin_point_radius, m_inf->getStyle().colors.pin_point); else { if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_hovered_radius, m_inf->style().colors.pin_point); + draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().colors.pin_point); else - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_radius, m_inf->getStyle().colors.pin_point); } if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) @@ -244,10 +244,10 @@ namespace ImFlow template void InPin::createLink(Pin *other) { - if (other == this || other->type() == PinType_Input || (m_parent == other->parent() && (m_filter & ConnectionFilter_SameNode) == 0)) + if (other == this || other->getType() == PinType_Input || (m_parent == other->getParent() && (m_filter & ConnectionFilter_SameNode) == 0)) return; - if (!((m_filter & other->filter()) != 0 || m_filter == ConnectionFilter_None || other->filter() == ConnectionFilter_None)) // Check Filter + if (!((m_filter & other->getFilter()) != 0 || m_filter == ConnectionFilter_None || other->getFilter() == ConnectionFilter_None)) // Check Filter return; if (m_link && m_link->left() == other) @@ -283,27 +283,27 @@ namespace ImFlow } ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 tl = pinPoint() - ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); - ImVec2 br = pinPoint() + ImVec2(m_inf->style().pin_point_empty_hovered_radius, m_inf->style().pin_point_empty_hovered_radius); + ImVec2 tl = pinPoint() - ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); + ImVec2 br = pinPoint() + ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); ImGui::SetCursorScreenPos(m_pos); ImGui::Text(m_name.c_str()); m_size = ImGui::GetItemRectSize(); if (ImGui::IsItemHovered()) - draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_hovered, m_inf->style().pin_radius); + draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_hovered, m_inf->getStyle().pin_radius); else - draw_list->AddRectFilled(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_bg, m_inf->style().pin_radius); - draw_list->AddRect(m_pos - m_inf->style().pin_padding, m_pos + m_size + m_inf->style().pin_padding, m_inf->style().colors.pin_border, m_inf->style().pin_radius, 0, m_inf->style().pin_border_thickness); + draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_bg, m_inf->getStyle().pin_radius); + draw_list->AddRect(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_border, m_inf->getStyle().pin_radius, 0, m_inf->getStyle().pin_border_thickness); if (m_links.empty()) { if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_hovered_radius, m_inf->style().colors.pin_point); + draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().colors.pin_point); else - draw_list->AddCircle(pinPoint(), m_inf->style().pin_point_empty_radius, m_inf->style().colors.pin_point); + draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_radius, m_inf->getStyle().colors.pin_point); } else - draw_list->AddCircleFilled(pinPoint(), m_inf->style().pin_point_radius, m_inf->style().colors.pin_point); + draw_list->AddCircleFilled(pinPoint(), m_inf->getStyle().pin_point_radius, m_inf->getStyle().colors.pin_point); if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) m_inf->hovering(this); @@ -312,7 +312,7 @@ namespace ImFlow template void OutPin::createLink(ImFlow::Pin *other) { - if (other == this || other->type() == PinType_Output) + if (other == this || other->getType() == PinType_Output) return; other->createLink(this); From b61461fdc2632c1a2b1ee00fcc4e5231c967f268 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 14 Feb 2024 12:45:34 +0100 Subject: [PATCH 035/116] Improved style customization --- include/ImNodeFlow.h | 248 ++++++++++++++++++++++++++----------------- src/ImNodeFlow.cpp | 50 ++++++--- src/ImNodeFlow.inl | 133 +++++++++-------------- 3 files changed, 231 insertions(+), 200 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index e3a92cc..f178ee5 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -49,7 +50,7 @@ namespace ImFlow class ImNodeFlow; // ----------------------------------------------------------------------------------------------------------------- - // FILTERS + // PIN'S PROPERTIES /** * @brief Basic filters @@ -68,6 +69,114 @@ namespace ImFlow }; typedef long ConnectionFilter; + typedef unsigned long long int PinUID; + + struct PinStyleExtras + { + ImVec2 padding = ImVec2(3.f, 1.f); + float bg_radius = 8.f; + float border_thickness = 1.f; + ImU32 bg_color = IM_COL32(23, 16, 16, 0); + ImU32 bg_hover_color = IM_COL32(100, 100, 255, 70); + ImU32 border_color = IM_COL32(255, 255, 255, 0); + + float link_thickness = 2.6f; + float link_dragged_thickness = 2.2f; + float link_hovered_thickness = 3.5f; + float link_selected_outline_thickness = 0.5f; + ImU32 outline_color = IM_COL32(80, 20, 255, 200); + + float socket_padding = 6.3f; + }; + + 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) {} + + ImU32 color; + + int socket_shape; + float socket_radius; + float socket_hovered_radius; + float socket_connected_radius; + float socket_thickness; + + PinStyleExtras extra; + public: + 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)); + } + 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)); + } + 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)); + } + 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)); + } + 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)); + } + 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 + + 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 Background of the node's body + ImU32 bg = IM_COL32(55,64,75,255); + /// @brief Background of the node's header + ImU32 header_bg; + /// @brief Text in the node's header + ImColor header_title_color; + /// @brief Node's border + ImU32 border_color = IM_COL32(30,38,41,140); + /// @brief Node's border when it's selected + ImU32 border_selected_color = IM_COL32(170, 190, 205, 230); + + /// @brief Padding of Node's content (Left Top Right Bottom) + ImVec4 padding = ImVec4(13.7f, 6.f, 13.7f, 2.f); + /// @brief Node's edges rounding + float radius; + /// @brief Node's border thickness + float border_thickness = -1.35f; + /// @brief Node's border thickness when selected + float border_selected_thickness = 2.f; + public: + static std::shared_ptr cyan() + { + return std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f); + } + static std::shared_ptr green() + { + return std::make_shared(IM_COL32(90,191,93,255), ImColor(233,241,244,255), 3.5f); + } + static std::shared_ptr red() + { + return std::make_shared(IM_COL32(191,90,90,255), ImColor(233,241,244,255), 11.f); + } + static std::shared_ptr brown() + { + return std::make_shared(IM_COL32(191,134,90,255), ImColor(233,241,244,255), 6.5f); + } + }; + // ----------------------------------------------------------------------------------------------------------------- // LINK @@ -136,35 +245,8 @@ namespace ImFlow */ struct InfColors { - /// @brief Background of the Pin - ImU32 pin_bg = IM_COL32(23, 16, 16, 0); - /// @brief Overlay to be displayed when Pin is hovered - ImU32 pin_hovered = IM_COL32(100, 100, 255, 70); - /// @brief Border to be displayed around the Pin - ImU32 pin_border = IM_COL32(255, 255, 255, 0); - /// @brief Border to be displayed around the Pin - ImU32 pin_point = IM_COL32(255, 255, 240, 230); - - /// @brief Link - ImU32 link = IM_COL32(230, 230, 200, 230); - /// @brief Link while dragging - ImU32 drag_out_link = IM_COL32(230, 230, 200, 230); - /// @brief Outline of a selected link - ImU32 link_selected_outline = IM_COL32(80, 20, 255, 200); - - /// @brief Background of the node's body - ImU32 node_bg = IM_COL32(97, 103, 122, 100); - /// @brief Background of the node's header - ImU32 node_header = IM_COL32(23, 16, 16, 150); - /// @brief Text in the node's header - ImColor node_header_title = ImColor(255, 246, 240, 255); - /// @brief Node's border - ImU32 node_border = IM_COL32(100, 100, 100, 255); - /// @brief Node's border when it's selected - ImU32 node_selected_border = IM_COL32(170, 190, 205, 230); - /// @brief Background of the grid - ImU32 background = IM_COL32(44, 51, 51, 255); + 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 @@ -176,37 +258,6 @@ namespace ImFlow */ struct InfStyler { - /// @brief Padding between Pin border and content - ImVec2 pin_padding = ImVec2(3.f, 1.f); - /// @brief Pin's edges rounding - float pin_radius = 8.f; - /// @brief Thickness of the border drawn around the Pin - float pin_border_thickness = 1.f; - /// @brief Radius of the circle in front of the Pin when connected - float pin_point_radius = 3.5f; - /// @brief Radius of the circle in front of the Pin when not connected - float pin_point_empty_radius = 4.f; - /// @brief Radius of the circle in front of the Pin when not connected and hovered - float pin_point_empty_hovered_radius = 4.67f; - - /// @brief Thickness of the drawn link - float link_thickness = 2.6f; - /// @brief Thickness of the drawn link 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 Thickness of the dummy link while dragging - float drag_out_link_thickness = 2.f; - - /// @brief Padding of Node's content (Left Top Right Bottom) - ImVec4 node_padding = ImVec4(9.f, 6.f, 9.f, 2.f); - /// @brief Node's edges rounding - float node_radius = 8.f; - /// @brief Node's border thickness - float node_border_thickness = 1.f; - /// @brief Node's border thickness when selected - float node_border_selected_thickness = 2.f; - /// @brief Size of main grid float grid_size = 50.f; /// @brief Sub-grid divisions for Node snapping @@ -226,9 +277,8 @@ namespace ImFlow static int m_instances; public: /** - * @brief Instantiate a new editor with default name - * - *
Editor name will be "FlowGrid + the number of editors". + * @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)) {} @@ -263,7 +313,7 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* addNode(const std::string& name, const ImVec2& pos, Params&&... args); + T* addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); /** * @brief Adds a node to the editor using mouse position @@ -277,7 +327,7 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* placeNode(const std::string& name, Params&&... args); + T* placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); /** * @brief Adds a node to the editor @@ -291,7 +341,7 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* placeNodeAt(const std::string& name, const ImVec2& pos, Params&&... args); + T* placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); /** * @brief Add link to the handler internal list @@ -464,8 +514,6 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - typedef unsigned long long int PinUID; - /** * @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. @@ -479,7 +527,7 @@ namespace ImFlow * @param pos Position in grid coordinates * @param inf Pointer to the Grid Handler the node is in */ - explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf); + explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf, std::shared_ptr style); /** * @brief Main loop of the node @@ -505,7 +553,7 @@ namespace ImFlow * @param filter Connection filter */ template - InPin* addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + InPin* addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief Add an Input to the node @@ -519,13 +567,13 @@ namespace ImFlow * @param filter Connection filter */ template - InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); template - const T& showIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + const T& showIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); template - const T& showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None); + const T& showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief Add an Output to the node @@ -538,7 +586,7 @@ namespace ImFlow * @return Pointer to the newly added pin. Must be used to set behaviour */ template - [[nodiscard]] OutPin* addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None); + [[nodiscard]] OutPin* addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief Add an Output to the node @@ -552,13 +600,13 @@ namespace ImFlow * @return Pointer to the newly added pin. Must be used to set behaviour */ template - [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None); + [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); template - void showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None); + void showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); template - void showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None); + void showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief Get Input value from an InPin @@ -637,6 +685,8 @@ namespace ImFlow */ const ImVec2& getPos() { return m_pos; } + std::shared_ptr& getStyle() { return m_style; } + /** * @brief Get selected status * @return [TRUE] if the node is selected @@ -672,6 +722,7 @@ namespace ImFlow ImVec2 m_pos, m_posTarget; ImVec2 m_size; ImNodeFlow* m_inf; + std::shared_ptr m_style; bool m_selected = false, m_selectedNext = false; bool m_dragged = false; ImVec2 m_paddingTL; @@ -709,14 +760,18 @@ namespace ImFlow * @param parent Pointer to the Node containing the pin * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ - explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf) - :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf) {} + explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) + :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) + { + if(!m_style) + m_style = PinStyle::cyan(); + } /** * @brief Main loop of the pin * @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame. */ - virtual void update() = 0; + void update(); /** * @brief Used by output pins to calculate their values @@ -746,6 +801,8 @@ namespace ImFlow */ virtual void deleteLink() {} + virtual bool isConnected() = 0; + /** * @brief Get pin's link * @return Weak_ptr reference to the link connected to the pin @@ -790,6 +847,8 @@ namespace ImFlow */ [[nodiscard]] ConnectionFilter getFilter() const { return m_filter; } + std::shared_ptr& getStyle() { return m_style; } + /** * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin @@ -814,6 +873,7 @@ namespace ImFlow ImVec2 m_size = ImVec2(0.f, 0.f); PinType m_type; ConnectionFilter m_filter; + std::shared_ptr m_style; BaseNode* m_parent = nullptr; ImNodeFlow* m_inf; std::function m_renderer; @@ -835,14 +895,8 @@ namespace ImFlow * @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) */ - explicit InPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf) - : Pin(uid, name, filter, PinType_Input, parent, inf), m_emptyVal(defReturn) {} - - /** - * @brief Main loop of the pin - * @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame. - */ - void update() override; + explicit InPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf, std::shared_ptr style) + : Pin(uid, name, filter, PinType_Input, parent, inf, style), m_emptyVal(defReturn) {} /** * @brief Create link between pins @@ -855,6 +909,8 @@ namespace ImFlow */ void deleteLink() override { m_link.reset(); } + bool isConnected() override { return m_link != nullptr; } + /** * @brief Get pin's link * @return Weak_ptr reference to the link connected to the pin @@ -865,7 +921,7 @@ namespace ImFlow * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin */ - ImVec2 pinPoint() override { return m_pos + ImVec2(-m_inf->getStyle().node_padding.z, m_size.y / 2); } + ImVec2 pinPoint() override { return m_pos + ImVec2(-m_style->extra.socket_padding, m_size.y / 2); } /** * @brief Get value carried by the link @@ -892,20 +948,14 @@ namespace ImFlow * @param parent Pointer to the Node containing the pin * @param inf Pointer to the Grid Handler the pin is in (same as parent) */ - explicit OutPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf) - :Pin(uid, name, filter, PinType_Output, parent, inf) {} + explicit OutPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) + :Pin(uid, name, filter, PinType_Output, parent, inf, style) {} /** * @brief When parent gets deleted, remove the links */ ~OutPin() { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } - /** - * @brief Main loop of the pin - * @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame. - */ - void update() override; - /** * @brief Calculate value based on set behaviour */ @@ -928,11 +978,13 @@ namespace ImFlow */ void deleteLink() override; + bool isConnected() override { return !m_links.empty(); } + /** * @brief Get pin's link attachment point * @return Canvas coordinates to the attachment point between the link and the pin */ - ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_inf->getStyle().node_padding.z, m_size.y / 2); } + ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_style->extra.socket_padding, m_size.y / 2); } /** * @brief Calculate and get pin's value diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index c778786..b2409c4 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -1,4 +1,5 @@ #include +#include #include "ImNodeFlow.h" namespace ImFlow @@ -10,7 +11,7 @@ namespace ImFlow { ImVec2 start = m_left->pinPoint(); ImVec2 end = m_right->pinPoint(); - float thickness = m_inf->getStyle().link_thickness; + float thickness = m_left->getStyle()->extra.link_thickness; bool mouseClickState = m_inf->getSingleUseClick(); if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) @@ -19,7 +20,7 @@ namespace ImFlow if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) { m_hovered = true; - thickness = m_inf->getStyle().link_hovered_thickness; + thickness = m_left->getStyle()->extra.link_hovered_thickness; if (mouseClickState) { m_inf->consumeSingleUseClick(); @@ -29,8 +30,8 @@ namespace ImFlow else { m_hovered = false; } if (m_selected) - smart_bezier(start, end, m_inf->getStyle().colors.link_selected_outline, thickness + m_inf->getStyle().link_selected_outline_thickness); - smart_bezier(start, end, m_inf->getStyle().colors.link, thickness); + 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(); @@ -44,11 +45,13 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) - :m_name(std::move(name)), m_pos(pos), m_inf(inf) + BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf, std::shared_ptr style) + :m_name(std::move(name)), m_pos(pos), m_inf(inf), m_style(std::move(style)) { - m_paddingTL = {m_inf->getStyle().node_padding.x, m_inf->getStyle().node_padding.y}; - m_paddingBR = {m_inf->getStyle().node_padding.z, m_inf->getStyle().node_padding.w}; + if (!m_style) + m_style = NodeStyle::cyan(); + m_paddingTL = {m_style->padding.x, m_style->padding.y}; + m_paddingBR = {m_style->padding.z, m_style->padding.w}; m_posTarget = m_pos; } @@ -70,7 +73,7 @@ namespace ImFlow // Header ImGui::BeginGroup(); - ImGui::TextColored(m_inf->getStyle().colors.node_header_title, m_name.c_str()); + ImGui::TextColored(m_style->header_title_color, m_name.c_str()); ImGui::Spacing(); ImGui::EndGroup(); float headerH = ImGui::GetItemRectSize().y; @@ -141,16 +144,29 @@ namespace ImFlow ImGui::EndGroup(); m_size = ImGui::GetItemRectSize(); - ImVec2 headerSize = ImVec2(m_size.x + m_paddingTL.x, headerH); + ImVec2 headerSize = ImVec2(m_size.x + m_paddingBR.x, headerH); // Background draw_list->ChannelsSetCurrent(0); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_bg, m_inf->getStyle().node_radius); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + headerSize, m_inf->getStyle().colors.node_header, m_inf->getStyle().node_radius, ImDrawFlags_RoundCornersTop); + draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_style->bg, m_style->radius); + draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + headerSize, m_style->header_bg, m_style->radius, ImDrawFlags_RoundCornersTop); + + ImU32 col = m_style->border_color; + float thickness = m_style->border_thickness; + ImVec2 ptl = m_paddingTL; + ImVec2 pbr = m_paddingBR; if(m_selected) - draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_selected_border, m_inf->getStyle().node_radius, 0, m_inf->getStyle().node_border_selected_thickness); - else - draw_list->AddRect(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_inf->getStyle().colors.node_border, m_inf->getStyle().node_radius, 0, m_inf->getStyle().node_border_thickness); + { + 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::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) @@ -303,9 +319,9 @@ namespace ImFlow if (m_dragOut) { if (m_dragOut->getType() == PinType_Output) - smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_style.colors.drag_out_link, m_style.drag_out_link_thickness); + 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_style.colors.drag_out_link, m_style.drag_out_link_thickness); + 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; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index aa49992..3a7b9d2 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -35,50 +35,50 @@ namespace ImFlow // HANDLER template - T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, Params&&... args) + T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, pos, this, std::forward(args)...)); + m_nodes.emplace_back(std::make_shared(name, pos, this, style, std::forward(args)...)); return static_cast(m_nodes.back().get()); } template - T* ImNodeFlow::placeNode(const std::string& name, Params&&... args) + T* ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) { - return placeNodeAt(name, ImGui::GetMousePos(), std::forward(args)...); + return placeNodeAt(name, ImGui::GetMousePos(), style, std::forward(args)...); } template - T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, Params&&... args) + T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { - return addNode(name, screen2content(pos), std::forward(args)...); + return addNode(name, screen2content(pos), style, std::forward(args)...); } // ----------------------------------------------------------------------------------------------------------------- // BASE NODE template - InPin* BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter) + InPin* BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { - return addIN_uid(name, name, defReturn, filter); + return addIN_uid(name, name, defReturn, filter, style); } template - InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) + InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf)); + m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf, style)); return static_cast*>(m_ins.back().get()); } template - const T& BaseNode::showIN(const std::string& name, T defReturn, ConnectionFilter filter) + const T& BaseNode::showIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { - return showIN_uid(name, name, defReturn, filter); + return showIN_uid(name, name, defReturn, filter, style); } template - const T& BaseNode::showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter) + const T& BaseNode::showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicIns) @@ -90,32 +90,32 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf, style))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } template - OutPin* BaseNode::addOUT(const std::string& name, ConnectionFilter filter) + OutPin* BaseNode::addOUT(const std::string& name, ConnectionFilter filter, std::shared_ptr style) { - return addOUT_uid(name, name, filter); + return addOUT_uid(name, name, filter, style); } template - OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter) + OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf)); + m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf, style)); return static_cast*>(m_outs.back().get()); } template - void BaseNode::showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter) + void BaseNode::showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) { - showOUT_uid(name, name, std::move(behaviour), filter); + showOUT_uid(name, name, std::move(behaviour), filter, style); } template - void BaseNode::showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter) + void BaseNode::showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicOuts) @@ -127,7 +127,7 @@ namespace ImFlow } } - m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf))); + m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf, style))); static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); } @@ -190,19 +190,9 @@ namespace ImFlow } // ----------------------------------------------------------------------------------------------------------------- - // IN PIN - - template - const T& InPin::val() - { - if(!m_link) - return m_emptyVal; + // PIN - return reinterpret_cast*>(m_link->left())->val(); - } - - template - void InPin::update() + inline void Pin::update() { // Custom rendering if (m_renderer) @@ -217,30 +207,45 @@ namespace ImFlow } ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 tl = pinPoint() - ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); - ImVec2 br = pinPoint() + ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); + ImVec2 tl = pinPoint() - ImVec2(m_style->socket_radius, m_style->socket_radius); + ImVec2 br = pinPoint() + ImVec2(m_style->socket_radius, m_style->socket_radius); + ImGui::SetCursorPos(m_pos); ImGui::Text(m_name.c_str()); m_size = ImGui::GetItemRectSize(); + if (ImGui::IsItemHovered()) - draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_hovered, m_inf->getStyle().pin_radius); + 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_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_bg, m_inf->getStyle().pin_radius); - draw_list->AddRect(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_border, m_inf->getStyle().pin_radius, 0, m_inf->getStyle().pin_border_thickness); - if (m_link) - draw_list->AddCircleFilled(pinPoint(), m_inf->getStyle().pin_point_radius, m_inf->getStyle().colors.pin_point); + 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); + + 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_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().colors.pin_point); + 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_inf->getStyle().pin_point_empty_radius, m_inf->getStyle().colors.pin_point); + draw_list->AddCircle(pinPoint(), m_style->socket_radius, m_style->color, m_style->socket_shape, m_style->socket_thickness); } if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) 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) { @@ -267,48 +272,6 @@ namespace ImFlow template const T &OutPin::val() { return m_val; } - template - void OutPin::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; - } - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 tl = pinPoint() - ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); - ImVec2 br = pinPoint() + ImVec2(m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().pin_point_empty_hovered_radius); - - ImGui::SetCursorScreenPos(m_pos); - ImGui::Text(m_name.c_str()); - - m_size = ImGui::GetItemRectSize(); - if (ImGui::IsItemHovered()) - draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_hovered, m_inf->getStyle().pin_radius); - else - draw_list->AddRectFilled(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_bg, m_inf->getStyle().pin_radius); - draw_list->AddRect(m_pos - m_inf->getStyle().pin_padding, m_pos + m_size + m_inf->getStyle().pin_padding, m_inf->getStyle().colors.pin_border, m_inf->getStyle().pin_radius, 0, m_inf->getStyle().pin_border_thickness); - if (m_links.empty()) - { - if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) - draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_hovered_radius, m_inf->getStyle().colors.pin_point); - else - draw_list->AddCircle(pinPoint(), m_inf->getStyle().pin_point_empty_radius, m_inf->getStyle().colors.pin_point); - } - else - draw_list->AddCircleFilled(pinPoint(), m_inf->getStyle().pin_point_radius, m_inf->getStyle().colors.pin_point); - - if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) - m_inf->hovering(this); - } - template void OutPin::createLink(ImFlow::Pin *other) { From d151ab619c95fcc260193520cab2fd283767e990 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 14 Feb 2024 13:07:01 +0100 Subject: [PATCH 036/116] changed to using std::move --- src/ImNodeFlow.inl | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 3a7b9d2..10cb420 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -38,20 +38,20 @@ namespace ImFlow T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, pos, this, style, std::forward(args)...)); + m_nodes.emplace_back(std::make_shared(name, pos, this, std::move(style), std::forward(args)...)); return static_cast(m_nodes.back().get()); } template T* ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) { - return placeNodeAt(name, ImGui::GetMousePos(), style, std::forward(args)...); + return placeNodeAt(name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); } template T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { - return addNode(name, screen2content(pos), style, std::forward(args)...); + return addNode(name, screen2content(pos), std::move(style), std::forward(args)...); } // ----------------------------------------------------------------------------------------------------------------- @@ -60,21 +60,21 @@ namespace ImFlow template InPin* BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { - return addIN_uid(name, name, defReturn, filter, style); + return addIN_uid(name, name, defReturn, filter, std::move(style)); } template InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf, style)); + m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf, std::move(style))); return static_cast*>(m_ins.back().get()); } template const T& BaseNode::showIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { - return showIN_uid(name, name, defReturn, filter, style); + return showIN_uid(name, name, defReturn, filter, std::move(style)); } template @@ -90,28 +90,28 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf, style))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf, std::move(style)))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } template OutPin* BaseNode::addOUT(const std::string& name, ConnectionFilter filter, std::shared_ptr style) { - return addOUT_uid(name, name, filter, style); + return addOUT_uid(name, name, filter, std::move(style)); } template OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf, style)); + m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf, std::move(style))); return static_cast*>(m_outs.back().get()); } template void BaseNode::showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) { - showOUT_uid(name, name, std::move(behaviour), filter, style); + showOUT_uid(name, name, std::move(behaviour), filter, std::move(style)); } template @@ -127,7 +127,7 @@ namespace ImFlow } } - m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf, style))); + m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf, std::move(style)))); static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); } From 29daa58ee79b3deb3b9d4d895d925df316c514b2 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:59:48 +0100 Subject: [PATCH 037/116] Updated image preview --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c4e2edd..3f10b83 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ Create your custom nodes, and their logic. ImNodeFlow will handle connections, editor logic and rendering. -https://github.com/Fattorino/ImNodeFlow/assets/90210751/c2c1e7a6-8f83-42df-8a26-037de8835f9d +![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/605f8cc5-794f-45bd-b4dd-2d6ffdb706e7) ## Features - Support for Zoom From b67402ce7b62340128ca3d227dfc5965a8a1072a Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 15 Feb 2024 17:03:13 +0100 Subject: [PATCH 038/116] Updated doxygen documentation And minor code clean-up --- include/ImNodeFlow.h | 456 +++++++++++++++++++++++++------------------ src/ImNodeFlow.cpp | 41 ++-- src/ImNodeFlow.inl | 6 +- 3 files changed, 287 insertions(+), 216 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index f178ee5..b52acb6 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -19,7 +19,7 @@ namespace ImFlow // HELPERS /** - * @brief Draw a sensible bezier between two points + * @brief
Draw a sensible bezier between two points * @param p1 Starting point * @param p2 Ending point * @param color Color of the curve @@ -28,7 +28,7 @@ namespace ImFlow inline static void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness); /** - * @brief Collider checker for smart_bezier + * @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 @@ -53,7 +53,7 @@ namespace ImFlow // PIN'S PROPERTIES /** - * @brief Basic filters + * @brief
Basic filters * @details List of, ready to use, basic filters. It's possible to create more filters with the help of "ConnectionFilter_MakeCustom". */ enum ConnectionFilter_ @@ -71,110 +71,117 @@ namespace ImFlow 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); - float socket_padding = 6.3f; + /// @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: - 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)); - } - 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)); - } - 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)); - } - 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)); - } - 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)); - } - 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)); - } + /// @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 + /** + * @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 Background of the node's body + /// @brief Body's background color ImU32 bg = IM_COL32(55,64,75,255); - /// @brief Background of the node's header + /// @brief Header's background color ImU32 header_bg; - /// @brief Text in the node's header + /// @brief Header title color ImColor header_title_color; - /// @brief Node's border + /// @brief Border color ImU32 border_color = IM_COL32(30,38,41,140); - /// @brief Node's border when it's selected + /// @brief Border color when selected ImU32 border_selected_color = IM_COL32(170, 190, 205, 230); - /// @brief Padding of Node's content (Left Top Right Bottom) + /// @brief Body's content padding (Left Top Right Bottom) ImVec4 padding = ImVec4(13.7f, 6.f, 13.7f, 2.f); - /// @brief Node's edges rounding + /// @brief Edges rounding float radius; - /// @brief Node's border thickness + /// @brief Border thickness float border_thickness = -1.35f; - /// @brief Node's border thickness when selected + /// @brief Border thickness when selected float border_selected_thickness = 2.f; public: - static std::shared_ptr cyan() - { - return std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f); - } - static std::shared_ptr green() - { - return std::make_shared(IM_COL32(90,191,93,255), ImColor(233,241,244,255), 3.5f); - } - static std::shared_ptr red() - { - return std::make_shared(IM_COL32(191,90,90,255), ImColor(233,241,244,255), 11.f); - } - static std::shared_ptr brown() - { - return std::make_shared(IM_COL32(191,134,90,255), ImColor(233,241,244,255), 6.5f); - } + /// @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); } }; // ----------------------------------------------------------------------------------------------------------------- @@ -187,7 +194,7 @@ namespace ImFlow { public: /** - * @brief Construct a link + * @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 @@ -195,37 +202,37 @@ namespace ImFlow explicit Link(Pin* left, Pin* right, ImNodeFlow* inf) :m_left(left), m_right(right), m_inf(inf) {} /** - * @brief Destruction of a link + * @brief
Destruction of a link * @details Deletes references of this links form connected pins */ ~Link(); /** - * @brief Looping function to update the 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 + * @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 + * @brief
Get Right pin of the link * @return Pointer to the Pin */ [[nodiscard]] Pin* right() const { return m_right; } /** - * @brief Get hovering status + * @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 + * @brief
Get selected status * @return [TRUE] If the link is selected in the current frame */ [[nodiscard]] bool isSelected() const { return m_selected; } @@ -241,7 +248,7 @@ namespace ImFlow // HANDLER /** - * @brief All the color parameters + * @brief Grid's the color parameters */ struct InfColors { @@ -254,7 +261,7 @@ namespace ImFlow }; /** - * @brief ALl the Styling parameters. Sizes + Colors + * @brief ALl the grid's appearance parameters. Sizes + Colors */ struct InfStyler { @@ -262,7 +269,6 @@ namespace ImFlow float grid_size = 50.f; /// @brief Sub-grid divisions for Node snapping float grid_subdivisions = 5.f; - /// @brief ImNodeFlow colors InfColors colors; }; @@ -283,7 +289,7 @@ namespace ImFlow ImNodeFlow() : ImNodeFlow("FlowGrid" + std::to_string(m_instances)) {} /** - * @brief Instantiate a new editor with given name + * @brief
Instantiate a new editor with given name * @details Creates a new Node Editor with the given name. * @param name Name of the editor */ @@ -295,18 +301,19 @@ namespace ImFlow } /** - * @brief Handler loop + * @brief
Handler loop * @details Main update function. Refreshes all the logic and draws everything. Must be called every frame. */ void update(); /** - * @brief Adds a node to the editor + * @brief
Adds a node to the editor * @tparam T Derived class of to be added * @tparam Params types of optional args to forward to derived class ctor * * @param name Name to be given to the Node - * @param pos Position of the Node in canvas coordinates + * @param pos Position of the Node in grid coordinates + * @param style Optional node's style override * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * @@ -316,11 +323,12 @@ namespace ImFlow T* addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); /** - * @brief Adds a node to the editor using mouse position + * @brief
Adds a node to the editor using mouse position * @tparam T Derived class of to be added * @tparam Params types of optional args to forward to derived class ctor * * @param name Name to be given to the Node + * @param style Optional node's style override * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * @@ -330,11 +338,12 @@ namespace ImFlow T* placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); /** - * @brief Adds a node to the editor + * @brief
Adds a node to the editor * @tparam T Derived class of to be added * @tparam Params types of optional args to forward to derived class ctor * @param name Name to be given to the Node * @param pos Position of the Node in screen coordinates + * @param style Optional node's style override * @param args Optional arguments to be forwarded to derived class ctor * @return Pointer of the pushed type to the newly added Node * @@ -344,13 +353,13 @@ namespace ImFlow T* placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); /** - * @brief Add link to the handler internal list + * @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" + * @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 @@ -359,86 +368,86 @@ namespace ImFlow void droppedLinkPopUpContent(std::function content, ImGuiKey key = ImGuiKey_None) { m_droppedLinkPopUp = std::move(content); m_droppedLinkPupUpComboKey = key; } /** - * @brief Pop-up when right-clicking + * @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 + * @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 + * @brief
Consume the click for the given frame */ void consumeSingleUseClick() { m_singleUseClick = false; } /** - * @brief Get editor's name + * @brief
Get editor's name * @return Const reference to editor's name */ const std::string& getName() { return m_name; } /** - * @brief Get editor's position + * @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 with the middle mouse. + * @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 editor's list of nodes + * @brief
Get editor's list of nodes * @return Const reference to editor's internal nodes list */ const std::vector>& getNodes() { return m_nodes; } /** - * @brief Get nodes count + * @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 + * @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 + * @brief
Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ const ContainedContext& getGrid() { return m_context; } /** - * @brief Get dragging status + * @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 + * @brief
Get current style * @return Reference to style variables */ InfStyler& getStyle() { return m_style; } /** - * @brief Set editor's size + * @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 + * @brief
Set dragging status * @param state New dragging state * * The new state will only be updated one at the start of each frame. @@ -446,47 +455,33 @@ namespace ImFlow void draggingNode(bool state) { m_draggingNodeNext = state; } /** - * @brief Set what pin is being hovered + * @brief
Set what pin is being hovered * @param hovering Pointer to the hovered pin */ void hovering(Pin* hovering) { m_hovering = hovering; } /** - * @brief Convert coordinates from grid to zooming viewport - * @param p Point in canvas coordinates to be converted - * @return Point in screen coordinates - */ - ImVec2 content2canvas(const ImVec2& p); - - /** - * @brief Convert coordinates from canvas to screen - * @param p Point in canvas coordinates to be converted - * @return Point in screen coordinates - */ - ImVec2 canvas2screen(const ImVec2& p); - - /** - * @brief Convert coordinates from screen to canvas + * @brief
Convert coordinates from screen to grid * @param p Point in screen coordinates to be converted - * @return Point in canvas coordinates + * @return Point in grid's coordinates */ - ImVec2 screen2content(const ImVec2 &p); + ImVec2 screen2grid(const ImVec2& p); /** - * @brief Convert coordinates from screen to zooming viewport - * @param p Point in screen coordinates to be converted - * @return Point in canvas coordinates + * @brief
Convert coordinates from grid to screen + * @param p Point in grid's coordinates to be converted + * @return Point in screen coordinates */ - ImVec2 screen2canvas(const ImVec2& p); + ImVec2 grid2screen(const ImVec2 &p); /** - * @brief Check if mouse is on selected node + * @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 + * @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(); @@ -522,94 +517,152 @@ namespace ImFlow { public: /** - * @brief Basic constructor + * @brief
Basic constructor * @param name Name of the node * @param pos Position in grid coordinates * @param inf Pointer to the Grid Handler the node is in */ - explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf, std::shared_ptr style); + explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf); /** - * @brief Main loop of the node + * @brief
Main loop of the node * @details Updates position, hovering and selected status, and renders the node. Must be called each frame. - * @param offset Position of the grid Origin in screen coordinates */ - void update(ImVec2& offset); + void update(); /** - * @brief Content of the node + * @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() = 0; /** - * @brief Add an Input to the node - * @details Must be called in the node constructor. 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 his UID. + * @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 Pointer to the newly added pin */ template InPin* addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** - * @brief Add an Input to the node - * @details Must be called in the node constructor. 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. + * @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 Pointer to the newly added pin */ template InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + /** + * @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, ConnectionFilter filter = ConnectionFilter_None, 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(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** - * @brief Add an Output to the node + * @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 his UID. - *

The UID must be unique only in the context of the current node. + *

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 - * @return Pointer to the newly added pin. Must be used to set behaviour + * @param style Style of the pin + * @return Pointer to the newly added pin. Must be used to set the behaviour */ template [[nodiscard]] OutPin* addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** - * @brief Add an Output to the node + * @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. + *

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 - * @return Pointer to the newly added pin. Must be used to set behaviour + * @param style Style of the pin + * @return Pointer to the newly added pin. Must be used to set the behaviour */ template [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, 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 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, ConnectionFilter filter = ConnectionFilter_None, 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(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** - * @brief Get Input value from an InPin + * @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 @@ -620,7 +673,7 @@ namespace ImFlow const T& getInVal(U uid); /** - * @brief Get Input value from an InPin + * @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 @@ -630,7 +683,7 @@ namespace ImFlow const T& getInVal(const char* uid); /** - * @brief Get generic reference to input pin + * @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 @@ -639,14 +692,14 @@ namespace ImFlow Pin* inPin(U uid); /** - * @brief Get generic reference to input pin + * @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 + * @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 @@ -655,58 +708,62 @@ namespace ImFlow Pin* outPin(U uid); /** - * @brief Get generic reference to output pin + * @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 hovered status + * @brief
Get hovered status * @return [TRUE] if the mouse is hovering the node */ bool isHovered(); /** - * @brief Get node name + * @brief
Get node name * @return Const reference to the node's name */ const std::string& getName() { return m_name; } /** - * @brief Get node size + * @brief
Get node size * @return Const reference to the node's size */ const ImVec2& getSize() { return m_size; } /** - * @brief Get node position + * @brief
Get node position * @return Const reference to the node's position */ const ImVec2& getPos() { return m_pos; } + /** + * @brief
Get node's style + * @return Shared pointer to the node's style + */ std::shared_ptr& getStyle() { return m_style; } /** - * @brief Get selected status + * @brief
Get selected status * @return [TRUE] if the node is selected */ [[nodiscard]] bool isSelected() const { return m_selected; } /** - * @brief Get dragged status + * @brief
Get dragged status * @return [TRUE] if the node is being dragged */ [[nodiscard]] bool isDragged() const { return m_dragged; } /** - * @brief Set node's name + * @brief
Set node's name * @param name New name */ void setName(const std::string& name) { m_name = name; } /** - * @brief Set selected status + * @brief
Set selected status * @param state New selected state * * Status only updates when updatePublicStatus() is called @@ -714,7 +771,7 @@ namespace ImFlow void selected(bool state) { m_selectedNext = state; } /** - * @brief Updates the selected status of the node + * @brief
Update the isSelected status of the node */ void updatePublicStatus() { m_selected = m_selectedNext; } private: @@ -753,12 +810,13 @@ namespace ImFlow { public: /** - * @brief Generic pin constructor + * @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, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) @@ -768,101 +826,113 @@ namespace ImFlow } /** - * @brief Main loop of the pin + * @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 Used by output pins to calculate their values + * @brief
Used by output pins to calculate their values */ virtual void resolve() {} /** - * @brief Custom render function to override Pin appearance + * @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 + * @brief
Create link between pins * @param other Pointer to the other pin */ virtual void createLink(Pin* other) = 0; /** - * @brief Sets the reference to a link - * @param link Pointer to the link + * @brief
Set the reference to a link + * @param link Smart pointer to the link */ virtual void setLink(std::shared_ptr& link) {} /** - * @brief Deletes the link from pin + * @brief
Delete link reference */ - virtual void deleteLink() {} + 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 the link connected to the pin + * @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 + * @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 canvas coordinates + * @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 + * @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 Const reference to pin's parent node. Node that contains it + * @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 + * @brief
Get pin's type * @return The pin type. Either Input or Output */ PinType getType() { return m_type; } /** - * @brief Get pin's connection filter + * @brief
Get pin's connection filter * @return Pin's connection filter configuration */ [[nodiscard]] ConnectionFilter getFilter() const { return m_filter; } + /** + * @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 - * @return Canvas coordinates to the attachment point between the link and the pin + * @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 + * @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 + * @brief
Set pin's position * @param pos Position in screen coordinates */ void setPos(ImVec2 pos) { m_pos = pos; } @@ -888,43 +958,48 @@ namespace ImFlow { public: /** - * @brief Input pin constructor + * @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, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf, std::shared_ptr style) : Pin(uid, name, filter, PinType_Input, parent, inf, style), m_emptyVal(defReturn) {} /** - * @brief Create link between pins + * @brief
Create link between pins * @param other Pointer to the other pin */ void createLink(Pin* other) override; /** - * @brief Deletes the link from pin + * @brief
Delete the link connected to the pin */ void deleteLink() override { m_link.reset(); } + /** + * @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 + * @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 pin's link attachment point - * @return Canvas coordinates to the attachment point between the link and the pin + * @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 link + * @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(); @@ -942,60 +1017,65 @@ namespace ImFlow { public: /** - * Output pin constructor + * @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, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) :Pin(uid, name, filter, PinType_Output, parent, inf, style) {} /** - * @brief When parent gets deleted, remove the links + * @brief
When parent gets deleted, remove the links */ ~OutPin() { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } /** - * @brief Calculate value based on set behaviour + * @brief
Calculate output value based on set behaviour */ void resolve() override { m_val = m_behaviour(); } /** - * @brief Create link between pins + * @brief
Create link between pins * @param other Pointer to the other pin */ void createLink(Pin* other) override; /** - * @brief Sets the reference to a link + * @brief
Add a connected link to the internal list * @param link Pointer to the link */ void setLink(std::shared_ptr& link) override; /** - * @brief Deletes any expired pointer to a (now deleted) link + * @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 - * @return Canvas coordinates to the attachment point between the link and the pin + * @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 Calculate and get pin's value - * @return Reference to the internal value of the pin + * @brief
Get output value + * @return Const reference to the internal value of the pin */ const T& val(); /** - * @brief Set logic to calculate output value + * @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 to be called by val() + * @param func Function or lambda expression used to calculate output value */ OutPin* behaviour(std::function func) { m_behaviour = std::move(func); return this; } private: diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b2409c4..4b65e9f 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -45,11 +45,10 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf, std::shared_ptr style) - :m_name(std::move(name)), m_pos(pos), m_inf(inf), m_style(std::move(style)) + BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) + :m_name(std::move(name)), m_pos(pos), m_inf(inf) { - if (!m_style) - m_style = NodeStyle::cyan(); + m_style = NodeStyle::cyan(); m_paddingTL = {m_style->padding.x, m_style->padding.y}; m_paddingBR = {m_style->padding.z, m_style->padding.w}; m_posTarget = m_pos; @@ -57,14 +56,15 @@ namespace ImFlow bool BaseNode::isHovered() { - return ImGui::IsMouseHoveringRect(m_inf->content2canvas(m_pos - m_paddingTL), m_inf->content2canvas(m_pos + m_size + m_paddingBR)); + return ImGui::IsMouseHoveringRect(m_inf->grid2screen(m_pos - m_paddingTL), m_inf->grid2screen(m_pos + m_size + m_paddingBR)); } - void BaseNode::update(ImVec2& offset) + 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}); draw_list->ChannelsSetCurrent(1); // Foreground ImGui::SetCursorScreenPos(offset + m_pos); @@ -123,19 +123,19 @@ namespace ImFlow for (auto& p : m_outs) { // FIXME: This looks horrible - if (m_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) + 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_inf->content2canvas(m_pos + ImVec2(titleW - p->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + 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_inf->content2canvas(m_pos + ImVec2(titleW, 0)).x < ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW) + 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_inf->content2canvas(m_pos + ImVec2(titleW - p.second->calcWidth(), 0)).x, ImGui::GetCursorPos().y + ImGui::GetWindowPos().y)); + 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; } @@ -236,24 +236,14 @@ namespace ImFlow [](auto& l) {return !l.lock()->isHovered();}); } - ImVec2 ImNodeFlow::content2canvas(const ImVec2& p) + ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) { - return p + m_context.scroll() + ImGui::GetWindowPos(); - } - - ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p) - { - return (p + m_context.scroll()) * m_context.scale() + m_context.origin(); - } - - ImVec2 ImNodeFlow::screen2content(const ImVec2 &p) - { - return p - m_context.scroll(); + return p - getPos() - m_context.scroll(); } - ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p) + ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) { - return p - getPos() - m_context.scroll(); + return p + getPos() + m_context.scroll(); } void ImNodeFlow::addLink(std::shared_ptr& link) @@ -271,7 +261,6 @@ namespace ImFlow // Create child canvas m_context.begin(); - ImVec2 offset = ImGui::GetCursorScreenPos() + m_context.scroll(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Display grid @@ -288,7 +277,7 @@ namespace ImFlow // Update and draw nodes draw_list->ChannelsSplit(2); - for (auto& node : m_nodes) { node->update(offset); } + for (auto& node : m_nodes) { node->update(); } draw_list->ChannelsMerge(); for (auto& node : m_nodes) { node->updatePublicStatus(); } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 10cb420..26898a7 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -38,7 +38,9 @@ namespace ImFlow T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, pos, this, std::move(style), std::forward(args)...)); + m_nodes.emplace_back(std::make_shared(name, pos, this, std::forward(args)...)); + if (style) + m_nodes.back()->getStyle() = std::move(style); return static_cast(m_nodes.back().get()); } @@ -51,7 +53,7 @@ namespace ImFlow template T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { - return addNode(name, screen2content(pos), std::move(style), std::forward(args)...); + return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); } // ----------------------------------------------------------------------------------------------------------------- From 4a59a01eb8e24ac78ae75ea4df7c70b58dee1ec4 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 15 Feb 2024 20:09:51 +0100 Subject: [PATCH 039/116] Added ability to delete "static" pins --- include/ImNodeFlow.h | 42 +++++++++++++++++++++----- src/ImNodeFlow.inl | 70 ++++++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b52acb6..6b8f38d 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -566,7 +566,21 @@ namespace ImFlow * @return Pointer to the newly added pin */ template - InPin* addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + InPin* addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, 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 @@ -599,7 +613,7 @@ namespace ImFlow * @return Const reference to the value of the connected link for the current frame of defReturn */ template - const T& showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + const T& showIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Add an Output to the node @@ -628,7 +642,21 @@ namespace ImFlow * @return Pointer to the newly added pin. Must be used to set the behaviour */ template - [[nodiscard]] OutPin* addOUT_uid(U uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + [[nodiscard]] OutPin* addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, 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 @@ -659,7 +687,7 @@ namespace ImFlow * @param style Style of the pin */ template - void showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + void showOUT_uid(const U& uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Get Input value from an InPin @@ -670,7 +698,7 @@ namespace ImFlow * @return Const reference to the value */ template - const T& getInVal(U uid); + const T& getInVal(const U& uid); /** * @brief
Get Input value from an InPin @@ -689,7 +717,7 @@ namespace ImFlow * @return Generic pointer to the pin */ template - Pin* inPin(U uid); + Pin* inPin(const U& uid); /** * @brief
Get generic reference to input pin @@ -705,7 +733,7 @@ namespace ImFlow * @return Generic pointer to the pin */ template - Pin* outPin(U uid); + Pin* outPin(const U& uid); /** * @brief
Get generic reference to output pin diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 26898a7..e9376f2 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -66,13 +66,32 @@ namespace ImFlow } template - InPin* BaseNode::addIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + InPin* BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf, std::move(style))); return static_cast*>(m_ins.back().get()); } + 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, ConnectionFilter filter, std::shared_ptr style) { @@ -80,7 +99,7 @@ namespace ImFlow } template - const T& BaseNode::showIN_uid(U uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + const T& BaseNode::showIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicIns) @@ -103,13 +122,32 @@ namespace ImFlow } template - OutPin* BaseNode::addOUT_uid(U uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) + OutPin* BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf, std::move(style))); return static_cast*>(m_outs.back().get()); } + 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, ConnectionFilter filter, std::shared_ptr style) { @@ -117,7 +155,7 @@ namespace ImFlow } template - void BaseNode::showOUT_uid(U uid, const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) + void BaseNode::showOUT_uid(const U& uid, const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); for (std::pair>& p : m_dynamicOuts) @@ -134,7 +172,7 @@ namespace ImFlow } template - const T& BaseNode::getInVal(U uid) + 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) @@ -146,15 +184,11 @@ namespace ImFlow template const T& BaseNode::getInVal(const char* uid) { - PinUID h = std::hash{}(std::string(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(); + return getInVal(uid); } template - Pin* BaseNode::inPin(U uid) + 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) @@ -165,15 +199,11 @@ namespace ImFlow inline Pin* BaseNode::inPin(const char* uid) { - PinUID h = std::hash{}(std::string(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(); + return inPin(uid); } template - Pin* BaseNode::outPin(U uid) + 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) @@ -184,11 +214,7 @@ namespace ImFlow inline Pin* BaseNode::outPin(const char* uid) { - PinUID h = std::hash{}(std::string(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(); + return outPin(uid); } // ----------------------------------------------------------------------------------------------------------------- From 4acc9e435eabeccb6ce9963259ec831c3a9c1e60 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 15 Feb 2024 23:56:11 +0100 Subject: [PATCH 040/116] First tests with NodeUID system --- CMakeLists.txt | 2 +- include/ImNodeFlow.h | 41 ++++++++++++++++++++++++++++++++++------- src/ImNodeFlow.cpp | 13 ++++++------- src/ImNodeFlow.inl | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f88aba..fb25ea8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) # CREATE PROJECT project(ImNodeFlow) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 6b8f38d..d565634 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "../src/imgui_bezier_math.h" #include "../src/context_wrapper.h" @@ -146,6 +147,8 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // NODE'S PROPERTIES + typedef unsigned long long int NodeUID; + /** * @brief Defines the visual appearance of a node */ @@ -320,7 +323,25 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - T* addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); + std::shared_ptr addNode(const std::string& name, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); + + /** + * @brief
Adds a node to the editor + * @tparam T Derived class of to be added + * @tparam U Type of the UID + * @tparam Params types of optional args to forward to derived class ctor + * + * @param uid Unique identifier of the node + * @param name Name to be given to the Node + * @param pos Position of the Node in grid coordinates + * @param style Optional node's style override + * @param args Optional arguments to be forwarded to derived class ctor + * @return 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_uid(const U& uid, const std::string& name, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); /** * @brief
Adds a node to the editor using mouse position @@ -334,8 +355,8 @@ namespace ImFlow * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - template - T* placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); + //template + //T* placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); /** * @brief
Adds a node to the editor @@ -349,8 +370,14 @@ namespace ImFlow * * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ - template - T* placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); + //template + //T* placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); + + template + std::shared_ptr findNode(const U& uid); + + template + void dropNode(const U& uid); /** * @brief
Add link to the handler internal list @@ -408,7 +435,7 @@ namespace ImFlow * @brief
Get editor's list of nodes * @return Const reference to editor's internal nodes list */ - const std::vector>& getNodes() { return m_nodes; } + const std::unordered_map>& getNodes() { return m_nodes; } /** * @brief
Get nodes count @@ -491,7 +518,7 @@ namespace ImFlow bool m_singleUseClick = false; - std::vector> m_nodes; + std::unordered_map> m_nodes; std::vector> m_links; std::function m_rightClickPopUp; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 4b65e9f..40d4d0e 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -225,15 +225,15 @@ namespace ImFlow bool ImNodeFlow::on_selected_node() { return std::any_of(m_nodes.begin(), m_nodes.end(), - [](auto& n) { return n->isSelected() && n->isHovered();}); + [](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(), - [](auto& n) {return !n->isHovered();}) + [](const auto& n) {return !n.second->isHovered();}) && std::all_of(m_links.begin(), m_links.end(), - [](auto& l) {return !l.lock()->isHovered();}); + [](const auto& l) {return !l.lock()->isHovered();}); } ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) @@ -277,9 +277,9 @@ namespace ImFlow // Update and draw nodes draw_list->ChannelsSplit(2); - for (auto& node : m_nodes) { node->update(); } + for (auto& node : m_nodes) { node.second->update(); } draw_list->ChannelsMerge(); - for (auto& node : m_nodes) { node->updatePublicStatus(); } + for (auto& node : m_nodes) { node.second->updatePublicStatus(); } // Update and draw links for (auto& l : m_links) { if(!l.expired()) l.lock()->update(); } @@ -319,8 +319,7 @@ namespace ImFlow // Deletion of selected stuff if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) { - m_nodes.erase(std::remove_if(m_nodes.begin(), m_nodes.end(), - [](const std::shared_ptr& n) { return n->isSelected(); }), m_nodes.end()); + std::erase_if(m_nodes, [](const std::pair>& n){ return n.second->isSelected(); }); } // Right-click PopUp diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index e9376f2..c4a7fc2 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -35,16 +35,26 @@ namespace ImFlow // HANDLER template - T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) + std::shared_ptr ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) + { + return addNode_uid(name, name, pos, style, std::forward(args)...); + } + + template + std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& name, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); - m_nodes.emplace_back(std::make_shared(name, pos, this, std::forward(args)...)); + NodeUID h = std::hash{}(uid); + assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); + std::shared_ptr n = std::make_shared(name, pos, this, std::forward(args)...); if (style) - m_nodes.back()->getStyle() = std::move(style); - return static_cast(m_nodes.back().get()); + n->getStyle() = style; + + m_nodes[h] = n; + return n; } - template + /*template T* ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) { return placeNodeAt(name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); @@ -54,6 +64,22 @@ namespace ImFlow T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); + }*/ + + template + std::shared_ptr ImNodeFlow::findNode(const U& uid) + { + NodeUID h = std::hash{}(uid); + auto n = m_nodes.find(h); + assert(n != m_nodes.end() && "Node UID not found!"); + return n->second; + } + + template + void ImNodeFlow::dropNode(const U& uid) + { + NodeUID h = std::hash{}(uid); + m_nodes.erase(h); } // ----------------------------------------------------------------------------------------------------------------- From 4ececd6da3af392e0a545f5086b44a188afa8f37 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 16 Feb 2024 16:58:37 +0100 Subject: [PATCH 041/116] added more methods to add a node and fixed self deletion bug --- include/ImNodeFlow.h | 79 +++++++++++++++++++++++++++++--------------- src/ImNodeFlow.cpp | 7 ++++ src/ImNodeFlow.inl | 37 +++++++++++++++++---- 3 files changed, 89 insertions(+), 34 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index d565634..48f844c 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -343,42 +343,48 @@ namespace ImFlow template std::shared_ptr addNode_uid(const U& uid, const std::string& name, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); - /** - * @brief
Adds a node to the editor using mouse position - * @tparam T Derived class of to be added - * @tparam Params types of optional args to forward to derived class ctor - * - * @param name Name to be given to the Node - * @param style Optional node's style override - * @param args Optional arguments to be forwarded to derived class ctor - * @return Pointer of the pushed type to the newly added Node - * - * Inheritance is checked at compile time, \ MUST be derived from BaseNode. - */ - //template - //T* placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); + template + std::shared_ptr placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); + + template + std::shared_ptr placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr, Params&&... args); + + template + std::shared_ptr placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); + + template + std::shared_ptr placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); /** - * @brief
Adds a node to the editor - * @tparam T Derived class of to be added - * @tparam Params types of optional args to forward to derived class ctor - * @param name Name to be given to the Node - * @param pos Position of the Node in screen coordinates - * @param style Optional node's style override - * @param args Optional arguments to be forwarded to derived class ctor - * @return Pointer of the pushed type to the newly added Node - * - * Inheritance is checked at compile time, \ MUST be derived from BaseNode. + * @brief
Find a node on the grid + * @tparam U Type of the UID + * @param uid Unique identifier of the node + * @return Shared pointer to the node */ - //template - //T* placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); - template std::shared_ptr findNode(const U& uid); + /** + * @brief
Find a node on the grid by raw UID + * @param uid Unique identifier of the node + * @return Shared pointer to the node + */ + std::shared_ptr findNode_raw(NodeUID uid); + + /** + * @brief
Delete a node on the grid + * @tparam U Type of the UID + * @param uid Unique identifier of the node + */ template void dropNode(const U& uid); + /** + * @brief
Delete a node on the grid by raw UID + * @param uid Unique identifier of the node + */ + void dropNode_raw(NodeUID uid); + /** * @brief
Add link to the handler internal list * @param link Reference to the link @@ -769,12 +775,23 @@ namespace ImFlow */ Pin* outPin(const char* uid); + /** + * @brief
Delete itself + */ + void destroy() { m_inf->dropNode_raw(m_uid); m_destroyed = true; } + /** * @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 + */ + NodeUID getUID() { return m_uid; } + /** * @brief
Get node name * @return Const reference to the node's name @@ -811,6 +828,12 @@ namespace ImFlow */ [[nodiscard]] bool isDragged() const { return m_dragged; } + /** + * @brief
Set node's uid + * @param uid Node's unique identifier + */ + void setUID(NodeUID uid) { m_uid = uid; } + /** * @brief
Set node's name * @param name New name @@ -830,6 +853,7 @@ namespace ImFlow */ void updatePublicStatus() { m_selected = m_selectedNext; } private: + NodeUID m_uid; std::string m_name; ImVec2 m_pos, m_posTarget; ImVec2 m_size; @@ -837,6 +861,7 @@ namespace ImFlow std::shared_ptr m_style; bool m_selected = false, m_selectedNext = false; bool m_dragged = false; + bool m_destroyed = false; ImVec2 m_paddingTL; ImVec2 m_paddingBR; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 40d4d0e..6b99a15 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -105,6 +105,13 @@ namespace ImFlow ImGui::EndGroup(); ImGui::SameLine(); + if (m_destroyed) + { + ImGui::EndGroup(); + ImGui::PopID(); + return; + } + // Outputs float maxW = 0.0f; for (auto& p : m_outs) diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index c4a7fc2..4e8717e 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -47,6 +47,7 @@ namespace ImFlow NodeUID h = std::hash{}(uid); assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); std::shared_ptr n = std::make_shared(name, pos, this, std::forward(args)...); + n->setUID(h); if (style) n->getStyle() = style; @@ -54,23 +55,40 @@ namespace ImFlow return n; } - /*template - T* ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) + template + std::shared_ptr ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) + { + return placeNodeAt_uid(name, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); + } + + template + std::shared_ptr ImNodeFlow::placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style, Params&&... args) { - return placeNodeAt(name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); + return placeNodeAt_uid(uid, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); } template - T* ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) + std::shared_ptr ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) + { + return placeNodeAt_uid(name, name, pos, std::move(style), std::forward(args)...); + } + + template + std::shared_ptr ImNodeFlow::placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) { return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); - }*/ + } template std::shared_ptr ImNodeFlow::findNode(const U& uid) { NodeUID h = std::hash{}(uid); - auto n = m_nodes.find(h); + return findNode_raw(h); + } + + inline std::shared_ptr ImNodeFlow::findNode_raw(NodeUID uid) + { + auto n = m_nodes.find(uid); assert(n != m_nodes.end() && "Node UID not found!"); return n->second; } @@ -79,7 +97,12 @@ namespace ImFlow void ImNodeFlow::dropNode(const U& uid) { NodeUID h = std::hash{}(uid); - m_nodes.erase(h); + dropNode_raw(h); + } + + inline void ImNodeFlow::dropNode_raw(NodeUID uid) + { + m_nodes.erase(uid); } // ----------------------------------------------------------------------------------------------------------------- From 07bd3afa51b8518eb646bdd278480857ce23a8e7 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 16 Feb 2024 20:14:43 +0100 Subject: [PATCH 042/116] Simplified BaseNode constructor Now the only parameters needed in a Custom Node ctor are the optional forwarded ones --- include/ImNodeFlow.h | 48 ++++++++++++++++++++++++++------------------ src/ImNodeFlow.cpp | 29 +++++++++++--------------- src/ImNodeFlow.inl | 25 ++++++++++++++--------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 48f844c..990a532 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -549,13 +549,7 @@ namespace ImFlow class BaseNode { public: - /** - * @brief
Basic constructor - * @param name Name of the node - * @param pos Position in grid coordinates - * @param inf Pointer to the Grid Handler the node is in - */ - explicit BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf); + BaseNode() = default; /** * @brief
Main loop of the node @@ -790,13 +784,13 @@ namespace ImFlow * @brief
Get node's UID * @return Node's unique identifier */ - NodeUID getUID() { return m_uid; } + [[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_name; } + const std::string& getName() { return m_title; } /** * @brief
Get node size @@ -810,6 +804,12 @@ namespace ImFlow */ 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 @@ -838,7 +838,19 @@ namespace ImFlow * @brief
Set node's name * @param name New name */ - void setName(const std::string& name) { m_name = name; } + void setTitle(const std::string& name) { m_title = name; } + + /** + * @brief
Set node's position + * @param pos Position in grid coordinates + */ + void setPos(const ImVec2& pos) { m_pos = pos; m_posTarget = pos; } + + /** + * @brief
Set ImNodeFlow handler + * @param inf Grid handler for the node + */ + void setHandler(ImNodeFlow* inf) { m_inf = inf; } /** * @brief
Set selected status @@ -853,17 +865,15 @@ namespace ImFlow */ void updatePublicStatus() { m_selected = m_selectedNext; } private: - NodeUID m_uid; - std::string m_name; + NodeUID m_uid = 0; + std::string m_title; ImVec2 m_pos, m_posTarget; ImVec2 m_size; - ImNodeFlow* m_inf; + ImNodeFlow* m_inf = nullptr; std::shared_ptr m_style; bool m_selected = false, m_selectedNext = false; bool m_dragged = false; bool m_destroyed = false; - ImVec2 m_paddingTL; - ImVec2 m_paddingBR; std::vector> m_ins; std::vector>> m_dynamicIns; @@ -898,7 +908,7 @@ namespace ImFlow * @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, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) + explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) { if(!m_style) @@ -1025,7 +1035,7 @@ namespace ImFlow ConnectionFilter m_filter; std::shared_ptr m_style; BaseNode* m_parent = nullptr; - ImNodeFlow* m_inf; + ImNodeFlow** m_inf; std::function m_renderer; }; @@ -1046,7 +1056,7 @@ namespace ImFlow * @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, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf, std::shared_ptr style) + explicit InPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow** inf, std::shared_ptr style) : Pin(uid, name, filter, PinType_Input, parent, inf, style), m_emptyVal(defReturn) {} /** @@ -1104,7 +1114,7 @@ namespace ImFlow * @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, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf, std::shared_ptr style) + explicit OutPin(PinUID uid, const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) :Pin(uid, name, filter, PinType_Output, parent, inf, style) {} /** diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 6b99a15..e7f9401 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -45,18 +45,11 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - BaseNode::BaseNode(std::string name, ImVec2 pos, ImNodeFlow* inf) - :m_name(std::move(name)), m_pos(pos), m_inf(inf) - { - m_style = NodeStyle::cyan(); - m_paddingTL = {m_style->padding.x, m_style->padding.y}; - m_paddingBR = {m_style->padding.z, m_style->padding.w}; - m_posTarget = m_pos; - } - bool BaseNode::isHovered() { - return ImGui::IsMouseHoveringRect(m_inf->grid2screen(m_pos - m_paddingTL), m_inf->grid2screen(m_pos + m_size + m_paddingBR)); + 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() @@ -65,6 +58,8 @@ namespace ImFlow 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); @@ -73,7 +68,7 @@ namespace ImFlow // Header ImGui::BeginGroup(); - ImGui::TextColored(m_style->header_title_color, m_name.c_str()); + ImGui::TextColored(m_style->header_title_color, m_title.c_str()); ImGui::Spacing(); ImGui::EndGroup(); float headerH = ImGui::GetItemRectSize().y; @@ -151,17 +146,17 @@ namespace ImFlow ImGui::EndGroup(); m_size = ImGui::GetItemRectSize(); - ImVec2 headerSize = ImVec2(m_size.x + m_paddingBR.x, headerH); + ImVec2 headerSize = ImVec2(m_size.x + paddingBR.x, headerH); // Background draw_list->ChannelsSetCurrent(0); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + m_size + m_paddingBR, m_style->bg, m_style->radius); - draw_list->AddRectFilled(offset + m_pos - m_paddingTL, offset + m_pos + headerSize, m_style->header_bg, m_style->radius, ImDrawFlags_RoundCornersTop); + 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); ImU32 col = m_style->border_color; float thickness = m_style->border_thickness; - ImVec2 ptl = m_paddingTL; - ImVec2 pbr = m_paddingBR; + ImVec2 ptl = paddingTL; + ImVec2 pbr = paddingBR; if(m_selected) { col = m_style->border_selected_color; @@ -185,7 +180,7 @@ namespace ImFlow m_inf->consumeSingleUseClick(); } - bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - m_paddingTL, offset + m_pos + headerSize); + bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); if (onHeader && mouseClickState) { m_inf->consumeSingleUseClick(); diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 4e8717e..f7fee9a 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -41,15 +41,20 @@ namespace ImFlow } template - std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& name, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) + std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) { static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); NodeUID h = std::hash{}(uid); assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); - std::shared_ptr n = std::make_shared(name, pos, this, std::forward(args)...); + std::shared_ptr n = std::make_shared(std::forward(args)...); n->setUID(h); + n->setTitle(title); + n->setPos(pos); + n->setHandler(this); if (style) n->getStyle() = style; + else if (!n->getStyle()) + n->getStyle() = NodeStyle::cyan(); m_nodes[h] = n; return n; @@ -118,7 +123,7 @@ namespace ImFlow InPin* BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, m_inf, std::move(style))); + m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style))); return static_cast*>(m_ins.back().get()); } @@ -160,7 +165,7 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, m_inf, std::move(style)))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } @@ -174,7 +179,7 @@ namespace ImFlow OutPin* BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_outs.emplace_back(std::make_shared>(h, name, filter, this, m_inf, std::move(style))); + m_outs.emplace_back(std::make_shared>(h, name, filter, this, &m_inf, std::move(style))); return static_cast*>(m_outs.back().get()); } @@ -216,7 +221,7 @@ namespace ImFlow } } - m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, m_inf, std::move(style)))); + m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, &m_inf, std::move(style)))); static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); } @@ -279,7 +284,7 @@ namespace ImFlow ImGui::EndGroup(); m_size = ImGui::GetItemRectSize(); if (ImGui::IsItemHovered()) - m_inf->hovering(this); + (*m_inf)->hovering(this); return; } @@ -308,7 +313,7 @@ namespace ImFlow } if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) - m_inf->hovering(this); + (*m_inf)->hovering(this); } // ----------------------------------------------------------------------------------------------------------------- @@ -338,9 +343,9 @@ namespace ImFlow return; } - m_link = std::make_shared(other, this, m_inf); + m_link = std::make_shared(other, this, (*m_inf)); other->setLink(m_link); - m_inf->addLink(m_link); + (*m_inf)->addLink(m_link); } // ----------------------------------------------------------------------------------------------------------------- From 8893f0a7b6a7eac7b4013e3b5f5575340e0bd52a Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 17 Feb 2024 00:10:25 +0100 Subject: [PATCH 043/116] Minor tweaks Fixed coordinates conversion bugs Added more methods to help with custom pin rendering --- include/ImNodeFlow.h | 27 ++++++++++++++++++++-- src/ImNodeFlow.cpp | 12 ++++++---- src/ImNodeFlow.inl | 54 +++++++++++++++++++++++++++---------------- src/context_wrapper.h | 7 +++--- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 990a532..b72c221 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -2,6 +2,7 @@ #define IM_NODE_FLOW #pragma once +#include #include #include #include @@ -341,7 +342,7 @@ namespace ImFlow * Inheritance is checked at compile time, \ MUST be derived from BaseNode. */ template - std::shared_ptr addNode_uid(const U& uid, const std::string& name, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); + std::shared_ptr addNode_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); template std::shared_ptr placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); @@ -562,7 +563,7 @@ namespace ImFlow * @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() = 0; + virtual void draw() {} /** * @brief
Add an Input to the node @@ -769,6 +770,18 @@ namespace ImFlow */ 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 */ @@ -921,6 +934,16 @@ namespace ImFlow */ 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 */ diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index e7f9401..b1b3109 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -1,5 +1,3 @@ -#include -#include #include "ImNodeFlow.h" namespace ImFlow @@ -240,12 +238,18 @@ namespace ImFlow ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) { - return p - getPos() - m_context.scroll(); + if (ImGui::GetCurrentContext() == m_context.getRawContext()) + return p - m_context.scroll(); + else + return p - m_context.origin() - m_context.scroll() * m_context.scale(); } ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) { - return p + getPos() + m_context.scroll(); + if (ImGui::GetCurrentContext() == m_context.getRawContext()) + return p + m_context.scroll(); + else + return p + m_context.origin() + m_context.scroll() * m_context.scale(); } void ImNodeFlow::addLink(std::shared_ptr& link) diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index f7fee9a..a8fb801 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -274,6 +274,37 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // 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 @@ -288,31 +319,14 @@ namespace ImFlow return; } - 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); - ImGui::SetCursorPos(m_pos); ImGui::Text(m_name.c_str()); m_size = ImGui::GetItemRectSize(); - 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); - - 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); - } + drawDecoration(); + drawSocket(); - if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) + if (ImGui::IsItemHovered()) (*m_inf)->hovering(this); } diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 49d1d71..7ed316b 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -50,7 +50,7 @@ inline static void AppendDrawData(ImDrawList* src, ImVec2 origin, float scale) dl->_IdxWritePtr = dl->IdxBuffer.Data + dl->IdxBuffer.size(); } -struct ViewPortConfig +struct ContainedContextConfig { bool extra_window_wrapper = false; ImVec2 size = {0.f, 0.f}; @@ -69,15 +69,16 @@ class ContainedContext { public: ~ContainedContext(); - ViewPortConfig& config() { return m_config; } + ContainedContextConfig& config() { return m_config; } void begin(); void end(); [[nodiscard]] float scale() const { return m_scale; } [[nodiscard]] const ImVec2& origin() const { return m_origin; } [[nodiscard]] bool hovered() const { return m_hovered; } [[nodiscard]] const ImVec2& scroll() const { return m_scroll; } + ImGuiContext* getRawContext() { return m_ctx; } private: - ViewPortConfig m_config; + ContainedContextConfig m_config; ImVec2 m_origin; ImVec2 m_pos; From b0f21eeb6053828d4a7dca03acd4862952d9e632 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 17 Feb 2024 13:04:16 +0100 Subject: [PATCH 044/116] Done UID system and used it to improve deletion of node --- include/ImNodeFlow.h | 140 +++++++++++++++++++++---------------------- src/ImNodeFlow.cpp | 9 +-- src/ImNodeFlow.inl | 135 +++++++++++++++++++++-------------------- 3 files changed, 140 insertions(+), 144 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index b72c221..5c173d3 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -148,7 +148,7 @@ namespace ImFlow // ----------------------------------------------------------------------------------------------------------------- // NODE'S PROPERTIES - typedef unsigned long long int NodeUID; + typedef uintptr_t NodeUID; /** * @brief Defines the visual appearance of a node @@ -311,80 +311,74 @@ namespace ImFlow void update(); /** - * @brief
Adds a node to the editor + * @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 name Name to be given to the Node * @param pos Position of the Node in grid coordinates - * @param style Optional node's style override * @param args Optional arguments to be forwarded to derived class ctor - * @return Pointer of the pushed type to the newly added Node + * @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 std::string& name, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); + std::shared_ptr addNode(const ImVec2& pos, Params&&... args); /** - * @brief
Adds a node to the editor + * @brief
Add a node to the grid * @tparam T Derived class of to be added - * @tparam U Type of the UID * @tparam Params types of optional args to forward to derived class ctor - * - * @param uid Unique identifier of the node - * @param name Name to be given to the Node - * @param pos Position of the Node in grid coordinates - * @param style Optional node's style override + * @param pos Position of the Node in screen coordinates * @param args Optional arguments to be forwarded to derived class ctor - * @return Pointer of the pushed type to the newly added Node + * @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_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); - - template - std::shared_ptr placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); - - template - std::shared_ptr placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr, Params&&... args); - template - std::shared_ptr placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); - - template - std::shared_ptr placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); + std::shared_ptr placeNodeAt(const ImVec2& pos, Params&&... args); /** - * @brief
Find a node on the grid - * @tparam U Type of the UID - * @param uid Unique identifier of the node - * @return Shared pointer to the node - */ - template - std::shared_ptr findNode(const U& uid); - - /** - * @brief
Find a node on the grid by raw UID - * @param uid Unique identifier of the node - * @return Shared pointer to the node + * @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. */ - std::shared_ptr findNode_raw(NodeUID uid); + template + std::shared_ptr placeNode(Params&&... args); + +// /** +// * @brief
Adds a node to the editor +// * @tparam T Derived class of to be added +// * @tparam U Type of the UID +// * @tparam Params types of optional args to forward to derived class ctor +// * +// * @param uid Unique identifier of the node +// * @param name Name to be given to the Node +// * @param pos Position of the Node in grid coordinates +// * @param style Optional node's style override +// * @param args Optional arguments to be forwarded to derived class ctor +// * @return 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_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); +// +// template +// std::shared_ptr placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); +// +// template +// std::shared_ptr placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr, Params&&... args); +// +// template +// std::shared_ptr placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); +// +// template +// std::shared_ptr placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); - /** - * @brief
Delete a node on the grid - * @tparam U Type of the UID - * @param uid Unique identifier of the node - */ - template - void dropNode(const U& uid); - /** - * @brief
Delete a node on the grid by raw UID - * @param uid Unique identifier of the node - */ - void dropNode_raw(NodeUID uid); /** * @brief
Add link to the handler internal list @@ -442,7 +436,7 @@ namespace ImFlow * @brief
Get editor's list of nodes * @return Const reference to editor's internal nodes list */ - const std::unordered_map>& getNodes() { return m_nodes; } + std::unordered_map>& getNodes() { return m_nodes; } /** * @brief
Get nodes count @@ -575,10 +569,10 @@ namespace ImFlow * @param defReturn Default return value when the pin is not connected * @param filter Connection filter * @param style Style of the pin - * @return Pointer to the newly added pin + * @return Shared pointer to the newly added pin */ template - InPin* addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + std::shared_ptr> addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Add an Input to the node @@ -591,10 +585,10 @@ namespace ImFlow * @param defReturn Default return value when the pin is not connected * @param filter Connection filter * @param style Style of the pin - * @return Pointer to the newly added pin + * @return Shared pointer to the newly added pin */ template - InPin* addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + std::shared_ptr> addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Remove input pin @@ -652,10 +646,10 @@ namespace ImFlow * @param name Name of the pin * @param filter Connection filter * @param style Style of the pin - * @return Pointer to the newly added pin. Must be used to set the behaviour + * @return Shared pointer to the newly added pin. Must be used to set the behaviour */ template - [[nodiscard]] OutPin* addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + [[nodiscard]] std::shared_ptr> addOUT(const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Add an Output to the node @@ -667,10 +661,10 @@ namespace ImFlow * @param name Name of the pin * @param filter Connection filter * @param style Style of the pin - * @return Pointer to the newly added pin. Must be used to set the behaviour + * @return Shared pointer to the newly added pin. Must be used to set the behaviour */ template - [[nodiscard]] OutPin* addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + [[nodiscard]] std::shared_ptr> addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); /** * @brief
Remove output pin @@ -785,7 +779,7 @@ namespace ImFlow /** * @brief
Delete itself */ - void destroy() { m_inf->dropNode_raw(m_uid); m_destroyed = true; } + void destroy() { m_destroyed = true; m_inf->getNodes().erase(m_uid); } /** * @brief
Get hovered status @@ -827,7 +821,7 @@ namespace ImFlow * @brief
Get node's style * @return Shared pointer to the node's style */ - std::shared_ptr& getStyle() { return m_style; } + const std::shared_ptr& getStyle() { return m_style; } /** * @brief
Get selected status @@ -845,25 +839,31 @@ namespace ImFlow * @brief
Set node's uid * @param uid Node's unique identifier */ - void setUID(NodeUID uid) { m_uid = uid; } + BaseNode* setUID(NodeUID uid) { m_uid = uid; return this; } /** * @brief
Set node's name - * @param name New name + * @param name New title */ - void setTitle(const std::string& name) { m_title = name; } + BaseNode* setTitle(const std::string& title) { m_title = title; return this; } /** * @brief
Set node's position * @param pos Position in grid coordinates */ - void setPos(const ImVec2& pos) { m_pos = pos; m_posTarget = pos; } + BaseNode* setPos(const ImVec2& pos) { m_pos = pos; m_posTarget = pos; return this; } /** * @brief
Set ImNodeFlow handler * @param inf Grid handler for the node */ - void setHandler(ImNodeFlow* inf) { m_inf = inf; } + 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 @@ -871,7 +871,7 @@ namespace ImFlow * * Status only updates when updatePublicStatus() is called */ - void selected(bool state) { m_selectedNext = state; } + BaseNode* selected(bool state) { m_selectedNext = state; return this; } /** * @brief
Update the isSelected status of the node diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b1b3109..14f5518 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -178,6 +178,9 @@ namespace ImFlow m_inf->consumeSingleUseClick(); } + if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) + destroy(); + bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); if (onHeader && mouseClickState) { @@ -322,12 +325,6 @@ namespace ImFlow m_dragOut = nullptr; } - // Deletion of selected stuff - if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) - { - std::erase_if(m_nodes, [](const std::pair>& n){ return n.second->isSelected(); }); - } - // Right-click PopUp if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered() && on_free_space()) { diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index a8fb801..c590c5c 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -35,96 +35,94 @@ namespace ImFlow // HANDLER template - std::shared_ptr ImNodeFlow::addNode(const std::string& name, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) - { - return addNode_uid(name, name, pos, style, std::forward(args)...); - } - - template - std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) + 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!"); - NodeUID h = std::hash{}(uid); - assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); + std::shared_ptr n = std::make_shared(std::forward(args)...); - n->setUID(h); - n->setTitle(title); n->setPos(pos); n->setHandler(this); - if (style) - n->getStyle() = style; - else if (!n->getStyle()) - n->getStyle() = NodeStyle::cyan(); + if (!n->getStyle()) + n->setStyle(NodeStyle::cyan()); - m_nodes[h] = n; + auto uid = reinterpret_cast(n.get()); + n->setUID(uid); + m_nodes[uid] = n; return n; } template - std::shared_ptr ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) - { - return placeNodeAt_uid(name, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); - } - - template - std::shared_ptr ImNodeFlow::placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style, Params&&... args) + std::shared_ptr ImNodeFlow::placeNodeAt(const ImVec2& pos, Params&&... args) { - return placeNodeAt_uid(uid, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); + return addNode(screen2grid(pos), std::forward(args)...); } template - std::shared_ptr ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) - { - return placeNodeAt_uid(name, name, pos, std::move(style), std::forward(args)...); - } - - template - std::shared_ptr ImNodeFlow::placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) - { - return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); - } - - template - std::shared_ptr ImNodeFlow::findNode(const U& uid) - { - NodeUID h = std::hash{}(uid); - return findNode_raw(h); - } - - inline std::shared_ptr ImNodeFlow::findNode_raw(NodeUID uid) - { - auto n = m_nodes.find(uid); - assert(n != m_nodes.end() && "Node UID not found!"); - return n->second; - } - - template - void ImNodeFlow::dropNode(const U& uid) - { - NodeUID h = std::hash{}(uid); - dropNode_raw(h); - } - - inline void ImNodeFlow::dropNode_raw(NodeUID uid) - { - m_nodes.erase(uid); - } + std::shared_ptr ImNodeFlow::placeNode(Params&&... args) + { + return placeNodeAt(ImGui::GetMousePos(), std::forward(args)...); + } + +// template +// std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) +// { +// static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); +// NodeUID h = std::hash{}(uid); +// assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); +// std::shared_ptr n = std::make_shared(std::forward(args)...); +// n->setUID(h); +// n->setTitle(title); +// n->setPos(pos); +// n->setHandler(this); +// if (style) +// n->getStyle() = style; +// else if (!n->getStyle()) +// n->getStyle() = NodeStyle::cyan(); +// +// m_nodes[h] = n; +// return n; +// } +// +// template +// std::shared_ptr ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) +// { +// return placeNodeAt_uid(name, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); +// } +// +// template +// std::shared_ptr ImNodeFlow::placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style, Params&&... args) +// { +// return placeNodeAt_uid(uid, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); +// } +// +// template +// std::shared_ptr ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) +// { +// return placeNodeAt_uid(name, name, pos, std::move(style), std::forward(args)...); +// } +// +// template +// std::shared_ptr ImNodeFlow::placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) +// { +// return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); +// } // ----------------------------------------------------------------------------------------------------------------- // BASE NODE template - InPin* BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { return addIN_uid(name, name, defReturn, filter, std::move(style)); } template - InPin* BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_ins.emplace_back(std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style))); - return static_cast*>(m_ins.back().get()); + auto p = std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)); + m_ins.emplace_back(p); + return p; } template @@ -170,17 +168,18 @@ namespace ImFlow } template - OutPin* BaseNode::addOUT(const std::string& name, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addOUT(const std::string& name, ConnectionFilter filter, std::shared_ptr style) { return addOUT_uid(name, name, filter, std::move(style)); } template - OutPin* BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - m_outs.emplace_back(std::make_shared>(h, name, filter, this, &m_inf, std::move(style))); - return static_cast*>(m_outs.back().get()); + auto p = std::make_shared>(h, name, filter, this, &m_inf, std::move(style)); + m_outs.emplace_back(p); + return p; } template From 2b84fb3beba913824cee1f066ee3f0a3cbb6a629 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 17 Feb 2024 13:14:38 +0100 Subject: [PATCH 045/116] Added Bool connection filter (And removed commented code) --- include/ImNodeFlow.h | 43 ++++++------------------------------------- src/ImNodeFlow.inl | 44 -------------------------------------------- 2 files changed, 6 insertions(+), 81 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 5c173d3..7cf06ff 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -62,11 +62,12 @@ namespace ImFlow { ConnectionFilter_None = 0, ConnectionFilter_SameNode = 1 << 1, - ConnectionFilter_Int = 1 << 2, - ConnectionFilter_Float = 1 << 3, - ConnectionFilter_Double = 1 << 4, - ConnectionFilter_String = 1 << 5, - ConnectionFilter_MakeCustom = 1 << 6, + ConnectionFilter_Bool = 1 << 2, + ConnectionFilter_Int = 1 << 3, + ConnectionFilter_Float = 1 << 4, + ConnectionFilter_Double = 1 << 5, + ConnectionFilter_String = 1 << 6, + ConnectionFilter_MakeCustom = 1 << 7, ConnectionFilter_Numbers = ConnectionFilter_Int | ConnectionFilter_Float | ConnectionFilter_Double }; typedef long ConnectionFilter; @@ -348,38 +349,6 @@ namespace ImFlow template std::shared_ptr placeNode(Params&&... args); -// /** -// * @brief
Adds a node to the editor -// * @tparam T Derived class of to be added -// * @tparam U Type of the UID -// * @tparam Params types of optional args to forward to derived class ctor -// * -// * @param uid Unique identifier of the node -// * @param name Name to be given to the Node -// * @param pos Position of the Node in grid coordinates -// * @param style Optional node's style override -// * @param args Optional arguments to be forwarded to derived class ctor -// * @return 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_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style = nullptr, Params&&... args); -// -// template -// std::shared_ptr placeNode(const std::string& name, std::shared_ptr style = nullptr, Params&&... args); -// -// template -// std::shared_ptr placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr, Params&&... args); -// -// template -// std::shared_ptr placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); -// -// template -// std::shared_ptr placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style = nullptr, Params&&... args); - - - /** * @brief
Add link to the handler internal list * @param link Reference to the link diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index c590c5c..c7ab99e 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -63,50 +63,6 @@ namespace ImFlow return placeNodeAt(ImGui::GetMousePos(), std::forward(args)...); } -// template -// std::shared_ptr ImNodeFlow::addNode_uid(const U& uid, const std::string& title, const ImVec2& pos, const std::shared_ptr& style, Params&&... args) -// { -// static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!"); -// NodeUID h = std::hash{}(uid); -// assert(m_nodes.find(h) == m_nodes.end() && "Node UID already exists"); -// std::shared_ptr n = std::make_shared(std::forward(args)...); -// n->setUID(h); -// n->setTitle(title); -// n->setPos(pos); -// n->setHandler(this); -// if (style) -// n->getStyle() = style; -// else if (!n->getStyle()) -// n->getStyle() = NodeStyle::cyan(); -// -// m_nodes[h] = n; -// return n; -// } -// -// template -// std::shared_ptr ImNodeFlow::placeNode(const std::string& name, std::shared_ptr style, Params&&... args) -// { -// return placeNodeAt_uid(name, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); -// } -// -// template -// std::shared_ptr ImNodeFlow::placeNode_uid(const U& uid, const std::string& name, std::shared_ptr style, Params&&... args) -// { -// return placeNodeAt_uid(uid, name, ImGui::GetMousePos(), std::move(style), std::forward(args)...); -// } -// -// template -// std::shared_ptr ImNodeFlow::placeNodeAt(const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) -// { -// return placeNodeAt_uid(name, name, pos, std::move(style), std::forward(args)...); -// } -// -// template -// std::shared_ptr ImNodeFlow::placeNodeAt_uid(const U& uid, const std::string& name, const ImVec2& pos, std::shared_ptr style, Params&&... args) -// { -// return addNode(name, screen2grid(pos), std::move(style), std::forward(args)...); -// } - // ----------------------------------------------------------------------------------------------------------------- // BASE NODE From e8ee3dd7c6be72ecbaa06410fcc7865e8a30d3c3 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:30:08 +0100 Subject: [PATCH 046/116] Added license --- LICENSE.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..dd4735b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +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. From bfc6933b1287d68b1fd88ca6a4aa8153f2589f68 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 23 Feb 2024 11:02:45 +0100 Subject: [PATCH 047/116] renamed old doc --- documentation.md => quick_guide.md | 0 readme.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename documentation.md => quick_guide.md (100%) diff --git a/documentation.md b/quick_guide.md similarity index 100% rename from documentation.md rename to quick_guide.md diff --git a/readme.md b/readme.md index 3f10b83..1286a70 100644 --- a/readme.md +++ b/readme.md @@ -71,7 +71,7 @@ private: ## Full documentation -For a more detailed explanation please refer to the [full documentation](documentation.md) +For a more detailed explanation please refer to the [quick guide](quick_guide) *** ### Special credits - [ocornut](https://github.com/ocornut) for Dear ImGui From a514c6d54d5bb5a162281f8d751c40bae5573a8e Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 23 Feb 2024 12:06:51 +0100 Subject: [PATCH 048/116] renaming --- quick_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quick_guide.md b/quick_guide.md index 50302d2..0d97a71 100644 --- a/quick_guide.md +++ b/quick_guide.md @@ -1,4 +1,4 @@ -# ImNodeFlow Documentation +# ImNodeFlow Quick Guide *** ## Index @@ -50,7 +50,7 @@ Custom nodes **must** derive from the class BaseNode. This shows at a glance the class CustomNode : public ImFlow::BaseNode { explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) - : BaseNode(name, pos, inf) { /* omitted */} + : BaseNode(name, pos, inf) { /* omitted */ } void draw() override { /* omitted */ } }; ``` From 0f61c040f02e1119ac256acf87eae876206a77e1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 23 Feb 2024 19:59:58 +0100 Subject: [PATCH 049/116] Wrote new documentation about nodes and pins --- documentation.md | 200 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 documentation.md diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000..45a389f --- /dev/null +++ b/documentation.md @@ -0,0 +1,200 @@ +# ImNodeFlow Documentation +*** + +## Index +- [NODES](#nodes) + - [Definition](#definition) + - [Body content](#body-content) + - [Static pins](#static-pins) + - [Dynamic pins](#dynamic-pins) + - [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) + - [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_Int); +``` +_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)._ + +### Styling system +The node's style can be fully customized. +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: `setTitle(node_title)`. + +*** +## 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. +The user is provided with some pre-built filters in the `ConnectionFilters_` enumerator. +

It is also possible to create more filters with the help of `ConnectionFilter_MakeCustom` +```c++ +ConnectionFilter myFilter = ConnectionFilter_MakeCustom << 0; + +enum MyFilters +{ + FilterA = ConnectionFilter_MakeCustom << 1, + FilterB = ConnectionFilter_MakeCustom << 2, + FilterC = FilterA | FilterB +}; +``` +_NB: each filter is nothing else than a number, so `ConnectionFilter_Int` for example, does not imply in any way a pin of `int` type._ + +### 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, filter) + ->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, filter) + ->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 +### Main loop + +### Adding nodes + +### Pop-ups + +### Customization From ed037802b2dfcf8174327acb9d5b441b9874a73d Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 26 Feb 2024 17:11:15 +0100 Subject: [PATCH 050/116] Finished new documentation --- documentation.md | 65 ++++++++++- include/ImNodeFlow.h | 14 ++- quick_guide.md | 262 ------------------------------------------- readme.md | 19 ++-- src/ImNodeFlow.cpp | 19 ++-- 5 files changed, 96 insertions(+), 283 deletions(-) delete mode 100644 quick_guide.md diff --git a/documentation.md b/documentation.md index 45a389f..cf875f3 100644 --- a/documentation.md +++ b/documentation.md @@ -16,6 +16,7 @@ - [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) @@ -92,7 +93,7 @@ showOUT(pin_name, behaviour, filter, style); _As mentioned in Static pins, `behaviour` is explained at [Output pins](#output-pins)._ ### Styling system -The node's style can be fully customized. +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++ @@ -103,7 +104,7 @@ 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: `setTitle(node_title)`. +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 @@ -191,10 +192,70 @@ The content of the pin is a custom `ImGui::Text` with the name and the value of *** ## 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._ diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7cf06ff..c8e2228 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -369,7 +369,7 @@ namespace ImFlow * @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); } + void rightClickPopUpContent(std::function content) { m_rightClickPopUp = std::move(content); } /** * @brief
Get mouse clicking status @@ -423,7 +423,7 @@ namespace ImFlow * @brief
Get zooming viewport * @return Const reference to editor's internal viewport for zoom support */ - const ContainedContext& getGrid() { return m_context; } + ContainedContext& getGrid() { return m_context; } /** * @brief
Get dragging status @@ -457,6 +457,12 @@ namespace ImFlow */ 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 @@ -491,11 +497,13 @@ namespace ImFlow std::unordered_map> m_nodes; std::vector> m_links; - std::function m_rightClickPopUp; 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; diff --git a/quick_guide.md b/quick_guide.md deleted file mode 100644 index 0d97a71..0000000 --- a/quick_guide.md +++ /dev/null @@ -1,262 +0,0 @@ -# ImNodeFlow Quick Guide -*** - -## Index -- [Getting started](#getting-started) -- [Custom Nodes 101](#creating-a-custom-node) - - [Inputs](#adding-input-pins) - - [Outputs](#adding-output-pins) - - [Body](#nodes-body) - - [Example](#what-have-we-learned) - - [Adding it](#adding-nodes-to-the-grid) -- [Pins 101](#pins) - - [Static vs Dynamic](#static-vs-dynamic) - - [Custom rendering](#custom-rendering) -- [Editor Handling 101](#handling-the-editor) -- [Connection filters 101](#custom-filters) - - [Basic filters](#basic-filters) - - [Creating filters](#creating-more-filters) -- [Pop-ups 101](#custom-pop-ups) - - [Right click](#right-click-pop-up) - - [Dropped link](#dropped-link-pop-up) -- [Custom styles 101](#custom-styles) - -*** - -## Getting started -After having included the necessary files into the project. A few simple steps are necessary. -```c++ -#include // Include dependencies -using namespace ImFlow; -``` -```c++ -ImNodeFlow INF; // Create an editor with default name -ImNodeFlow INF("Name"); // Create an editor with given name -ImNodeFlow INF = ImNodeFlow("Name"); // Create an editor with given name -``` -```c++ -// Inside Dear ImGui loop -INF.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. -
A custom size can be specified using `.size(newSize)`. - -*** - -## Creating a custom node -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. Descriptions for this required structure are in the subsequent sections. -```c++ -class CustomNode : public ImFlow::BaseNode -{ - explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) - : BaseNode(name, pos, inf) { /* omitted */ } - void draw() override { /* omitted */ } -}; -``` - -### The constructor -```c++ -explicit CustomNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf) -: BaseNode(name, pos, inf) { /* omitted */ } -``` -The first 3 arguments to the constructor are standard and must **not** deviate. You are free to add additional arguments after the first 3 to support custom behavior. This is to ensure nodes construct correctly. -```c++ -explicit InputAveragingNode(const std::string& name, ImVec2 pos, ImNodeFlow* inf, int numInputs) -: BaseNode(name, pos, inf) { /* omitted */ } -``` - -### Adding input pins -```c++ -explicit CustomNode(. . .) -{ - addIN("Pin name", 0, Connection filter); // The name is also used as the UID - addIN_uid(uid, "Pin name", 0, Connection filter); // Custom UID of generic type -} -``` -`addIN` will add an input pin to the node. Usually called in the node's constructor. -
UIDs must be unique but only in the context of the inputs of the current node -_(an output or an input of another node can have the same uid)_ -
The UID can be any of type and of different types between pins. -#### Getting the value -BaseNode provides the following getter -```c++ -int value = getInVal(uid); -``` -Returns a read only reference to the value associated with the input pin identified with given uid. -
_Refer to the doxygen documentation for more details on the different use cases_ -#### Referencing the pin -BaseNode provides the following getter -```c++ -Pin* pin = inPin(uid); // From inside the node -Pin* pin = node.inPin(uid); // Elsewhere -``` -Returns a generic pin type pointer to the input pin identified with given uid. - -### Adding output pins -```c++ -explicit CustomNode(. . .) -{ - addOUT("Pin name", Connection filter); // The name is also used as the UID - addOUT_uid(uid, "Pin name", Connection filter); // Custom UID of generic type -} -``` -`addOUT` will add an output pin to the node. Usually called in the node's constructor. -
UIDs must be unique but only in the context of the inputs of the current node -_(an output or an input of another node can have the same uid)_ -
The UID can be any of type and of different types between pins. -#### Defining logic -```c++ -behaviour([this](){ return . . .; }); -``` -Node's logic for a specific pin. -
Takes either a function or lambda expression. The return value is the output of the pin. -#### All together -```c++ -addOUT("Pin name", Connection filter) - ->behaviour([this](){ return 0; }); -``` -Creates a pin with given name and filter and sets its logic. -
This pin will be rather useless since it always returns 0 (also known as the author's IQ). -#### Referencing the pin -BaseNode provides the following getter -```c++ -Pin* pin = outPin(uid); // From inside the node -Pin* pin = node.outPin(uid); // Elsewhere -``` -Returns a generic pin type pointer to the output pin identified with given uid. - -### Node's body -```c++ -void draw() override { . . . } -``` -Called each frame to draw ImGui widgets inside the node's body. -
Can be left empty if the nodes only needs inputs and outputs. - -### What have we learned? -To create a custom node, all it's needed is to define input pins, output pins + custom logic, and an optional body. -
Everything else will be handled internally. -```c++ -class SimpleSum : public BaseNode -{ -public: - explicit SimpleSum(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) - { - addIN("IN_VAL", 0, ConnectionFilter_Int); - addOUT("OUT_VAL", ConnectionFilter_Int) - ->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; -}; -``` -The example presented in the readme. -
SimpleSum has one input pin of type int called `IN_VAL` and one output pin called `OUT_VAL`. -Both have their filter set to `int`. -
The output pin returns the input + the slider's value. -
In the body the slider is rendered. - -### Adding nodes to the grid -It's now time to add our beautifully useless node to the grid. -```c++ -INF.addNode("Node's name", ImVec2(0, 0)); // Add node at given canvas coordinates -INF.placeNode("Node's name", ImVec2(0, 0)); // Add node at given screen coordinates -INF.placeNode("Node's name"); // Add node at Mouse position -``` - -*** - -## Pins -### Static vs Dynamic - -### Custom rendering - -*** - -## Handling the editor - -*** - -## Custom filters -Filters can be used to block unwanted links between pins. -### Basic filters -- `ConnectionFilter_None`: The pin will allow any connection -- `ConnectionFilter_Int`: The pin will only allow other `int` connections -- `ConnectionFilter_Float`: The pin will only allow other `float` connections -- `ConnectionFilter_Double`: The pin will only allow other `double` connections -- `ConnectionFilter_String`: The pin will only allow other `string` connections -- `ConnectionFilter_Numbers`: The pin will only allow `int`, `float` and `double` connections - -### Creating more filters -It is possible to create more filters with the help of `ConnectionFilter_MakeCustom`. -```c++ -ConnectionFilter myFilter = ConnectionFilter_MakeCustom << 0; - -enum MyFilters -{ - FilterA = ConnectionFilter_MakeCustom << 1, - FilterB = ConnectionFilter_MakeCustom << 2, - FilterC = FilterA | FilterB -}; -``` -Demonstrates different approaches to creating filters. -
Note that `FilterC` will allow `FilterA`, `FilterB` and `FilterC` connections. - -*** - -## Custom pop-ups -ImNodeFlow supports two pop-up events. -
The pop-up state is handled internally, so we only need to handle the content and out custom logic. - -### Right-click pop-up -Triggered when right-clicking on an empty point on the grid. -```c++ -INF.rightClickPopUpContent([]() { - if (ImGui::Selectable("Dummy example")) - { - // Very smart logic - } - // . . . -}); -``` -`rightClickPopUpContent` takes either a function or lambda expression. -
Said function must contain pop-up contents to be displayed and the logic. - -### Dropped link pop-up -Triggered when a link id _dropped_ in an empty point on the grid. And, if specified, the correct key is pressed. -
In this example the pop-up will only opened if the _Shift_ key is being pressed while _dropping_ the link. -```c++ -INF.droppedLinkPopUpContent([](Pin* dragged) { - if (ImGui::Selectable("Dummy example")) - { - // Very smart logic - } - // . . . -}, ImGuiKey_LeftShift); -``` -`droppedLinkPopUpContent` takes either a function or lambda expression. -
Said function must contain pop-up contents to be displayed and the logic. -
An optional key to press can also be specified. - -*** - -## Custom styles -It is possible to change every color and size used by the editor. -```c++ -INF.style() // Get access to all the sizes -INF.style().colors // Get access to all the colors -``` -Sizes and colors can be updated every frame. But it is not possible to change them mid-rendering. -_
It is not possible for example to have links of multiple colors or thickness._ - -*** - -_Please refer to the doxygen documentation for a list of public methods and their details._ - -_In case of problems or questions, consider opening an issue._ diff --git a/readme.md b/readme.md index 1286a70..dd4a9fd 100644 --- a/readme.md +++ b/readme.md @@ -8,10 +8,10 @@ ImNodeFlow will handle connections, editor logic and rendering. ## Features - Support for Zoom -- Backed-in Input and Output logic -- Backed-in links handling +- Built-in Input and Output logic +- Built-in links handling - Customizable filters for different connections -- Backed-in customizable pop-ups +- Built-in customizable pop-up events - Appearance 100% customizable ## Implementation (CMake project) @@ -21,7 +21,7 @@ ImNodeFlow will handle connections, editor logic and rendering. include(FetchContent) FetchContent_Declare(ImNodeFlow GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" - GIT_TAG "v1.1.1" + GIT_TAG "v1.2.0" SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" ) FetchContent_MakeAvailable(ImNodeFlow) @@ -50,8 +50,10 @@ ImNodeFlow will handle connections, editor logic and rendering. class SimpleSum : public BaseNode { public: - explicit SimpleSum(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf) + SimpleSum() { + setTitle("Simple sum"); + setStyle(NodeStyle::green()); addIN("IN_VAL", 0, ConnectionFilter_Int); addOUT("OUT_VAL", ConnectionFilter_Int) ->behaviour([this](){ return getInVal("IN_VAL") + m_valB; }); @@ -66,12 +68,11 @@ private: int m_valB = 0; }; ``` - -![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/4722b1e8-a52c-4ae2-b3f7-babfc713d8db) - +--------------- Insert image ## Full documentation -For a more detailed explanation please refer to the [quick guide](quick_guide) +For a more detailed explanation please refer to the [documentation](documentation.md) + *** ### Special credits - [ocornut](https://github.com/ocornut) for Dear ImGui diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 14f5518..84e4a36 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -172,10 +172,14 @@ namespace ImFlow if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) selected(false); - if (isHovered() && mouseClickState) + if (isHovered()) { - selected(true); - m_inf->consumeSingleUseClick(); + m_inf->hoveredNode(this); + if (mouseClickState) + { + selected(true); + m_inf->consumeSingleUseClick(); + } } if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) @@ -264,6 +268,7 @@ namespace ImFlow { // Updating looping stuff m_hovering = nullptr; + m_hoveredNode = nullptr; m_draggingNode = m_draggingNodeNext; m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); @@ -326,14 +331,14 @@ namespace ImFlow } // Right-click PopUp - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered() && on_free_space()) + if (m_rightClickPopUp && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered()) { - if (m_rightClickPopUp) - ImGui::OpenPopup("RightClickPopUp"); + m_hoveredNodeAux = m_hoveredNode; + ImGui::OpenPopup("RightClickPopUp"); } if (ImGui::BeginPopup("RightClickPopUp")) { - m_rightClickPopUp(); + m_rightClickPopUp(m_hoveredNodeAux); ImGui::EndPopup(); } From 9b6c81a5c432e42a32c36106be72a3f0f573db0b Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:14:38 +0100 Subject: [PATCH 051/116] Added new image --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index dd4a9fd..14302cf 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,7 @@ # ImNodeFlow -**Node based editor/blueprints for ImGui** +**Node-based editor/blueprints for ImGui** -Create your custom nodes, and their logic. -ImNodeFlow will handle connections, editor logic and rendering. +Create your custom nodes and their logic... ImNodeFlow will handle connections, editor logic, and rendering. ![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/605f8cc5-794f-45bd-b4dd-2d6ffdb706e7) @@ -68,7 +67,8 @@ private: int m_valB = 0; }; ``` ---------------- Insert image +![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/0ef78533-23f6-4cda-96aa-dabb121d1503) + ## Full documentation For a more detailed explanation please refer to the [documentation](documentation.md) From 48661a9ddfae704f034a8c39849931e684aef0a6 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 8 Mar 2024 22:23:28 +0100 Subject: [PATCH 052/116] Fixed crash when deleting node --- include/ImNodeFlow.h | 7 +- readme.md | 2 +- src/ImNodeFlow.cpp | 199 +++++++++++++++++++------------------------ 3 files changed, 94 insertions(+), 114 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index c8e2228..75772b6 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -756,7 +756,12 @@ namespace ImFlow /** * @brief
Delete itself */ - void destroy() { m_destroyed = true; m_inf->getNodes().erase(m_uid); } + void destroy() { m_destroyed = true; } + + /* + * @brief
Get if node must be deleted + */ + [[nodiscard]] bool toDestroy() const { return m_destroyed; } /** * @brief
Get hovered status diff --git a/readme.md b/readme.md index 14302cf..d1a276b 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, include(FetchContent) FetchContent_Declare(ImNodeFlow GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" - GIT_TAG "v1.2.0" + GIT_TAG "v1.2.1" SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" ) FetchContent_MakeAvailable(ImNodeFlow) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 84e4a36..e845d43 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -1,58 +1,52 @@ #include "ImNodeFlow.h" -namespace ImFlow -{ +namespace ImFlow { // ----------------------------------------------------------------------------------------------------------------- // LINK - void Link::update() - { + void Link::update() { ImVec2 start = m_left->pinPoint(); - ImVec2 end = m_right->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)) - { + if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) { m_hovered = true; thickness = m_left->getStyle()->extra.link_hovered_thickness; - if (mouseClickState) - { + if (mouseClickState) { m_inf->consumeSingleUseClick(); m_selected = true; } - } - else { m_hovered = false; } + } 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()->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() - { + Link::~Link() { m_left->deleteLink(); } // ----------------------------------------------------------------------------------------------------------------- // BASE NODE - bool BaseNode::isHovered() - { + 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)); + 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(); + 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}); @@ -74,15 +68,12 @@ namespace ImFlow // Inputs ImGui::BeginGroup(); - for (auto& p : m_ins) - { + for (auto &p: m_ins) { p->setPos(ImGui::GetCursorPos()); p->update(); } - for (auto& p : m_dynamicIns) - { - if (p.first == 1) - { + for (auto &p: m_dynamicIns) { + if (p.first == 1) { p.second->setPos(ImGui::GetCursorPos()); p.second->update(); p.first = 0; @@ -98,44 +89,39 @@ namespace ImFlow ImGui::EndGroup(); ImGui::SameLine(); - if (m_destroyed) - { - ImGui::EndGroup(); - ImGui::PopID(); - return; - } - // Outputs float maxW = 0.0f; - for (auto& p : m_outs) - { + for (auto &p: m_outs) { float w = p->calcWidth(); if (w > maxW) maxW = w; } - for (auto& p :m_dynamicOuts) - { + for (auto &p: m_dynamicOuts) { float w = p.second->calcWidth(); if (w > maxW) maxW = w; } ImGui::BeginGroup(); - for (auto& p : m_outs) - { + 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) + 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->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) - { + 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)); + 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->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; } @@ -148,35 +134,36 @@ namespace ImFlow // 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); + 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); ImU32 col = m_style->border_color; float thickness = m_style->border_thickness; ImVec2 ptl = paddingTL; ImVec2 pbr = paddingBR; - if(m_selected) - { + 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; + 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::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) + if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && + !m_inf->on_selected_node()) selected(false); - if (isHovered()) - { + if (isHovered()) { m_inf->hoveredNode(this); - if (mouseClickState) - { + if (mouseClickState) { selected(true); m_inf->consumeSingleUseClick(); } @@ -186,22 +173,19 @@ namespace ImFlow destroy(); bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); - if (onHeader && mouseClickState) - { + if (onHeader && mouseClickState) { m_inf->consumeSingleUseClick(); m_dragged = true; m_inf->draggingNode(true); } - if(m_dragged || (m_selected && m_inf->isNodeDragged())) - { + if (m_dragged || (m_selected && m_inf->isNodeDragged())) { float step = m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions; m_posTarget += ImGui::GetIO().MouseDelta; // "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)) - { + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { m_dragged = false; m_inf->draggingNode(false); m_posTarget = m_pos; @@ -210,17 +194,17 @@ namespace ImFlow ImGui::PopID(); // Resolve output pins values - for (auto& p : m_outs) + for (auto &p: m_outs) p->resolve(); - for (auto& p :m_dynamicOuts) + for (auto &p: m_dynamicOuts) p.second->resolve(); // Deleting dead pins m_dynamicIns.erase(std::remove_if(m_dynamicIns.begin(), m_dynamicIns.end(), - [](const std::pair>& p){ return p.first == 0; }), + [](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; }), + [](const std::pair> &p) { return p.first == 0; }), m_dynamicOuts.end()); } @@ -229,43 +213,37 @@ namespace ImFlow int ImNodeFlow::m_instances = 0; - bool ImNodeFlow::on_selected_node() - { + 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();}); + [](const auto &n) { return n.second->isSelected() && n.second->isHovered(); }); } - bool ImNodeFlow::on_free_space() - { + bool ImNodeFlow::on_free_space() { return std::all_of(m_nodes.begin(), m_nodes.end(), - [](const auto& n) {return !n.second->isHovered();}) + [](const auto &n) { return !n.second->isHovered(); }) && std::all_of(m_links.begin(), m_links.end(), - [](const auto& l) {return !l.lock()->isHovered();}); + [](const auto &l) { return !l.lock()->isHovered(); }); } - ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) - { + ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) { if (ImGui::GetCurrentContext() == m_context.getRawContext()) return p - m_context.scroll(); else return p - m_context.origin() - m_context.scroll() * m_context.scale(); } - ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) - { + ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) { if (ImGui::GetCurrentContext() == m_context.getRawContext()) return p + m_context.scroll(); else - return p + m_context.origin() + m_context.scroll() * m_context.scale(); + return p + m_context.origin() + m_context.scroll() * m_context.scale(); } - void ImNodeFlow::addLink(std::shared_ptr& link) - { + void ImNodeFlow::addLink(std::shared_ptr &link) { m_links.push_back(link); } - void ImNodeFlow::update() - { + void ImNodeFlow::update() { // Updating looping stuff m_hovering = nullptr; m_hoveredNode = nullptr; @@ -275,7 +253,7 @@ namespace ImFlow // Create child canvas m_context.begin(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); // Display grid ImVec2 win_pos = ImGui::GetCursorScreenPos(); @@ -284,74 +262,71 @@ namespace ImFlow draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.grid); for (float y = fmodf(m_context.scroll().y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.grid); - for (float x = fmodf(m_context.scroll().x, m_style.grid_size / m_style.grid_subdivisions); x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) + for (float x = fmodf(m_context.scroll().x, m_style.grid_size / m_style.grid_subdivisions); + x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.subGrid); - for (float y = fmodf(m_context.scroll().y, m_style.grid_size / m_style.grid_subdivisions); y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) + for (float y = fmodf(m_context.scroll().y, m_style.grid_size / m_style.grid_subdivisions); + y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, 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(); } + for (auto &node: m_nodes) { node.second->update(); } + std::erase_if(m_nodes, [](const auto &n) { return n.second->toDestroy(); }); draw_list->ChannelsMerge(); - for (auto& node : m_nodes) { node.second->updatePublicStatus(); } + for (auto &node: m_nodes) { node.second->updatePublicStatus(); } // Update and draw links - for (auto& l : m_links) { if(!l.expired()) l.lock()->update(); } + 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)) - { + 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 + } 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) { if (m_dragOut->getType() == PinType_Output) - smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_dragOut->getStyle()->color, m_dragOut->getStyle()->extra.link_dragged_thickness); + 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); + 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()) - { + if (m_rightClickPopUp && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered()) { m_hoveredNodeAux = m_hoveredNode; ImGui::OpenPopup("RightClickPopUp"); } - if (ImGui::BeginPopup("RightClickPopUp")) - { + if (ImGui::BeginPopup("RightClickPopUp")) { m_rightClickPopUp(m_hoveredNodeAux); ImGui::EndPopup(); } // Dropped Link PopUp - if (ImGui::BeginPopup("DroppedLinkPopUp")) - { + 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()); + [](const std::weak_ptr &l) { return l.expired(); }), m_links.end()); m_context.end(); } From 7958f1aab74193c30b17ab88b500c5fc8df99358 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:19:50 +0100 Subject: [PATCH 053/116] Added requirements list --- readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index d1a276b..f8dc0f7 100644 --- a/readme.md +++ b/readme.md @@ -29,8 +29,6 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) target_link_libraries(YourProject ImNodeFlow) ``` -2. Make sure you have the following dependencies available for `find_package()`: - - [Dear ImGui](https://github.com/ocornut/imgui) ### Manually 1. Download and copy, or clone the repo (or the latest release) inside your project @@ -41,7 +39,10 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) target_link_libraries(YourProject ImNodeFlow) ``` -3. Make sure you have the following dependencies available for `find_package()`: + +## Requirements +1. C++20 or greater +2. Make sure you have the following dependencies available for `find_package()`: - [Dear ImGui](https://github.com/ocornut/imgui) ## Simple Node example From 845ad4114ad48548542bf4bb0c26b6232daf7741 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 13 Mar 2024 22:14:28 +0100 Subject: [PATCH 054/116] Removed C20 dependent code --- CMakeLists.txt | 2 +- src/ImNodeFlow.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb25ea8..6c7e059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.26) -set(CMAKE_CXX_STANDARD 20) +#set(CMAKE_CXX_STANDARD 20) # CREATE PROJECT project(ImNodeFlow) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index e845d43..aa54356 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -273,7 +273,13 @@ namespace ImFlow { // TODO: I don't like this draw_list->ChannelsSplit(2); for (auto &node: m_nodes) { node.second->update(); } - std::erase_if(m_nodes, [](const auto &n) { return n.second->toDestroy(); }); + // 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(); } From c39cf12fad1b98ffd2f58ddeb85509c7de7fda1c Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:17:46 +0100 Subject: [PATCH 055/116] Updated dependencies list --- readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/readme.md b/readme.md index f8dc0f7..c6e50ac 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,6 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, ``` ## Requirements -1. C++20 or greater 2. Make sure you have the following dependencies available for `find_package()`: - [Dear ImGui](https://github.com/ocornut/imgui) From 8bbb1262a59588598af1c90b1bc634b7b4d3d9b4 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:19:20 +0100 Subject: [PATCH 056/116] Fixed dependencies list numeration --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c6e50ac..a0a6d96 100644 --- a/readme.md +++ b/readme.md @@ -41,7 +41,7 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, ``` ## Requirements -2. Make sure you have the following dependencies available for `find_package()`: +1. Make sure you have the following dependencies available for `find_package()`: - [Dear ImGui](https://github.com/ocornut/imgui) ## Simple Node example From 75d4883f57ec45cf15c21e6fd08a27a5479293f7 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 13 Mar 2024 22:21:08 +0100 Subject: [PATCH 057/116] Removed comment line OMG why am I making so many commits! --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c7e059..f814023 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ cmake_minimum_required(VERSION 3.26) -#set(CMAKE_CXX_STANDARD 20) # CREATE PROJECT project(ImNodeFlow) From a16e0fc62343d0a73b106910f9286312d3a861f9 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 15 Mar 2024 08:26:06 +0100 Subject: [PATCH 058/116] Removed dead code --- src/ImNodeFlow.cpp | 24 ++++++++++++------------ src/context_wrapper.h | 7 ++++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index aa54356..332bb7d 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -256,18 +256,18 @@ namespace ImFlow { ImDrawList *draw_list = ImGui::GetWindowDrawList(); // Display grid - ImVec2 win_pos = ImGui::GetCursorScreenPos(); - ImVec2 canvas_sz = ImGui::GetWindowSize(); - for (float x = fmodf(m_context.scroll().x, m_style.grid_size); x < canvas_sz.x; x += m_style.grid_size) - draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.grid); - for (float y = fmodf(m_context.scroll().y, m_style.grid_size); y < canvas_sz.y; y += m_style.grid_size) - draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.grid); - for (float x = fmodf(m_context.scroll().x, m_style.grid_size / m_style.grid_subdivisions); - x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions) - draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.subGrid); - for (float y = fmodf(m_context.scroll().y, m_style.grid_size / m_style.grid_subdivisions); - y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions) - draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.subGrid); + 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 diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 7ed316b..857cadd 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -72,6 +72,7 @@ class ContainedContext ContainedContextConfig& config() { return m_config; } void begin(); void end(); + [[nodiscard]] ImVec2 size() const { return m_size; } [[nodiscard]] float scale() const { return m_scale; } [[nodiscard]] const ImVec2& origin() const { return m_origin; } [[nodiscard]] bool hovered() const { return m_hovered; } @@ -82,6 +83,7 @@ class ContainedContext ImVec2 m_origin; ImVec2 m_pos; + ImVec2 m_size; ImGuiContext* m_ctx = nullptr; ImGuiContext* m_original_ctx = nullptr; @@ -104,10 +106,9 @@ inline void ContainedContext::begin() ImGui::PushStyleColor(ImGuiCol_ChildBg, m_config.color); ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); ImGui::PopStyleColor(); -// m_size = ImGui::GetWindowSize(); m_pos = ImGui::GetWindowPos(); - ImVec2 size = ImGui::GetContentRegionAvail(); + m_size = ImGui::GetContentRegionAvail(); m_origin = ImGui::GetCursorScreenPos(); m_original_ctx = ImGui::GetCurrentContext(); const ImGuiStyle& orig_style = ImGui::GetStyle(); @@ -118,7 +119,7 @@ inline void ContainedContext::begin() CopyIOEvents(m_original_ctx, m_ctx, m_origin, m_scale); - ImGui::GetIO().DisplaySize = size / m_scale; + ImGui::GetIO().DisplaySize = m_size / m_scale; ImGui::GetIO().ConfigInputTrickleEventQueue = false; ImGui::NewFrame(); From fd661c95bd5fc8a30a99acbcd19163a8e8ed5976 Mon Sep 17 00:00:00 2001 From: Benjamin Biglari Date: Fri, 15 Mar 2024 18:58:14 +0100 Subject: [PATCH 059/116] Fixed compiler warnings: - missing virtual destructors - using non-const strings in fmt - initialization order in constructor initializer list different from variable declaration --- include/ImNodeFlow.h | 7 +++++-- src/ImNodeFlow.cpp | 2 +- src/ImNodeFlow.inl | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 75772b6..c73e28c 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -204,7 +204,7 @@ namespace ImFlow * @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) {} + explicit Link(Pin* left, Pin* right, ImNodeFlow* inf) : m_inf(inf), m_left(left), m_right(right) {} /** * @brief
Destruction of a link @@ -521,6 +521,7 @@ namespace ImFlow class BaseNode { public: + virtual ~BaseNode() = default; BaseNode() = default; /** @@ -904,12 +905,14 @@ namespace ImFlow * @param style Style of the pin */ explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) - :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) + :m_uid(uid), m_name(std::move(name)), m_type(kind), m_filter(filter), m_style(std::move(style)), m_parent(parent), m_inf(inf) { 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. diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 332bb7d..cea17f5 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -60,7 +60,7 @@ namespace ImFlow { // Header ImGui::BeginGroup(); - ImGui::TextColored(m_style->header_title_color, m_title.c_str()); + ImGui::TextColored(m_style->header_title_color, "%s", m_title.c_str()); ImGui::Spacing(); ImGui::EndGroup(); float headerH = ImGui::GetItemRectSize().y; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index c7ab99e..091ebb0 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -275,7 +275,7 @@ namespace ImFlow } ImGui::SetCursorPos(m_pos); - ImGui::Text(m_name.c_str()); + ImGui::Text("%s", m_name.c_str()); m_size = ImGui::GetItemRectSize(); drawDecoration(); From 462279055feebb18b133d88183a25623880c6ce7 Mon Sep 17 00:00:00 2001 From: Benjamin Biglari Date: Sat, 16 Mar 2024 00:21:03 +0100 Subject: [PATCH 060/116] reverted changes to initialization list in constructors instead changed order of variable declarations so they match order in constructor initializer list --- include/ImNodeFlow.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index c73e28c..c03d9df 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -204,7 +204,7 @@ namespace ImFlow * @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_inf(inf), m_left(left), m_right(right) {} + explicit Link(Pin* left, Pin* right, ImNodeFlow* inf) : m_left(left), m_right(right), m_inf(inf) {} {} /** * @brief
Destruction of a link @@ -242,9 +242,9 @@ namespace ImFlow */ [[nodiscard]] bool isSelected() const { return m_selected; } private: - ImNodeFlow* m_inf; Pin* m_left; Pin* m_right; + ImNodeFlow* m_inf; bool m_hovered = false; bool m_selected = false; }; @@ -905,7 +905,7 @@ namespace ImFlow * @param style Style of the pin */ explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) - :m_uid(uid), m_name(std::move(name)), m_type(kind), m_filter(filter), m_style(std::move(style)), m_parent(parent), m_inf(inf) + :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) { if(!m_style) m_style = PinStyle::cyan(); @@ -1039,11 +1039,11 @@ namespace ImFlow std::string m_name; ImVec2 m_pos = ImVec2(0.f, 0.f); ImVec2 m_size = ImVec2(0.f, 0.f); - PinType m_type; ConnectionFilter m_filter; - std::shared_ptr m_style; + PinType m_type; BaseNode* m_parent = nullptr; ImNodeFlow** m_inf; + std::shared_ptr m_style; std::function m_renderer; }; From 5b53ec002fe7c46500fc328142c2216ad76ffd1d Mon Sep 17 00:00:00 2001 From: Benjamin Biglari Date: Sat, 16 Mar 2024 00:24:11 +0100 Subject: [PATCH 061/116] replace tabs with spaces --- include/ImNodeFlow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index c03d9df..de532fc 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -521,7 +521,7 @@ namespace ImFlow class BaseNode { public: - virtual ~BaseNode() = default; + virtual ~BaseNode() = default; BaseNode() = default; /** From 715582f3c063d9927fc619f46db403dd983f6b96 Mon Sep 17 00:00:00 2001 From: Benjamin Biglari Date: Sun, 17 Mar 2024 00:30:20 +0100 Subject: [PATCH 062/116] fix InPin Parameter order change order of InPin constructor parameters to be consistent with parameter order in BaseNode member functions --- include/ImNodeFlow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index de532fc..7fc4461 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -1064,7 +1064,7 @@ namespace ImFlow * @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, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow** inf, std::shared_ptr style) + explicit InPin(PinUID uid, const std::string& name, T defReturn, ConnectionFilter filter, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) : Pin(uid, name, filter, PinType_Input, parent, inf, style), m_emptyVal(defReturn) {} /** From 16cd1f96f4885c39debc1cb74aac56569766637a Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 18 Mar 2024 21:50:57 +0100 Subject: [PATCH 063/116] Fixed compiler error in latest PR --- include/ImNodeFlow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7fc4461..a2d9db1 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -204,7 +204,7 @@ namespace ImFlow * @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) {} {} + explicit Link(Pin* left, Pin* right, ImNodeFlow* inf) : m_left(left), m_right(right), m_inf(inf) {} /** * @brief
Destruction of a link From 664b08e0761bdf550e32163f425bc6ae4cffc3b9 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 18 Mar 2024 23:10:42 +0100 Subject: [PATCH 064/116] Started implementing simplified rendering --- include/ImNodeFlow.h | 14 +++++++++--- src/ImNodeFlow.cpp | 52 +++++++++++++++++++++++++++++--------------- src/ImNodeFlow.inl | 15 ++++++++----- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index a2d9db1..92d13ba 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -27,7 +27,7 @@ namespace ImFlow * @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); + inline static void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness, int segments = 0); /** * @brief
Collider checker for smart_bezier @@ -382,6 +382,8 @@ namespace ImFlow */ void consumeSingleUseClick() { m_singleUseClick = false; } + bool fullRender() { return m_context.scale() > 0.7f; } + /** * @brief
Get editor's name * @return Const reference to editor's name @@ -525,11 +527,16 @@ namespace ImFlow BaseNode() = default; /** - * @brief
Main loop of the node - * @details Updates position, hovering and selected status, and renders the node. Must be called each frame. + * @brief
Updates node's state + * @details Updates position, hovering and selected status. Must be called each frame. */ void update(); + /** + * @brief
Renders the node + */ + void render(); + /** * @brief
Content of the node * @details Function to be implemented by derived custom nodes. @@ -865,6 +872,7 @@ namespace ImFlow std::string m_title; ImVec2 m_pos, m_posTarget; ImVec2 m_size; + ImVec2 m_headerSize; ImNodeFlow* m_inf = nullptr; std::shared_ptr m_style; bool m_selected = false, m_selectedNext = false; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index cea17f5..574de12 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -24,8 +24,10 @@ namespace ImFlow { 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); + thickness + m_left->getStyle()->extra.link_selected_outline_thickness, + m_inf->fullRender() ? 0 : 3); + smart_bezier(start, end, m_left->getStyle()->color, thickness, + m_inf->fullRender() ? 0 : 3); if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false)) m_right->deleteLink(); @@ -45,10 +47,9 @@ namespace ImFlow { m_inf->grid2screen(m_pos + m_size + paddingBR)); } - void BaseNode::update() { + void BaseNode::render() { 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}; @@ -59,12 +60,20 @@ namespace ImFlow { 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; + float headerH; + float titleW; + if (m_inf->fullRender()) { + ImGui::BeginGroup(); + ImGui::TextColored(m_style->header_title_color, "%s", m_title.c_str()); + ImGui::Spacing(); + ImGui::EndGroup(); + headerH = ImGui::GetItemRectSize().y; + titleW = ImGui::GetItemRectSize().x; + } else { + headerH = ImGui::CalcTextSize(m_title.c_str()).y; + titleW = ImGui::CalcTextSize(m_title.c_str()).x; + ImGui::Dummy(ImVec2(titleW, headerH)); + } // Inputs ImGui::BeginGroup(); @@ -130,14 +139,14 @@ namespace ImFlow { ImGui::EndGroup(); m_size = ImGui::GetItemRectSize(); - ImVec2 headerSize = ImVec2(m_size.x + paddingBR.x, headerH); + m_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_inf->fullRender() ? m_style->radius : 0); + draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + m_headerSize, m_style->header_bg, + m_inf->fullRender() ? m_style->radius : 0, ImDrawFlags_RoundCornersTop); ImU32 col = m_style->border_color; float thickness = m_style->border_thickness; @@ -154,8 +163,15 @@ namespace ImFlow { 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); + draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, + m_inf->fullRender() ? m_style->radius : 0, 0, thickness); + } + void BaseNode::update() { + 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}; if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) @@ -172,7 +188,7 @@ namespace ImFlow { if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) destroy(); - bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); + bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + m_headerSize); if (onHeader && mouseClickState) { m_inf->consumeSingleUseClick(); m_dragged = true; @@ -272,7 +288,7 @@ namespace ImFlow { // Update and draw nodes // TODO: I don't like this draw_list->ChannelsSplit(2); - for (auto &node: m_nodes) { node.second->update(); } + for (auto &node: m_nodes) { node.second->render(); node.second->update(); } // Remove "toDelete" nodes for (auto iter = m_nodes.begin(); iter != m_nodes.end();) { if (iter->second->toDestroy()) @@ -335,5 +351,7 @@ namespace ImFlow { [](const std::weak_ptr &l) { return l.expired(); }), m_links.end()); m_context.end(); + + std::cout << "Zoom: " << m_context.scale() << std::endl; } } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 091ebb0..ea76f6e 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -4,7 +4,7 @@ namespace ImFlow { - inline void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness) + inline void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness, int segments) { ImDrawList* dl = ImGui::GetWindowDrawList(); float distance = sqrt(pow((p2.x - p1.x), 2.f) + pow((p2.y - p1.y), 2.f)); @@ -15,7 +15,7 @@ namespace ImFlow 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); + dl->AddBezierCubic(p1, p11, p22, p2, color, thickness, segments); } inline bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius) @@ -76,7 +76,7 @@ namespace ImFlow std::shared_ptr> BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - auto p = std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)); + auto p = std::make_shared>(h, name, defReturn, filter, this, &m_inf, std::move(style)); m_ins.emplace_back(p); return p; } @@ -119,7 +119,7 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, defReturn, filter, this, &m_inf, std::move(style)))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } @@ -275,7 +275,12 @@ namespace ImFlow } ImGui::SetCursorPos(m_pos); - ImGui::Text("%s", m_name.c_str()); + if ((*m_inf)->fullRender()) + ImGui::Text("%s", m_name.c_str()); + else { + ImVec2 d = ImGui::CalcTextSize(m_name.c_str()); + ImGui::Dummy(d); + } m_size = ImGui::GetItemRectSize(); drawDecoration(); From 73d8633a7992391b29b837b6ebd483546fb26f41 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 27 Apr 2024 15:17:25 +0200 Subject: [PATCH 065/116] Revert "Started implementing simplified rendering" This reverts commit b9ded55a3b144f7c079339317d3f35e4c9d3ec87. --- include/ImNodeFlow.h | 14 +++--------- src/ImNodeFlow.cpp | 52 +++++++++++++++----------------------------- src/ImNodeFlow.inl | 15 +++++-------- 3 files changed, 25 insertions(+), 56 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 92d13ba..a2d9db1 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -27,7 +27,7 @@ namespace ImFlow * @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, int segments = 0); + inline static void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness); /** * @brief
Collider checker for smart_bezier @@ -382,8 +382,6 @@ namespace ImFlow */ void consumeSingleUseClick() { m_singleUseClick = false; } - bool fullRender() { return m_context.scale() > 0.7f; } - /** * @brief
Get editor's name * @return Const reference to editor's name @@ -527,16 +525,11 @@ namespace ImFlow BaseNode() = default; /** - * @brief
Updates node's state - * @details Updates position, hovering and selected status. Must be called each frame. + * @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
Renders the node - */ - void render(); - /** * @brief
Content of the node * @details Function to be implemented by derived custom nodes. @@ -872,7 +865,6 @@ namespace ImFlow std::string m_title; ImVec2 m_pos, m_posTarget; ImVec2 m_size; - ImVec2 m_headerSize; ImNodeFlow* m_inf = nullptr; std::shared_ptr m_style; bool m_selected = false, m_selectedNext = false; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 574de12..cea17f5 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -24,10 +24,8 @@ namespace ImFlow { if (m_selected) smart_bezier(start, end, m_left->getStyle()->extra.outline_color, - thickness + m_left->getStyle()->extra.link_selected_outline_thickness, - m_inf->fullRender() ? 0 : 3); - smart_bezier(start, end, m_left->getStyle()->color, thickness, - m_inf->fullRender() ? 0 : 3); + 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(); @@ -47,9 +45,10 @@ namespace ImFlow { m_inf->grid2screen(m_pos + m_size + paddingBR)); } - void BaseNode::render() { + 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}; @@ -60,20 +59,12 @@ namespace ImFlow { ImGui::BeginGroup(); // Header - float headerH; - float titleW; - if (m_inf->fullRender()) { - ImGui::BeginGroup(); - ImGui::TextColored(m_style->header_title_color, "%s", m_title.c_str()); - ImGui::Spacing(); - ImGui::EndGroup(); - headerH = ImGui::GetItemRectSize().y; - titleW = ImGui::GetItemRectSize().x; - } else { - headerH = ImGui::CalcTextSize(m_title.c_str()).y; - titleW = ImGui::CalcTextSize(m_title.c_str()).x; - ImGui::Dummy(ImVec2(titleW, headerH)); - } + 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 ImGui::BeginGroup(); @@ -139,14 +130,14 @@ namespace ImFlow { ImGui::EndGroup(); m_size = ImGui::GetItemRectSize(); - m_headerSize = ImVec2(m_size.x + paddingBR.x, headerH); + 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_inf->fullRender() ? m_style->radius : 0); - draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + m_headerSize, m_style->header_bg, - m_inf->fullRender() ? m_style->radius : 0, ImDrawFlags_RoundCornersTop); + m_style->radius); + draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + headerSize, m_style->header_bg, + m_style->radius, ImDrawFlags_RoundCornersTop); ImU32 col = m_style->border_color; float thickness = m_style->border_thickness; @@ -163,15 +154,8 @@ namespace ImFlow { pbr.y -= thickness / 2; thickness *= -1.f; } - draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, - m_inf->fullRender() ? m_style->radius : 0, 0, thickness); - } + draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, m_style->radius, 0, thickness); - void BaseNode::update() { - 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}; if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) @@ -188,7 +172,7 @@ namespace ImFlow { if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) destroy(); - bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + m_headerSize); + bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); if (onHeader && mouseClickState) { m_inf->consumeSingleUseClick(); m_dragged = true; @@ -288,7 +272,7 @@ namespace ImFlow { // Update and draw nodes // TODO: I don't like this draw_list->ChannelsSplit(2); - for (auto &node: m_nodes) { node.second->render(); node.second->update(); } + 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()) @@ -351,7 +335,5 @@ namespace ImFlow { [](const std::weak_ptr &l) { return l.expired(); }), m_links.end()); m_context.end(); - - std::cout << "Zoom: " << m_context.scale() << std::endl; } } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index ea76f6e..091ebb0 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -4,7 +4,7 @@ namespace ImFlow { - inline void smart_bezier(const ImVec2& p1, const ImVec2& p2, ImU32 color, float thickness, int segments) + 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)); @@ -15,7 +15,7 @@ namespace ImFlow 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, segments); + dl->AddBezierCubic(p1, p11, p22, p2, color, thickness); } inline bool smart_bezier_collider(const ImVec2& p, const ImVec2& p1, const ImVec2& p2, float radius) @@ -76,7 +76,7 @@ namespace ImFlow std::shared_ptr> BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - auto p = std::make_shared>(h, name, defReturn, filter, this, &m_inf, std::move(style)); + auto p = std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)); m_ins.emplace_back(p); return p; } @@ -119,7 +119,7 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, defReturn, filter, this, &m_inf, std::move(style)))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } @@ -275,12 +275,7 @@ namespace ImFlow } ImGui::SetCursorPos(m_pos); - if ((*m_inf)->fullRender()) - ImGui::Text("%s", m_name.c_str()); - else { - ImVec2 d = ImGui::CalcTextSize(m_name.c_str()); - ImGui::Dummy(d); - } + ImGui::Text("%s", m_name.c_str()); m_size = ImGui::GetItemRectSize(); drawDecoration(); From 690311056b716541b1f092c3cbe0447406a3436c Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 27 Apr 2024 15:34:42 +0200 Subject: [PATCH 066/116] Parameters order refactored Reordered for consistency --- include/ImNodeFlow.h | 12 ++++++------ src/ImNodeFlow.inl | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index a2d9db1..745ca97 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -904,8 +904,8 @@ namespace ImFlow * @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, ConnectionFilter filter, PinType kind, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) - :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style)) + explicit Pin(PinUID uid, std::string name, ConnectionFilter filter, std::shared_ptr style, PinType kind, BaseNode* parent, ImNodeFlow** inf) + :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_style(std::move(style)), m_type(kind), m_parent(parent), m_inf(inf) { if(!m_style) m_style = PinStyle::cyan(); @@ -1064,8 +1064,8 @@ namespace ImFlow * @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, ConnectionFilter filter, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) - : Pin(uid, name, filter, PinType_Input, parent, inf, style), m_emptyVal(defReturn) {} + explicit InPin(PinUID uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf) + : Pin(uid, name, filter, style, PinType_Input, parent, inf), m_emptyVal(defReturn) {} /** * @brief
Create link between pins @@ -1122,8 +1122,8 @@ namespace ImFlow * @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, ConnectionFilter filter, BaseNode* parent, ImNodeFlow** inf, std::shared_ptr style) - :Pin(uid, name, filter, PinType_Output, parent, inf, style) {} + explicit OutPin(PinUID uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf) + :Pin(uid, name, filter, style, PinType_Output, parent, inf) {} /** * @brief
When parent gets deleted, remove the links diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 091ebb0..02534c0 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -76,7 +76,7 @@ namespace ImFlow std::shared_ptr> BaseNode::addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - auto p = std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)); + auto p = std::make_shared>(h, name, defReturn, filter, std::move(style), this, &m_inf); m_ins.emplace_back(p); return p; } @@ -119,7 +119,7 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, filter, this, defReturn, &m_inf, std::move(style)))); + m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, defReturn, filter, std::move(style), this, &m_inf))); return static_cast*>(m_dynamicIns.back().second.get())->val(); } @@ -133,7 +133,7 @@ namespace ImFlow std::shared_ptr> BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) { PinUID h = std::hash{}(uid); - auto p = std::make_shared>(h, name, filter, this, &m_inf, std::move(style)); + auto p = std::make_shared>(h, name, filter, std::move(style), this, &m_inf); m_outs.emplace_back(p); return p; } @@ -176,7 +176,7 @@ namespace ImFlow } } - m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, this, &m_inf, std::move(style)))); + m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, std::move(style), this, &m_inf))); static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); } From 3e5eed064b2ea6c8f698d6ead452b82e5f6cc8d1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 27 Apr 2024 23:12:39 +0200 Subject: [PATCH 067/116] Complete filters rework --- include/ImNodeFlow.h | 91 ++++++++++++++++++++++++++------------------ src/ImNodeFlow.inl | 40 +++++++++---------- 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 745ca97..914103d 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -15,6 +15,10 @@ #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 { // ----------------------------------------------------------------------------------------------------------------- @@ -49,29 +53,11 @@ namespace ImFlow template class InPin; template class OutPin; class Pin; class BaseNode; - class ImNodeFlow; + class ImNodeFlow; class ConnectionFilter; // ----------------------------------------------------------------------------------------------------------------- // PIN'S PROPERTIES - /** - * @brief
Basic filters - * @details List of, ready to use, basic filters. It's possible to create more filters with the help of "ConnectionFilter_MakeCustom". - */ - enum ConnectionFilter_ - { - ConnectionFilter_None = 0, - ConnectionFilter_SameNode = 1 << 1, - ConnectionFilter_Bool = 1 << 2, - ConnectionFilter_Int = 1 << 3, - ConnectionFilter_Float = 1 << 4, - ConnectionFilter_Double = 1 << 5, - ConnectionFilter_String = 1 << 6, - ConnectionFilter_MakeCustom = 1 << 7, - ConnectionFilter_Numbers = ConnectionFilter_Int | ConnectionFilter_Float | ConnectionFilter_Double - }; - typedef long ConnectionFilter; - typedef unsigned long long int PinUID; /** @@ -550,7 +536,7 @@ namespace ImFlow * @return Shared pointer to the newly added pin */ template - std::shared_ptr> addIN(const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + 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 @@ -566,7 +552,7 @@ namespace ImFlow * @return Shared pointer to the newly added pin */ template - std::shared_ptr> addIN_uid(const U& uid, const std::string& name, T defReturn, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + 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 @@ -596,7 +582,7 @@ namespace ImFlow * @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, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + const T& showIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr); /** * @brief
Show a temporary input pin @@ -613,7 +599,7 @@ namespace ImFlow * @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, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + 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 @@ -627,7 +613,7 @@ namespace ImFlow * @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, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + [[nodiscard]] std::shared_ptr> addOUT(const std::string& name, std::shared_ptr style = nullptr); /** * @brief
Add an Output to the node @@ -642,7 +628,7 @@ namespace ImFlow * @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, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + [[nodiscard]] std::shared_ptr> addOUT_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr); /** * @brief
Remove output pin @@ -671,7 +657,7 @@ namespace ImFlow * @param style Style of the pin */ template - void showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + void showOUT(const std::string& name, std::function behaviour, std::shared_ptr style = nullptr); /** * @brief
Show a temporary output pin @@ -687,7 +673,7 @@ namespace ImFlow * @param style Style of the pin */ template - void showOUT_uid(const U& uid, const std::string& name, std::function behaviour, ConnectionFilter filter = ConnectionFilter_None, std::shared_ptr style = nullptr); + 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 @@ -904,8 +890,8 @@ namespace ImFlow * @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, ConnectionFilter filter, std::shared_ptr style, PinType kind, BaseNode* parent, ImNodeFlow** inf) - :m_uid(uid), m_name(std::move(name)), m_filter(filter), m_style(std::move(style)), m_type(kind), m_parent(parent), m_inf(inf) + 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_style(std::move(style)), m_type(kind), m_parent(parent), m_inf(inf) { if(!m_style) m_style = PinStyle::cyan(); @@ -1006,10 +992,10 @@ namespace ImFlow PinType getType() { return m_type; } /** - * @brief
Get pin's connection filter - * @return Pin's connection filter configuration + * @brief
Get pin's data type (aka: \) + * @return String containing unique information identifying the data type */ - [[nodiscard]] ConnectionFilter getFilter() const { return m_filter; } + [[nodiscard]] virtual const std::type_info& getDataType() const = 0; /** * @brief
Get pin's style @@ -1039,7 +1025,6 @@ namespace ImFlow std::string m_name; ImVec2 m_pos = ImVec2(0.f, 0.f); ImVec2 m_size = ImVec2(0.f, 0.f); - ConnectionFilter m_filter; PinType m_type; BaseNode* m_parent = nullptr; ImNodeFlow** m_inf; @@ -1047,6 +1032,17 @@ namespace ImFlow 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. @@ -1064,8 +1060,8 @@ namespace ImFlow * @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, ConnectionFilter filter, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf) - : Pin(uid, name, filter, style, PinType_Input, parent, inf), m_emptyVal(defReturn) {} + 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 @@ -1090,6 +1086,18 @@ namespace ImFlow */ 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 @@ -1104,6 +1112,7 @@ namespace ImFlow private: std::shared_ptr m_link; T m_emptyVal; + std::function m_filter; }; /** @@ -1122,13 +1131,13 @@ namespace ImFlow * @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, ConnectionFilter filter, std::shared_ptr style, BaseNode* parent, ImNodeFlow** inf) - :Pin(uid, name, filter, style, PinType_Output, parent, inf) {} + 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() { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } + ~OutPin() override { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } /** * @brief
Calculate output value based on set behaviour @@ -1176,6 +1185,12 @@ namespace ImFlow * @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; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 02534c0..0a21b71 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -67,16 +67,16 @@ namespace ImFlow // BASE NODE template - std::shared_ptr> BaseNode::addIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style) { - return addIN_uid(name, name, defReturn, filter, std::move(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, ConnectionFilter filter, std::shared_ptr style) + 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, filter, std::move(style), this, &m_inf); + auto p = std::make_shared>(h, name, defReturn, std::move(filter), std::move(style), this, &m_inf); m_ins.emplace_back(p); return p; } @@ -101,13 +101,13 @@ namespace ImFlow } template - const T& BaseNode::showIN(const std::string& name, T defReturn, ConnectionFilter filter, std::shared_ptr style) + const T& BaseNode::showIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style) { - return showIN_uid(name, name, defReturn, filter, std::move(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, ConnectionFilter filter, std::shared_ptr style) + 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) @@ -119,21 +119,21 @@ namespace ImFlow } } - m_dynamicIns.emplace_back(std::make_pair(1, std::make_shared>(h, name, defReturn, filter, std::move(style), this, &m_inf))); + 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, ConnectionFilter filter, std::shared_ptr style) + std::shared_ptr> BaseNode::addOUT(const std::string& name, std::shared_ptr style) { - return addOUT_uid(name, name, filter, std::move(style)); + return addOUT_uid(name, name, std::move(style)); } template - std::shared_ptr> BaseNode::addOUT_uid(const U& uid, const std::string& name, ConnectionFilter filter, std::shared_ptr style) + 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, filter, std::move(style), this, &m_inf); + auto p = std::make_shared>(h, name, std::move(style), this, &m_inf); m_outs.emplace_back(p); return p; } @@ -158,13 +158,13 @@ namespace ImFlow } template - void BaseNode::showOUT(const std::string& name, std::function behaviour, ConnectionFilter filter, std::shared_ptr style) + void BaseNode::showOUT(const std::string& name, std::function behaviour, std::shared_ptr style) { - showOUT_uid(name, name, std::move(behaviour), filter, std::move(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, ConnectionFilter filter, std::shared_ptr style) + 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) @@ -176,7 +176,7 @@ namespace ImFlow } } - m_dynamicOuts.emplace_back(std::make_pair(2, std::make_shared>(h, name, filter, std::move(style), this, &m_inf))); + 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)); } @@ -300,10 +300,7 @@ namespace ImFlow template void InPin::createLink(Pin *other) { - if (other == this || other->getType() == PinType_Input || (m_parent == other->getParent() && (m_filter & ConnectionFilter_SameNode) == 0)) - return; - - if (!((m_filter & other->getFilter()) != 0 || m_filter == ConnectionFilter_None || other->getFilter() == ConnectionFilter_None)) // Check Filter + if (other == this || other->getType() == PinType_Input) return; if (m_link && m_link->left() == other) @@ -312,6 +309,9 @@ namespace ImFlow return; } + if (!m_filter(other, this)) // Check Filter + return; + m_link = std::make_shared(other, this, (*m_inf)); other->setLink(m_link); (*m_inf)->addLink(m_link); From fcc7145307a7aca590e0f8faa930413f922f906b Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 5 May 2024 11:23:42 +0200 Subject: [PATCH 068/116] Fixed imgui.ini logging bug --- src/ImNodeFlow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index cea17f5..6a50cda 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -252,6 +252,7 @@ namespace ImFlow { // Create child canvas m_context.begin(); + ImGui::GetIO().IniFilename = nullptr; ImDrawList *draw_list = ImGui::GetWindowDrawList(); From e66f3d1788e669c9b6e29d08ca8b7997f18c3ad3 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 7 Jun 2024 20:30:26 +0200 Subject: [PATCH 069/116] Fixed nodes interactions misshapes --- src/ImNodeFlow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 6a50cda..10b6873 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -157,8 +157,8 @@ namespace ImFlow { draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, m_style->radius, 0, thickness); - if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && - !m_inf->on_selected_node()) + if (ImGui::IsWindowHovered() && !ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && + ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !m_inf->on_selected_node()) selected(false); if (isHovered()) { @@ -169,7 +169,7 @@ namespace ImFlow { } } - if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) + if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected()) destroy(); bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize); From 546da889f070f482b400093a14cf303ed93a2d2f Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 7 Jun 2024 20:56:26 +0200 Subject: [PATCH 070/116] Added flag to InPins to support self node connections --- include/ImNodeFlow.h | 7 +++++++ src/ImNodeFlow.inl | 3 +++ 2 files changed, 10 insertions(+) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 914103d..4b7d139 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -1074,6 +1074,12 @@ namespace ImFlow */ 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 @@ -1113,6 +1119,7 @@ namespace ImFlow std::shared_ptr m_link; T m_emptyVal; std::function m_filter; + bool m_allowSelfConnection = false; }; /** diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 0a21b71..06942b6 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -303,6 +303,9 @@ namespace ImFlow 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(); From 7269399330a9ebf2d6971f18f814250f734a6bb0 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 9 Jun 2024 20:32:10 +0200 Subject: [PATCH 071/116] Fixed behaviour management bug and subsequent call order errors --- include/ImNodeFlow.h | 12 +++++++----- src/ImNodeFlow.cpp | 9 +++------ src/ImNodeFlow.inl | 11 ++++++++++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 4b7d139..cede05a 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -474,6 +474,12 @@ namespace ImFlow * @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_nodeRecursionBlacklist; } private: std::string m_name; ContainedContext m_context; @@ -481,6 +487,7 @@ namespace ImFlow bool m_singleUseClick = false; std::unordered_map> m_nodes; + std::vector m_nodeRecursionBlacklist; std::vector> m_links; std::function m_droppedLinkPopUp; @@ -1146,11 +1153,6 @@ namespace ImFlow */ ~OutPin() override { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } - /** - * @brief
Calculate output value based on set behaviour - */ - void resolve() override { m_val = m_behaviour(); } - /** * @brief
Create link between pins * @param other Pointer to the other pin diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 10b6873..fa13976 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -193,12 +193,6 @@ namespace ImFlow { } ImGui::PopID(); - // Resolve output pins values - for (auto &p: m_outs) - p->resolve(); - for (auto &p: m_dynamicOuts) - p.second->resolve(); - // Deleting dead pins m_dynamicIns.erase(std::remove_if(m_dynamicIns.begin(), m_dynamicIns.end(), [](const std::pair> &p) { return p.first == 0; }), @@ -335,6 +329,9 @@ namespace ImFlow { 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_nodeRecursionBlacklist.clear(); + m_context.end(); } } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 06942b6..4ee26b1 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -324,7 +324,16 @@ namespace ImFlow // OUT PIN template - const T &OutPin::val() { return m_val; } + const T &OutPin::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; + } template void OutPin::createLink(ImFlow::Pin *other) From a86637d078b8a4d968b275cd29513d920573c58f Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 9 Jun 2024 20:35:53 +0200 Subject: [PATCH 072/116] Updated documentation --- documentation.md | 19 ++++--------------- readme.md | 6 +++--- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/documentation.md b/documentation.md index cf875f3..e4fad2b 100644 --- a/documentation.md +++ b/documentation.md @@ -79,7 +79,7 @@ _The **necessary** `behaviour()` method for Output pins is explained at [Output 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_Int); +addIN("Input A", 0, ConnectionFilter::SameType()); ``` _For a detailed explanation of pins see [PINS](#pins)._ @@ -119,31 +119,20 @@ The UID can be used to get a reference to the pin, and in case of an input pin, ### Connection filters Filters are useful to avoid unwanted connection between pins. -The user is provided with some pre-built filters in the `ConnectionFilters_` enumerator. -

It is also possible to create more filters with the help of `ConnectionFilter_MakeCustom` -```c++ -ConnectionFilter myFilter = ConnectionFilter_MakeCustom << 0; -enum MyFilters -{ - FilterA = ConnectionFilter_MakeCustom << 1, - FilterB = ConnectionFilter_MakeCustom << 2, - FilterC = FilterA | FilterB -}; -``` -_NB: each filter is nothing else than a number, so `ConnectionFilter_Int` for example, does not imply in any way a pin of `int` type._ +_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, filter) +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, filter) +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. diff --git a/readme.md b/readme.md index a0a6d96..544fcf5 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, include(FetchContent) FetchContent_Declare(ImNodeFlow GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" - GIT_TAG "v1.2.1" + GIT_TAG "origin/master" SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" ) FetchContent_MakeAvailable(ImNodeFlow) @@ -53,8 +53,8 @@ public: { setTitle("Simple sum"); setStyle(NodeStyle::green()); - addIN("IN_VAL", 0, ConnectionFilter_Int); - addOUT("OUT_VAL", ConnectionFilter_Int) + addIN("IN_VAL", 0, ConnectionFilter::SameType()); + addOUT("OUT_VAL", ConnectionFilter::SameType()) ->behaviour([this](){ return getInVal("IN_VAL") + m_valB; }); } From 0d06080d3dc5b0329814d7e71733547e3ef9bcc4 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Fri, 28 Jun 2024 07:57:28 +0200 Subject: [PATCH 073/116] Cleanup crash patch attempt --- include/ImNodeFlow.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index cede05a..26956ee 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -1070,6 +1070,8 @@ namespace ImFlow 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)) {} + ~InPin() override { m_link.reset(); } + /** * @brief
Create link between pins * @param other Pointer to the other pin From 2c6dba6f9e1390b32f56727e0adb31580afc9176 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Wed, 17 Jul 2024 16:32:09 +0200 Subject: [PATCH 074/116] Fixed cleanup crash Credits to @eminor1988 --- include/ImNodeFlow.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 26956ee..c78dfd4 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -1070,8 +1070,6 @@ namespace ImFlow 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)) {} - ~InPin() override { m_link.reset(); } - /** * @brief
Create link between pins * @param other Pointer to the other pin @@ -1153,7 +1151,10 @@ namespace ImFlow /** * @brief
When parent gets deleted, remove the links */ - ~OutPin() override { for (auto &l: m_links) if (!l.expired()) l.lock()->right()->deleteLink(); } + ~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 From e72d9fda71b504107e4cd3565deaafba2afb03e1 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 25 Jul 2024 12:15:53 +0200 Subject: [PATCH 075/116] Fixed data propagation bug when multiple output pins involved --- include/ImNodeFlow.h | 2 +- src/ImNodeFlow.inl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index c78dfd4..7461d16 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -479,7 +479,7 @@ namespace ImFlow * @brief
Get recursion blacklist for nodes * @return Reference to blacklist */ - std::vector& get_recursion_blacklist() { return m_nodeRecursionBlacklist; } + std::vector& get_recursion_blacklist() { return m_nodeRecursionBlacklist; } private: std::string m_name; ContainedContext m_context; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 4ee26b1..c8772bb 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -326,9 +326,9 @@ namespace ImFlow template const T &OutPin::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()) + if (std::find((*m_inf)->get_recursion_blacklist().begin(), (*m_inf)->get_recursion_blacklist().end(), m_uid) == (*m_inf)->get_recursion_blacklist().end()) { - (*m_inf)->get_recursion_blacklist().emplace_back(m_parent->getUID()); + (*m_inf)->get_recursion_blacklist().emplace_back(m_uid); m_val = m_behaviour(); } From 89dafd5921daddc903c8c6e1b1ab04e6a469f516 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 25 Jul 2024 17:15:31 +0200 Subject: [PATCH 076/116] Fixed data propagation bug when multiple output pins share same name --- include/ImNodeFlow.h | 4 ++-- src/ImNodeFlow.cpp | 2 +- src/ImNodeFlow.inl | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 7461d16..f9bbe68 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -479,7 +479,7 @@ namespace ImFlow * @brief
Get recursion blacklist for nodes * @return Reference to blacklist */ - std::vector& get_recursion_blacklist() { return m_nodeRecursionBlacklist; } + std::vector& get_recursion_blacklist() { return m_pinRecursionBlacklist; } private: std::string m_name; ContainedContext m_context; @@ -487,7 +487,7 @@ namespace ImFlow bool m_singleUseClick = false; std::unordered_map> m_nodes; - std::vector m_nodeRecursionBlacklist; + std::vector m_pinRecursionBlacklist; std::vector> m_links; std::function m_droppedLinkPopUp; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index fa13976..b918247 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -330,7 +330,7 @@ namespace ImFlow { [](const std::weak_ptr &l) { return l.expired(); }), m_links.end()); // Clearing recursion blacklist - m_nodeRecursionBlacklist.clear(); + m_pinRecursionBlacklist.clear(); m_context.end(); } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index c8772bb..6abd638 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -75,7 +75,7 @@ namespace ImFlow 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); + PinUID h = std::hash{}(uid) + m_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; @@ -326,9 +326,10 @@ namespace ImFlow template const T &OutPin::val() { - if (std::find((*m_inf)->get_recursion_blacklist().begin(), (*m_inf)->get_recursion_blacklist().end(), m_uid) == (*m_inf)->get_recursion_blacklist().end()) + std::string s = std::to_string(m_uid) + std::to_string(m_parent->getUID()); + if (std::find((*m_inf)->get_recursion_blacklist().begin(), (*m_inf)->get_recursion_blacklist().end(), s) == (*m_inf)->get_recursion_blacklist().end()) { - (*m_inf)->get_recursion_blacklist().emplace_back(m_uid); + (*m_inf)->get_recursion_blacklist().emplace_back(s); m_val = m_behaviour(); } From 8ef72daf172dd37b10f45fe8c42eea5643eb7b39 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Thu, 25 Jul 2024 17:18:17 +0200 Subject: [PATCH 077/116] Removed typo --- src/ImNodeFlow.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 6abd638..8fb45a1 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -75,7 +75,7 @@ namespace ImFlow 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) + m_uid; + 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; From 744a8810d9491d7fc8c38640719dfbc4feb8aaa2 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 14:30:38 +0200 Subject: [PATCH 078/116] Example and fixed warnings of unused vars --- .gitignore | 2 + example/CMakeLists.txt | 48 +++++++++++ example/cmake/desktop.cmake | 15 ++++ example/cmake/emscripten.cmake | 13 +++ example/nd.cpp | 150 +++++++++++++++++++++++++++++++++ example/nd.hpp | 58 +++++++++++++ include/ImNodeFlow.h | 2 +- readme.md | 62 ++------------ src/imgui_bezier_math.inl | 4 +- 9 files changed, 298 insertions(+), 56 deletions(-) create mode 100644 .gitignore create mode 100644 example/CMakeLists.txt create mode 100644 example/cmake/desktop.cmake create mode 100644 example/cmake/emscripten.cmake create mode 100644 example/nd.cpp create mode 100644 example/nd.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d41b216 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +example/build +example/includes diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..ec3342c --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,48 @@ +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/pavanakumar/ImNodeFlow.git" + GIT_TAG "origin/master" + SOURCE_DIR ${IMNODEFLOW_DIR} +) +FetchContent_GetProperties(imgui) +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(nd nd.cpp ${imgui_sources} ${imnode_flow_sources}) +set_property(TARGET nd PROPERTY CXX_STANDARD 17) +target_include_directories(nd PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends) +target_compile_definitions(nd PRIVATE IMGUI_DEFINE_MATH_OPERATORS) + +if(CMAKE_SYSTEM_NAME MATCHES Emscripten) + include(cmake/emscripten.cmake) +else() + include(cmake/desktop.cmake) +endif() + diff --git a/example/cmake/desktop.cmake b/example/cmake/desktop.cmake new file mode 100644 index 0000000..e5bfd2f --- /dev/null +++ b/example/cmake/desktop.cmake @@ -0,0 +1,15 @@ +find_package(OpenGL REQUIRED) +find_package(SDL2 REQUIRED) +if (UNIX) + if (NOT APPLE) + find_package(Threads REQUIRED) + find_package(X11 REQUIRED) + target_link_libraries(nd PRIVATE + ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) + endif() +endif() + +# Fix for GNU libstdfs +# target_link_libraries(nd PUBLIC "$<$:stdc++fs>") +target_link_libraries(nd PUBLIC OpenGL::GL SDL2::SDL2) + diff --git a/example/cmake/emscripten.cmake b/example/cmake/emscripten.cmake new file mode 100644 index 0000000..b4e2f48 --- /dev/null +++ b/example/cmake/emscripten.cmake @@ -0,0 +1,13 @@ +message("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") +message("^^^^^^^^^^ Enabling emscripten compile ^^^^^^^^^^^") +message("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") + +set(CMAKE_EXECUTABLE_SUFFIX ".html") +target_compile_options(nd PUBLIC -sUSE_SDL=2 -fwasm-exceptions) +target_compile_definitions(nd PUBLIC "-DIMGUI_DISABLE_FILE_FUNCTIONS -Wall -Wformat -Os") +target_link_options(nd 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(nd PRIVATE ${IMGUI_DIR}/examples/libs) + diff --git a/example/nd.cpp b/example/nd.cpp new file mode 100644 index 0000000..e48dc97 --- /dev/null +++ b/example/nd.cpp @@ -0,0 +1,150 @@ +#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 "nd.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("Anamika DSL", 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); + + // 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 + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} diff --git a/example/nd.hpp b/example/nd.hpp new file mode 100644 index 0000000..fb6d7ce --- /dev/null +++ b/example/nd.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "ImNodeFlow.h" + +using namespace ImFlow; + +class SimpleSum : public BaseNode +{ + +public: + SimpleSum() + { + setTitle("Simple sum"); + setStyle(NodeStyle::green()); + BaseNode::addIN("In", 0, ConnectionFilter::SameType()); + BaseNode::addOUT("Out", nullptr)->behaviour([this](){ return getInVal("In") + m_valB; }); + } + + void draw() override + { + if(BaseNode::isSelected()) { + ImGui::SetNextItemWidth(100.f); + ImGui::InputInt("##ValB", &m_valB); + ImGui::Button("Hello"); + } + } + +private: + int m_valB = 0; +}; + +struct NodeEditor : ImFlow::BaseNode +{ + ImFlow::ImNodeFlow mINF; + NodeEditor(float d, std::size_t r) + : BaseNode() + { + setTitle("glhf"); + mINF.setSize({d,d}); + if(r > 0) { + mINF.addNode({0,0}); + mINF.addNode({10,10}); + } + } + + void set_size(ImVec2 d) + { + mINF.setSize(d); + } + + void draw() override + { + mINF.update(); + } +}; + +NodeEditor neditor(500, 1500); + diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index f9bbe68..262fd7a 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -898,7 +898,7 @@ namespace ImFlow * @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_style(std::move(style)), m_type(kind), m_parent(parent), m_inf(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(); diff --git a/readme.md b/readme.md index 544fcf5..a0ba466 100644 --- a/readme.md +++ b/readme.md @@ -13,60 +13,16 @@ Create your custom nodes and their logic... ImNodeFlow will handle connections, - Built-in customizable pop-up events - Appearance 100% customizable -## Implementation (CMake project) -### CMake `FetchContent` -1. Add the following lines to your CMakeLists.txt: - ``` - include(FetchContent) - FetchContent_Declare(ImNodeFlow - GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" - GIT_TAG "origin/master" - SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow" - ) - FetchContent_MakeAvailable(ImNodeFlow) - ``` - ``` - add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) - target_link_libraries(YourProject ImNodeFlow) - ``` - -### Manually -1. Download and copy, or clone the repo (or the latest release) inside your project -2. Add the following lines to your CMakeLists.txt: - ``` - add_subdirectory(path/to/ImNodeFlow) - . . . - add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) - target_link_libraries(YourProject ImNodeFlow) - ``` - -## Requirements -1. Make sure you have the following dependencies available for `find_package()`: - - [Dear ImGui](https://github.com/ocornut/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; -}; +## 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 .. + > ./nd +``` + ![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/0ef78533-23f6-4cda-96aa-dabb121d1503) diff --git a/src/imgui_bezier_math.inl b/src/imgui_bezier_math.inl index 3020bdb..addc2ee 100644 --- a/src/imgui_bezier_math.inl +++ b/src/imgui_bezier_math.inl @@ -595,7 +595,7 @@ inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void float t_end = t_max; float t = t_0; - float t_best = t; +// float t_best = t; float error_best = total_length; while (true) @@ -615,7 +615,7 @@ inline void ImCubicBezierFixedStep(ImCubicBezierFixedStepCallback callback, void if (error < error_best) { error_best = error; - t_best = t; +// t_best = t; } if (ImFabs(error) <= max_value_error || ImFabs(t_start - t_end) <= max_t_error) From 88bdd50001505309ecf3a18bf7d23f1838d0e8ff Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 14:32:29 +0200 Subject: [PATCH 079/116] Use example branch --- example/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ec3342c..de87b1e 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,7 +5,7 @@ include(FetchContent) FetchContent_Declare(ImNodeFlow GIT_REPOSITORY "https://github.com/pavanakumar/ImNodeFlow.git" - GIT_TAG "origin/master" + GIT_TAG "example" SOURCE_DIR ${IMNODEFLOW_DIR} ) FetchContent_GetProperties(imgui) From 438f6355f59584f75d9a8269bb63d9b1d253e4f3 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 14:43:57 +0200 Subject: [PATCH 080/116] Added sample CMake --- readme.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/readme.md b/readme.md index a0ba466..57cb7e2 100644 --- a/readme.md +++ b/readme.md @@ -25,6 +25,67 @@ A simple example using SDL2 + OpenGL3 backend is provided in the /example folder ![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/0ef78533-23f6-4cda-96aa-dabb121d1503) +## 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. +``` +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/pavanakumar/ImNodeFlow.git" + GIT_TAG "example" + SOURCE_DIR ${IMNODEFLOW_DIR} +) +FetchContent_GetProperties(imgui) +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(custom_exe )` to the correct sets. For example SDL2+OpenGL requires the following defintitions, + +``` +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) From 2933c6582415ec2576c1670e56dfaf4a337a88ce Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 14:48:44 +0200 Subject: [PATCH 081/116] Removed CMake list --- CMakeLists.txt | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index f814023..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.26) - -# CREATE PROJECT -project(ImNodeFlow) - -add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) - -# SET SOURCE FILES FOR PROJECT -file(GLOB_RECURSE _HDRS "include/*.h") -file(GLOB_RECURSE _SRCS "src/*.cpp" "src/*.h" "src/*.inl") - -# CREATE LIBRARY FROM SOURCE_FILES -add_library(ImNodeFlow ${_SRCS} ${_HDRS}) - -# FIND DEPENDENCIES -find_package(imgui) - -# LINK CONAN LIBS -target_link_libraries(ImNodeFlow imgui::imgui) - -# PREP TO USE "#include <>" -target_include_directories(ImNodeFlow PUBLIC include) From 28d7991d9a3199597b0fbbbe01a548505b549695 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 15:06:42 +0200 Subject: [PATCH 082/116] Fixed readme to reflect changes to repo --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 57cb7e2..e94ddb0 100644 --- a/readme.md +++ b/readme.md @@ -34,11 +34,11 @@ set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow) include(FetchContent) FetchContent_Declare(ImNodeFlow - GIT_REPOSITORY "https://github.com/pavanakumar/ImNodeFlow.git" - GIT_TAG "example" + GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git" + GIT_TAG "master" SOURCE_DIR ${IMNODEFLOW_DIR} ) -FetchContent_GetProperties(imgui) +FetchContent_GetProperties(ImNodeFlow) if(NOT imnodeflow_POPULATED) FetchContent_Populate(ImNodeFlow) endif() @@ -70,7 +70,7 @@ 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(custom_exe )` to the correct sets. For example SDL2+OpenGL requires the following defintitions, +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, ``` find_package(OpenGL REQUIRED) From e171622e41df5a3b42a334ea48ac94f02a1cf751 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 15:10:37 +0200 Subject: [PATCH 083/116] Fixed CMake to obtain sources from current root --- example/CMakeLists.txt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index de87b1e..06ce6c0 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,18 +1,8 @@ set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui) -set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow) +set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/..) include(FetchContent) -FetchContent_Declare(ImNodeFlow - GIT_REPOSITORY "https://github.com/pavanakumar/ImNodeFlow.git" - GIT_TAG "example" - SOURCE_DIR ${IMNODEFLOW_DIR} -) -FetchContent_GetProperties(imgui) -if(NOT imnodeflow_POPULATED) - FetchContent_Populate(ImNodeFlow) -endif() - FetchContent_Declare(imgui GIT_REPOSITORY "https://github.com/ocornut/imgui.git" GIT_TAG "origin/master" From 53beda0047e722342cb31e70251828d0be9b35be Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 15:23:16 +0200 Subject: [PATCH 084/116] changed name nd to example --- .gitignore | 2 ++ example/CMakeLists.txt | 8 ++++---- example/cmake/desktop.cmake | 7 ++----- example/cmake/emscripten.cmake | 8 ++++---- example/{nd.cpp => example.cpp} | 2 +- example/{nd.hpp => example.hpp} | 0 readme.md | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) rename example/{nd.cpp => example.cpp} (99%) rename example/{nd.hpp => example.hpp} (100%) diff --git a/.gitignore b/.gitignore index d41b216..343fc9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ example/build example/includes +build +settings.json \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 06ce6c0..a9221fe 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -25,10 +25,10 @@ list(APPEND imgui_sources list(APPEND imnode_flow_sources ${IMNODEFLOW_DIR}/src/ImNodeFlow.cpp) -add_executable(nd nd.cpp ${imgui_sources} ${imnode_flow_sources}) -set_property(TARGET nd PROPERTY CXX_STANDARD 17) -target_include_directories(nd PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends) -target_compile_definitions(nd PRIVATE IMGUI_DEFINE_MATH_OPERATORS) +add_executable(example example.cpp ${imgui_sources} ${imnode_flow_sources}) +set_property(TARGET example PROPERTY CXX_STANDARD 17) +target_include_directories(example PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends) +target_compile_definitions(example PRIVATE IMGUI_DEFINE_MATH_OPERATORS) if(CMAKE_SYSTEM_NAME MATCHES Emscripten) include(cmake/emscripten.cmake) diff --git a/example/cmake/desktop.cmake b/example/cmake/desktop.cmake index e5bfd2f..5dbc5a4 100644 --- a/example/cmake/desktop.cmake +++ b/example/cmake/desktop.cmake @@ -4,12 +4,9 @@ if (UNIX) if (NOT APPLE) find_package(Threads REQUIRED) find_package(X11 REQUIRED) - target_link_libraries(nd PRIVATE + target_link_libraries(example PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS}) endif() endif() -# Fix for GNU libstdfs -# target_link_libraries(nd PUBLIC "$<$:stdc++fs>") -target_link_libraries(nd PUBLIC OpenGL::GL SDL2::SDL2) - +target_link_libraries(example PUBLIC OpenGL::GL SDL2::SDL2) diff --git a/example/cmake/emscripten.cmake b/example/cmake/emscripten.cmake index b4e2f48..3d16d14 100644 --- a/example/cmake/emscripten.cmake +++ b/example/cmake/emscripten.cmake @@ -3,11 +3,11 @@ message("^^^^^^^^^^ Enabling emscripten compile ^^^^^^^^^^^") message("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") set(CMAKE_EXECUTABLE_SUFFIX ".html") -target_compile_options(nd PUBLIC -sUSE_SDL=2 -fwasm-exceptions) -target_compile_definitions(nd PUBLIC "-DIMGUI_DISABLE_FILE_FUNCTIONS -Wall -Wformat -Os") -target_link_options(nd PUBLIC -sUSE_SDL=2 -fwasm-exceptions -sWASM=1 -sALLOW_MEMORY_GROWTH=1 +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(nd PRIVATE ${IMGUI_DIR}/examples/libs) +target_include_directories(example PRIVATE ${IMGUI_DIR}/examples/libs) diff --git a/example/nd.cpp b/example/example.cpp similarity index 99% rename from example/nd.cpp rename to example/example.cpp index e48dc97..1f7c115 100644 --- a/example/nd.cpp +++ b/example/example.cpp @@ -14,7 +14,7 @@ #include "../libs/emscripten/emscripten_mainloop_stub.h" #endif -#include "nd.hpp" +#include "example.hpp" // Main code int main(int, char**) diff --git a/example/nd.hpp b/example/example.hpp similarity index 100% rename from example/nd.hpp rename to example/example.hpp diff --git a/readme.md b/readme.md index e94ddb0..25744c8 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ A simple example using SDL2 + OpenGL3 backend is provided in the /example folder > mkdir build > cd build > cmake .. - > ./nd + > ./example ``` ![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/0ef78533-23f6-4cda-96aa-dabb121d1503) From 05153ad619e005f99455757c31885cfbd6e2ad6c Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Wed, 4 Sep 2024 15:30:13 +0200 Subject: [PATCH 085/116] Example working in emscripten --- example/html/shell_min.html | 144 ++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 example/html/shell_min.html diff --git a/example/html/shell_min.html b/example/html/shell_min.html new file mode 100644 index 0000000..36bfe59 --- /dev/null +++ b/example/html/shell_min.html @@ -0,0 +1,144 @@ + + + + + + Emscripten-Generated Code + + + +
+
emscripten
+
Downloading...
+
+ +
+
+ +
+
+
+ Resize canvas + Lock/hide mouse pointer +     + +
+ +
+ +
+ + {{{ SCRIPT }}} + + + From 350f80b1465f6a99ad9f0fd358ebc3a1e2ffa698 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Tue, 3 Dec 2024 22:21:38 -0800 Subject: [PATCH 086/116] feat: support lambda defined nodes Add an interface for creating simple node definitions from lambdas using dynamic pins and a helper class. Added documentation for how it should be used. --- documentation.md | 16 ++++++++++++++++ include/ImNodeFlow.h | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/documentation.md b/documentation.md index e4fad2b..a171ea7 100644 --- a/documentation.md +++ b/documentation.md @@ -92,6 +92,22 @@ showOUT(pin_name, behaviour, filter, style); ``` _As mentioned in Static pins, `behaviour` is explained at [Output pins](#output-pins)._ +### Lambda Node Alternative + +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 . diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index f9bbe68..4d437dc 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -310,6 +310,30 @@ namespace ImFlow template std::shared_ptr addNode(const ImVec2& pos, Params&&... args); + /** + * @brief
Helper struct for creating a node struct from a lambda. + * @tparam L the type of the lambda + * @tparam B always BaseNode, tparam because BaseNode is incomplete here + */ + template + struct NodeWrapper : public B, public L + { + NodeWrapper(L&& l): BaseNode(), L(std::forward(l)) {} + void draw() { L::operator()(this); } + }; + + /** + * @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> addLambda(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 From 1177e0eec28bca49b779867c045c05bbb3d6a821 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Thu, 19 Dec 2024 22:49:29 -0800 Subject: [PATCH 087/116] fix: name change and remove lambda inherit change names as per code review request. change the inheritance of the lambda to composition so that the value can be perfectly forwarded via a constructor. --- documentation.md | 3 ++- include/ImNodeFlow.h | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/documentation.md b/documentation.md index a171ea7..5936a63 100644 --- a/documentation.md +++ b/documentation.md @@ -7,6 +7,7 @@ - [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) @@ -92,7 +93,7 @@ showOUT(pin_name, behaviour, filter, style); ``` _As mentioned in Static pins, `behaviour` is explained at [Output pins](#output-pins)._ -### Lambda Node Alternative +### 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. diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 4d437dc..222e1df 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -310,18 +310,22 @@ namespace ImFlow template std::shared_ptr addNode(const ImVec2& pos, Params&&... args); + private: /** - * @brief
Helper struct for creating a node struct from a lambda. + * @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, public L + struct NodeWrapper : public B { - NodeWrapper(L&& l): BaseNode(), L(std::forward(l)) {} - void draw() { L::operator()(this); } + 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 @@ -329,7 +333,7 @@ namespace ImFlow * @param pos the position at which to place the node */ template - std::shared_ptr> addLambda(L&& lambda, const ImVec2& pos) + std::shared_ptr> addLambdaNode(L&& lambda, const ImVec2& pos) { return addNode>(pos, std::forward(lambda)); } From cb0633686655864f29571473ef8cc7a7a23cc2a7 Mon Sep 17 00:00:00 2001 From: Alec Cox Date: Thu, 19 Dec 2024 22:50:43 -0800 Subject: [PATCH 088/116] fix: showIN update lambda showIN was not updating the lambda on subsequent calls which prevented lambda captures from working as expected. --- src/ImNodeFlow.inl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 8fb45a1..7546123 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -172,6 +172,7 @@ namespace ImFlow if (p.second->getUid() == h) { p.first = 2; + static_cast*>(m_dynamicOuts.back().second.get())->behaviour(std::move(behaviour)); return; } } From ed0ace70a20ab4812ade36f74502be510d9ded47 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 4 Jan 2025 12:27:05 +0100 Subject: [PATCH 089/116] Removed useless variable --- src/context_wrapper.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 857cadd..bf9431b 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -92,7 +92,7 @@ class ContainedContext bool m_hovered = false; float m_scale = m_config.default_zoom, m_scaleTarget = m_config.default_zoom; - ImVec2 m_scroll = {0.f, 0.f}, m_scrollTarget = {0.f, 0.f}; + ImVec2 m_scroll = {0.f, 0.f}; }; inline ContainedContext::~ContainedContext() @@ -190,7 +190,6 @@ inline void ContainedContext::end() if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) { m_scroll += ImGui::GetIO().MouseDelta / m_scale; - m_scrollTarget = m_scroll; } ImGui::EndChild(); From 9c5fc200ffd1b04ec172db57393b1d6a02ef8e11 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 4 Jan 2025 12:31:57 +0100 Subject: [PATCH 090/116] Possible fix for misalignment issues --- src/ImNodeFlow.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b918247..68af3fe 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -67,20 +67,22 @@ namespace ImFlow { float titleW = ImGui::GetItemRectSize().x; // Inputs - 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; + if (!m_ins.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(); } - ImGui::EndGroup(); - ImGui::SameLine(); // Content ImGui::BeginGroup(); From 4c0be468fc6ed39863db5a21890356ab7fddc483 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sat, 4 Jan 2025 12:54:53 +0100 Subject: [PATCH 091/116] fixed grid2screen and screen2grid Contribution of: Riztazz --- src/ImNodeFlow.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 68af3fe..fbe2976 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -221,18 +221,18 @@ namespace ImFlow { [](const auto &l) { return !l.lock()->isHovered(); }); } - ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) { - if (ImGui::GetCurrentContext() == m_context.getRawContext()) + ImVec2 ImNodeFlow::screen2grid( const ImVec2 & p ) + { + if ( ImGui::GetCurrentContext() == m_context.getRawContext() ) return p - m_context.scroll(); - else - return p - m_context.origin() - m_context.scroll() * m_context.scale(); + return ( p - m_context.origin() ) / m_context.scale() - m_context.scroll(); } - ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) { - if (ImGui::GetCurrentContext() == m_context.getRawContext()) + ImVec2 ImNodeFlow::grid2screen( const ImVec2 & p ) + { + if ( ImGui::GetCurrentContext() == m_context.getRawContext() ) return p + m_context.scroll(); - else - return p + m_context.origin() + m_context.scroll() * m_context.scale(); + return ( p + m_context.scroll() ) * m_context.scale() + m_context.origin(); } void ImNodeFlow::addLink(std::shared_ptr &link) { From 3e799e2db19383a6a5fabfbf7d3bec06e597918c Mon Sep 17 00:00:00 2001 From: Marcell Kiss Date: Fri, 18 Apr 2025 23:18:39 +0100 Subject: [PATCH 092/116] Fix logic for dynamic inputs --- src/ImNodeFlow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index fbe2976..bade7df 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -67,7 +67,7 @@ namespace ImFlow { float titleW = ImGui::GetItemRectSize().x; // Inputs - if (!m_ins.empty()) { + if (!m_ins.empty() && !m_dynamicIns.empty()) { ImGui::BeginGroup(); for (auto &p: m_ins) { p->setPos(ImGui::GetCursorPos()); From d04f6321b745674dd9163c22a5d8581b22c3416a Mon Sep 17 00:00:00 2001 From: Marcell Kiss Date: Fri, 18 Apr 2025 23:22:25 +0100 Subject: [PATCH 093/116] Update ImNodeFlow.cpp --- src/ImNodeFlow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index bade7df..9ec88e8 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -67,7 +67,7 @@ namespace ImFlow { float titleW = ImGui::GetItemRectSize().x; // Inputs - if (!m_ins.empty() && !m_dynamicIns.empty()) { + if (!m_ins.empty() || !m_dynamicIns.empty()) { ImGui::BeginGroup(); for (auto &p: m_ins) { p->setPos(ImGui::GetCursorPos()); From 46e85eae01eb74b0b8686d07199446f0d19a8e01 Mon Sep 17 00:00:00 2001 From: Marcell Kiss Date: Sat, 19 Apr 2025 15:04:59 +0100 Subject: [PATCH 094/116] add full node size calculation --- include/ImNodeFlow.h | 7 +++++++ src/ImNodeFlow.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 222e1df..5ab0f5d 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -809,6 +809,12 @@ namespace ImFlow */ 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 @@ -886,6 +892,7 @@ namespace ImFlow 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; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 9ec88e8..d77d496 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -140,7 +140,7 @@ namespace ImFlow { 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; From 8a1e6a13882fdfac2217e751829e3f78ecc7d8f0 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Mon, 28 Apr 2025 18:50:17 +0530 Subject: [PATCH 095/116] Latest clean up --- example/CMakeLists.txt | 18 ++++++++++-------- example/example.cpp | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index a9221fe..b71f186 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,17 +1,19 @@ +cmake_minimum_required(VERSION 3.14) +project(example VERSION 1.0 LANGUAGES CXX) + set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui) set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/..) include(FetchContent) -FetchContent_Declare(imgui - GIT_REPOSITORY "https://github.com/ocornut/imgui.git" - GIT_TAG "origin/master" - SOURCE_DIR ${IMGUI_DIR} +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_GetProperties(imgui) -if(NOT imgui_POPULATED) - FetchContent_Populate(imgui) -endif() +FetchContent_MakeAvailable(imgui) list(APPEND imgui_sources ${IMGUI_DIR}/imgui.cpp diff --git a/example/example.cpp b/example/example.cpp index 1f7c115..c7f7398 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -60,7 +60,7 @@ int main(int, char**) 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("Anamika DSL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); + 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()); From 42c56c458d824769a0ccd24d8c4a2aa2ac97ef83 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Tue, 29 Apr 2025 14:08:59 +0530 Subject: [PATCH 096/116] Put back old CMake imgui target for system imgui --- example/CMakeLists.txt | 59 +++++++++++++++++++++++++----------------- readme.md | 26 ++++++++++++++++++- 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index b71f186..72a1217 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,35 +1,46 @@ cmake_minimum_required(VERSION 3.14) project(example VERSION 1.0 LANGUAGES CXX) -set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui) -set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/..) - -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) +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) -add_executable(example example.cpp ${imgui_sources} ${imnode_flow_sources}) +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_include_directories(example PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends) target_compile_definitions(example PRIVATE IMGUI_DEFINE_MATH_OPERATORS) if(CMAKE_SYSTEM_NAME MATCHES Emscripten) diff --git a/readme.md b/readme.md index 25744c8..9b7a4cc 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,31 @@ A simple example using SDL2 + OpenGL3 backend is provided in the /example folder > 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; +}; +``` ![image](https://github.com/Fattorino/ImNodeFlow/assets/90210751/0ef78533-23f6-4cda-96aa-dabb121d1503) ## CMake for custom targets From b5dc0cae8402117c08a2fa6eb6195ae2663f1364 Mon Sep 17 00:00:00 2001 From: Pavanakumar Mohanamuraly Date: Tue, 29 Apr 2025 14:16:24 +0530 Subject: [PATCH 097/116] pull request review of hpp --- example/example.hpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/example/example.hpp b/example/example.hpp index fb6d7ce..5883777 100644 --- a/example/example.hpp +++ b/example/example.hpp @@ -1,24 +1,26 @@ +/** + * This is a minimal implementation of the Simple Sum example + */ #pragma once #include "ImNodeFlow.h" -using namespace ImFlow; - -class SimpleSum : public BaseNode +/* The simple sum basic node */ +class SimpleSum : public ImFlow::BaseNode { public: SimpleSum() { setTitle("Simple sum"); - setStyle(NodeStyle::green()); - BaseNode::addIN("In", 0, ConnectionFilter::SameType()); - BaseNode::addOUT("Out", nullptr)->behaviour([this](){ return getInVal("In") + m_valB; }); + 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 { - if(BaseNode::isSelected()) { + if(ImFlow::BaseNode::isSelected()) { ImGui::SetNextItemWidth(100.f); ImGui::InputInt("##ValB", &m_valB); ImGui::Button("Hello"); @@ -29,13 +31,13 @@ class SimpleSum : public BaseNode int m_valB = 0; }; +/* 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() { - setTitle("glhf"); mINF.setSize({d,d}); if(r > 0) { mINF.addNode({0,0}); @@ -54,5 +56,5 @@ struct NodeEditor : ImFlow::BaseNode } }; +/* Create a node editor with width and height */ NodeEditor neditor(500, 1500); - From 4c083c06a89f178677ec3417b092dde469fc89e2 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 30 Apr 2025 08:28:16 +0200 Subject: [PATCH 098/116] [readme.md] Added "cmake" specifier to code block --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9b7a4cc..0ab1d4b 100644 --- a/readme.md +++ b/readme.md @@ -51,7 +51,7 @@ private: ## 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) @@ -96,7 +96,7 @@ 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) From 0e37edb33a8ccdff54d945ed7213e3e59ff69763 Mon Sep 17 00:00:00 2001 From: Fattorino Date: Sun, 8 Jun 2025 15:58:00 +0200 Subject: [PATCH 099/116] fixed deltatime not coinciding inside context wrapper Contribution of: Cursed-Gato --- src/context_wrapper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index bf9431b..e2bc169 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -5,6 +5,7 @@ inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 origin, float scale) { + dst->IO.DeltaTime = src->IO.DeltaTime; dst->InputEventsQueue = src->InputEventsTrail; for (ImGuiInputEvent& e : dst->InputEventsQueue) { if (e.Type == ImGuiInputEventType_MousePos) { From b42f18803fa71012493c0a18c315ac0b6d41868d Mon Sep 17 00:00:00 2001 From: Fattorino Date: Mon, 9 Jun 2025 09:19:40 +0200 Subject: [PATCH 100/116] Updated example --- example/example.hpp | 63 +++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/example/example.hpp b/example/example.hpp index 5883777..fcd7a9c 100644 --- a/example/example.hpp +++ b/example/example.hpp @@ -1,29 +1,40 @@ -/** - * This is a minimal implementation of the Simple Sum example - */ #pragma once #include "ImNodeFlow.h" -/* The simple sum basic node */ -class SimpleSum : public ImFlow::BaseNode -{ - +class SimpleSum : public ImFlow::BaseNode { public: - SimpleSum() - { + 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; }); + 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("In", 0, ImFlow::ConnectionFilter::SameType()); + ImFlow::BaseNode::addIN("Other", 0, ImFlow::ConnectionFilter::SameType()); + ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("In") + m_valB; }); } - void draw() override - { + void draw() override { if(ImFlow::BaseNode::isSelected()) { - ImGui::SetNextItemWidth(100.f); - ImGui::InputInt("##ValB", &m_valB); - ImGui::Button("Hello"); + ImGui::Text("You can only see me when the node is selected!"); + ImGui::SetNextItemWidth(100.f); + ImGui::InputInt("##ValB", &m_valB); } } @@ -32,26 +43,22 @@ class SimpleSum : public ImFlow::BaseNode }; /* Node editor that sets up the grid to place nodes */ -struct NodeEditor : ImFlow::BaseNode -{ +struct NodeEditor : ImFlow::BaseNode { ImFlow::ImNodeFlow mINF; - NodeEditor(float d, std::size_t r) - : BaseNode() - { - mINF.setSize({d,d}); - if(r > 0) { - mINF.addNode({0,0}); - mINF.addNode({10,10}); + + NodeEditor(float d, std::size_t r) : BaseNode() { + mINF.setSize({d, d}); + if (r > 0) { + mINF.addNode({0, 0}); + mINF.addNode({10, 10}); } } - void set_size(ImVec2 d) - { + void set_size(ImVec2 d) { mINF.setSize(d); } - void draw() override - { + void draw() override { mINF.update(); } }; From 9f5850241ea15a2194d48be2e47514e619ec4c0b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Jun 2025 16:24:07 +0200 Subject: [PATCH 101/116] fix: example create editor in main loop so it can be destroyed _before_ calling DestroyContext() on main context. --- example/example.cpp | 10 ++++++++-- example/example.hpp | 2 -- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/example/example.cpp b/example/example.cpp index c7f7398..f49c226 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -87,6 +87,9 @@ int main(int, char**) 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__ @@ -121,8 +124,8 @@ int main(int, char**) 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(); + neditor->set_size(node_editor_size); + neditor->draw(); ImGui::End(); // Rendering @@ -137,6 +140,9 @@ int main(int, char**) EMSCRIPTEN_MAINLOOP_END; #endif + delete neditor; + neditor = nullptr; + // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); diff --git a/example/example.hpp b/example/example.hpp index fcd7a9c..ca5d3a1 100644 --- a/example/example.hpp +++ b/example/example.hpp @@ -63,5 +63,3 @@ struct NodeEditor : ImFlow::BaseNode { } }; -/* Create a node editor with width and height */ -NodeEditor neditor(500, 1500); From 2827734827c5f03fe2952f209d057cbd31a604b7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Jun 2025 16:40:12 +0200 Subject: [PATCH 102/116] fix: copy BackendFlags in secondary context so ImGuiBackendFlags_RendererHasTextures matches between contexts. fix for imgui 1.92. --- src/context_wrapper.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index e2bc169..17d841b 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -122,6 +122,15 @@ inline void ContainedContext::begin() ImGui::GetIO().DisplaySize = m_size / m_scale; ImGui::GetIO().ConfigInputTrickleEventQueue = false; + + // Copy the ImGuiBackendFlags_RendererHasTextures flag as they need to be matching. + // This will also copy the ImGuiBackendFlags_RendererHasVtxOffset flag which will be more optimal in case large draw calls are being made. + ImGui::GetIO().ConfigFlags = m_original_ctx->IO.ConfigFlags; + ImGui::GetIO().BackendFlags = m_original_ctx->IO.BackendFlags; +#ifdef IMGUI_HAS_VIEWPORT + ImGui::GetIO().ConfigFlags &= ~(ImGuiConfigFlags_ViewportsEnable | ImGuiConfigFlags_DockingEnable); +#endif + ImGui::NewFrame(); if (!m_config.extra_window_wrapper) From 8ee3c57e9fc48093d6dcc20e5c04e876e41cfc0d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 12 Jun 2025 16:57:24 +0200 Subject: [PATCH 103/116] use SetFontRasterizerDensity() after Begin() call to benefit from dynamic font scaling in dear imgui 1.92. --- src/context_wrapper.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 17d841b..314e8fe 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -79,6 +79,7 @@ class ContainedContext [[nodiscard]] bool hovered() const { return m_hovered; } [[nodiscard]] const ImVec2& scroll() const { return m_scroll; } ImGuiContext* getRawContext() { return m_ctx; } + void setFontDensity(); private: ContainedContextConfig m_config; @@ -101,11 +102,20 @@ inline ContainedContext::~ContainedContext() if (m_ctx) ImGui::DestroyContext(m_ctx); } +// Call after Begin() +inline void ContainedContext::setFontDensity() +{ +#if IMGUI_VERSION_NUM >= 19198 + ImGui::SetFontRasterizerDensity(roundf(m_scale * 100.0f) / 100.0f); // Round density to two digits. +#endif +} + inline void ContainedContext::begin() { ImGui::PushID(this); ImGui::PushStyleColor(ImGuiCol_ChildBg, m_config.color); ImGui::BeginChild("view_port", m_config.size, 0, ImGuiWindowFlags_NoMove); + setFontDensity(); ImGui::PopStyleColor(); m_pos = ImGui::GetWindowPos(); @@ -140,6 +150,7 @@ inline void ContainedContext::begin() ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::Begin("viewport_container", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + setFontDensity(); ImGui::PopStyleVar(); } From f2559a7a407d248650fbe7b14483bf090cdd6dcf Mon Sep 17 00:00:00 2001 From: "anthony@rabine.fr" Date: Wed, 30 Jul 2025 11:27:01 +0200 Subject: [PATCH 104/116] better example with links --- example/example.hpp | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/example/example.hpp b/example/example.hpp index ca5d3a1..c3044f7 100644 --- a/example/example.hpp +++ b/example/example.hpp @@ -25,23 +25,36 @@ class CollapsingNode : public ImFlow::BaseNode { CollapsingNode() { setTitle("Collapsing node"); setStyle(ImFlow::NodeStyle::red()); - ImFlow::BaseNode::addIN("In", 0, ImFlow::ConnectionFilter::SameType()); - ImFlow::BaseNode::addIN("Other", 0, ImFlow::ConnectionFilter::SameType()); - ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("In") + m_valB; }); + 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::Text("You can only see me when the node is selected!"); ImGui::SetNextItemWidth(100.f); - ImGui::InputInt("##ValB", &m_valB); + ImGui::Text("You can only see me when the node is selected!"); } } -private: - int m_valB = 0; }; +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; @@ -49,8 +62,18 @@ struct NodeEditor : ImFlow::BaseNode { NodeEditor(float d, std::size_t r) : BaseNode() { mINF.setSize({d, d}); if (r > 0) { - mINF.addNode({0, 0}); - mINF.addNode({10, 10}); + 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}); + } } From 098ab9fe3f41f797ecdd9e41562b7f00eeae9a6f Mon Sep 17 00:00:00 2001 From: matty2048 <66888725+matty2048@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:32:18 +0000 Subject: [PATCH 105/116] Fix zooming messing up dragging currently when zooming this causes the links & nodes to become detached from the mouse pointer. This fix makes sure they stay attached to the mouse. --- src/context_wrapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 314e8fe..96d8036 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -212,7 +212,7 @@ inline void ContainedContext::end() { m_scroll += ImGui::GetIO().MouseDelta / m_scale; } - + this->m_ctx->IO.MousePos = (ImGui::GetMousePos() - m_origin) / m_scale; ImGui::EndChild(); ImGui::PopID(); } From efb00a62366f13b45095a85b64d4c1851911eef7 Mon Sep 17 00:00:00 2001 From: matty2048 <66888725+matty2048@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:06:04 +0000 Subject: [PATCH 106/116] Update ImNodeFlow.h --- include/ImNodeFlow.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index f6b1640..21ea501 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -414,6 +414,13 @@ namespace ImFlow * @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 From c6145b8152bc7192affac04aa68e39c4a732633b Mon Sep 17 00:00:00 2001 From: matty2048 <66888725+matty2048@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:06:45 +0000 Subject: [PATCH 107/116] Update context_wrapper.h --- src/context_wrapper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 96d8036..10346b4 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -78,6 +78,7 @@ class ContainedContext [[nodiscard]] const ImVec2& origin() const { return m_origin; } [[nodiscard]] bool hovered() const { return m_hovered; } [[nodiscard]] const ImVec2& scroll() const { return m_scroll; } + [[nodiscard]] ImVec2 getScreenDelta() { return m_original_ctx->IO.MouseDelta / scale(); } ImGuiContext* getRawContext() { return m_ctx; } void setFontDensity(); private: From 50f5c622bea40b1b0383f01df7dafb38a911255b Mon Sep 17 00:00:00 2001 From: matty2048 <66888725+matty2048@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:07:32 +0000 Subject: [PATCH 108/116] Update ImNodeFlow.cpp --- src/ImNodeFlow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index d77d496..dcb2d69 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -182,7 +182,7 @@ namespace ImFlow { } if (m_dragged || (m_selected && m_inf->isNodeDragged())) { float step = m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions; - m_posTarget += ImGui::GetIO().MouseDelta; + 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; From 0159e6f0ab61be7e63fbb442bdbf949dcf103e7a Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:50:59 +0100 Subject: [PATCH 109/116] Fix issue #60 (text inputs not working using SDL3) --- src/context_wrapper.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 10346b4..7d59a8b 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -5,6 +5,7 @@ inline static void CopyIOEvents(ImGuiContext* src, ImGuiContext* dst, ImVec2 origin, float scale) { + dst->PlatformImeData = src->PlatformImeData; dst->IO.DeltaTime = src->IO.DeltaTime; dst->InputEventsQueue = src->InputEventsTrail; for (ImGuiInputEvent& e : dst->InputEventsQueue) { @@ -170,6 +171,7 @@ inline void ContainedContext::end() ImDrawData* draw_data = ImGui::GetDrawData(); + m_original_ctx->PlatformImeData = m_ctx->PlatformImeData; ImGui::SetCurrentContext(m_original_ctx); m_original_ctx = nullptr; From 87d9db4da2a4a2f42121b2f2aefa7f180d3863e0 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:52:27 +0100 Subject: [PATCH 110/116] Fix #57 (missing include) --- include/ImNodeFlow.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 21ea501..3fd17cd 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "../src/imgui_bezier_math.h" #include "../src/context_wrapper.h" From ae15425ad42dbfa8776382071e2e477ff3f31ff9 Mon Sep 17 00:00:00 2001 From: Gabriele Torelli <90210751+Fattorino@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:04:00 +0100 Subject: [PATCH 111/116] Fix #58 Added nullptr check in Link destructor --- src/ImNodeFlow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index dcb2d69..2f49467 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -32,6 +32,7 @@ namespace ImFlow { } Link::~Link() { + if (!m_left) return; m_left->deleteLink(); } From db378de97e6d665d565db1e28e31f0c516c09b78 Mon Sep 17 00:00:00 2001 From: fgfxf <38068739+fgfxf@users.noreply.github.com> Date: Sat, 23 May 2026 01:44:28 +0800 Subject: [PATCH 112/116] Modify ImNodeFlow for custom socket/link behavior --- include/ImNodeFlow.h | 13 +++++++------ src/ImNodeFlow.inl | 23 ++++++++++++----------- src/context_wrapper.h | 6 ++++-- src/imgui_extra_math.inl | 3 +-- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 3fd17cd..685f19e 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -12,6 +12,7 @@ #include #include #include +#define IMGUI_DEFINE_MATH_OPERATORS #include #include "../src/imgui_bezier_math.h" #include "../src/context_wrapper.h" @@ -439,7 +440,7 @@ namespace ImFlow * @brief
Get editor's list of links * @return Const reference to editor's internal links list */ - const std::vector>& getLinks() { return m_links; } + // const std::vector>& getLinks() { return m_links; } /** * @brief
Get zooming viewport @@ -1003,7 +1004,7 @@ namespace ImFlow * @brief
Get pin's link * @return Weak_ptr reference to pin's link */ - virtual std::weak_ptr getLink() { return std::weak_ptr{}; } + // virtual std::weak_ptr getLink() { return std::weak_ptr{}; } /** * @brief
Get pin's UID @@ -1122,7 +1123,7 @@ namespace ImFlow /** * @brief
Delete the link connected to the pin */ - void deleteLink() override { m_link.reset(); } + void deleteLink() override { m_link.clear(); } /** * @brief Specify if connections from an output on the same node are allowed @@ -1134,13 +1135,13 @@ namespace ImFlow * @brief
Get connected status * @return [TRUE] is pin is connected to a link */ - bool isConnected() override { return m_link != nullptr; } + bool isConnected() override { return !m_link .empty(); } /** * @brief
Get pin's link * @return Weak_ptr reference to the link connected to the pin */ - std::weak_ptr getLink() override { return m_link; } + // std::weak_ptr getLink() override { return m_link; } /** * @brief
Get InPin's connection filter @@ -1166,7 +1167,7 @@ namespace ImFlow */ const T& val(); private: - std::shared_ptr m_link; + std::map> m_link; T m_emptyVal; std::function m_filter; bool m_allowSelfConnection = false; diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 7546123..a6c59f6 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -292,10 +292,10 @@ namespace ImFlow template const T& InPin::val() { - if(!m_link) + if(!m_link.empty()) return m_emptyVal; - return reinterpret_cast*>(m_link->left())->val(); + return reinterpret_cast*>(m_link.begin()->second->left())->val(); } template @@ -307,18 +307,19 @@ namespace ImFlow if (m_parent == other->getParent() && !m_allowSelfConnection) return; - if (m_link && m_link->left() == other) - { - m_link.reset(); - 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(other, this, (*m_inf)); - other->setLink(m_link); - (*m_inf)->addLink(m_link); + std::shared_ptr link = std::make_shared(other, this, (*m_inf)); + m_link.insert(std::make_pair(other, link)); + other->setLink(link); + (*m_inf)->addLink(link); } // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 7d59a8b..6ae3ac2 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -207,9 +207,11 @@ inline void ContainedContext::end() } // Zoom reset - if (ImGui::IsKeyPressed(m_config.reset_zoom_key, false)) + if (ImGui::IsKeyPressed(m_config.reset_zoom_key, false)){ + m_scroll=ImVec2(0.f, 0.f); m_scaleTarget = m_config.default_zoom; - + + } // Scrolling if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) { diff --git a/src/imgui_extra_math.inl b/src/imgui_extra_math.inl index 8e2347f..978d3c9 100644 --- a/src/imgui_extra_math.inl +++ b/src/imgui_extra_math.inl @@ -29,13 +29,12 @@ inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } -# endif inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } - +# endif # if IMGUI_VERSION_NUM < 18955 inline ImVec2 operator-(const ImVec2& lhs) { From 18d005705044d8a0b17a8cdb5fe2be0759bfb1d0 Mon Sep 17 00:00:00 2001 From: fgfxf <38068739+fgfxf@users.noreply.github.com> Date: Sat, 23 May 2026 03:22:35 +0800 Subject: [PATCH 113/116] update --- include/ImNodeFlow.h | 21 +++++++++++++++------ src/ImNodeFlow.cpp | 8 +++++--- src/ImNodeFlow.inl | 19 ++++++++++++------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 685f19e..bca43b0 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -87,9 +87,9 @@ namespace ImFlow /// @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; + float link_selected_outline_thickness = 1.5f; /// @brief Color of the outline of a selected link - ImU32 outline_color = IM_COL32(80, 20, 255, 200); + ImU32 outline_color =(0xb6f4ffff);// ImColor(80, 20, 255, 200); /// @brief Spacing between pin content and socket float socket_padding = 6.6f; @@ -992,7 +992,7 @@ namespace ImFlow /** * @brief
Delete link reference */ - virtual void deleteLink() = 0; + virtual void deleteLink(Pin *pin) = 0; /** * @brief
Get connected status @@ -1123,7 +1123,16 @@ namespace ImFlow /** * @brief
Delete the link connected to the pin */ - void deleteLink() override { m_link.clear(); } + void deleteLink(Pin *pin) override { + if(!pin) return; + if(m_link.find(pin)!=m_link.end()){ + if(m_link.find(pin)->second.get()){ + m_link.find(pin)->second.reset(); + } + m_link.erase(pin); + } + + } /** * @brief Specify if connections from an output on the same node are allowed @@ -1197,7 +1206,7 @@ namespace ImFlow */ ~OutPin() override { std::vector> links = std::move(m_links); - for (auto &l: links) if (!l.expired()) l.lock()->right()->deleteLink(); + for (auto &l: links) if (!l.expired()) l.lock()->right()->deleteLink(l.lock()->left()); } /** @@ -1215,7 +1224,7 @@ namespace ImFlow /** * @brief
Delete any expired weak pointers to a (now deleted) link */ - void deleteLink() override; + void deleteLink(Pin *pin) override; /** * @brief
Get connected status diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 2f49467..b4d5acf 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -28,12 +28,12 @@ namespace ImFlow { smart_bezier(start, end, m_left->getStyle()->color, thickness); if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false)) - m_right->deleteLink(); + m_right->deleteLink(m_left); } Link::~Link() { if (!m_left) return; - m_left->deleteLink(); + m_left->deleteLink(m_left); } // ----------------------------------------------------------------------------------------------------------------- @@ -274,7 +274,9 @@ namespace ImFlow { // Remove "toDelete" nodes for (auto iter = m_nodes.begin(); iter != m_nodes.end();) { if (iter->second->toDestroy()) - iter = m_nodes.erase(iter); + { + iter = m_nodes.erase(iter); + } else ++iter; } diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index a6c59f6..912fd0e 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -307,12 +307,17 @@ namespace ImFlow if (m_parent == other->getParent() && !m_allowSelfConnection) return; - - // if (m_link && m_link->left() == other) - // { - // m_link.reset(); - // return; - // } + if (m_link.find(other)!=m_link.end()) + { + if(m_link.find(other)->second.get()){ + if(m_link.find(other)->second->left() == other){ + m_link.find(other)->second.reset(); + m_link.erase(other); + return; + } + } + m_link.erase(other); + } if (!m_filter(other, this)) // Check Filter return; @@ -354,7 +359,7 @@ namespace ImFlow } template - void OutPin::deleteLink() + void OutPin::deleteLink(Pin *pin) { m_links.erase(std::remove_if(m_links.begin(), m_links.end(), [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); From 233b08a85ccae8b380160bfd00160937497c6299 Mon Sep 17 00:00:00 2001 From: fgfxf <38068739+fgfxf@users.noreply.github.com> Date: Sat, 23 May 2026 04:15:59 +0800 Subject: [PATCH 114/116] update --- include/ImNodeFlow.h | 14 ++++++-- src/ImNodeFlow.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++ src/ImNodeFlow.inl | 1 + 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index bca43b0..5bd1cb0 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -896,6 +896,10 @@ namespace ImFlow * @brief
Update the isSelected status of the node */ void updatePublicStatus() { m_selected = m_selectedNext; } + + void destroyLinks(); + + private: NodeUID m_uid = 0; std::string m_title; @@ -1004,8 +1008,9 @@ namespace ImFlow * @brief
Get pin's link * @return Weak_ptr reference to pin's link */ - // virtual std::weak_ptr getLink() { return std::weak_ptr{}; } - + virtual std::map> *getLink(){ return nullptr ;}; + + virtual std::vector> *getWeakLink(){ return nullptr;}; /** * @brief
Get pin's UID * @return Unique identifier of the pin @@ -1124,6 +1129,7 @@ namespace ImFlow * @brief
Delete the link connected to the pin */ void deleteLink(Pin *pin) override { + printf("%s %d\n",__FILE__,__LINE__); if(!pin) return; if(m_link.find(pin)!=m_link.end()){ if(m_link.find(pin)->second.get()){ @@ -1150,7 +1156,7 @@ namespace ImFlow * @brief
Get pin's link * @return Weak_ptr reference to the link connected to the pin */ - // std::weak_ptr getLink() override { return m_link; } + std::map> *getLink() override { return &m_link; } /** * @brief
Get InPin's connection filter @@ -1256,6 +1262,8 @@ namespace ImFlow * @return String containing unique information identifying the data type */ [[nodiscard]] const std::type_info& getDataType() const override { return typeid(T); }; + + virtual std::vector> *getWeakLink() override { return &m_links; }; private: std::vector> m_links; std::function m_behaviour; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index b4d5acf..4221582 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -240,6 +240,86 @@ namespace ImFlow { m_links.push_back(link); } + void BaseNode::destroyLinks() + { + auto eraseLinksOfInPin = [](Pin *pin) + { + if (!pin) + return; + + std::map> *links = pin->getLink(); + for (auto it = links->begin(); it != links->end();) + { + std::shared_ptr link = it->second; + + if (!link) + { + it = links->erase(it); + continue; + } + + Pin *leftPin = link->left(); + Pin *rightPin = link->right(); + leftPin->deleteLink(rightPin); + // Pin *otherPin = nullptr; + // if (leftPin == pin) + // otherPin = rightPin; + // else if (rightPin == pin) + // otherPin = leftPin; + + // if (otherPin) + // { + // otherPin->deleteLink(pin); + // } + + it = links->erase(it); + } + + }; + + auto eraseLinksOfOutPin = [](Pin *pin){ + if(!pin) return; + std::vector> *links = pin->getWeakLink(); + for (auto it = links->begin(); it != links->end();) + { + + + if (!it->lock().get()) + { + it = links->erase(it); + continue; + } + + Pin *leftPin = it->lock().get()->left(); + Pin *rightPin = it->lock().get()->right(); + // if(rightPin) + rightPin->deleteLink(leftPin); + // Pin *otherPin = nullptr; + // if (leftPin == pin) + // otherPin = rightPin; + // else if (rightPin == pin) + // otherPin = leftPin; + + // if (otherPin) + // { + // otherPin->deleteLink(pin); + // } + + // it = links->erase(it); + } + }; + + for (auto &p : m_ins) + { + eraseLinksOfInPin(p.get()); + } + + for (auto &p : m_outs) + { + eraseLinksOfOutPin(p.get()); + } + } + void ImNodeFlow::update() { // Updating looping stuff m_hovering = nullptr; @@ -275,6 +355,7 @@ namespace ImFlow { for (auto iter = m_nodes.begin(); iter != m_nodes.end();) { if (iter->second->toDestroy()) { + iter->second->destroyLinks(); iter = m_nodes.erase(iter); } else diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index 912fd0e..a583bae 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -361,6 +361,7 @@ namespace ImFlow template void OutPin::deleteLink(Pin *pin) { + printf("%s %d\n",__FILE__,__LINE__); m_links.erase(std::remove_if(m_links.begin(), m_links.end(), [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); } From f6ff1940d0646933200fbea92f7d6afe8f80f6ca Mon Sep 17 00:00:00 2001 From: fgfxf <38068739+fgfxf@users.noreply.github.com> Date: Sat, 23 May 2026 18:39:54 +0800 Subject: [PATCH 115/116] Added some annotation --- include/ImNodeFlow.h | 30 ++++++-- src/ImNodeFlow.cpp | 166 ++++++++++++++++++++++++------------------ src/ImNodeFlow.inl | 30 +++++++- src/context_wrapper.h | 6 +- 4 files changed, 152 insertions(+), 80 deletions(-) diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h index 5bd1cb0..2dd9bc0 100644 --- a/include/ImNodeFlow.h +++ b/include/ImNodeFlow.h @@ -80,15 +80,16 @@ namespace ImFlow /// @brief Border color ImU32 border_color = IM_COL32(255, 255, 255, 0); - /// @brief Link thickness + /// @brief 线条粗细 / Link thickness float link_thickness = 2.6f; - /// @brief Link thickness when dragged + /// @brief 拖动时的线条粗细 / Link thickness when dragged float link_dragged_thickness = 2.2f; - /// @brief Link thickness when hovered + /// @brief 指针悬浮时的线条粗细 / Link thickness when hovered float link_hovered_thickness = 3.5f; - /// @brief Thickness of the outline of a selected link + /// @brief 选中线条时的边缘羽化厚度 / Thickness of the outline of a selected link float link_selected_outline_thickness = 1.5f; - /// @brief Color of the outline of a selected link + /// @brief 选中线条时边缘羽化的颜色 / Color of the outline of a selected link + /// @fgfxf 0xb6f4ffff 是偏向于白色的蓝色。The modification is biased towards white, making it more prominent ImU32 outline_color =(0xb6f4ffff);// ImColor(80, 20, 255, 200); /// @brief Spacing between pin content and socket @@ -897,6 +898,12 @@ namespace ImFlow */ void updatePublicStatus() { m_selected = m_selectedNext; } + /** + * @brief 删除一个蓝图节点的所有对外对内连线。 / Delete all external and internal connections of a blueprint node。 + * @author fgfxf + * @date 2026-05-23 + * @note new added for multi-link support. + */ void destroyLinks(); @@ -995,6 +1002,9 @@ namespace ImFlow /** * @brief
Delete link reference + * @authors 原作者 original author , fgfxf + * @date 2026-05-23 + * @note change the virtual function param[in] to support muliti-link */ virtual void deleteLink(Pin *pin) = 0; @@ -1127,9 +1137,12 @@ namespace ImFlow /** * @brief
Delete the link connected to the pin + * @authors original author , fgfxf + * @param[in] Pin* want to delete. 对端是weak_ptr, 不用管理. + * @note changed by fgfxf, to support multi-link delete + * @date 2026-05-23 */ void deleteLink(Pin *pin) override { - printf("%s %d\n",__FILE__,__LINE__); if(!pin) return; if(m_link.find(pin)!=m_link.end()){ if(m_link.find(pin)->second.get()){ @@ -1209,6 +1222,8 @@ namespace ImFlow /** * @brief
When parent gets deleted, remove the links + * @author changed by fgfxf + * @date 2026-05-23 */ ~OutPin() override { std::vector> links = std::move(m_links); @@ -1229,6 +1244,9 @@ namespace ImFlow /** * @brief
Delete any expired weak pointers to a (now deleted) link + * @author fgfxf + * @param[in] Pin* meaningless + * @note support multi-link delete. and param meaningless for Outpin */ void deleteLink(Pin *pin) override; diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp index 4221582..6ed4a2a 100644 --- a/src/ImNodeFlow.cpp +++ b/src/ImNodeFlow.cpp @@ -12,7 +12,9 @@ namespace ImFlow { if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) m_selected = false; - + /** + * @brief 贝塞尔曲线计算鼠标位置是否悬浮在线条上。 + */ if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) { m_hovered = true; thickness = m_left->getStyle()->extra.link_hovered_thickness; @@ -27,13 +29,23 @@ namespace ImFlow { thickness + m_left->getStyle()->extra.link_selected_outline_thickness); smart_bezier(start, end, m_left->getStyle()->color, thickness); + /** + * @author fgfxf + * @date 2026-05-23 + * @brief delete selected link + * @note InPin删除Outpin,因为InPin的map是真正管理的,OutPin只是weak_ptr + */ if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false)) m_right->deleteLink(m_left); } Link::~Link() { + /** + * @brief outpin清理一下 + * @note 传入参数无意义 / param meaningless + */ if (!m_left) return; - m_left->deleteLink(m_left); + m_left->deleteLink(m_left); } // ----------------------------------------------------------------------------------------------------------------- @@ -240,86 +252,102 @@ namespace ImFlow { m_links.push_back(link); } - void BaseNode::destroyLinks() + /** + * @brief 删除一个蓝图节点的所有对外对内连线。 / Delete all external and internal connections of a blueprint node。 + * @author fgfxf + * @date 2026-05-23 + * @note new added for multi-link support. + */ + void BaseNode::destroyLinks() + { + /** + * @brief lambda function, 删除所有的InPin , delete all inpin + * @author fgfxf + * @date 2026-05-23 + * @param[in] Pin* 连接到这个node的线的左侧pin,当前节点的InPIn. + * The left pin of the line connected to this node, which is the InPin of the current node. + */ + auto eraseLinksOfInPin = [](Pin *pin) { - auto eraseLinksOfInPin = [](Pin *pin) + if (!pin) + return; + /** + * @brief 遍历当前InPin的所有连线,一个pin可以有多条线。 + * Traverse all the connections of the current InPin, as one pin can have multiple lines. + */ + std::map> *links = pin->getLink(); + for (auto it = links->begin(); it != links->end();) { - if (!pin) - return; + std::shared_ptr link = it->second; - std::map> *links = pin->getLink(); - for (auto it = links->begin(); it != links->end();) + if (!link) { - std::shared_ptr link = it->second; - - if (!link) - { - it = links->erase(it); - continue; - } - - Pin *leftPin = link->left(); - Pin *rightPin = link->right(); - leftPin->deleteLink(rightPin); - // Pin *otherPin = nullptr; - // if (leftPin == pin) - // otherPin = rightPin; - // else if (rightPin == pin) - // otherPin = leftPin; - - // if (otherPin) - // { - // otherPin->deleteLink(pin); - // } - it = links->erase(it); + continue; } - - }; + // 当前连线的左侧节点 + Pin *leftPin = link->left(); + Pin *rightPin = link->right(); - auto eraseLinksOfOutPin = [](Pin *pin){ - if(!pin) return; - std::vector> *links = pin->getWeakLink(); - for (auto it = links->begin(); it != links->end();) - { - - - if (!it->lock().get()) - { - it = links->erase(it); - continue; - } + leftPin->deleteLink(rightPin); + it = links->erase(it); + } + }; + + /** + * @brief lambda function, 删除所有的OutPin , delete all Outpin + * @author fgfxf + * @date 2026-05-23 + * @param[in] Pin* 连接到这个node的线的右侧pin,当前节点的OutPIn. + * The right pin of the line connected to this node, which is the OutPin of the current node. + */ + auto eraseLinksOfOutPin = [](Pin *pin) + { + if (!pin) + return; + // OutPin保存的是weak_ptr 所以要获取对端的InPIn,让对端Node删除。 + // OutPin stores a weak_ptr, so we need to obtain the InPIn of the peer and ask the peer Node to delete it. + std::vector> *links = pin->getWeakLink(); + for (auto it = links->begin(); it != links->end();) + { - Pin *leftPin = it->lock().get()->left(); - Pin *rightPin = it->lock().get()->right(); - // if(rightPin) - rightPin->deleteLink(leftPin); - // Pin *otherPin = nullptr; - // if (leftPin == pin) - // otherPin = rightPin; - // else if (rightPin == pin) - // otherPin = leftPin; - - // if (otherPin) - // { - // otherPin->deleteLink(pin); - // } - - // it = links->erase(it); + if (!it->lock().get()) + { + it = links->erase(it); + continue; } - }; - for (auto &p : m_ins) - { - eraseLinksOfInPin(p.get()); + Pin *leftPin = it->lock().get()->left(); + Pin *rightPin = it->lock().get()->right(); + rightPin->deleteLink(leftPin); + // 让对端删除,本端不删除,不然会崩溃。 + // Let the peer delete it, and do not delete it on this side, otherwise it will crash. + + /** Pin *otherPin = nullptr; + // if (leftPin == pin) + // otherPin = rightPin; + // else if (rightPin == pin) + // otherPin = leftPin; + + // if (otherPin) + // { + // otherPin->deleteLink(pin); + // } + */ it = links->erase(it); } + }; - for (auto &p : m_outs) - { - eraseLinksOfOutPin(p.get()); - } + for (auto &p : m_ins) + { + eraseLinksOfInPin(p.get()); } + for (auto &p : m_outs) + { + eraseLinksOfOutPin(p.get()); + } + } + void ImNodeFlow::update() { // Updating looping stuff m_hovering = nullptr; @@ -355,7 +383,7 @@ namespace ImFlow { for (auto iter = m_nodes.begin(); iter != m_nodes.end();) { if (iter->second->toDestroy()) { - iter->second->destroyLinks(); + iter->second->destroyLinks(); // delete all links linked this node iter = m_nodes.erase(iter); } else diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl index a583bae..8f34b14 100644 --- a/src/ImNodeFlow.inl +++ b/src/ImNodeFlow.inl @@ -298,8 +298,17 @@ namespace ImFlow return reinterpret_cast*>(m_link.begin()->second->left())->val(); } + /** + * @brief 为一个连接点添加一个连线 / Add a link to an anchor-point(pin) + * @param[in] Pin* 另一个蓝图节点的Outpin的指针 / The pointer to the Outpin of another blueprint node + * @author author , fgfxf + * @date x , 2026-05-23 + * @note 原先一个pin只支持一个连线,现在改成支持多条 。 + * Originally, one pin only supported one connection, + * but now it has been changed to support multiple connections + */ template - void InPin::createLink(Pin *other) + void InPin::createLink(Pin *other) // wrong ! it means add a link for a pin { if (other == this || other->getType() == PinType_Input) return; @@ -311,8 +320,13 @@ namespace ImFlow { if(m_link.find(other)->second.get()){ if(m_link.find(other)->second->left() == other){ - m_link.find(other)->second.reset(); - m_link.erase(other); + /** + * @brief 重复连线==删除? / Should this line be deleted when it is placed repeatedly + * @author fgfxf + * @date 2026-05-23 + */ + // m_link.find(other)->second.reset(); + // m_link.erase(other); return; } } @@ -358,10 +372,18 @@ namespace ImFlow m_links.emplace_back(link); } + /** + * @authors original author , fgfxf + * @param[in] Pin* ,The input parameters of OutPin are meaningless + * @date x , 2026-05-23 + * @brief 重载父类的Pin, 在Outpin中,只是起weak_ptr 的作用,实际删除要对端删除 + * The overloaded Pin of the parent class, in Outpin, only serves as a weak_ptr. + * The actual deletion should be performed on the peer + * @note OutPin的传入参数没有用 / The input parameters for OutPin are not being utilized + */ template void OutPin::deleteLink(Pin *pin) { - printf("%s %d\n",__FILE__,__LINE__); m_links.erase(std::remove_if(m_links.begin(), m_links.end(), [](const std::weak_ptr& l) { return l.expired(); }), m_links.end()); } diff --git a/src/context_wrapper.h b/src/context_wrapper.h index 6ae3ac2..6f42ada 100644 --- a/src/context_wrapper.h +++ b/src/context_wrapper.h @@ -208,9 +208,13 @@ inline void ContainedContext::end() // Zoom reset if (ImGui::IsKeyPressed(m_config.reset_zoom_key, false)){ + /** + * @brief 重置缩放和位置 /Reset both zoom and scroll position when the configured reset key is pressed. + * @author fgfxf + * @date 2026-05-23 + */ m_scroll=ImVec2(0.f, 0.f); m_scaleTarget = m_config.default_zoom; - } // Scrolling if (m_hovered && !m_anyItemActive && ImGui::IsMouseDragging(m_config.scroll_button, 0.f)) From 1a0574d7af8d9f182a2122eebefc82e106bb8ad8 Mon Sep 17 00:00:00 2001 From: fgfxf <38068739+fgfxf@users.noreply.github.com> Date: Sat, 23 May 2026 23:02:31 +0800 Subject: [PATCH 116/116] update readme, fix bug --- example/IMG_202605141914.png | Bin 0 -> 12723 bytes readme.md | 17 +++++++++++++++++ src/ImNodeFlow.cpp | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 example/IMG_202605141914.png diff --git a/example/IMG_202605141914.png b/example/IMG_202605141914.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4f2700046d42bb5b2fcc72805d95afbba457eb GIT binary patch literal 12723 zcmbulRaBfy@Gm+EnjJLQ;K70f87ycZ!GgQHySs)2cMGl|Fu21EVF>OX+#zI;;O=(6 z?EOD?opm4X!#OW}^qTIj>Z>m&age@a2p$Y;$eF^-Ziun}yYrRzn z{O}SaBk^9{JA1#yCZ0kobJcG&@-5wE3zwadPT_YM!hxAqrOO6OgcXSV2<-r^YW14# z3Iz0_0a3y-}<3pBG(_Vfa3UC6+b%$X8OH z;LU<*c|`XUfYtWgC&7QeIH;*eeldvHcR9R17m6WQidlx&RN9To+K!9TGw8=nHZR)R)u$-59$(RiK)jdBgmQfc6rW2*@6e;ntbk3cZ~ zFUb54i}p$^l9;s)&2kBQmone_6C52SFz4YpT`^Do6{h7+|Ax%@urfUPiYlS+Q!t0d zDkcL{aG3q#s7Wut8dm0eZfgVY&7l42|2{!=#6FE5{>)uW6E5DF(n$;4$u=Q*S^hP1n(L8m;Jq3o9YosJ*Fu_k-sOz;i&CUAPwO(moo zsh9qx<L;vy}r8FK4B(aw?Y<_effk!ag!DpfSJ$kQw8HK{HMuc z32rF+is^d>5a{-)rNFEQzUxo+z~YKs1m@a73i=W+g1Oy$(G!OoohR?b$gR=xQ_v@D zZ!{avTcJ<^Fks5R-hlvCIfR%S&MJD+J%}dB_dS_ z4Lo0#SK#{*n|4cpEbVtX9Mysmk8#mO{*(}`)7Eer-MqY9=Ip&!dCYK6rdgj|9out4 zw!@8MRs|++NA&X%7t?;2tlZg{3Wap178_aDVwY0jpxc5D?zi}po}#UMfeF4!ke>iE zloV3IwZEFb1RQ2>f4O3u)uxL)+BKX1+D{j9Q}8(rJ&8J-92|8R*fXsDPH>Xoe&D)8 zF5o!8iDE7_LJ6!SX@6NdFBGF}#B+3Fyc6t(f>PTKk1Y&3xn7xzAH)8U5KEapL4bQ^ z{7Ef5TDdj9nb2iejYNBOnB<=*!*N~AeBVi?QiY6FzmK|D#7amU6Krti)tD zj#=0yUtX$1{nNnD1m4?kmNqQ*;*yHOS!J=hDU@$f$02WCledN@N-(YduTbFyN~c6HoId4(msN-hzKkttmI?&kECRn zKOPNgR?lShy4SWg!a3rw4KgV9slF)me!MG4yO_A4>Ev3snjI}ROAPjlUNc{3oMo?C zl!k~*6123b&R=a1r)-*PESn&Cm$l2vQtL0p5{jQdz`v4Bol7D=tG-4#a&~RF|%l0z1LMkFu$EnQR7`-j$X9!j=!!M_WN?r{%v)b=!bAngwpq^ zJmY7a_Eyj;x`u}(!gft?2C52)37QM`U=v1mfdn45>Ci1EstQr91xKbcu@9ky#O%jJ_v_9A;M&*P{N+?n^Vv2!Ph+I4d7cp_W zgQ&Cj+kTRd&7NPry5*g`@vSmmz!9_NAu_D8LmaPHouJhI@@CmnEz5#;*Y1jlyyuC- zej*_3O4L2gN9hStPx_uRz zamd}=2~E}qcBy1q{X$c_XiljZWpEa4o#eW52vN62iF5x)Ou*Ym^tjhCo*bAzO4GWV zy;&9TB~A)yWW1T6S@mXdt>ySw%lEJ-bSlS7M8lICCtpZ+nO6Ry534)4I^N=O|M6p%^#`MkYs$?6ay<~t5Jy9D`y%-G*W=yh_+>OSXKx7aZpXTp{M4ws_$*HuZ?vAEcGXPrNtTJs!OQ`q_{t=W1Y_)@V& zIy1U#lm7Xl*B272-hy|lt(j5%zhP_c8r%=}PL44}oq=vHR|U`;Ka==*`yJIL*P zS9Y-pgv#HHwOL(F&h|CYnE|EjYWB$E#i;&J-D%W;s;!`+jvXm{|6C5U7g(poeSNXf zb8UZjBYmTiZ>XC2bm-2S{OuAG7x?Z&S8VA{*zq*wi;HKX zZNjd6TH{#d9Ax}ykt}SxTB{?h(^I(#{!OCCH{S4X@P{MgV^t$l2vst_XVucx{e38P z>L6ymdjI8u^+h=J#ZZE~^hY=0TWZmJlk$~0*xH$~u)AMA81H)A+!{iK5XgGGS_zu; z$XI`P3ZxIG12guE7n;XH;k5e7nWzP$gf*|ZEvw|yK~?F0@2TR1HQ z4(!ZGp%!n4wV1_*JnlC-2*pZlboeY!4;3%aBp#Z3XvO+Hn5F(yMy}MFUr{@~WoJ={ zaarQ<$nE#-JW(3MT`^=0cxz;x2{#dPQaWo9No{FS7M>(fK+Tn(jc1^v5MKcYo|M`l zRK|64=v+&`a!@9(QjEblwDeL>r-ug-@teqbzTSGQ4%`!sK9^@5P6}taW4)f%ko)mp z@lw7YU3QPV#72Y+9(QL)H5z6BU#G5?Ln=stn{yb8+Z)6U?FD4v26y(WEhHtD`lDFo z&rTq;NZ_YkGLpO#p_*zzIh*3cPJSHtR!iH)8f+5wi?7eG-sJH(JvAH+10x2ta=})g)D_%E8ooZmPbCHNJiX4aof@m9}oJMbfcT=jl|}m6eTLw zgLS;hV5^Kg12UYbyOxcp39hX{>}km3A+>^S(YaBp|Ida!e{m&re^e9dv5x}vu73I6 z77&jBezuxn`OzXTpMv5g)>z5p>W5vhCB$=5;>d6huwtFkDzioV1^O>m2 zUX%Fa8hFCDI+hC4lut$f*74zfU@^KU=-cBYuGD4vVx)W#{1yLd2D&ZRmFxj zgbCWX`OiVQ(#rmhvLOD^vLO?%0yzzjnyoig*bj!!lvemL_?=2lI?F3fXvh)MnKe~G zg9;MDWl#N>S`#7oZtuO&$lGSYk^^#oSv~ATJj4KUC!i&E%K@~p;o@lfDP*>x1o6fy8`&_Gj zB^r3#KD1}8dy%7m4H&%VosiWHIH*r`ZQLA_hTb`5AHi|XC>oet?z+8{9K7~{@g}amaR3@ z$%hhx6p5juxu0#db2`ihY8-XZEO_mjEan4!zpsy&45yN>L}Z7@TcM?1!_7#)?yD}k zw78}ADbo_3MmKIo@7qv$aT|nf+#Pp(Lr!qN9k#~jSX&6aBEb=TDp9*c6rVR#?VQq`Wn3ph%$|;z+^2ulb;3?UmPH~s50Yy&y z_&27$*OR4C1%n;ez!!8&3{VYX{#*-x;YGvjah#cv;$Bco{bKJIk1b|#Zq`Yc9R(DR zP+AFmU#q%C8^NZ`C{DP6*Om;LNN(j$Z?L~Zd3_~vqPZ!`R@E2Z*C}KIs_%fz5siq-SZ%=ih4o<-pJMgW$eqX{oRghpC6I$ZdW3km7 z&ALW9(Xe;>dir3{OIq+x7Z?p%{9;kIEDmopWL?3_MIWJRM9C(BsjyN0xWCyo+sjoQ zbB5I?ZZwAR;Ovw?uKU=BM|l0IKe3nqCna^dVb8q1kINhvy`Jx-0x8JzpAPMHZJ32k zQ7E3P{!MWy^wHOBr#@URqvme%R2*44qUF*5G@O%6CT!NhDc&>#PJnm)@JH==Kk{Q_ zA4d8)Fl=iUo*>6r8cAt0V%f6Ysj3=c>l+DK|8BIm1U`-44~=3KXFq3nxH7IS`E!`J zPrVeNfSQ`n*23@K%JEzzj2EOFX5{a$^e=C7tnCJ1%sr`j1lG=BCTOf=1?gOWpy zQ5`$v)AcFj7diL!+fJpd+P}50uYu^2|7q?0qf1)8TIEtw^f}y;vT3LQWyWbfB6lsJnWY ze=v7GcXXod1A^w;FP&hcy;wQn+v0G!wri6&EYYHGMd`g*lZpvxV!mVVdN}uay+{mg zeC3;xdJQo;y-E8nzGkoEV&E%6j(gh;3mCs%`Cc!08^k1u)sd?QL5(l&#MLa@e@G3^ zh?+4a<(enZVeGs$#OfR2JNs;X1o+6W4Z4CfOvmILbe`V*t$M6vaG`8W&l|Ns9%JoM zq*!GOw7wm6j0>gKxW&f1t8g}K;$xzpe#PU#=-uOuCz+W$d%a9P*+|X7=3vOL(W!We zc1G8|F8`4Qd-eTKe~V7$r+u^`f*guH*m{4X!_mhhR`JoxUWLW$tA`-IPG<|qMvmQ} z?P>Hj(U42a8i{P8tu|I~SEO%)k~ z>HzBGtG22fA2Z?G1?|nPG^u~04iJPi-JBsEAr_ePI^nNYe-@uid%yFgA;0uII^HuD zK5K4v+lZr*pJQy84VN;n<@^0OgxrX-d>PWKmI##I-~y?kJIhw(>DY^i)s2VlHiY2# zLF{t+2%p03jxx*3DqUoTdtu3+JViFj&ELweGUd&Fj1e=nw?Cv5 zYsq32Ryb^&MmgJI?9NW^=fFyy?7iHOl3EHMuAiT?E6Au(`l--d)=0)iU`BWx8K4rV zO&t6Ul8hNi;b+%+)TKHvCx3H%-SOGFozeN}Pz^c?r0H@QwL%slE2d!ICRpv*COyM{=hv!#=OBw1_%^}SmO0>* z4Ikh;+wg5Hh{aRYozWzqdK`;?HFhQ;>IZS+pYmL8jR<7I9}T&ui5%n0%!m-(A&8cB z%l`(4lNVLJx%N+j{DPW4Mk}IJFfRRvHGYHj33m$??xxb<=+YXZ}-A8PH1tQR3$cFbj7M773 zC26#P^CAH))j`|I`u+O1wXM=GDUnN*X>6r38zJ#e?Vy&2laaMkj`M)L92+8EoJOXV}MjB-y1@35lx|{oC(~mR3L+! z0Lqbh`4|gl4el~Tt;0N6Rz=34=Wb$NUfnpu{im1DOO0vWwifO~Y(x~r?SVOU2bhNS zc<&wS)q=KVQ`P;&Xtu33W&fu&o zFE&jfS*lI5758|$Q8NF9|C<$RZ2udG7o*lna0`zc_st-XS2VV%3Ey-wh}FQ(hrwb@ z;3K&t4x>MsZ3{yI2P=PjlACJZGF17FCxph+R(9%;E5?`y&jx~PzjoKdit^zVWsZbI zEjbrAH`=K*b1tQ}+C}>%VFE^)KW1q~)>Mt2n%#ZZ&U*bClO~R49?&pV%q=tp@i7p3 z0+gO&8aicVO_`R>RqDnnW8md4F*aUxIY9|c9HX|t6AI>vJfiSkTBEt zVZTDl1l1~(EWfGf_$OD7q>u{;IXIdy*BeEYoSonV%(uK;8l&*$g-b#%%qixk2GI_Gn(S=PB7 zysDP8OPCgmx;&oH@g!pcCV`5~bTp13F0*akw2^-Cu)Nx)s-o9+b-~$@ZhxV?hMqc4qTLt-(#DO~w-jd^Rb-X0|E}YV!Rp#n^5*1;#)Lp5r)i5C^2bG+(EIJF&&WGhhHyTxaieu zYmk)he7M}al$-dkBDb0P5fAGJf##zArjenk|2-3YxoeLMN5mNf(NKc;As2#*|F)Jh z)mFKN-2Uwd>$yYg0)AQo%`;$u^UF^izz2l!6pZ!%H=LqH8DZ1|`ozsYjyXY~W)wOs zSby%*zpdS5M2Q`d9tf0+AI0={TCoZ@@cAST0z?69*#{&ppt-=A^YX7*XxWNiiAh2B zLA!#8BKva|Dd`0BRD-;QUxIOEQ^~qLB$!gB%pnOjJmj#bg_3jJJQ^%BGKEPV!G#X2 zp*o7TT4sDh1Q;?&D3uMcaFNnVS;=<^0>?x;=s;5>hq(9+&#v#1RVVH7#lA)6zJmlo%)6hA;r@L$ zz+6M4$+g`0s-nwT-t)G>7@irD;LJ1NO3_DQ(9RPSA9}_7xveG?c)>&Qgc6!kx z{Wa>`7=r-6X#+el@NJ#=c)3$;>$}98iRlTFg4StTLk$x?E&o=^f%9Jg~NDg{57U>JgmAM@-L*d!?OGQEMCn^jIb z%xh^hmNK4`HpE0b49P?rf`km=hcU2*lctgc{P@!q(t$-s?|T$R^@&5MkfC^Q9z$q$ z?+Z%Ko3G608=*E)*6_gYSJhyYe5#PI}c~eqd3FpLM&KM4Su5!|8{I!`nB96bwg}CWQ^L~zFbw`<_&Kg z(rf^WBSPqL3RKhfsUzDELKE|nQ&@@r^I8ZDLwF4>4Ec<)MmYYNII|dPb?(9J=qlX)K>`32r3sP=a~36X1#oL-%`)1($o^mC&Yez%B-{BwlQL zB%aIG>i1s<x=m8Wp^H25Y7`gV+w7^ z9DJEU`r?*e#r)0LnGj9LzprQ6Z${ELp7oie_ObRIJ?(nNP7|`hIRp$TKFm#P4eY2< zxd~hH54(mS@je!HsBY_ciX8mBo3L+DO-5OBU;T-A_F|Dsi`a@ZHTKm6Yo8F@fa8#- zxE(G(Y28#cS&}l;Losw5nftL560{!uYN97avY^!M$FF&mA=gx!Ff*9}#ztL1#)zVE zu2@U{RIHG0WqLS2x5jId^>@-ZA>Qw?q)boa4^mE>a$E8Lbc4n)hiW?BL4I8xUg-C| zQ4cjX^;qtJRyj0#Ko@zKyAu9BmZpCx*Rg?P4iOw|2ioa@4UN6u8_$iHqIMo%Z6Dun z5p(cHtL`8oRArgJvAY^{MyD?q8aXDP`1$4e!u0z`2s+I%q$FhwFUyMFBujZXpH=X^ z?y9keyx?FRJ1n$VgeUt>!(%kna6^#~%JhV>ejJe^f%K`YtQ;k=w)DADm^wu!dQwa; zK7gZ>STV#fX;P8FYw26>I;4MSGONyFhng9BDdU9k!W_xifC`P&MSqqHJ8fb)cUN0s z@>xgfvEclDobc@VL<7^PAYuc~s*g{Io7XQ9!0^L)O(p}XZICZpe-*eOF z_?kzYLH+-{pu5o?=!+5wTfO#))h#o%@enT+2wPhyveSukK%{Q#rrK3Mh*t&cFWU1O zEPk^iryNQ^ou`iQ#`gJgw_9X#1|=;>SnR6hjGdX-6Q6|Cq0r@9o0`3=)PEV`UF?UO ziuB~C?{WD-e{1mJ1cQ#X?>J~`1>cN9&O-JYswzSf^$OEuXF7=jlhbaO^O*^r7;9gn z*P720n(h;SznVm(cSr)#{k9(UQ_j0myUZ{Po#waHofN|&;$V1}*wgGE4^QR=p9o4l z9Rq>z9sgnhw&z{gCToA3)~%(70rIfnPhHd96ny*|7i&Lhy`yBvg}o!9K@S_ECSb(a zFz-zpzTrYq6()EV;#Kmq7E7%(OLj!vwg_?Yu2cvi=3s^^rC1uBTlj>|99Pqrud>+Ag`Yb1(1sy7#I#r9*%Zd~O)ZJr+BY?PC1ygDRllm5LhbaX z-c)M3IW*5-#Cv`~ZN_okw>tEw6vyfd7^vXLNc^H>L_6dV;Cfc$-gEjw&Th`OMW@=^ z(6o#fkx#a}@Rt7KiN{TDEB}4Wb2fajl;5(47l$(^_{_uAc--8^CdAaV+)m>u7YE{EuEi5Qs%Ks^ zO!BP1OW*2At>U%a7-h)7%rD-}aQY*z9Oe155mWMa(q1*87VA(o&h)i|)7NOsPpOY$xotUHXEP7sgZbftX+D0 zbtzb>$b!f-;G!&YSp=4xG8{T8I`$BHX>8^r1W`~F4;l048O6MUTC2vq z5)kMgdasC6BO>~Gk6{eHPwlA&^O#oNaS*EI>2ci7=?D)bYID;bXcNp&8Xg>|0MDDh zSuD^Q-3?`^tSKZ(J^P)sZ0lQeCEWB}tVF$ELl!6*W^2<(tOSkfl@7iFmnqDWotwc+z~TV}kdl33kJ{dbttc&g# z@1@NtIIQwgFSu+<45X3b;KYQZ_l6b^eQU_CDj-R15L4t}-3HG4T_UfQR^+4&Th>4a z2Ug#YL%j%hhvhSS%zDrpeIg0*g{^-HPNKkrTIKNOCljY`(bW)>C5VxNYA|~jO|X(=rUWhP zpC=)OIqKq3>%>?1)7z}>VKq?Z^QO|?JM{p^!G#<2xG~bA@x9!Rp69e5;R^x)qAEl? z4vE>ZJR9yp(O1hl>tS-eFzwjrd4bn;^bM?lnOF#xiAW3qEBv$OowkfGCIy=+BQDVt zN2sRzEtL^d)=z&VqMLeniBae?#(C?P9Wc=P?$Np?a5#_BkmMJb&Eq%`arOPpX}fya zzL%)$h+jmlgCAcxIlfqls=f1c&^D@9)uvrI>T3VxgLu@-Ry!0W$ES951SxEF;uQ5v$j=*7&Cq5H8zy_`ZS& zA6)KwnPlXasGg%wCkomnPSEjC`)+j(fn_#ZOYWu?dmSFz#9Xd2FLSuBeeYmTCSKaS zc>Z#GCEuV2rJXsPb_c;OhO@A7nCxLdLbKt zndJf)DA$~ldtHuVl=5@|^4kS7^YZPc4o~#jcRn0qGVm(@bB(+#+hK+2t)jEvwleHt zCUN?wt=-D94or<`APjWqRIP(-=`I2&M5#6?uoy8==HCZeLJ~-mCIj?B=h^ND=w%sJ zD+ukOo@ASfp3*Oc2!-4P1YK;*TC?l;_jRHXbp>5$V zsXiQKB~J!?9!?My0{3G%B3ODRQY-~lO<2&>N(V zFI8k_jcaXY_2}2DEP_!QA{F(ca+)Ye1!kL6IN1G=#yh_YCa1c(raa^edR(3^A@D&L zB(kM8WXTA>p>kp8;AglBVd0Ej;$N~xr^(Oj>UhdGEvJ_%40WYyF221EK_ab;q7?Q2 zIg4|5bYc}|S#8s34IF~WyOa5Of1g)}NQnrbQlWru(V*&+wh9Kzi&tPl{@`PV`dpa3 zzTbNTt&tIyCQ@F$a`ush-pv&1&x-s4cz_f=H|w%}ZR^Tzt`x5yDhGp|;$`PtKxTW0 zs;bwRpZh@V55JnCt4aYb9~AcMJDf!Vy{)-;gh{Cc<#JoyDpjhjhujth^#e>0IYX%S z(O?*gfluZpdM=T`^sbJa6-$U&Lz&tt@!`ix z))DvlOTyniF*a`$>Lc+i)7h}YOu+IH4myiGjr(kRIGpHz!Gm)~BP*S7n9%!QRzGC@ zG=pg<^7t=Z-G8gTZ2owhnVEH7v9S#!X}>_dl`^$r?;HIw*YvGN-FD-)u}rNocJQC{ zvm)b|XC@G-REy@SCXICZNQ$AI%vueQSOi`Wr)#$3^}}C4-TPA$+m&in89zR7r|YpB z2ji%LP8yLVPuY2fA8gwX;#-eVxH6uYCK{ojzq2&Qy4CW8v%oNnKEmPnpnrnl(-yx> zGFhx6Y!;Iy#CUe!v&So)6Tlu)V~jLgaKQQ@UEQw$-M3EJuuXX+H^h9A+*DK?bom!KO8s#-Zqv9>-RK0`Sh)A4C!oq9 z?O=&%x#?J+q)AZw?(IFWxaXCTy%T4dObwuKvPZH=F26HTKEHcdYgfQG z5>e7M=@foA_*5iqKF?ekA3QRAqsQO$PJj`qj{ze+NkIC-7rUfvc*~)O9^gmaj z!>~*L$e7D1&9c+HN4M>Rk**gP6o5Q?Mvda*LPQFG2ko za;!sK?0_Q9e{KvUrfmN8*5LZ)%TgNkBHMjLNRBVKFHAiuv)vdb@l?tmlxA&rs+4TJXu1?^^xan2Z zTERK1(wyWZpN4XXvV45paGCLc5f4CaU-`Z}J&qI@&78lQPiv`+4gKI2c!kC~G>1|; zRDG%Mmxiyo+d5Tr$R2PkeO_udS!E`9qvp{r2y#nqKd- zHF1dQ-uy0z<-Pc8ndId<=I0r=HT-6c5>jJU)oojHmMz7;HUgJDA5uyeKa>L)@L%aQ zwvZknzBtXkfv09kO%H|_H@DXNGe1%=8i^n87Byks4V^$&F%FkitVAS$q^j8~E_>Lr zs*Rd7pu=w`z4t_mn#3sp@DIX&NfH6G{BdpR50kH&(&}ed3B))LOY3;P+@8>Rtl`rv zzv%iqi>5;5HM5)A2R0;Xdz31m*lie^IA(t5z9y^P;Ge?{z5rEQm3F9T3E|_s%O7uhLs=n*aBR$KVu{_vly~2#z z2W;Lw+@nEos(0tadOeU?faxAs&s?ts`hmD$Vgz=a^7yw99n7%s|nWeRx z9*jd~F9Ft5%aMuX(BJpFFWzUkk;{9uyzZrrYuXf#12EM-uL0Y!#D=vxP-Y?ZcQ=3W zTmpCT5Y~1Z*0;Bsd+sK^IC5&5>WqRZiu zluglVi}T~00;0cnrp`E2`K*MOf`dP=?J%}2GB04juMD_XB99&p569#GUVfKP9m1i6 za+ht#Cf0vqE*pS2dr)tbg-J$ej7WH7M$e|5jC&VK63@~|Z zC@wGsLz{-sf?>XH=R@UsL}Z*1AXcXkm>wm^>eYQ`-p5$HAdMS9@_xY^>z}d@oZhsG z+A@5QIr;ti-pOB@f5lJmaezas^hXRZhcdNz5x86Nq(+FL1W>YHcmglh{^yqz1f+3F~Ue=9y+m&thi1%{}14PTHjq`S=k>>5VOv>oqtoQ z#P>gqFUjo*rY^jo$m#P~T<)Ug&D>cL`(`f=8eNqfYuS5SC359?deleteLink(pin); // } - */ it = links->erase(it); + it = links->erase(it); + */ } };