diff --git a/.gitignore b/.gitignore
index 724bfa2..343fc9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
-/ImGuiHandler/
-/cmake-build-debug/
-/cmake-build-release/
+example/build
+example/includes
+build
+settings.json
\ No newline at end of file
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
deleted file mode 100644
index 7dc8f33..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-cmake_minimum_required(VERSION 3.26)
-set(CMAKE_CXX_STANDARD 17)
-
-# CREATE PROJECT
-project(ImNodeFlow)
-
-# 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})
-
-# FIND DEPENDENCIES
-find_package(imgui)
-
-# LINK CONAN LIBS
-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/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.
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 bb78409..5936a63 100644
--- a/documentation.md
+++ b/documentation.md
@@ -1,7 +1,267 @@
# ImNodeFlow Documentation
+***
-## Creating a custom node
+## Index
+- [NODES](#nodes)
+ - [Definition](#definition)
+ - [Body content](#body-content)
+ - [Static pins](#static-pins)
+ - [Dynamic pins](#dynamic-pins)
+ - [Lambda Defined Nodes](#lambda-defined-nodes)
+ - [Styling system](#styling-system)
+- [PINS](#pins)
+ - [UID system](#uid-system)
+ - [Connection filters](#connection-filters)
+ - [Output pins](#output-pins)
+ - [Input pins](#input-pins)
+ - [Styling system](#styling-system-1)
+ - [Custom rendering](#custom-rendering)
+- [HANDLER](#handler)
+ - [Creation](#creation)
+ - [Main loop](#main-loop)
+ - [Adding nodes](#adding-nodes)
+ - [Pop-ups](#pop-ups)
+ - [Customization](#customization)
-## Custom filters
+***
-## Custom pop-ups
\ No newline at end of file
+## NODES
+### Definition
+Custom nodes **must** derive from the class BaseNode. This shows at a glance the requirements for the class and serves as a good starting point.
+```c++
+class CustomNode : public ImFlow::BaseNode
+{
+ explicit CustomNode() { /* omitted */ }
+};
+```
+
+You are free to add additional arguments to support custom behavior.
+Any additional arguments will be forwarded during the node creation (see [Adding nodes](#adding-nodes)).
+```c++
+explicit MultiSumNode(int numInputs) { /* omitted */ }
+```
+
+Inside the constructor is then possible to define the node's structure and appearance.
+ _NB: every method used inside the constructor can perfectly be used anywhere else._
+```c++
+class CustomNode : public ImFlow::BaseNode
+{
+ explicit CustomNode()
+ {
+ setTitle("I'm custom");
+ setStyle(NodeStyle::brown());
+ addIN("I'm input", 0, 0, PinStyle::red());
+ }
+};
+```
+_This is just an example and every method used will be explained in full details._
+
+### Body content
+In addition to input and output pins, each node can have a body that supports all the ImGui widgets.
+```c++
+class CustomNode : public ImFlow::BaseNode
+{
+ explicit CustomNode() { /* omitted */ }
+ void draw() override
+ {
+ /* Node's body with ImGui */
+ }
+};
+```
+
+### Static pins
+Static pins are Input or Output pins that can be added to the node and be deleted manually.
+```c++
+addIN(pin_name, default_value, filter, style);
+addOUT(pin_name, filter)->behaviour( /* omitted */ );
+```
+_The **necessary** `behaviour()` method for Output pins is explained at [Output pins](#output-pins)._
+
The following example adds to the node an input pin named `"Input A"` for `int` values,
+with a default return value of 0 (value returned when the pin is not connected), a filter,
+and the default pin style (cyan).
+```c++
+addIN("Input A", 0, ConnectionFilter::SameType());
+```
+_For a detailed explanation of pins see [PINS](#pins)._
+
+### Dynamic pins
+Dynamic pins are Input or Output pins that, just like any other ImGui widget, exist only as long as they are called each frame.
+ Dynamic inputs take the same parameters as static inputs, And they return each frame either the default or the connected value.
+```c++
+T val = showIN(pin_name, default_value, filter, style);
+showOUT(pin_name, behaviour, filter, style);
+```
+_As mentioned in Static pins, `behaviour` is explained at [Output pins](#output-pins)._
+
+### Lambda Defined Nodes
+
+When logic is straightforward enough or for quick prototyping this might be sufficient.
+This avoids all the general hassle that comes with writing classes by using dynamic pins.
+
+```c++
+ImFlow::ImNodeFlow INF;
+
+INF.addLambda([](ImFlow::BaseNode* self){
+ ImGui::Text("lambda");
+ self->showIN("INPUT", 0.0, ImFlow::ConnectionFilter::SameType());
+ self->showOUT("OUTPUT", [self](){ return self->getInVal("INPUT"); });
+}, {0,0});
+```
+
+
+### Styling system
+The node's style can be fully customized. Use `setStyle()` to change the style at any time.
+The default style is cyan, and the available pre-built styles are: cyan, green, red, and brown .
+ It is alo possible to create custom styles either from scratch or starting prom a pre-built one.
+```c++
+// Most common
+auto custom1 = NodeStyle::brown();
+custom1->radius = 10.f;
+
+// Less used
+auto custom2 std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f);
+```
+Other than the visual appearance of the node (colors and sizes), it is also possible to set and/or change the node's title at any time using `setTitle()`.
+
+***
+## PINS
+### UID system
+Each pin can be identified by a UID, in some cases the UID can coincide with the display name, or it can be a custom UID of any type.
+```c++
+addIN(pin_name, 0, filter);
+addIN_uid(0, pin_name, 0, filter);
+```
+The UID can be used to get a reference to the pin, and in case of an input pin, its value.
+ Searching for an UID that doesn't exist will throw an error.
+
+### Connection filters
+Filters are useful to avoid unwanted connection between pins.
+
+_TODO: Update this section_
+
+### Output pins
+Output pins are in charge of processing the output and, as per the name, outputting it to the connected link.
+ What is outputted is defined by the pin behaviour. _(See [Static pins](#static-pins) and/or [Dynamic pins](#dynamic-pins) to set the behaviour)._
+ In particular, the behaviour is a function or a lambda expression that returns the same type of the pin.
+```c++
+addOUT(pin_name)
+ ->behaviour([this](){ return 0; });
+```
+In this simple example, a static pin is added, such pin will always output a value of 0 to the connected link.
+```c++
+addOUT_uid(uid, pin_name)
+ ->behaviour([this](){ /* omitted */ });
+```
+In this other example, another static pi is added, a custom UID is used and the behaviour is some custom, more complex, logic.
+
_Dynamic pins also exist, see [Dynamic pins](#dynamic-pins)._
+
+### Input pins
+Input pins are in charge of getting the value from the connected link.
+If no link is connected to the pin, the default value is returned. (See)
+ The method `getInVal(uid)` can be used to retrieve an input value.
+```c++
+addIN(pin_name, default_value, filter, style);
+addIN_uid(uid, pin_name, default_value, filter, style);
+```
+_The method `addIN` adds a static pin where the name is also used as its UID._
+
+### Styling system
+When creating either a Static or Dynamic pin, it is possible to pass as an argument a style setting.
+ The default style is cyan, and the available pre-built styles are: cyan, green, blue, brown, red, and white.
+ It is alo possible to create custom styles either from scratch or starting prom a pre-built one.
+```c++
+// Most common
+auto custom1 = PinStyle::green();
+custom1->socket_radius = 10.f;
+
+// Less used
+auto custom2 std::make_shared(PinStyle(IM_COL32(87,155,185,255), 0, 4.f, 4.67f, 3.7f, 1.f));
+```
+_When creating a style from scratch, keep in mind that it must be a `smart_pointer` and not a simple instance._
+
+### Custom rendering
+Pin rendering is handled internally. But for extra customization, a custom renderer can be assigned at each pin.
+ The custom renderer is a function or a lambda expression containing the new logic to draw the pin.
+ Some helpers are provided: `drawSocket()` and `drawDecoration()`.
+```c++
+addIN("Custom", 0, 0)->renderer([](Pin* p) {
+ auto pp = dynamic_cast*>(p);
+ ImGui::Text("%s: %.3f", pp->getName().c_str(), pp->val());
+
+ p->drawSocket();
+ p->drawDecoration();
+});
+```
+In this example the pin is rendered with the same socket and hover background, thanks to the two helpers.
+The content of the pin is a custom `ImGui::Text` with the name and the value of the pin.
+ All the logic related to links is still handled internally as well as hover events.
+
+***
+## HANDLER
+### Creation
+The handler, as the name suggests, handles the grid. It's responsible for all the evens and the rendering.
+ It is possible to create an unlimited number of grid editors.
+ The default constructor creates a new editor named `"FlowGrid{i}"` where `{i}` is an increment counter.
+ Otherwise it is possible to specify a custom name.
+
+### Main loop
+Each frame the handler must be updated. On each update the events will be processed and nodes and links are drawn.
+```c++
+// Inside Dear ImGui window
+myGrid.update(); // Update logic and render
+// . . .
+```
+_This will only render the node editor, so it must be called inside a Dear ImGui window. The editor will auto-fit the available space by default.
+(See [Customization](#customization) for more options)._
+
+### Adding nodes
+The handler has ownership over the nodes. ALl the nodes are stored in a list.
+Three methods are provided to add nodes.
+```c++
+myGrid.addNode(pos, ...);
+```
+Adds a node at the given grid coordinates.
+ The `...` represents the extra optional parameters that may be required by the custom node.
+```c++
+myGrid.placeNode(...);
+```
+Adds a node at the mouse position _(screen coordinates)_.
+ The `...` represents the extra optional parameters that may be required by the custom node.
+```c++
+myGrid.placeNodeAt(pos, ...);
+```
+Adds a node at the given screen coordinates.
+ The `...` represents the extra optional parameters that may be required by the custom node.
+
+### Pop-ups
+The handler also provides pop-up events for right-click and dropped-link events.
+ The dropped-link even is triggered when the user is dragging a link and _drops it_ on an empty point on the grid.
+
**Right-click pup-up:**
+```c++
+myGrid.rightClickPopUpContent([this](BaseNode* node){
+ /* omitted */
+});
+```
+Takes a function or a lambda expression (like in the example) with the content of the pop-up and the subsequent logic.
+ The pointer `node` points to the right-clicked node. Can be `nullptr` if the right-click happened on an empty point.
+
+**Dropped-link pup-up:**
+```c++
+myGrid.droppedLinkPopUpContent([this](Pin* dragged){
+ /* omitted */
+}, key);
+```
+The first parameter is a function or a lambda expression (like in the example) with the content of the pop-up and the subsequent logic.
+ Additionally, an optional key can be specified. In this case the pop-up will trigger only if the given key is being held down at the moment of the _drop_.
+ The pointer `dragged` points to the pin the dropped link is attached to.
+
+### Customization
+The handler is fully customizable. A custom fixed size can be specified using `.setSize()`, and the visual appearance can be accessed using `.getStyle()`.
+ All the remaining configuration parameters can be accessed via `.getGrid().config()`.
+
+***
+_Also consult the [examples folder]() for hands-on practical examples **(coming soon)**_.
+
+_In case of problems or questions, consider opening an issue._
+
+_Please refer to the doxygen documentation for a list of public methods and their details._
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
new file mode 100644
index 0000000..72a1217
--- /dev/null
+++ b/example/CMakeLists.txt
@@ -0,0 +1,51 @@
+cmake_minimum_required(VERSION 3.14)
+project(example VERSION 1.0 LANGUAGES CXX)
+
+option(USE_SYSTEM_IMGUI "Use system Imgui instead of automatic download" OFF)
+
+set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+list(APPEND imnode_flow_sources
+ ${IMNODEFLOW_DIR}/src/ImNodeFlow.cpp)
+
+if(USE_SYSTEM_IMGUI)
+ # Make sure you have the Findimgui.cmake scripts
+ # available to CMake using correct path
+ find_package(imgui)
+ # Make sure the target imgui::imgui is setup in your scripts
+ add_executable(example example.cpp ${imnode_flow_sources})
+ target_link_libraries(ImNodeFlow imgui::imgui)
+else()
+ # Location to download Imgui sources
+ set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui)
+ include(FetchContent)
+ FetchContent_Declare(
+ imgui
+ GIT_REPOSITORY "https://github.com/ocornut/imgui.git"
+ GIT_TAG "v1.91.6" # Update with future minimum compatibility
+ SOURCE_DIR ${IMGUI_DIR}
+ GIT_SHALLOW TRUE # Limit history to download
+ )
+ FetchContent_MakeAvailable(imgui)
+ list(APPEND imgui_sources
+ ${IMGUI_DIR}/imgui.cpp
+ ${IMGUI_DIR}/misc/cpp/imgui_stdlib.cpp
+ ${IMGUI_DIR}/imgui_draw.cpp
+ ${IMGUI_DIR}/imgui_tables.cpp
+ ${IMGUI_DIR}/imgui_widgets.cpp
+ ${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp
+ ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp)
+ add_executable(example example.cpp ${imgui_sources} ${imnode_flow_sources})
+ target_include_directories(example PRIVATE ${IMGUI_DIR} ${IMGUI_DIR}/backends)
+endif()
+
+# Common definitions
+target_include_directories(example PRIVATE ${IMNODEFLOW_DIR}/include)
+set_property(TARGET example PROPERTY CXX_STANDARD 17)
+target_compile_definitions(example PRIVATE IMGUI_DEFINE_MATH_OPERATORS)
+
+if(CMAKE_SYSTEM_NAME MATCHES Emscripten)
+ include(cmake/emscripten.cmake)
+else()
+ include(cmake/desktop.cmake)
+endif()
+
diff --git a/example/IMG_202605141914.png b/example/IMG_202605141914.png
new file mode 100644
index 0000000..bf4f270
Binary files /dev/null and b/example/IMG_202605141914.png differ
diff --git a/example/cmake/desktop.cmake b/example/cmake/desktop.cmake
new file mode 100644
index 0000000..5dbc5a4
--- /dev/null
+++ b/example/cmake/desktop.cmake
@@ -0,0 +1,12 @@
+find_package(OpenGL REQUIRED)
+find_package(SDL2 REQUIRED)
+if (UNIX)
+ if (NOT APPLE)
+ find_package(Threads REQUIRED)
+ find_package(X11 REQUIRED)
+ target_link_libraries(example PRIVATE
+ ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
+ endif()
+endif()
+
+target_link_libraries(example PUBLIC OpenGL::GL SDL2::SDL2)
diff --git a/example/cmake/emscripten.cmake b/example/cmake/emscripten.cmake
new file mode 100644
index 0000000..3d16d14
--- /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(example PUBLIC -sUSE_SDL=2 -fwasm-exceptions)
+target_compile_definitions(example PUBLIC "-DIMGUI_DISABLE_FILE_FUNCTIONS -Wall -Wformat -Os")
+target_link_options(example PUBLIC -sUSE_SDL=2 -fwasm-exceptions -sWASM=1 -sALLOW_MEMORY_GROWTH=1
+ -sNO_EXIT_RUNTIME=0 -sASSERTIONS=1 -sNO_FILESYSTEM=1
+ --no-heap-copy --shell-file ${CMAKE_SOURCE_DIR}/html/shell_min.html
+ --llvm-lto -O2 -Oz -s ELIMINATE_DUPLICATE_FUNCTIONS=1)
+target_include_directories(example PRIVATE ${IMGUI_DIR}/examples/libs)
+
diff --git a/example/example.cpp b/example/example.cpp
new file mode 100644
index 0000000..f49c226
--- /dev/null
+++ b/example/example.cpp
@@ -0,0 +1,156 @@
+#include "imgui.h"
+#include "imgui_impl_sdl2.h"
+#include "imgui_impl_opengl3.h"
+#include
+#include
+
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+#include
+#else
+#include
+#endif
+
+#ifdef __EMSCRIPTEN__
+#include "../libs/emscripten/emscripten_mainloop_stub.h"
+#endif
+
+#include "example.hpp"
+
+// Main code
+int main(int, char**)
+{
+ // Setup SDL
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
+ {
+ printf("Error: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ // Decide GL+GLSL versions
+#if defined(IMGUI_IMPL_OPENGL_ES2)
+ // GL ES 2.0 + GLSL 100
+ const char* glsl_version = "#version 100";
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+#elif defined(__APPLE__)
+ // GL 3.2 Core + GLSL 150
+ const char* glsl_version = "#version 150";
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
+#else
+ // GL 3.0 + GLSL 130
+ const char* glsl_version = "#version 130";
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+#endif
+
+ // From 2.0.18: Enable native IME.
+#ifdef SDL_HINT_IME_SHOW_UI
+ SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
+#endif
+
+ // Create window with graphics context
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
+ SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
+ SDL_Window* window = SDL_CreateWindow("ImNodeFlow example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
+ if (window == nullptr)
+ {
+ printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
+ return -1;
+ }
+
+ SDL_GLContext gl_context = SDL_GL_CreateContext(window);
+ SDL_GL_MakeCurrent(window, gl_context);
+ SDL_GL_SetSwapInterval(1); // Enable vsync
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO(); (void)io;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+
+ // Setup Dear ImGui style
+ ImGui::StyleColorsDark();
+ //ImGui::StyleColorsLight();
+
+ // Setup Platform/Renderer backends
+ ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
+ ImGui_ImplOpenGL3_Init(glsl_version);
+ ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+
+ // Create a node editor with width and height
+ NodeEditor* neditor = new(NodeEditor)(500, 500);
+
+ // Main loop
+ bool done = false;
+#ifdef __EMSCRIPTEN__
+ io.IniFilename = nullptr;
+ EMSCRIPTEN_MAINLOOP_BEGIN
+#else
+ while (!done)
+#endif
+ {
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
+ {
+ ImGui_ImplSDL2_ProcessEvent(&event);
+ if (event.type == SDL_QUIT)
+ done = true;
+ if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
+ done = true;
+ }
+ if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
+ {
+ SDL_Delay(10);
+ continue;
+ }
+
+ // Start the Dear ImGui frame
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplSDL2_NewFrame();
+ ImGui::NewFrame();
+ const auto window_size = io.DisplaySize - ImVec2(1, 1);
+ const auto window_pos = ImVec2(1, 1);
+ const auto node_editor_size = window_size - ImVec2(16, 16);
+ ImGui::SetNextWindowSize(window_size);
+ ImGui::SetNextWindowPos(window_pos);
+ ImGui::Begin("Node Editor", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
+ neditor->set_size(node_editor_size);
+ neditor->draw();
+ ImGui::End();
+
+ // Rendering
+ ImGui::Render();
+ glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
+ glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ SDL_GL_SwapWindow(window);
+ }
+#ifdef __EMSCRIPTEN__
+ EMSCRIPTEN_MAINLOOP_END;
+#endif
+
+ delete neditor;
+ neditor = nullptr;
+
+ // Cleanup
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplSDL2_Shutdown();
+ ImGui::DestroyContext();
+
+ SDL_GL_DeleteContext(gl_context);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/example/example.hpp b/example/example.hpp
new file mode 100644
index 0000000..c3044f7
--- /dev/null
+++ b/example/example.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "ImNodeFlow.h"
+
+class SimpleSum : public ImFlow::BaseNode {
+public:
+ SimpleSum() {
+ setTitle("Simple sum");
+ setStyle(ImFlow::NodeStyle::green());
+ ImFlow::BaseNode::addIN("In", 0, ImFlow::ConnectionFilter::SameType());
+ ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("In") + m_valB; });
+ }
+
+ void draw() override {
+ ImGui::SetNextItemWidth(100.f);
+ ImGui::InputInt("##ValB", &m_valB);
+ }
+
+private:
+ int m_valB = 0;
+};
+
+class CollapsingNode : public ImFlow::BaseNode {
+public:
+ CollapsingNode() {
+ setTitle("Collapsing node");
+ setStyle(ImFlow::NodeStyle::red());
+ ImFlow::BaseNode::addIN("A", 0, ImFlow::ConnectionFilter::SameType());
+ ImFlow::BaseNode::addIN("B", 0, ImFlow::ConnectionFilter::SameType());
+ ImFlow::BaseNode::addOUT("Out", nullptr)->behaviour([this]() { return getInVal("A") + getInVal("B"); });
+ }
+
+ void draw() override {
+ if(ImFlow::BaseNode::isSelected()) {
+ ImGui::SetNextItemWidth(100.f);
+ ImGui::Text("You can only see me when the node is selected!");
+ }
+ }
+
+};
+
+class ResultNode : public ImFlow::BaseNode {
+public:
+ ResultNode() {
+ setTitle("Result node");
+ setStyle(ImFlow::NodeStyle::brown());
+ ImFlow::BaseNode::addIN("A", 0, ImFlow::ConnectionFilter::SameType());
+ ImFlow::BaseNode::addIN("B", 0, ImFlow::ConnectionFilter::SameType());
+ }
+
+ void draw() override {
+ ImGui::Text("Result: %d", getInVal("A") + getInVal("B"));
+ }
+
+};
+
+
+/* Node editor that sets up the grid to place nodes */
+struct NodeEditor : ImFlow::BaseNode {
+ ImFlow::ImNodeFlow mINF;
+
+ NodeEditor(float d, std::size_t r) : BaseNode() {
+ mINF.setSize({d, d});
+ if (r > 0) {
+ auto n1 = mINF.addNode({40, 40});
+ auto n2 = mINF.addNode({40, 150});
+ auto result = mINF.addNode({250, 80});
+
+ // Add links between nodes
+ n1->outPin("Out")->createLink(result->inPin("A"));
+ n2->outPin("Out")->createLink(result->inPin("B"));
+
+
+ // Add a collapsing node
+ auto collapsingNode = mINF.addNode({300, 300});
+
+ }
+ }
+
+ void set_size(ImVec2 d) {
+ mINF.setSize(d);
+ }
+
+ void draw() override {
+ mINF.update();
+ }
+};
+
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 }}}
+
+
+
diff --git a/include/ImNodeFlow.h b/include/ImNodeFlow.h
index 7c798be..2dd9bc0 100644
--- a/include/ImNodeFlow.h
+++ b/include/ImNodeFlow.h
@@ -2,6 +2,7 @@
#define IM_NODE_FLOW
#pragma once
+#include
#include
#include
#include
@@ -9,8 +10,16 @@
#include
#include
#include
+#include
+#include
+#define IMGUI_DEFINE_MATH_OPERATORS
#include
#include "../src/imgui_bezier_math.h"
+#include "../src/context_wrapper.h"
+
+//#define ConnectionFilter_None [](ImFlow::Pin* out, ImFlow::Pin* in){ return true; }
+//#define ConnectionFilter_SameType [](ImFlow::Pin* out, ImFlow::Pin* in){ return out->getDataType() == in->getDataType(); }
+//#define ConnectionFilter_Numbers [](ImFlow::Pin* out, ImFlow::Pin* in){ return out->getDataType() == typeid(double) || out->getDataType() == typeid(float) || out->getDataType() == typeid(int); }
namespace ImFlow
{
@@ -18,16 +27,16 @@ 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
* @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
+ * @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
@@ -38,7 +47,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
@@ -46,26 +55,128 @@ namespace ImFlow
template class InPin;
template class OutPin;
class Pin; class BaseNode;
- class ImNodeFlow;
+ class ImNodeFlow; class ConnectionFilter;
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // PIN'S PROPERTIES
+
+ typedef unsigned long long int PinUID;
+
+ /**
+ * @brief Extra pin's style setting
+ */
+ struct PinStyleExtras
+ {
+ /// @brief Top and bottom spacing
+ ImVec2 padding = ImVec2(3.f, 1.f);
+ /// @brief Border and background corner rounding
+ float bg_radius = 8.f;
+ /// @brief Border thickness
+ float border_thickness = 1.f;
+ /// @brief Background color
+ ImU32 bg_color = IM_COL32(23, 16, 16, 0);
+ /// @brief Background color when hovered
+ ImU32 bg_hover_color = IM_COL32(100, 100, 255, 70);
+ /// @brief Border color
+ ImU32 border_color = IM_COL32(255, 255, 255, 0);
+
+ /// @brief 线条粗细 / Link thickness
+ float link_thickness = 2.6f;
+ /// @brief 拖动时的线条粗细 / Link thickness when dragged
+ float link_dragged_thickness = 2.2f;
+ /// @brief 指针悬浮时的线条粗细 / Link thickness when hovered
+ float link_hovered_thickness = 3.5f;
+ /// @brief 选中线条时的边缘羽化厚度 / Thickness of the outline of a selected link
+ float link_selected_outline_thickness = 1.5f;
+ /// @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
+ float socket_padding = 6.6f;
+
+ };
+
+ /**
+ * @brief Defines the visual appearance of a pin
+ */
+ class PinStyle
+ {
+ public:
+ PinStyle(ImU32 color, int socket_shape, float socket_radius, float socket_hovered_radius, float socket_connected_radius, float socket_thickness)
+ :color(color), socket_shape(socket_shape), socket_radius(socket_radius), socket_hovered_radius(socket_hovered_radius), socket_connected_radius(socket_connected_radius), socket_thickness(socket_thickness) {}
+
+ /// @brief Socket and link color
+ ImU32 color;
+ /// @brief Socket shape ID
+ int socket_shape;
+ /// @brief Socket radius
+ float socket_radius;
+ /// @brief Socket radius when hovered
+ float socket_hovered_radius;
+ /// @brief Socket radius when connected
+ float socket_connected_radius;
+ /// @brief Socket outline thickness when empty
+ float socket_thickness;
+ /// @brief List of less common properties
+ PinStyleExtras extra;
+ public:
+ /// @brief Default cyan style
+ static std::shared_ptr cyan() { return std::make_shared(PinStyle(IM_COL32(87,155,185,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
+ /// @brief Default green style
+ static std::shared_ptr green() { return std::make_shared(PinStyle(IM_COL32(90,191,93,255), 4, 4.f, 4.67f, 4.2f, 1.3f)); }
+ /// @brief Default blue style
+ static std::shared_ptr blue() { return std::make_shared(PinStyle(IM_COL32(90,117,191,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
+ /// @brief Default brown style
+ static std::shared_ptr brown() { return std::make_shared(PinStyle(IM_COL32(191,134,90,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
+ /// @brief Default red style
+ static std::shared_ptr red() { return std::make_shared(PinStyle(IM_COL32(191,90,90,255), 0, 4.f, 4.67f, 3.7f, 1.f)); }
+ /// @brief Default white style
+ static std::shared_ptr white() { return std::make_shared(PinStyle(IM_COL32(255,255,255,255), 5, 4.f, 4.67f, 4.2f, 1.f)); }
+ };
// -----------------------------------------------------------------------------------------------------------------
- // FILTERS
+ // NODE'S PROPERTIES
+
+ typedef uintptr_t NodeUID;
/**
- * @brief Basic filters
- * @details List of, ready to use,basic filters. It's possible to create more filters with the help of "ConnectionFilter_MakeCustom".
+ * @brief Defines the visual appearance of a node
*/
- enum ConnectionFilter_
+ class NodeStyle
{
- ConnectionFilter_None = 0,
- ConnectionFilter_Int = 1 << 1,
- ConnectionFilter_Float = 1 << 2,
- ConnectionFilter_Double = 1 << 3,
- ConnectionFilter_String = 1 << 4,
- ConnectionFilter_MakeCustom = 1 << 5,
- ConnectionFilter_Numbers = ConnectionFilter_Int | ConnectionFilter_Float | ConnectionFilter_Double
+ public:
+ NodeStyle(ImU32 header_bg, ImColor header_title_color, float radius) :header_bg(header_bg), header_title_color(header_title_color), radius(radius) {}
+
+ /// @brief Body's background color
+ ImU32 bg = IM_COL32(55,64,75,255);
+ /// @brief Header's background color
+ ImU32 header_bg;
+ /// @brief Header title color
+ ImColor header_title_color;
+ /// @brief Border color
+ ImU32 border_color = IM_COL32(30,38,41,140);
+ /// @brief Border color when selected
+ ImU32 border_selected_color = IM_COL32(170, 190, 205, 230);
+
+ /// @brief Body's content padding (Left Top Right Bottom)
+ ImVec4 padding = ImVec4(13.7f, 6.f, 13.7f, 2.f);
+ /// @brief Edges rounding
+ float radius;
+ /// @brief Border thickness
+ float border_thickness = -1.35f;
+ /// @brief Border thickness when selected
+ float border_selected_thickness = 2.f;
+ public:
+ /// @brief Default cyan style
+ static std::shared_ptr cyan() { return std::make_shared(IM_COL32(71,142,173,255), ImColor(233,241,244,255), 6.5f); }
+ /// @brief Default green style
+ static std::shared_ptr green() { return std::make_shared(IM_COL32(90,191,93,255), ImColor(233,241,244,255), 3.5f); }
+ /// @brief Default red style
+ static std::shared_ptr red() { return std::make_shared(IM_COL32(191,90,90,255), ImColor(233,241,244,255), 11.f); }
+ /// @brief Default brown style
+ static std::shared_ptr brown() { return std::make_shared(IM_COL32(191,134,90,255), ImColor(233,241,244,255), 6.5f); }
};
- typedef long ConnectionFilter;
// -----------------------------------------------------------------------------------------------------------------
// LINK
@@ -77,46 +188,52 @@ 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
*/
- 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 Looping function to update the Link
+ * @brief Destruction of a link
+ * @details Deletes references of this links form connected pins
+ */
+ ~Link();
+
+ /**
+ * @brief Looping function to update the Link
* @details Draws the Link and updates Hovering and Selected status.
*/
void update();
/**
- * @brief Get Left pin of the link
+ * @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 hovered() const { return m_hovered; }
+ [[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 selected() const { return m_selected; }
+ [[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;
};
@@ -125,54 +242,29 @@ namespace ImFlow
// HANDLER
/**
- * @brief All the color parameters
+ * @brief Grid's the color parameters
*/
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 grid
+ ImU32 background = IM_COL32(33,41,45,255);
+ /// @brief Main lines of the grid
+ ImU32 grid = IM_COL32(200, 200, 200, 40);
+ /// @brief Secondary lines
+ ImU32 subGrid = IM_COL32(200, 200, 200, 10);
};
/**
- * @brief ALl the Styling parameters. Sizes + Colors
+ * @brief ALl the grid's appearance parameters. Sizes + Colors
*/
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
-
- 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 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;
};
/**
@@ -185,72 +277,103 @@ 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++; }
+ 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
*/
- explicit ImNodeFlow(std::string name) :m_name(std::move(name)) { m_instances++; }
-
- /**
- * @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(std::string name) :m_name(std::move(name))
+ {
+ m_instances++;
+ m_context.config().extra_window_wrapper = true;
+ m_context.config().color = m_style.colors.background;
+ }
/**
- * @brief Handler loop
+ * @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 Add a node to the grid
* @tparam T Derived class of to be added
- * @param name Name to be given to the Node
- * @param pos Position of the Node in canvas coordinates
- * @return Pointer of the pushed type to the newly added Node
+ * @tparam Params types of optional args to forward to derived class ctor
+ * @param pos Position of the Node in grid coordinates
+ * @param args Optional arguments to be forwarded to derived class ctor
+ * @return Shared pointer of the pushed type to the newly added node
*
- * Inheritance is checked at compile time, MUST be derived from BaseNode.
+ * Inheritance is checked at compile time, \ MUST be derived from BaseNode.
*/
- template
- T* addNode(const std::string& name, const ImVec2& pos);
+ template
+ std::shared_ptr addNode(const ImVec2& pos, Params&&... args);
+
+ private:
+ /**
+ * @brief Helper struct for creating a node struct from a lambda
+ * @sa addLambdaNode which wraps creating one of these
+ * @tparam L the type of the lambda
+ * @tparam B always BaseNode, tparam because BaseNode is incomplete here
+ */
+ template
+ struct NodeWrapper : public B
+ {
+ L mLambda;
+ NodeWrapper(L&& l): BaseNode(), mLambda(std::forward(l)) {}
+ void draw() { mLambda(this); }
+ };
+
+ public:
+ /**
+ * @brief Add a node whos operation can be defined within a lambda.
+ * @tparam L the type of the lambda
+ * @param lambda the lambda that defines the nodes operation
+ * @param pos the position at which to place the node
+ */
+ template
+ std::shared_ptr> addLambdaNode(L&& lambda, const ImVec2& pos)
+ {
+ return addNode>(pos, std::forward(lambda));
+ }
/**
- * @brief Adds a node to the editor
+ * @brief Add a node to the grid
* @tparam T Derived class of to be added
- * @param name Name to be given to the Node
+ * @tparam Params types of optional args to forward to derived class ctor
* @param pos Position of the Node in screen coordinates
- * @return Pointer of the pushed type to the newly added Node
+ * @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.
+ * Inheritance is checked at compile time, \ MUST be derived from BaseNode.
*/
- template
- T* dropNode(const std::string& name, const ImVec2& pos);
+ template
+ std::shared_ptr placeNodeAt(const ImVec2& pos, Params&&... args);
/**
- * @brief Get nodes count
- * @return Number of nodes present in the editor
+ * @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.
*/
- int nodesCount() { return (int)m_nodes.size(); }
+ template
+ std::shared_ptr placeNode(Params&&... args);
/**
- * @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
+ * @brief Add link to the handler internal list
+ * @param link Reference to the link
*/
- void createLink(Pin* start, Pin* end);
+ 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
@@ -259,62 +382,93 @@ 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); }
+ 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& name() { return m_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& pos() { return m_pos; }
+ 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& scroll() { return m_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
+ * @brief Get editor's list of nodes
* @return Const reference to editor's internal nodes list
*/
- const std::vector>& nodes() { return m_nodes; }
+ std::unordered_map>& getNodes() { return m_nodes; }
/**
- * @brief Get editor's list of links
+ * @brief Get nodes count
+ * @return Number of nodes present in the editor
+ */
+ uint32_t getNodesCount() { return (uint32_t)m_nodes.size(); }
+
+ /**
+ * @brief Get editor's list of links
* @return Const reference to editor's internal links list
*/
- const std::vector>& links() { return m_links; }
+ // const std::vector>& getLinks() { return m_links; }
/**
- * @brief Get dragging status
+ * @brief Get zooming viewport
+ * @return Const reference to editor's internal viewport for zoom support
+ */
+ ContainedContext& getGrid() { return m_context; }
+
+ /**
+ * @brief Get dragging status
* @return [TRUE] if a Node is being dragged around the grid
*/
- [[nodiscard]] bool draggingNode() const { return m_draggingNode; }
+ [[nodiscard]] bool isNodeDragged() const { return m_draggingNode; }
/**
- * @brief Set dragging status
+ * @brief Get current style
+ * @return Reference to style variables
+ */
+ InfStyler& getStyle() { return m_style; }
+
+ /**
+ * @brief Set editor's size
+ * @param size Editor's size. Set to (0, 0) to auto-fit.
+ */
+ void setSize(const ImVec2& size) { m_context.config().size = size; }
+
+ /**
+ * @brief Set dragging status
* @param state New dragging state
*
* The new state will only be updated one at the start of each frame.
@@ -322,57 +476,65 @@ 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 canvas to screen
- * @param p Point in canvas coordinates to be converted
- * @return Point in screen coordinates
+ * @brief Set what node is being hovered
+ * @param hovering Pointer to the hovered node
*/
- ImVec2 canvas2screen(const ImVec2& p);
+ void hoveredNode(BaseNode* hovering) { m_hoveredNode = hovering; }
/**
- * @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 screen2grid(const ImVec2& p);
+
+ /**
+ * @brief Convert coordinates from grid to screen
+ * @param p Point in grid's coordinates to be converted
+ * @return Point in screen coordinates
*/
- ImVec2 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();
/**
- * @brief Get current style
- * @return Reference to style variables
+ * @brief Get recursion blacklist for nodes
+ * @return Reference to blacklist
*/
- InfStyler& style() { return m_style; }
+ std::vector& get_recursion_blacklist() { return m_pinRecursionBlacklist; }
private:
std::string m_name;
- ImVec2 m_pos;
- ImVec2 m_scroll = ImVec2(0, 0);
+ ContainedContext m_context;
bool m_singleUseClick = false;
- std::vector> m_nodes;
+ std::unordered_map> m_nodes;
+ std::vector m_pinRecursionBlacklist;
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;
@@ -390,139 +552,377 @@ 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)
- :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};
- }
+ virtual ~BaseNode() = default;
+ BaseNode() = default;
/**
- * @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;
+ virtual void draw() {}
+
+ /**
+ * @brief Add an Input to the node
+ * @details Will add an Input pin to the node with the given name and data type.
+ *
In this case the name of the pin will also be its UID.
+ *
The UID must be unique only in the context of the current node's inputs.
+ * @tparam T Type of the data the pin will handle
+ * @param name Name of the pin
+ * @param defReturn Default return value when the pin is not connected
+ * @param filter Connection filter
+ * @param style Style of the pin
+ * @return Shared pointer to the newly added pin
+ */
+ template
+ std::shared_ptr> addIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Add an Input to the node
+ * @details Will add an Input pin to the node with the given name and data type.
+ *
The UID must be unique only in the context of the current node's inputs.
+ * @tparam T Type of the data the pin will handle
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @param name Name of the pin
+ * @param defReturn Default return value when the pin is not connected
+ * @param filter Connection filter
+ * @param style Style of the pin
+ * @return Shared pointer to the newly added pin
+ */
+ template
+ std::shared_ptr> addIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Remove input pin
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ */
+ template
+ void dropIN(const U& uid);
/**
- * @brief 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.
+ * @brief Remove input pin
+ * @param uid Unique identifier of the pin
+ */
+ void dropIN(const char* uid);
+
+ /**
+ * @brief Show a temporary input pin
+ * @details Will show an input pin with the given name.
+ * The pin is created the first time showIN is called and kept alive as long as showIN is called each frame.
+ *
In this case the name of the pin will also be its UID.
+ *
The UID must be unique only in the context of the current node's inputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param defReturn Default return value when the pin is not connected
* @param filter Connection filter
+ * @param style Style of the pin
+ * @return Const reference to the value of the connected link for the current frame of defReturn
*/
template
- void addIN(std::string name, T defReturn, ConnectionFilter filter = ConnectionFilter_None);
+ const T& showIN(const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Show a temporary input pin
+ * @details Will show an input pin with the given name and UID.
+ * The pin is created the first time showIN_uid is called and kept alive as long as showIN_uid is called each frame.
+ *
The UID must be unique only in the context of the current node's inputs.
+ * @tparam T Type of the data the pin will handle
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @param name Name of the pin
+ * @param defReturn Default return value when the pin is not connected
+ * @param filter Connection filter
+ * @param style Style of the pin
+ * @return Const reference to the value of the connected link for the current frame of defReturn
+ */
+ template
+ const T& showIN_uid(const U& uid, const std::string& name, T defReturn, std::function filter, std::shared_ptr style = nullptr);
/**
- * @brief Add an Output to the node
+ * @brief Add an Output to the node
* @details Must be called in the node constructor. WIll add an Output pin to the node with the given name and data type.
+ *
In this case the name of the pin will also be its UID.
+ *
The UID must be unique only in the context of the current node's outputs.
* @tparam T Type of the data the pin will handle
* @param name Name of the pin
* @param filter Connection filter
- * @return Pointer to the newly added pin. Must be used to set behaviour
+ * @param style Style of the pin
+ * @return Shared pointer to the newly added pin. Must be used to set the behaviour
*/
template
- [[nodiscard]] OutPin* addOUT(std::string name, ConnectionFilter filter = ConnectionFilter_None);
+ [[nodiscard]] std::shared_ptr> addOUT(const std::string& name, std::shared_ptr style = nullptr);
/**
- * @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's outputs.
+ * @tparam T Type of the data the pin will handle
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @param name Name of the pin
+ * @param filter Connection filter
+ * @param style Style of the pin
+ * @return Shared pointer to the newly added pin. Must be used to set the behaviour
+ */
+ template
+ [[nodiscard]] std::shared_ptr> addOUT_uid(const U& uid, const std::string& name, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Remove output pin
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ */
+ template
+ void dropOUT(const U& uid);
+
+ /**
+ * @brief Remove output pin
+ * @param uid Unique identifier of the pin
+ */
+ void dropOUT(const char* uid);
+
+ /**
+ * @brief Show a temporary output pin
+ * @details Will show an output pin with the given name.
+ * The pin is created the first time showOUT is called and kept alive as long as showOUT is called each frame.
+ *
In this case the name of the pin will also be its UID.
+ *
The UID must be unique only in the context of the current node's outputs.
+ * @tparam T Type of the data the pin will handle
+ * @param name Name of the pin
+ * @param behaviour Function or lambda expression used to calculate output value
+ * @param filter Connection filter
+ * @param style Style of the pin
+ */
+ template
+ void showOUT(const std::string& name, std::function behaviour, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Show a temporary output pin
+ * @details Will show an output pin with the given name.
+ * The pin is created the first time showOUT_uid is called and kept alive as long as showOUT_uid is called each frame.
+ *
The UID must be unique only in the context of the current node's outputs.
+ * @tparam T Type of the data the pin will handle
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @param name Name of the pin
+ * @param behaviour Function or lambda expression used to calculate output value
+ * @param filter Connection filter
+ * @param style Style of the pin
+ */
+ template
+ void showOUT_uid(const U& uid, const std::string& name, std::function behaviour, std::shared_ptr style = nullptr);
+
+ /**
+ * @brief Get Input value from an InPin
+ * @details Get a reference to the value of an input pin, the value is stored in the output pin at the other end of the link.
+ * @tparam T Data type
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @return Const reference to the value
+ */
+ template
+ const T& getInVal(const U& uid);
+
+ /**
+ * @brief Get Input value from an InPin
+ * @details Get a reference to the value of an input pin, the value is stored in the output pin at the other end of the link.
* @tparam T Data type
- * @param i Index of the pin
- * @return Reference to the value
+ * @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
+ * @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
*/
- Pin* ins(int i) { return m_ins[i].get(); }
+ template
+ Pin* inPin(const U& uid);
/**
- * @brief Get generic reference to output pin
- * @param i Index of the pin
+ * @brief Get generic reference to input pin
+ * @param uid Unique identifier of the pin
* @return Generic pointer to the pin
*/
- Pin* outs(int i) { return m_outs[i].get(); }
+ Pin* inPin(const char* uid);
+
+ /**
+ * @brief Get generic reference to output pin
+ * @tparam U Type of the UID
+ * @param uid Unique identifier of the pin
+ * @return Generic pointer to the pin
+ */
+ template
+ Pin* outPin(const U& uid);
+
+ /**
+ * @brief Get generic reference to output pin
+ * @param uid Unique identifier of the pin
+ * @return Generic pointer to the pin
+ */
+ Pin* outPin(const char* uid);
+
+ /**
+ * @brief Get internal input pins list
+ * @return Const reference to node's internal list
+ */
+ const std::vector>& getIns() { return m_ins; }
+
+ /**
+ * @brief Get internal output pins list
+ * @return Const reference to node's internal list
+ */
+ const std::vector>& getOuts() { return m_outs; }
+
+ /**
+ * @brief Delete itself
+ */
+ void destroy() { m_destroyed = true; }
+
+ /*
+ * @brief Get if node must be deleted
+ */
+ [[nodiscard]] bool toDestroy() const { return m_destroyed; }
/**
- * @brief Get hovered status
+ * @brief Get hovered status
* @return [TRUE] if the mouse is hovering the node
*/
- bool hovered();
+ bool isHovered();
+
+ /**
+ * @brief Get node's UID
+ * @return Node's unique identifier
+ */
+ [[nodiscard]] NodeUID getUID() const { return m_uid; }
/**
- * @brief Get node name
+ * @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_title; }
+
+ /**
+ * @brief Get node size
+ * @return Const reference to the node's size
+ */
+ const ImVec2& getSize() { return m_size; }
/**
- * @brief Get node size
+ * @brief Get node size
* @return Const reference to the node's size
*/
- const ImVec2& size() { return m_size; }
+ const ImVec2& getFullSize() { return m_fullSize; }
/**
- * @brief Get node position
+ * @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
+ * @brief Get grid handler bound to node
+ * @return Pointer to the handler
+ */
+ ImNodeFlow* getHandler() { return m_inf; }
+
+ /**
+ * @brief Get node's style
+ * @return Shared pointer to the node's style
+ */
+ const std::shared_ptr& getStyle() { return m_style; }
+
+ /**
+ * @brief Get selected status
* @return [TRUE] if the node is selected
*/
- [[nodiscard]] bool selected() const { return m_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 dragged() const { return m_dragged; }
+ [[nodiscard]] bool isDragged() const { return m_dragged; }
/**
- * @brief Set selected status
+ * @brief Set node's uid
+ * @param uid Node's unique identifier
+ */
+ BaseNode* setUID(NodeUID uid) { m_uid = uid; return this; }
+
+ /**
+ * @brief Set node's name
+ * @param name New title
+ */
+ BaseNode* setTitle(const std::string& title) { m_title = title; return this; }
+
+ /**
+ * @brief Set node's position
+ * @param pos Position in grid coordinates
+ */
+ BaseNode* setPos(const ImVec2& pos) { m_pos = pos; m_posTarget = pos; return this; }
+
+ /**
+ * @brief Set ImNodeFlow handler
+ * @param inf Grid handler for the node
+ */
+ BaseNode* setHandler(ImNodeFlow* inf) { m_inf = inf; return this; }
+
+ /**
+ * @brief Set node's style
+ * @param style New style
+ */
+ BaseNode* setStyle(std::shared_ptr style) { m_style = std::move(style); return this; }
+
+ /**
+ * @brief Set selected status
* @param state New selected state
*
* Status only updates when updatePublicStatus() is called
*/
- void selected(bool state) { m_selectedNext = state; }
+ BaseNode* selected(bool state) { m_selectedNext = state; return this; }
/**
- * @brief Updates the selected status of the node
+ * @brief Update the isSelected status of the node
*/
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();
+
+
private:
- std::string m_name;
- ImVec2 m_pos, m_posOld = m_pos;
+ NodeUID m_uid = 0;
+ std::string m_title;
+ ImVec2 m_pos, m_posTarget;
ImVec2 m_size;
- ImNodeFlow* m_inf;
+ ImVec2 m_fullSize;
+ ImNodeFlow* m_inf = nullptr;
+ std::shared_ptr m_style;
bool m_selected = false, m_selectedNext = false;
bool m_dragged = false;
- ImVec2 m_paddingTL;
- ImVec2 m_paddingBR;
+ bool m_destroyed = false;
std::vector> m_ins;
+ std::vector>> m_dynamicIns;
std::vector> m_outs;
+ std::vector>> m_dynamicOuts;
};
// -----------------------------------------------------------------------------------------------------------------
@@ -531,10 +931,10 @@ namespace ImFlow
/**
* @brief Pins type identifier
*/
- enum PinKind
+ enum PinType
{
- PinKind_Input,
- PinKind_Output
+ PinType_Input,
+ PinType_Output
};
/**
@@ -544,106 +944,169 @@ 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(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(PinUID uid, std::string name, std::shared_ptr style, PinType kind, BaseNode* parent, ImNodeFlow** inf)
+ :m_uid(uid), m_name(std::move(name)), m_type(kind), m_parent(parent), m_inf(inf), m_style(std::move(style))
+ {
+ if(!m_style)
+ m_style = PinStyle::cyan();
+ }
+
+ virtual ~Pin() = default;
/**
- * @brief Main loop of the pin
+ * @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 Create link between pins
- * @param left Pointer to the other pin
+ * @brief Draw default pin's socket
*/
- virtual void createLink(Pin* left) {}
+ void drawSocket();
/**
- * @brief Sets the reference to a link
- * @param link Pointer to the link
+ * @brief Draw default pin's decoration (border, bg, and hover overlay)
+ */
+ void drawDecoration();
+
+ /**
+ * @brief Used by output pins to calculate their values
+ */
+ virtual void resolve() {}
+
+ /**
+ * @brief Custom render function to override Pin appearance
+ * @param r Function or lambda expression with new ImGui rendering
+ */
+ Pin* renderer(std::function r) { m_renderer = std::move(r); return this; }
+
+ /**
+ * @brief Create link between pins
+ * @param other Pointer to the other pin
+ */
+ virtual void createLink(Pin* other) = 0;
+
+ /**
+ * @brief Set the reference to a link
+ * @param link Smart pointer to the link
*/
virtual void setLink(std::shared_ptr& link) {}
/**
- * @brief Deletes the link from pin
+ * @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() {}
+ virtual void deleteLink(Pin *pin) = 0;
/**
- * @brief Get pin's link
- * @return Weak_ptr reference to the link connected to the pin
+ * @brief Get connected status
+ * @return [TRUE] if the pin is connected
*/
- virtual std::weak_ptr getLink() { return std::weak_ptr{}; }
+ virtual bool isConnected() = 0;
/**
- * @brief Get pin's name
+ * @brief Get pin's link
+ * @return Weak_ptr reference to pin's link
+ */
+ virtual std::map> *getLink(){ return nullptr ;};
+
+ virtual std::vector> *getWeakLink(){ return nullptr;};
+ /**
+ * @brief Get pin's UID
+ * @return Unique identifier of the pin
+ */
+ [[nodiscard]] PinUID getUid() const { return m_uid; }
+
+ /**
+ * @brief Get pin's name
* @return Const reference to pin's name
*/
- const std::string& 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
+ * @brief Get pin's position
+ * @return Const reference to pin's position in grid coordinates
*/
- [[nodiscard]] const ImVec2& pos() { return m_pos; }
+ [[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& 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
+ * @brief Get pin's parent node
+ * @return Generic type pointer to pin's parent node. (Node that contains it)
*/
- BaseNode* parent() { return m_parent; }
+ BaseNode* getParent() { return m_parent; }
/**
- * @brief Get pin's type
+ * @brief Get pin's type
* @return The pin type. Either Input or Output
*/
- PinKind kind() { return m_kind; }
+ 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 filter() const { return m_filter; }
+ [[nodiscard]] virtual const std::type_info& getDataType() const = 0;
/**
- * @brief Get pin's link attachment point
- * @return Canvas coordinates to the attachment point between the link and the pin
+ * @brief Get pin's style
+ * @return Smart pointer to pin's style
+ */
+ std::shared_ptr& getStyle() { return m_style; }
+
+ /**
+ * @brief Get pin's link attachment point (socket)
+ * @return Grid coordinates to the attachment point between the link and the pin's socket
*/
virtual ImVec2 pinPoint() = 0;
/**
- * @brief Calculate pin's width pre-rendering
+ * @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 pos(ImVec2 pos) { m_pos = pos; }
+ void setPos(ImVec2 pos) { m_pos = pos; }
protected:
+ PinUID m_uid;
std::string m_name;
ImVec2 m_pos = ImVec2(0.f, 0.f);
ImVec2 m_size = ImVec2(0.f, 0.f);
- PinKind m_kind;
- ConnectionFilter m_filter;
+ PinType m_type;
BaseNode* m_parent = nullptr;
- ImNodeFlow* m_inf;
+ ImNodeFlow** m_inf;
+ std::shared_ptr m_style;
+ std::function m_renderer;
+ };
+
+ /**
+ * @brief Collection of Pin's collection filters
+ */
+ class ConnectionFilter
+ {
+ public:
+ static std::function None() { return [](Pin* out, Pin* in){ return true; }; }
+ static std::function SameType() { return [](Pin* out, Pin* in) { return out->getDataType() == in->getDataType(); }; }
+ static std::function Numbers() { return [](Pin* out, Pin* in){ return out->getDataType() == typeid(double) || out->getDataType() == typeid(float) || out->getDataType() == typeid(int); }; }
};
/**
@@ -655,53 +1118,87 @@ 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(const std::string& name, ConnectionFilter filter, BaseNode* parent, T defReturn, ImNodeFlow* inf)
- :Pin(name, filter, PinKind_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 Main loop of the pin
- * @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame.
+ * @brief Create link between pins
+ * @param other Pointer to the other pin
*/
- void update() override;
+ void createLink(Pin* other) override;
+
+ /**
+ * @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 {
+ 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 Create link between pins
- * @param left Pointer to the other pin
+ * @brief Specify if connections from an output on the same node are allowed
+ * @param state New state of the flag
*/
- void createLink(Pin* left) override;
+ void allowSameNodeConnections(bool state) { m_allowSelfConnection = state; }
/**
- * @brief Deletes the link from 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 .empty(); }
/**
- * @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; }
+ std::map> *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 InPin's connection filter
+ * @return InPin's connection filter configuration
*/
- ImVec2 pinPoint() override { return m_pos + ImVec2(-m_inf->style().node_padding.z, m_size.y / 2); }
+ [[nodiscard]] const std::function& getFilter() const { return m_filter; }
/**
- * @brief Get value carried by the link
+ * @brief Get pin's data type (aka: \)
+ * @return String containing unique information identifying the data type
+ */
+ [[nodiscard]] const std::type_info& getDataType() const override { return typeid(T); };
+
+ /**
+ * @brief Get pin's link attachment point (socket)
+ * @return Grid coordinates to the attachment point between the link and the pin's socket
+ */
+ ImVec2 pinPoint() override { return m_pos + ImVec2(-m_style->extra.socket_padding, m_size.y / 2); }
+
+ /**
+ * @brief Get value carried by the connected link
* @return Reference to the value of the connected OutPin. Or the default value if not connected
*/
const T& val();
private:
- std::shared_ptr m_link;
+ std::map> m_link;
T m_emptyVal;
+ std::function m_filter;
+ bool m_allowSelfConnection = false;
};
/**
@@ -713,52 +1210,80 @@ 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(const std::string& name, ConnectionFilter filter, BaseNode* parent, ImNodeFlow* inf)
- :Pin(name, filter, PinKind_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 link
+ * @brief When parent gets deleted, remove the links
+ * @author changed by fgfxf
+ * @date 2026-05-23
*/
- ~OutPin() { if (!m_link.expired()) m_link.lock()->right()->deleteLink(); }
+ ~OutPin() override {
+ std::vector> links = std::move(m_links);
+ for (auto &l: links) if (!l.expired()) l.lock()->right()->deleteLink(l.lock()->left());
+ }
/**
- * @brief Main loop of the pin
- * @details Updates position, hovering and dragging status, and renders the pin. Must be called each frame.
+ * @brief Create link between pins
+ * @param other Pointer to the other pin
*/
- void update() override;
+ 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 { m_link = link; }
+ void setLink(std::shared_ptr& link) override;
/**
- * @brief Get pin's link attachment point
- * @return Canvas coordinates to the attachment point between the link and the pin
+ * @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
*/
- ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_inf->style().node_padding.z, m_size.y / 2); }
+ void deleteLink(Pin *pin) override;
/**
- * @brief Calculate and get pin's value
- * @return Reference to the internal value of the pin
+ * @brief Get connected status
+ * @return [TRUE] is pin is connected to one or more links
+ */
+ bool isConnected() override { return !m_links.empty(); }
+
+ /**
+ * @brief Get pin's link attachment point (socket)
+ * @return Grid coordinates to the attachment point between the link and the pin's socket
+ */
+ ImVec2 pinPoint() override { return m_pos + ImVec2(m_size.x + m_style->extra.socket_padding, m_size.y / 2); }
+
+ /**
+ * @brief Get output value
+ * @return Const reference to the internal value of the pin
*/
const T& val();
/**
- * @brief Set logic to calculate output value
+ * @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
*/
- void behaviour(std::function func) { m_behaviour = std::move(func); }
+ 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); };
+
+ virtual std::vector> *getWeakLink() override { return &m_links; };
private:
- std::weak_ptr m_link;
+ std::vector> m_links;
std::function m_behaviour;
T m_val;
};
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 ed6bf08..d3c6bf2 100644
--- a/readme.md
+++ b/readme.md
@@ -1,40 +1,58 @@
-# ImNodeFLow
-**Node based editor/blueprints for ImGui**
+# ImNodeFlow
-Create your custom nodes, and their logic.
-ImNodeFLow will handle connections, editor logic and rendering.
+**fgfxf highly customized**
+
-[INSERT IMAGE HERE]
+个人定制化
+- 蓝图的里的一个方框是节点,节点的Inpin和另一个节点的OutPin连线连接起来。原版Pin不支持多个连线,本仓库修改后支持多个连线。
+- 删除节点时,对应的连线也会消失。原版有点问题。
+- 对于新版imgui,原版编译存在一些小问题。
+
+personal customization
+
+- A square box in the blueprint represents a node, and the InPin of one node is connected to the OutPin of another node through a line. The original Pin does not support multiple connections, but this repository has been modified to support multiple connections.
+- When a node is deleted, the corresponding connection will also disappear. There is a slight issue with the original version.
+- For the new version of imgui, there are some minor issues with the original compilation.
+
+
+
+**Node-based editor/blueprints for ImGui**
+
+Create your custom nodes and their logic... ImNodeFlow will handle connections, editor logic, and rendering.
+
+
## Features
-- Object based nodes creation
-- Backed-in Input and Output logic
-- Backed-in links handling
+- Support for Zoom
+- 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)
-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.
-
-## Simple Node example
+## Example using SDL2 + OpenGL3 (CMake project)
+A simple example using SDL2 + OpenGL3 backend is provided in the /example folder. The CMakeLists.txt file downloads the necessary sources automatically and populates. Simply copy the contents of example folder, configure and build. You can use this example as your starting point to build your code.
+```
+ > cd example
+ > mkdir build
+ > cd build
+ > cmake ..
+ > ./example
+```
+To use system installed Imgui pass the `-DUSE_SYSTEM_IMGUI=ON` option while configuring CMake. Note that you should have the Findimgui.cmake script to find the Imgui libs and headers (to use `find_package(imgui)`).
+
+### Simple Node example
```c++
class SimpleSum : public BaseNode
{
public:
- explicit Somma(const std::string& name, ImVec2 pos, ImNodeFlow* inf) : BaseNode(name, pos, inf)
+ SimpleSum()
{
- addIN("IN_VAL", 0, ConnectionFilter_Int);
- addOUT("OUT_VAL", ConnectionFilter_Int)
- ->behaviour([this](){ return ins(0) + m_valB; });
+ 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
@@ -46,8 +64,75 @@ private:
int m_valB = 0;
};
```
+
+
+## CMake for custom targets
+Shown below is a simple CMake script to setup your own program to compile using Imgui and ImNodeFlow. You can adapt this to your needs.
+```cmake
+set(IMGUI_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/imgui)
+set(IMNODEFLOW_DIR ${CMAKE_CURRENT_LIST_DIR}/includes/ImNodeFlow)
-[INSERT IMAGE HERE]
+include(FetchContent)
+
+FetchContent_Declare(ImNodeFlow
+ GIT_REPOSITORY "https://github.com/Fattorino/ImNodeFlow.git"
+ GIT_TAG "master"
+ SOURCE_DIR ${IMNODEFLOW_DIR}
+)
+FetchContent_GetProperties(ImNodeFlow)
+if(NOT imnodeflow_POPULATED)
+ FetchContent_Populate(ImNodeFlow)
+endif()
+
+FetchContent_Declare(imgui
+ GIT_REPOSITORY "https://github.com/ocornut/imgui.git"
+ GIT_TAG "origin/master"
+ SOURCE_DIR ${IMGUI_DIR}
+)
+FetchContent_GetProperties(imgui)
+if(NOT imgui_POPULATED)
+ FetchContent_Populate(imgui)
+endif()
+
+list(APPEND imgui_sources
+ ${IMGUI_DIR}/imgui.cpp
+ ${IMGUI_DIR}/misc/cpp/imgui_stdlib.cpp
+ ${IMGUI_DIR}/imgui_draw.cpp
+ ${IMGUI_DIR}/imgui_tables.cpp
+ ${IMGUI_DIR}/imgui_widgets.cpp
+ ${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp
+ ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp)
+
+list(APPEND imnode_flow_sources
+ ${IMNODEFLOW_DIR}/src/ImNodeFlow.cpp)
+
+add_executable(custom_exe custom_exe.cpp ${imgui_sources} ${imnode_flow_sources})
+set_property(TARGET custom_exe PROPERTY CXX_STANDARD 17)
+target_include_directories(custom_exe PRIVATE ${IMGUI_DIR} ${IMNODEFLOW_DIR}/include ${IMGUI_DIR}/backends)
+target_compile_definitions(custom_exe PRIVATE IMGUI_DEFINE_MATH_OPERATORS)
+```
+Depending on the backend you choose for Imgui you can set `target_link_libraries` to the correct sets. For example SDL2+OpenGL requires the following defintitions,
+
+```cmake
+find_package(OpenGL REQUIRED)
+find_package(SDL2 REQUIRED)
+if (UNIX)
+ if (NOT APPLE)
+ find_package(Threads REQUIRED)
+ find_package(X11 REQUIRED)
+ target_link_libraries(custom_exe PRIVATE
+ ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
+ endif()
+endif()
+
+target_link_libraries(custom_exe PUBLIC OpenGL::GL SDL2::SDL2)
+```
## Full documentation
-For a more detailed explanation please refer to the [full documentation](documentation.md)
+For a more detailed explanation please refer to the [documentation](documentation.md)
+
+***
+### Special credits
+- [ocornut](https://github.com/ocornut) for Dear ImGui
+- [thedmd](https://github.com/thedmd) for _imgui_bezier_math.h_
+- [nem0](https://github.com/nem0) for helping with Zoom support
diff --git a/src/ImNodeFlow.cpp b/src/ImNodeFlow.cpp
index 87ea176..e83588c 100644
--- a/src/ImNodeFlow.cpp
+++ b/src/ImNodeFlow.cpp
@@ -1,53 +1,70 @@
#include "ImNodeFlow.h"
-namespace ImFlow
-{
+namespace ImFlow {
// -----------------------------------------------------------------------------------------------------------------
// LINK
- void Link::update()
- {
+ void Link::update() {
ImVec2 start = m_left->pinPoint();
- ImVec2 end = m_right->pinPoint();
- float thickness = m_inf->style().link_thickness;
+ 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))
- {
+ /**
+ * @brief 贝塞尔曲线计算鼠标位置是否悬浮在线条上。
+ */
+ if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) {
m_hovered = true;
- thickness = m_inf->style().link_hovered_thickness;
- if (mouseClickState)
- {
+ thickness = m_left->getStyle()->extra.link_hovered_thickness;
+ if (mouseClickState) {
m_inf->consumeSingleUseClick();
m_selected = true;
}
- }
- else { m_hovered = false; }
+ } 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_left->getStyle()->extra.outline_color,
+ 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_right->deleteLink(m_left);
+ }
+
+ Link::~Link() {
+ /**
+ * @brief outpin清理一下
+ * @note 传入参数无意义 / param meaningless
+ */
+ if (!m_left) return;
+ m_left->deleteLink(m_left);
}
// -----------------------------------------------------------------------------------------------------------------
// BASE NODE
- bool BaseNode::hovered()
- {
- return ImGui::IsMouseHoveringRect(m_inf->canvas2screen(m_pos - m_paddingTL), m_inf->canvas2screen(m_pos + m_size + m_paddingBR));
+ bool BaseNode::isHovered() {
+ ImVec2 paddingTL = {m_style->padding.x, m_style->padding.y};
+ ImVec2 paddingBR = {m_style->padding.z, m_style->padding.w};
+ return ImGui::IsMouseHoveringRect(m_inf->grid2screen(m_pos - paddingTL),
+ m_inf->grid2screen(m_pos + m_size + paddingBR));
}
- void BaseNode::update(ImVec2& offset)
- {
- 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});
+ 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);
@@ -56,21 +73,29 @@ namespace ImFlow
// Header
ImGui::BeginGroup();
- ImGui::TextColored(m_inf->style().colors.node_header_title, m_name.c_str());
+ 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();
- for(auto& p : m_ins)
- {
- p->pos(ImGui::GetCursorPos() + ImGui::GetWindowPos());
- p->update();
+ if (!m_ins.empty() || !m_dynamicIns.empty()) {
+ ImGui::BeginGroup();
+ for (auto &p: m_ins) {
+ p->setPos(ImGui::GetCursorPos());
+ p->update();
+ }
+ for (auto &p: m_dynamicIns) {
+ if (p.first == 1) {
+ p.second->setPos(ImGui::GetCursorPos());
+ p.second->update();
+ p.first = 0;
+ }
+ }
+ ImGui::EndGroup();
+ ImGui::SameLine();
}
- ImGui::EndGroup();
- ImGui::SameLine();
// Content
ImGui::BeginGroup();
@@ -81,71 +106,115 @@ namespace ImFlow
// 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) {
+ 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_inf->canvas2screen(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));
+ 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->pos(ImVec2(m_inf->canvas2screen(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_pos + ImVec2(titleW, 0) + m_inf->getGrid().scroll()).x <
+ ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW)
+ p.second->setPos(
+ ImGui::GetCursorPos() + ImGui::GetWindowPos() + ImVec2(maxW - p.second->calcWidth(), 0.f));
+ else
+ p.second->setPos(
+ ImVec2((m_pos + ImVec2(titleW - p.second->calcWidth(), 0) + m_inf->getGrid().scroll()).x,
+ ImGui::GetCursorPos().y + ImGui::GetWindowPos().y));
+ p.second->update();
+ p.first -= 1;
+ }
+
ImGui::EndGroup();
ImGui::EndGroup();
m_size = ImGui::GetItemRectSize();
- ImVec2 headerSize = ImVec2(m_size.x + m_paddingTL.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_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);
- 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);
- 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->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + m_size + paddingBR, m_style->bg,
+ m_style->radius);
+ draw_list->AddRectFilled(offset + m_pos - paddingTL, offset + m_pos + headerSize, m_style->header_bg,
+ m_style->radius, ImDrawFlags_RoundCornersTop);
+ m_fullSize = m_size + paddingTL + paddingBR;
+ ImU32 col = m_style->border_color;
+ float thickness = m_style->border_thickness;
+ ImVec2 ptl = paddingTL;
+ ImVec2 pbr = paddingBR;
+ if (m_selected) {
+ col = m_style->border_selected_color;
+ thickness = m_style->border_selected_thickness;
+ }
+ if (thickness < 0.f) {
+ ptl.x -= thickness / 2;
+ ptl.y -= thickness / 2;
+ pbr.x -= thickness / 2;
+ pbr.y -= thickness / 2;
+ thickness *= -1.f;
+ }
+ draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col, m_style->radius, 0, thickness);
- if (!ImGui::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 (hovered() && mouseClickState)
- {
- selected(true);
- m_inf->consumeSingleUseClick();
+ if (isHovered()) {
+ m_inf->hoveredNode(this);
+ if (mouseClickState) {
+ selected(true);
+ m_inf->consumeSingleUseClick();
+ }
}
- bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - m_paddingTL, offset + m_pos + headerSize);
- if (onHeader && mouseClickState)
- {
+ if (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() && isSelected())
+ destroy();
+
+ bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL, offset + m_pos + headerSize);
+ if (onHeader && mouseClickState) {
m_inf->consumeSingleUseClick();
m_dragged = true;
m_inf->draggingNode(true);
- 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);
+ if (m_dragged || (m_selected && m_inf->isNodeDragged())) {
+ float step = m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions;
+ m_posTarget += m_inf->getScreenSpaceDelta();
// "Slam" The position
- m_pos.x = 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))
- {
+ if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
m_dragged = false;
m_inf->draggingNode(false);
- m_posOld = m_pos;
+ m_posTarget = m_pos;
}
}
ImGui::PopID();
+
+ // Deleting dead pins
+ m_dynamicIns.erase(std::remove_if(m_dynamicIns.begin(), m_dynamicIns.end(),
+ [](const std::pair> &p) { return p.first == 0; }),
+ m_dynamicIns.end());
+ m_dynamicOuts.erase(std::remove_if(m_dynamicOuts.begin(), m_dynamicOuts.end(),
+ [](const std::pair> &p) { return p.first == 0; }),
+ m_dynamicOuts.end());
}
// -----------------------------------------------------------------------------------------------------------------
@@ -153,166 +222,231 @@ 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(),
- [](auto& n) { return n->selected() && n->hovered();});
+ [](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(),
- [](auto& n) {return !n->hovered();})
+ [](const auto &n) { return !n.second->isHovered(); })
&& std::all_of(m_links.begin(), m_links.end(),
- [](auto& l) {return !l.lock()->hovered();});
+ [](const auto &l) { return !l.lock()->isHovered(); });
}
- ImVec2 ImNodeFlow::canvas2screen(const ImVec2 &p)
+ ImVec2 ImNodeFlow::screen2grid( const ImVec2 & p )
{
- return p + m_scroll + ImGui::GetWindowPos();
+ if ( ImGui::GetCurrentContext() == m_context.getRawContext() )
+ return p - m_context.scroll();
+ return ( p - m_context.origin() ) / m_context.scale() - m_context.scroll();
}
- ImVec2 ImNodeFlow::screen2canvas(const ImVec2 &p)
+ ImVec2 ImNodeFlow::grid2screen( const ImVec2 & p )
{
- return p - m_pos - m_scroll;
+ if ( ImGui::GetCurrentContext() == m_context.getRawContext() )
+ return p + m_context.scroll();
+ return ( p + m_context.scroll() ) * m_context.scale() + m_context.origin();
}
- void ImNodeFlow::createLink(Pin* start, Pin* end)
- {
- if (start->parent() == end->parent())
- return;
- if (!((start->filter() & end->filter()) != 0 || start->filter() == ConnectionFilter_None || end->filter() == ConnectionFilter_None)) // Check Filter
- return;
+ void ImNodeFlow::addLink(std::shared_ptr &link) {
+ m_links.push_back(link);
+ }
- if (start->kind() == PinKind_Output && end->kind() == PinKind_Input) // OUT to IN
+ /**
+ * @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)
{
- if (end->getLink().expired() || end->getLink().lock()->left() != start)
+ 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();)
{
- end->createLink(start);
- m_links.emplace_back(end->getLink());
+ std::shared_ptr link = it->second;
+
+ if (!link)
+ {
+ it = links->erase(it);
+ continue;
+ }
+ // 当前连线的左侧节点
+ Pin *leftPin = link->left();
+ Pin *rightPin = link->right();
+
+ leftPin->deleteLink(rightPin);
+ it = links->erase(it);
}
- else
- end->deleteLink();
- }
- if (start->kind() == PinKind_Input && end->kind() == PinKind_Output) // IN to OUT
+ };
+
+ /**
+ * @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 (start->getLink().expired() || start->getLink().lock()->left() != end)
+ 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();)
{
- start->createLink(end);
- m_links.emplace_back(start->getLink());
+
+ if (!it->lock().get())
+ {
+ it = links->erase(it);
+ continue;
+ }
+
+ 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);
+ */
}
- else
- start->deleteLink();
+ };
+
+ for (auto &p : m_ins)
+ {
+ eraseLinksOfInPin(p.get());
+ }
+
+ for (auto &p : m_outs)
+ {
+ eraseLinksOfOutPin(p.get());
}
}
- void ImNodeFlow::update()
- {
+ void ImNodeFlow::update() {
// Updating looping stuff
m_hovering = nullptr;
+ m_hoveredNode = nullptr;
m_draggingNode = m_draggingNodeNext;
m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
// Create child canvas
- 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_context.begin();
+ ImGui::GetIO().IniFilename = nullptr;
- ImVec2 offset = ImGui::GetCursorScreenPos() + m_scroll;
- ImDrawList* draw_list = ImGui::GetWindowDrawList();
+ 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)
- 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)
- 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)
- 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)
- 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
draw_list->ChannelsSplit(2);
- for (auto& node : m_nodes) { node->update(offset); }
+ for (auto &node: m_nodes) { node.second->update(); }
+ // Remove "toDelete" nodes
+ for (auto iter = m_nodes.begin(); iter != m_nodes.end();) {
+ if (iter->second->toDestroy())
+ {
+ iter->second->destroyLinks(); // delete all links linked this node
+ iter = m_nodes.erase(iter);
+ }
+ else
+ ++iter;
+ }
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(); }
+ 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
- createLink(m_dragOut, m_hovering);
+ } 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)
- {
- 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);
+ if (m_dragOut) {
+ if (m_dragOut->getType() == PinType_Output)
+ smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(), m_dragOut->getStyle()->color,
+ m_dragOut->getStyle()->extra.link_dragged_thickness);
else
- smart_bezier(ImGui::GetMousePos(), m_dragOut->pinPoint(), m_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;
}
- // 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->selected(); }), m_nodes.end());
- }
-
// Right-click PopUp
- if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && on_free_space())
- {
- if (m_rightClickPopUp)
- ImGui::OpenPopup("RightClickPopUp");
+ if (m_rightClickPopUp && ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsWindowHovered()) {
+ m_hoveredNodeAux = m_hoveredNode;
+ ImGui::OpenPopup("RightClickPopUp");
}
- if (ImGui::BeginPopup("RightClickPopUp"))
- {
- m_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());
- // Scrolling
- if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 0.0f))
- m_scroll = m_scroll + ImGui::GetIO().MouseDelta;
+ // Clearing recursion blacklist
+ m_pinRecursionBlacklist.clear();
- ImGui::EndChild();
+ m_context.end();
}
}
diff --git a/src/ImNodeFlow.inl b/src/ImNodeFlow.inl
index 514d09e..8f34b14 100644
--- a/src/ImNodeFlow.inl
+++ b/src/ImNodeFlow.inl
@@ -34,100 +34,357 @@ namespace ImFlow
// -----------------------------------------------------------------------------------------------------------------
// HANDLER
- template
- T* ImNodeFlow::addNode(const std::string& name, const ImVec2& pos)
+ template
+ std::shared_ptr ImNodeFlow::addNode(const ImVec2& pos, Params&&... args)
{
- static_assert(std::is_base_of::value, "Pushed type is not subclass of BaseNode!");
- m_nodes.emplace_back(std::make_shared(name, pos, this));
- return static_cast(m_nodes.back().get());
+ static_assert(std::is_base_of::value, "Pushed type is not a subclass of BaseNode!");
+
+ std::shared_ptr n = std::make_shared(std::forward(args)...);
+ n->setPos(pos);
+ n->setHandler(this);
+ if (!n->getStyle())
+ n->setStyle(NodeStyle::cyan());
+
+ auto uid = reinterpret_cast(n.get());
+ n->setUID(uid);
+ m_nodes[uid] = n;
+ return n;
}
- template
- T* ImNodeFlow::dropNode(const std::string& name, const ImVec2& pos)
+ template
+ std::shared_ptr ImNodeFlow::placeNodeAt(const ImVec2& pos, Params&&... args)
+ {
+ return addNode(screen2grid(pos), std::forward(args)...);
+ }
+
+ template
+ std::shared_ptr ImNodeFlow::placeNode(Params&&... args)
{
- 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));
- return static_cast(m_nodes.back().get());
+ return placeNodeAt