├── .appveyor.yml ├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── NodeEditorConfig.cmake.in ├── examples ├── CMakeLists.txt ├── calculator │ ├── AdditionModel.hpp │ ├── CMakeLists.txt │ ├── Converters.cpp │ ├── Converters.hpp │ ├── DecimalData.hpp │ ├── DivisionModel.hpp │ ├── IntegerData.hpp │ ├── MathOperationDataModel.cpp │ ├── MathOperationDataModel.hpp │ ├── ModuloModel.cpp │ ├── ModuloModel.hpp │ ├── MultiplicationModel.hpp │ ├── NumberDisplayDataModel.cpp │ ├── NumberDisplayDataModel.hpp │ ├── NumberSourceDataModel.cpp │ ├── NumberSourceDataModel.hpp │ ├── SubtractionModel.hpp │ └── main.cpp ├── connection_colors │ ├── CMakeLists.txt │ ├── main.cpp │ ├── models.cpp │ └── models.hpp ├── example2 │ ├── CMakeLists.txt │ ├── TextData.hpp │ ├── TextDisplayDataModel.cpp │ ├── TextDisplayDataModel.hpp │ ├── TextSourceDataModel.cpp │ ├── TextSourceDataModel.hpp │ └── main.cpp ├── images │ ├── CMakeLists.txt │ ├── ImageLoaderModel.cpp │ ├── ImageLoaderModel.hpp │ ├── ImageShowModel.cpp │ ├── ImageShowModel.hpp │ ├── PixmapData.hpp │ └── main.cpp └── styles │ ├── CMakeLists.txt │ ├── main.cpp │ ├── models.cpp │ └── models.hpp ├── external ├── CMakeLists.txt └── Catch2 │ └── CMakeLists.txt ├── include └── nodes │ ├── Connection │ ├── ConnectionStyle │ ├── DataModelRegistry │ ├── FlowScene │ ├── FlowView │ ├── FlowViewStyle │ ├── Node │ ├── NodeData │ ├── NodeDataModel │ ├── NodeGeometry │ ├── NodePainterDelegate │ ├── NodeState │ ├── NodeStyle │ ├── StyleCollection │ ├── TypeConverter │ └── internal │ ├── Compiler.hpp │ ├── Connection.hpp │ ├── ConnectionGeometry.hpp │ ├── ConnectionGraphicsObject.hpp │ ├── ConnectionState.hpp │ ├── ConnectionStyle.hpp │ ├── DataModelRegistry.hpp │ ├── Export.hpp │ ├── FlowScene.hpp │ ├── FlowView.hpp │ ├── FlowViewStyle.hpp │ ├── Node.hpp │ ├── NodeData.hpp │ ├── NodeDataModel.hpp │ ├── NodeGeometry.hpp │ ├── NodeGraphicsObject.hpp │ ├── NodePainterDelegate.hpp │ ├── NodeState.hpp │ ├── NodeStyle.hpp │ ├── OperatingSystem.hpp │ ├── PortType.hpp │ ├── QStringStdHash.hpp │ ├── QUuidStdHash.hpp │ ├── Serializable.hpp │ ├── Style.hpp │ ├── StyleCollection.hpp │ ├── TypeConverter.hpp │ └── memory.hpp ├── pictures ├── calculator.png ├── chigraph.png ├── flow.png ├── spkgen.png ├── style_example.png └── vid1.png ├── resources ├── DefaultStyle.json ├── convert.png └── resources.qrc ├── src ├── Connection.cpp ├── ConnectionBlurEffect.cpp ├── ConnectionBlurEffect.hpp ├── ConnectionGeometry.cpp ├── ConnectionGraphicsObject.cpp ├── ConnectionPainter.cpp ├── ConnectionPainter.hpp ├── ConnectionState.cpp ├── ConnectionStyle.cpp ├── DataModelRegistry.cpp ├── FlowScene.cpp ├── FlowView.cpp ├── FlowViewStyle.cpp ├── Node.cpp ├── NodeConnectionInteraction.cpp ├── NodeConnectionInteraction.hpp ├── NodeDataModel.cpp ├── NodeGeometry.cpp ├── NodeGraphicsObject.cpp ├── NodePainter.cpp ├── NodePainter.hpp ├── NodeState.cpp ├── NodeStyle.cpp ├── Properties.cpp ├── Properties.hpp └── StyleCollection.cpp └── test ├── CMakeLists.txt ├── include ├── ApplicationSetup.hpp ├── Stringify.hpp └── StubNodeDataModel.hpp ├── src ├── TestDataModelRegistry.cpp ├── TestDragging.cpp ├── TestFlowScene.cpp └── TestNodeGraphicsObject.cpp └── test_main.cpp /.appveyor.yml: -------------------------------------------------------------------------------- 1 | clone_depth: 5 2 | 3 | environment: 4 | matrix: 5 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 6 | GENERATOR : "Visual Studio 16 2019" 7 | ARCHITECTURE : "-A Win32" 8 | QTDIR: C:\Qt\5.15\msvc2019 9 | QT_MAJOR: 5 10 | PLATFORM: Win32 11 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 12 | GENERATOR : "Visual Studio 16 2019" 13 | ARCHITECTURE : "-A x64" 14 | QTDIR: C:\Qt\6.2.2\msvc2019_64 15 | QT_MAJOR: 6 16 | PLATFORM: x64 17 | 18 | configuration: 19 | - Release 20 | 21 | install: 22 | - set PATH=%QTDIR%\bin;%PATH% 23 | - set Qt%QT_MAJOR%_DIR=%QTDIR%\lib\cmake\Qt%QT_MAJOR% 24 | - set PATH=%PATH:C:\Program Files\Git\usr\bin=% # trick to remove sh.exe 25 | 26 | before_build: 27 | - mkdir build 28 | - cd build 29 | - mkdir bin 30 | - set OUTPUT_DIR=%cd%\bin 31 | - cmake "-G%GENERATOR%" %ARCHITECTURE% 32 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="%OUTPUT_DIR%" 33 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="%OUTPUT_DIR%" 34 | -DCMAKE_CXX_FLAGS_INIT="%CMAKE_CXX_FLAGS_INIT%" 35 | .. 36 | 37 | 38 | build_script: 39 | - cmake --build . 40 | 41 | test_script: 42 | - ps: $env:isX86 = $env:PLATFORM.Contains("x86") 43 | - IF %isX86% == False ctest --output-on-failure -C Debug 44 | 45 | 46 | after_build: 47 | - 7z a examples.zip %APPVEYOR_BUILD_FOLDER%/build/bin 48 | - cmd: cd 49 | - cmd: dir \S \P "examples.zip" 50 | 51 | artifacts: 52 | - path: build\examples.zip 53 | name: ex 54 | 55 | #deploy: 56 | #release: $(APPVEYOR_REPO_TAG_NAME) 57 | #provider: GitHub 58 | #artifact: /.*\.exe/ 59 | #auth_token: 60 | #secure: j0nBV9xVItdG3j6d0gHoyvrzi7TOhAy9/QIeyCbFeP8PTqq7DPr1oYwL5WIkPaXe 61 | #draft: false 62 | #prerelease: false 63 | #on: 64 | #appveyor_repo_tag: true 65 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | ratings: 5 | paths: [] 6 | exclude_paths: [] 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py 2 | *.pyc 3 | CMakeLists.txt.user 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | matrix: 4 | include: 5 | - os: osx 6 | osx_image: xcode11.3 7 | compiler: clang 8 | env: Qt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 9 | 10 | - os: linux 11 | dist: xenial 12 | sudo: false 13 | compiler: clang 14 | env: CXX=clang++-7 CC=clang-7 QT=512 15 | addons: 16 | apt: 17 | sources: 18 | - llvm-toolchain-xenial-7 19 | packages: 20 | - clang-7 21 | 22 | - os: linux 23 | dist: xenial 24 | sudo: false 25 | compiler: gcc 26 | env: 27 | - CXX=g++-7 CC=gcc-7 QT=512 28 | - CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer" 29 | - LDFLAGS=-fsanitize=address 30 | # Too many false positive leaks: 31 | - ASAN_OPTIONS=detect_leaks=0 32 | addons: 33 | apt: 34 | sources: 35 | - ubuntu-toolchain-r-test 36 | packages: 37 | - g++-7 38 | 39 | git: 40 | depth: 10 41 | 42 | before_install: 43 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi 44 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt; fi 45 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; fi 46 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install build-essential libgl1-mesa-dev ; fi 47 | - if [[ "$QT" == "512" ]]; then sudo add-apt-repository ppa:beineri/opt-qt-5.12.1-xenial -y; fi 48 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi 49 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -yqq install qt${QT}base; source /opt/qt${QT}/bin/qt${QT}-env.sh; fi 50 | 51 | script: 52 | - mkdir build 53 | - cd build 54 | - cmake -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_BUILD .. && make -j 55 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run --server-args="-screen 0 1024x768x24" ctest --output-on-failure; fi 56 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ctest --output-on-failure; fi 57 | 58 | notifications: 59 | email: false 60 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(NodeEditor CXX) 4 | 5 | set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) 6 | set(CMAKE_DISABLE_SOURCE_CHANGES ON) 7 | set(OpenGL_GL_PREFERENCE LEGACY) 8 | 9 | get_directory_property(_has_parent PARENT_DIRECTORY) 10 | if(_has_parent) 11 | set(is_root_project OFF) 12 | else() 13 | set(is_root_project ON) 14 | endif() 15 | 16 | set(NE_DEVELOPER_DEFAULTS "${is_root_project}" CACHE BOOL "Turns on default settings for development of NodeEditor") 17 | 18 | option(BUILD_TESTING "Build tests" "${NE_DEVELOPER_DEFAULTS}") 19 | option(BUILD_EXAMPLES "Build Examples" "${NE_DEVELOPER_DEFAULTS}") 20 | option(BUILD_SHARED_LIBS "Build as shared library" ON) 21 | option(BUILD_DEBUG_POSTFIX_D "Append d suffix to debug libraries" OFF) 22 | option(NE_FORCE_TEST_COLOR "Force colorized unit test output" OFF) 23 | 24 | enable_testing() 25 | 26 | if(NE_DEVELOPER_DEFAULTS) 27 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 28 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 29 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 30 | endif() 31 | 32 | if(BUILD_DEBUG_POSTFIX_D) 33 | set(CMAKE_DEBUG_POSTFIX d) 34 | endif() 35 | 36 | add_subdirectory(external) 37 | 38 | 39 | # Find the QtWidgets library 40 | find_package(Qt6 41 | COMPONENTS 42 | Core 43 | Widgets 44 | Gui 45 | OpenGL 46 | ) 47 | 48 | if (NOT Qt6_FOUND) 49 | find_package(Qt5 5.13 50 | COMPONENTS 51 | Core 52 | Widgets 53 | Gui 54 | OpenGL 55 | ) 56 | endif() 57 | 58 | if (NOT (Qt6_FOUND OR Qt5_FOUND)) 59 | message(FATAL_ERRROR "Qt libraries were not found.") 60 | endif() 61 | 62 | 63 | qt_add_resources(RESOURCES ./resources/resources.qrc) 64 | 65 | # Unfortunately, as we have a split include/src, AUTOMOC doesn't work. 66 | # We'll have to manually specify some files 67 | set(CMAKE_AUTOMOC ON) 68 | 69 | set(CPP_SOURCE_FILES 70 | src/Connection.cpp 71 | src/ConnectionBlurEffect.cpp 72 | src/ConnectionGeometry.cpp 73 | src/ConnectionGraphicsObject.cpp 74 | src/ConnectionPainter.cpp 75 | src/ConnectionState.cpp 76 | src/ConnectionStyle.cpp 77 | src/DataModelRegistry.cpp 78 | src/FlowScene.cpp 79 | src/FlowView.cpp 80 | src/FlowViewStyle.cpp 81 | src/Node.cpp 82 | src/NodeConnectionInteraction.cpp 83 | src/NodeDataModel.cpp 84 | src/NodeGeometry.cpp 85 | src/NodeGraphicsObject.cpp 86 | src/NodePainter.cpp 87 | src/NodeState.cpp 88 | src/NodeStyle.cpp 89 | src/Properties.cpp 90 | src/StyleCollection.cpp 91 | ) 92 | 93 | # If we want to give the option to build a static library, 94 | # set BUILD_SHARED_LIBS option to OFF 95 | add_library(nodes 96 | ${CPP_SOURCE_FILES} 97 | ${RESOURCES} 98 | ) 99 | add_library(NodeEditor::nodes ALIAS nodes) 100 | 101 | target_include_directories(nodes 102 | PUBLIC 103 | $ 104 | $ 105 | PRIVATE 106 | $ 107 | $ 108 | ) 109 | 110 | target_link_libraries(nodes 111 | PUBLIC 112 | Qt::Core 113 | Qt::Widgets 114 | Qt::Gui 115 | Qt::OpenGL 116 | ) 117 | 118 | target_compile_definitions(nodes 119 | PUBLIC 120 | NODE_EDITOR_SHARED 121 | PRIVATE 122 | NODE_EDITOR_EXPORTS 123 | #NODE_DEBUG_DRAWING 124 | QT_NO_KEYWORDS 125 | ) 126 | 127 | target_compile_options(nodes 128 | PRIVATE 129 | $<$:/W4 /wd4127 /EHsc> 130 | $<$:-Wall -Wextra> 131 | ) 132 | if(NOT "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") 133 | # Clang-Cl on MSVC identifies as "Clang" but behaves more like MSVC: 134 | target_compile_options(nodes 135 | PRIVATE 136 | $<$:-Wall -Wextra> 137 | ) 138 | endif() 139 | 140 | if(NE_DEVELOPER_DEFAULTS) 141 | target_compile_features(nodes PUBLIC cxx_std_14) 142 | set_target_properties(nodes PROPERTIES CXX_EXTENSIONS OFF) 143 | endif() 144 | 145 | set_target_properties(nodes 146 | PROPERTIES 147 | ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 148 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib 149 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 150 | ) 151 | 152 | ###### 153 | # Moc 154 | ## 155 | 156 | file(GLOB_RECURSE HEADERS_TO_MOC include/nodes/internal/*.hpp) 157 | 158 | qt_wrap_cpp(nodes_moc 159 | ${HEADERS_TO_MOC} 160 | TARGET nodes 161 | OPTIONS --no-notes # Don't display a note for the headers which don't produce a moc_*.cpp 162 | ) 163 | 164 | target_sources(nodes PRIVATE ${nodes_moc}) 165 | 166 | ########### 167 | # Examples 168 | ## 169 | 170 | if(BUILD_EXAMPLES) 171 | add_subdirectory(examples) 172 | endif() 173 | 174 | ################## 175 | # Automated Tests 176 | ## 177 | 178 | if(BUILD_TESTING) 179 | add_subdirectory(test) 180 | endif() 181 | 182 | ############### 183 | # Installation 184 | ## 185 | 186 | include(GNUInstallDirs) 187 | 188 | set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/NodeEditor) 189 | 190 | install(TARGETS nodes 191 | EXPORT NodeEditorTargets 192 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 193 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 194 | ) 195 | 196 | install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 197 | 198 | install(EXPORT NodeEditorTargets 199 | FILE NodeEditorTargets.cmake 200 | NAMESPACE NodeEditor:: 201 | DESTINATION ${INSTALL_CONFIGDIR} 202 | ) 203 | 204 | include(CMakePackageConfigHelpers) 205 | 206 | configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/NodeEditorConfig.cmake.in 207 | ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake 208 | INSTALL_DESTINATION ${INSTALL_CONFIGDIR} 209 | ) 210 | 211 | install(FILES 212 | ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake 213 | DESTINATION ${INSTALL_CONFIGDIR} 214 | ) 215 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Dmitry Pinaev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of copyright holder, nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Purpose 2 | 3 | **NodeEditor** is conceived as a general-purpose Qt-based library aimed at 4 | graph-controlled data processing. Nodes represent algorithms with certain inputs 5 | and outputs. Connections transfer data from the output (source) of the first node 6 | to the input (sink) of the second one. 7 | 8 | **NodeEditor** framework is a Visual [Dataflow 9 | Programming](https://en.wikipedia.org/wiki/Dataflow_programming) tool. A library 10 | client defines models and registers them in the data model registry. Further 11 | work is driven by events taking place in DataModels and Nodes. The model 12 | computing is triggered upon arriving of any new input data. The computed result 13 | is propagated to the output connections. Each new connection fetches available 14 | data and propagates is further. 15 | 16 | Each change in the source node is immediately propagated through all the 17 | connections updating the whole graph. 18 | 19 | ### Platforms 20 | 21 | * OSX (Apple Clang - LLVM 3.6), Linux (x64, gcc-7.0, clang-7): [![Build Status](https://travis-ci.org/paceholder/nodeeditor.svg?branch=master)](https://travis-ci.org/paceholder/nodeeditor) 22 | * Windows (msvc2019/Qt5/Win32, msvc2019/Qt6/x64): [![Build status](https://ci.appveyor.com/api/projects/status/wxp47wv3uyyiujjw/branch/master?svg=true)](https://ci.appveyor.com/project/paceholder/nodeeditor/branch/master) 23 | 24 | 25 | ### Dependencies 26 | 27 | * Qt >5.15 28 | * CMake 3.8 29 | * Catch2 30 | 31 | ### Current state 32 | 33 | * Model-based nodes 34 | * Automatic data propagation 35 | * Datatype-aware connections 36 | * Embedded Qt widgets 37 | * One-output to many-input connections 38 | * JSON-based interface styles 39 | * Saving scenes to JSON files 40 | 41 | ### Building 42 | 43 | #### Linux 44 | 45 | ~~~ 46 | git clone git@github.com:paceholder/nodeeditor.git 47 | cd nodeeditor 48 | mkdir build 49 | cd build 50 | cmake .. 51 | make -j && make install 52 | ~~~ 53 | 54 | #### Qt Creator 55 | 56 | 1. Open `CMakeLists.txt` as project. 57 | 2. If you don't have the `Catch2` library installed, go to `Build Settings`, disable the checkbox `BUILD_TESTING`. 58 | 3. `Build -> Run CMake` 59 | 4. `Build -> Build All` 60 | 5. Click the button `Run` 61 | 62 | ### >>> Version 3 Roadmap <<< 63 | 64 | 1. Headless mode. [done] 65 | You can create, populate, modify the derivative of AbsractGraphModel 66 | without adding it to the actual Flow Scene. 67 | The library is now designed to be general-purpose graph 68 | visualization and modification tool, without specialization on only 69 | data propagation. 70 | 2. Build data propagation on top of the graph code [done]. 71 | - Fix old unit-tests. [in progress]. 72 | - Fix save/restore. [in progress]. 73 | - Fix CI scriptst on travis and appveyor. [not started]. 74 | 3. Backward compatibility with Qt5 [not started/help needed]. 75 | 3. Write improved documentation based on Sphynx platform [done]. 76 | 4. Extend set of examples [partially done]. 77 | 5. Undo Redo [not started]. 78 | 6. Python wrappring using PySide [HELP NEEDED]. 79 | 7. Implement grouping nodes [not started]. 80 | 8. GUI: fix scrolling for scene view window scrolling [need to check Qt6] 81 | 82 | Any suggestions are welcome. 83 | 84 | ### Citing 85 | 86 | Dmitry Pinaev et al, Qt Node Editor, (2017), GitHub repository, https://github.com/paceholder/nodeeditor 87 | 88 | BibTeX 89 | 90 | @misc{Pinaev2017, 91 | author = {Dmitry Pinaev et al}, 92 | title = {Qt5 Node Editor}, 93 | year = {2017}, 94 | publisher = {GitHub}, 95 | journal = {GitHub repository}, 96 | howpublished = {\url{https://github.com/paceholder/nodeeditor}}, 97 | commit = {1d1757d09b03cea0e4921bc19659465fe6e65b9b} 98 | } 99 | 100 | 101 | ### Youtube video: 102 | 103 | [![Youtube demonstration](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/vid1.png)](https://www.youtube.com/watch?v=pxMXjSvlOFw) 104 | 105 | ### Now with styles 106 | 107 | 108 | [![Styles](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/style_example.png)](https://www.youtube.com/watch?v=i_pB-Y0hCYQ) 109 | 110 | 111 | ### Buy me a beer 112 | 113 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/DmitryPinaev) 114 | 115 | ### Showcase 116 | 117 | #### [Chigraph](https://github.com/chigraph/chigraph) 118 | 119 | Chigraph is a programming language for beginners that is unique in that it is an 120 | intuitive flow graph: 121 | 122 | ![chigraph screenshot](pictures/chigraph.png) 123 | 124 | It features easy bindings to C/C++, package management, and a cool interface. 125 | 126 | #### [Spkgen particle engine editor](https://github.com/fredakilla/spkgen) 127 | 128 | ![spkgen screenshot](pictures/spkgen.png) 129 | 130 | Spkgen is an editor for the SPARK particles engine using a node-based interface 131 | to create particles effects for games. 132 | -------------------------------------------------------------------------------- /cmake/NodeEditorConfig.cmake.in: -------------------------------------------------------------------------------- 1 | get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | # NOTE Had to use find_package because find_dependency does not support COMPONENTS or MODULE until 3.8.0 6 | 7 | find_package(Qt5 REQUIRED COMPONENTS 8 | Core 9 | Widgets 10 | Gui 11 | OpenGL) 12 | 13 | if(NOT TARGET NodeEditor::nodes) 14 | include("${NodeEditor_CMAKE_DIR}/NodeEditorTargets.cmake") 15 | endif() 16 | 17 | set(NodeEditor_LIBRARIES NodeEditor::nodes) 18 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(connection_colors) 2 | 3 | add_subdirectory(example2) 4 | 5 | add_subdirectory(calculator) 6 | 7 | add_subdirectory(images) 8 | 9 | add_subdirectory(styles) 10 | -------------------------------------------------------------------------------- /examples/calculator/AdditionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "MathOperationDataModel.hpp" 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class AdditionModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~AdditionModel() {} 20 | 21 | public: 22 | 23 | QString 24 | caption() const override 25 | { return QStringLiteral("Addition"); } 26 | 27 | QString 28 | name() const override 29 | { return QStringLiteral("Addition"); } 30 | 31 | private: 32 | 33 | void 34 | compute() override 35 | { 36 | PortIndex const outPortIndex = 0; 37 | 38 | auto n1 = _number1.lock(); 39 | auto n2 = _number2.lock(); 40 | 41 | if (n1 && n2) 42 | { 43 | modelValidationState = NodeValidationState::Valid; 44 | modelValidationError = QString(); 45 | _result = std::make_shared(n1->number() + 46 | n2->number()); 47 | } 48 | else 49 | { 50 | modelValidationState = NodeValidationState::Warning; 51 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 52 | _result.reset(); 53 | } 54 | 55 | Q_EMIT dataUpdated(outPortIndex); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /examples/calculator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(calculator ${CPPS}) 4 | 5 | target_link_libraries(calculator nodes) 6 | -------------------------------------------------------------------------------- /examples/calculator/Converters.cpp: -------------------------------------------------------------------------------- 1 | #include "Converters.hpp" 2 | 3 | #include 4 | 5 | #include "DecimalData.hpp" 6 | #include "IntegerData.hpp" 7 | 8 | 9 | std::shared_ptr 10 | DecimalToIntegerConverter:: 11 | operator()(std::shared_ptr data) 12 | { 13 | auto numberData = 14 | std::dynamic_pointer_cast(data); 15 | 16 | if (numberData) 17 | { 18 | _integer = std::make_shared(numberData->number()); 19 | } 20 | else 21 | { 22 | _integer.reset(); 23 | } 24 | 25 | return _integer; 26 | } 27 | 28 | 29 | std::shared_ptr 30 | IntegerToDecimalConverter:: 31 | operator()(std::shared_ptr data) 32 | { 33 | auto numberData = 34 | std::dynamic_pointer_cast(data); 35 | 36 | if (numberData) 37 | { 38 | _decimal = std::make_shared(numberData->number()); 39 | } 40 | else 41 | { 42 | _decimal.reset(); 43 | } 44 | 45 | return _decimal; 46 | } 47 | -------------------------------------------------------------------------------- /examples/calculator/Converters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DecimalData.hpp" 4 | #include "IntegerData.hpp" 5 | 6 | using QtNodes::PortType; 7 | using QtNodes::PortIndex; 8 | using QtNodes::NodeData; 9 | using QtNodes::NodeDataType; 10 | using QtNodes::NodeDataModel; 11 | 12 | class DecimalData; 13 | class IntegerData; 14 | 15 | 16 | class DecimalToIntegerConverter 17 | { 18 | 19 | public: 20 | 21 | std::shared_ptr 22 | operator()(std::shared_ptr data); 23 | 24 | private: 25 | 26 | std::shared_ptr _integer; 27 | }; 28 | 29 | 30 | class IntegerToDecimalConverter 31 | { 32 | 33 | public: 34 | 35 | std::shared_ptr 36 | operator()(std::shared_ptr data); 37 | 38 | private: 39 | 40 | std::shared_ptr _decimal; 41 | }; 42 | -------------------------------------------------------------------------------- /examples/calculator/DecimalData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using QtNodes::NodeDataType; 6 | using QtNodes::NodeData; 7 | 8 | /// The class can potentially incapsulate any user data which 9 | /// need to be transferred within the Node Editor graph 10 | class DecimalData : public NodeData 11 | { 12 | public: 13 | 14 | DecimalData() 15 | : _number(0.0) 16 | {} 17 | 18 | DecimalData(double const number) 19 | : _number(number) 20 | {} 21 | 22 | NodeDataType type() const override 23 | { 24 | return NodeDataType {"decimal", 25 | "Decimal"}; 26 | } 27 | 28 | double number() const 29 | { return _number; } 30 | 31 | QString numberAsText() const 32 | { return QString::number(_number, 'f'); } 33 | 34 | private: 35 | 36 | double _number; 37 | }; 38 | -------------------------------------------------------------------------------- /examples/calculator/DivisionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MathOperationDataModel.hpp" 9 | 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class DivisionModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~DivisionModel() {} 20 | 21 | public: 22 | QString 23 | caption() const override 24 | { return QStringLiteral("Division"); } 25 | 26 | bool 27 | portCaptionVisible(PortType portType, PortIndex portIndex) const override 28 | { 29 | Q_UNUSED(portType); Q_UNUSED(portIndex); 30 | return true; 31 | } 32 | 33 | QString 34 | portCaption(PortType portType, PortIndex portIndex) const override 35 | { 36 | switch (portType) 37 | { 38 | case PortType::In: 39 | if (portIndex == 0) 40 | return QStringLiteral("Dividend"); 41 | else if (portIndex == 1) 42 | return QStringLiteral("Divisor"); 43 | 44 | break; 45 | 46 | case PortType::Out: 47 | return QStringLiteral("Result"); 48 | 49 | default: 50 | break; 51 | } 52 | return QString(); 53 | } 54 | 55 | QString 56 | name() const override 57 | { return QStringLiteral("Division"); } 58 | 59 | private: 60 | 61 | void 62 | compute() override 63 | { 64 | PortIndex const outPortIndex = 0; 65 | 66 | auto n1 = _number1.lock(); 67 | auto n2 = _number2.lock(); 68 | 69 | if (n2 && (n2->number() == 0.0)) 70 | { 71 | modelValidationState = NodeValidationState::Error; 72 | modelValidationError = QStringLiteral("Division by zero error"); 73 | _result.reset(); 74 | } 75 | else if (n1 && n2) 76 | { 77 | modelValidationState = NodeValidationState::Valid; 78 | modelValidationError = QString(); 79 | _result = std::make_shared(n1->number() / 80 | n2->number()); 81 | } 82 | else 83 | { 84 | modelValidationState = NodeValidationState::Warning; 85 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 86 | _result.reset(); 87 | } 88 | 89 | Q_EMIT dataUpdated(outPortIndex); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /examples/calculator/IntegerData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using QtNodes::NodeDataType; 6 | using QtNodes::NodeData; 7 | 8 | /// The class can potentially incapsulate any user data which 9 | /// need to be transferred within the Node Editor graph 10 | class IntegerData : public NodeData 11 | { 12 | public: 13 | 14 | IntegerData() 15 | : _number(0.0) 16 | {} 17 | 18 | IntegerData(int const number) 19 | : _number(number) 20 | {} 21 | 22 | NodeDataType type() const override 23 | { 24 | return NodeDataType {"integer", 25 | "Integer"}; 26 | } 27 | 28 | int number() const 29 | { return _number; } 30 | 31 | QString numberAsText() const 32 | { return QString::number(_number); } 33 | 34 | private: 35 | 36 | int _number; 37 | }; 38 | -------------------------------------------------------------------------------- /examples/calculator/MathOperationDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "MathOperationDataModel.hpp" 2 | 3 | #include "DecimalData.hpp" 4 | 5 | unsigned int 6 | MathOperationDataModel:: 7 | nPorts(PortType portType) const 8 | { 9 | unsigned int result; 10 | 11 | if (portType == PortType::In) 12 | result = 2; 13 | else 14 | result = 1; 15 | 16 | return result; 17 | } 18 | 19 | 20 | NodeDataType 21 | MathOperationDataModel:: 22 | dataType(PortType, PortIndex) const 23 | { 24 | return DecimalData().type(); 25 | } 26 | 27 | 28 | std::shared_ptr 29 | MathOperationDataModel:: 30 | outData(PortIndex) 31 | { 32 | return std::static_pointer_cast(_result); 33 | } 34 | 35 | 36 | void 37 | MathOperationDataModel:: 38 | setInData(std::shared_ptr data, PortIndex portIndex) 39 | { 40 | auto numberData = 41 | std::dynamic_pointer_cast(data); 42 | 43 | if (portIndex == 0) 44 | { 45 | _number1 = numberData; 46 | } 47 | else 48 | { 49 | _number2 = numberData; 50 | } 51 | 52 | compute(); 53 | } 54 | 55 | 56 | NodeValidationState 57 | MathOperationDataModel:: 58 | validationState() const 59 | { 60 | return modelValidationState; 61 | } 62 | 63 | 64 | QString 65 | MathOperationDataModel:: 66 | validationMessage() const 67 | { 68 | return modelValidationError; 69 | } 70 | -------------------------------------------------------------------------------- /examples/calculator/MathOperationDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | class DecimalData; 12 | 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | using QtNodes::NodeData; 16 | using QtNodes::NodeDataType; 17 | using QtNodes::NodeDataModel; 18 | using QtNodes::NodeValidationState; 19 | 20 | /// The model dictates the number of inputs and outputs for the Node. 21 | /// In this example it has no logic. 22 | class MathOperationDataModel : public NodeDataModel 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | 28 | virtual 29 | ~MathOperationDataModel() {} 30 | 31 | public: 32 | 33 | unsigned int 34 | nPorts(PortType portType) const override; 35 | 36 | NodeDataType 37 | dataType(PortType portType, 38 | PortIndex portIndex) const override; 39 | 40 | std::shared_ptr 41 | outData(PortIndex port) override; 42 | 43 | void 44 | setInData(std::shared_ptr data, PortIndex portIndex) override; 45 | 46 | QWidget * 47 | embeddedWidget() override { return nullptr; } 48 | 49 | NodeValidationState 50 | validationState() const override; 51 | 52 | QString 53 | validationMessage() const override; 54 | 55 | protected: 56 | 57 | virtual void 58 | compute() = 0; 59 | 60 | protected: 61 | 62 | std::weak_ptr _number1; 63 | std::weak_ptr _number2; 64 | 65 | std::shared_ptr _result; 66 | 67 | NodeValidationState modelValidationState = NodeValidationState::Warning; 68 | QString modelValidationError = QString("Missing or incorrect inputs"); 69 | }; 70 | -------------------------------------------------------------------------------- /examples/calculator/ModuloModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ModuloModel.hpp" 2 | 3 | #include 4 | 5 | #include "IntegerData.hpp" 6 | 7 | QJsonObject 8 | ModuloModel:: 9 | save() const 10 | { 11 | QJsonObject modelJson; 12 | 13 | modelJson["name"] = name(); 14 | 15 | return modelJson; 16 | } 17 | 18 | 19 | unsigned int 20 | ModuloModel:: 21 | nPorts(PortType portType) const 22 | { 23 | unsigned int result = 1; 24 | 25 | switch (portType) 26 | { 27 | case PortType::In: 28 | result = 2; 29 | break; 30 | 31 | case PortType::Out: 32 | result = 1; 33 | 34 | default: 35 | break; 36 | } 37 | 38 | return result; 39 | } 40 | 41 | 42 | NodeDataType 43 | ModuloModel:: 44 | dataType(PortType, PortIndex) const 45 | { 46 | return IntegerData().type(); 47 | } 48 | 49 | 50 | std::shared_ptr 51 | ModuloModel:: 52 | outData(PortIndex) 53 | { 54 | return _result; 55 | } 56 | 57 | 58 | void 59 | ModuloModel:: 60 | setInData(std::shared_ptr data, PortIndex portIndex) 61 | { 62 | auto numberData = 63 | std::dynamic_pointer_cast(data); 64 | 65 | if (portIndex == 0) 66 | { 67 | _number1 = numberData; 68 | } 69 | else 70 | { 71 | _number2 = numberData; 72 | } 73 | 74 | { 75 | PortIndex const outPortIndex = 0; 76 | 77 | auto n1 = _number1.lock(); 78 | auto n2 = _number2.lock(); 79 | 80 | if (n2 && (n2->number() == 0.0)) 81 | { 82 | modelValidationState = NodeValidationState::Error; 83 | modelValidationError = QStringLiteral("Division by zero error"); 84 | _result.reset(); 85 | } 86 | else if (n1 && n2) 87 | { 88 | modelValidationState = NodeValidationState::Valid; 89 | modelValidationError = QString(); 90 | _result = std::make_shared(n1->number() % 91 | n2->number()); 92 | } 93 | else 94 | { 95 | modelValidationState = NodeValidationState::Warning; 96 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 97 | _result.reset(); 98 | } 99 | 100 | Q_EMIT dataUpdated(outPortIndex); 101 | } 102 | } 103 | 104 | 105 | NodeValidationState 106 | ModuloModel:: 107 | validationState() const 108 | { 109 | return modelValidationState; 110 | } 111 | 112 | 113 | QString 114 | ModuloModel:: 115 | validationMessage() const 116 | { 117 | return modelValidationError; 118 | } 119 | -------------------------------------------------------------------------------- /examples/calculator/ModuloModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::NodeData; 13 | using QtNodes::NodeDataType; 14 | using QtNodes::NodeDataModel; 15 | using QtNodes::NodeValidationState; 16 | 17 | class IntegerData; 18 | 19 | class ModuloModel 20 | : public NodeDataModel 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | ModuloModel() = default; 26 | 27 | virtual 28 | ~ModuloModel() = default; 29 | 30 | public: 31 | 32 | QString 33 | caption() const override 34 | { return QStringLiteral("Modulo"); } 35 | 36 | bool 37 | captionVisible() const override 38 | { return true; } 39 | 40 | bool 41 | portCaptionVisible(PortType, PortIndex ) const override 42 | { return true; } 43 | 44 | QString 45 | portCaption(PortType portType, PortIndex portIndex) const override 46 | { 47 | switch (portType) 48 | { 49 | case PortType::In: 50 | if (portIndex == 0) 51 | return QStringLiteral("Dividend"); 52 | else if (portIndex == 1) 53 | return QStringLiteral("Divisor"); 54 | 55 | break; 56 | 57 | case PortType::Out: 58 | return QStringLiteral("Result"); 59 | 60 | default: 61 | break; 62 | } 63 | return QString(); 64 | } 65 | 66 | QString 67 | name() const override 68 | { return QStringLiteral("Modulo"); } 69 | 70 | public: 71 | 72 | QJsonObject 73 | save() const override; 74 | 75 | public: 76 | 77 | unsigned int 78 | nPorts(PortType portType) const override; 79 | 80 | NodeDataType 81 | dataType(PortType portType, PortIndex portIndex) const override; 82 | 83 | std::shared_ptr 84 | outData(PortIndex port) override; 85 | 86 | void 87 | setInData(std::shared_ptr, int) override; 88 | 89 | QWidget * 90 | embeddedWidget() override { return nullptr; } 91 | 92 | NodeValidationState 93 | validationState() const override; 94 | 95 | QString 96 | validationMessage() const override; 97 | 98 | private: 99 | 100 | std::weak_ptr _number1; 101 | std::weak_ptr _number2; 102 | 103 | std::shared_ptr _result; 104 | 105 | NodeValidationState modelValidationState = NodeValidationState::Warning; 106 | QString modelValidationError = QStringLiteral("Missing or incorrect inputs"); 107 | }; 108 | -------------------------------------------------------------------------------- /examples/calculator/MultiplicationModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MathOperationDataModel.hpp" 9 | 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class MultiplicationModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~MultiplicationModel() {} 20 | 21 | public: 22 | 23 | QString 24 | caption() const override 25 | { return QStringLiteral("Multiplication"); } 26 | 27 | QString 28 | name() const override 29 | { return QStringLiteral("Multiplication"); } 30 | 31 | private: 32 | 33 | void 34 | compute() override 35 | { 36 | PortIndex const outPortIndex = 0; 37 | 38 | auto n1 = _number1.lock(); 39 | auto n2 = _number2.lock(); 40 | 41 | if (n1 && n2) 42 | { 43 | modelValidationState = NodeValidationState::Valid; 44 | modelValidationError = QString(); 45 | _result = std::make_shared(n1->number() * 46 | n2->number()); 47 | } 48 | else 49 | { 50 | modelValidationState = NodeValidationState::Warning; 51 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 52 | _result.reset(); 53 | } 54 | 55 | Q_EMIT dataUpdated(outPortIndex); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /examples/calculator/NumberDisplayDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NumberDisplayDataModel.hpp" 2 | 3 | #include "DecimalData.hpp" 4 | 5 | NumberDisplayDataModel:: 6 | NumberDisplayDataModel() 7 | : _label(new QLabel()) 8 | { 9 | _label->setMargin(3); 10 | } 11 | 12 | 13 | unsigned int 14 | NumberDisplayDataModel:: 15 | nPorts(PortType portType) const 16 | { 17 | unsigned int result = 1; 18 | 19 | switch (portType) 20 | { 21 | case PortType::In: 22 | result = 1; 23 | break; 24 | 25 | case PortType::Out: 26 | result = 0; 27 | 28 | default: 29 | break; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | 36 | NodeDataType 37 | NumberDisplayDataModel:: 38 | dataType(PortType, PortIndex) const 39 | { 40 | return DecimalData().type(); 41 | } 42 | 43 | 44 | std::shared_ptr 45 | NumberDisplayDataModel:: 46 | outData(PortIndex) 47 | { 48 | std::shared_ptr ptr; 49 | return ptr; 50 | } 51 | 52 | 53 | void 54 | NumberDisplayDataModel:: 55 | setInData(std::shared_ptr data, int) 56 | { 57 | auto numberData = std::dynamic_pointer_cast(data); 58 | 59 | if (numberData) 60 | { 61 | modelValidationState = NodeValidationState::Valid; 62 | modelValidationError = QString(); 63 | _label->setText(numberData->numberAsText()); 64 | } 65 | else 66 | { 67 | modelValidationState = NodeValidationState::Warning; 68 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 69 | _label->clear(); 70 | } 71 | 72 | _label->adjustSize(); 73 | } 74 | 75 | 76 | NodeValidationState 77 | NumberDisplayDataModel:: 78 | validationState() const 79 | { 80 | return modelValidationState; 81 | } 82 | 83 | 84 | QString 85 | NumberDisplayDataModel:: 86 | validationMessage() const 87 | { 88 | return modelValidationError; 89 | } 90 | -------------------------------------------------------------------------------- /examples/calculator/NumberDisplayDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::NodeData; 13 | using QtNodes::NodeDataType; 14 | using QtNodes::NodeDataModel; 15 | using QtNodes::NodeValidationState; 16 | 17 | /// The model dictates the number of inputs and outputs for the Node. 18 | /// In this example it has no logic. 19 | class NumberDisplayDataModel : public NodeDataModel 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | NumberDisplayDataModel(); 25 | 26 | virtual 27 | ~NumberDisplayDataModel() {} 28 | 29 | public: 30 | 31 | QString 32 | caption() const override 33 | { return QStringLiteral("Result"); } 34 | 35 | bool 36 | captionVisible() const override 37 | { return false; } 38 | 39 | QString 40 | name() const override 41 | { return QStringLiteral("Result"); } 42 | 43 | public: 44 | 45 | unsigned int 46 | nPorts(PortType portType) const override; 47 | 48 | NodeDataType 49 | dataType(PortType portType, 50 | PortIndex portIndex) const override; 51 | 52 | std::shared_ptr 53 | outData(PortIndex port) override; 54 | 55 | void 56 | setInData(std::shared_ptr data, int) override; 57 | 58 | QWidget * 59 | embeddedWidget() override { return _label; } 60 | 61 | NodeValidationState 62 | validationState() const override; 63 | 64 | QString 65 | validationMessage() const override; 66 | 67 | private: 68 | 69 | NodeValidationState modelValidationState = NodeValidationState::Warning; 70 | QString modelValidationError = QStringLiteral("Missing or incorrect inputs"); 71 | 72 | QLabel * _label; 73 | }; 74 | -------------------------------------------------------------------------------- /examples/calculator/NumberSourceDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NumberSourceDataModel.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "DecimalData.hpp" 7 | 8 | NumberSourceDataModel:: 9 | NumberSourceDataModel() 10 | : _lineEdit(new QLineEdit()) 11 | { 12 | _lineEdit->setValidator(new QDoubleValidator()); 13 | 14 | _lineEdit->setMaximumSize(_lineEdit->sizeHint()); 15 | 16 | connect(_lineEdit, &QLineEdit::textChanged, 17 | this, &NumberSourceDataModel::onTextEdited); 18 | 19 | _lineEdit->setText("0.0"); 20 | } 21 | 22 | 23 | QJsonObject 24 | NumberSourceDataModel:: 25 | save() const 26 | { 27 | QJsonObject modelJson = NodeDataModel::save(); 28 | 29 | if (_number) 30 | modelJson["number"] = QString::number(_number->number()); 31 | 32 | return modelJson; 33 | } 34 | 35 | 36 | void 37 | NumberSourceDataModel:: 38 | restore(QJsonObject const &p) 39 | { 40 | QJsonValue v = p["number"]; 41 | 42 | if (!v.isUndefined()) 43 | { 44 | QString strNum = v.toString(); 45 | 46 | bool ok; 47 | double d = strNum.toDouble(&ok); 48 | if (ok) 49 | { 50 | _number = std::make_shared(d); 51 | _lineEdit->setText(strNum); 52 | } 53 | } 54 | } 55 | 56 | 57 | unsigned int 58 | NumberSourceDataModel:: 59 | nPorts(PortType portType) const 60 | { 61 | unsigned int result = 1; 62 | 63 | switch (portType) 64 | { 65 | case PortType::In: 66 | result = 0; 67 | break; 68 | 69 | case PortType::Out: 70 | result = 1; 71 | 72 | default: 73 | break; 74 | } 75 | 76 | return result; 77 | } 78 | 79 | 80 | void 81 | NumberSourceDataModel:: 82 | onTextEdited(QString const &string) 83 | { 84 | Q_UNUSED(string); 85 | 86 | bool ok = false; 87 | 88 | double number = _lineEdit->text().toDouble(&ok); 89 | 90 | if (ok) 91 | { 92 | _number = std::make_shared(number); 93 | 94 | Q_EMIT dataUpdated(0); 95 | } 96 | else 97 | { 98 | Q_EMIT dataInvalidated(0); 99 | } 100 | } 101 | 102 | 103 | NodeDataType 104 | NumberSourceDataModel:: 105 | dataType(PortType, PortIndex) const 106 | { 107 | return DecimalData().type(); 108 | } 109 | 110 | 111 | std::shared_ptr 112 | NumberSourceDataModel:: 113 | outData(PortIndex) 114 | { 115 | return _number; 116 | } 117 | -------------------------------------------------------------------------------- /examples/calculator/NumberSourceDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | class DecimalData; 11 | 12 | using QtNodes::PortType; 13 | using QtNodes::PortIndex; 14 | using QtNodes::NodeData; 15 | using QtNodes::NodeDataType; 16 | using QtNodes::NodeDataModel; 17 | using QtNodes::NodeValidationState; 18 | 19 | /// The model dictates the number of inputs and outputs for the Node. 20 | /// In this example it has no logic. 21 | class NumberSourceDataModel 22 | : public NodeDataModel 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | NumberSourceDataModel(); 28 | 29 | virtual 30 | ~NumberSourceDataModel() {} 31 | 32 | public: 33 | 34 | QString 35 | caption() const override 36 | { return QStringLiteral("Number Source"); } 37 | 38 | bool 39 | captionVisible() const override 40 | { return false; } 41 | 42 | QString 43 | name() const override 44 | { return QStringLiteral("NumberSource"); } 45 | 46 | public: 47 | 48 | QJsonObject 49 | save() const override; 50 | 51 | void 52 | restore(QJsonObject const &p) override; 53 | 54 | public: 55 | 56 | unsigned int 57 | nPorts(PortType portType) const override; 58 | 59 | NodeDataType 60 | dataType(PortType portType, PortIndex portIndex) const override; 61 | 62 | std::shared_ptr 63 | outData(PortIndex port) override; 64 | 65 | void 66 | setInData(std::shared_ptr, int) override 67 | { } 68 | 69 | QWidget * 70 | embeddedWidget() override { return _lineEdit; } 71 | 72 | private Q_SLOTS: 73 | 74 | void 75 | onTextEdited(QString const &string); 76 | 77 | private: 78 | 79 | std::shared_ptr _number; 80 | 81 | QLineEdit * _lineEdit; 82 | }; 83 | -------------------------------------------------------------------------------- /examples/calculator/SubtractionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MathOperationDataModel.hpp" 9 | 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class SubtractionModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~SubtractionModel() {} 20 | 21 | public: 22 | 23 | QString 24 | caption() const override 25 | { return QStringLiteral("Subtraction"); } 26 | 27 | virtual bool 28 | portCaptionVisible(PortType portType, PortIndex portIndex) const override 29 | { 30 | Q_UNUSED(portType); Q_UNUSED(portIndex); 31 | return true; 32 | } 33 | 34 | virtual QString 35 | portCaption(PortType portType, PortIndex portIndex) const override 36 | { 37 | switch (portType) 38 | { 39 | case PortType::In: 40 | if (portIndex == 0) 41 | return QStringLiteral("Minuend"); 42 | else if (portIndex == 1) 43 | return QStringLiteral("Subtrahend"); 44 | 45 | break; 46 | 47 | case PortType::Out: 48 | return QStringLiteral("Result"); 49 | 50 | default: 51 | break; 52 | } 53 | return QString(); 54 | } 55 | 56 | QString 57 | name() const override 58 | { return QStringLiteral("Subtraction"); } 59 | 60 | private: 61 | 62 | void 63 | compute() override 64 | { 65 | PortIndex const outPortIndex = 0; 66 | 67 | auto n1 = _number1.lock(); 68 | auto n2 = _number2.lock(); 69 | 70 | if (n1 && n2) 71 | { 72 | modelValidationState = NodeValidationState::Valid; 73 | modelValidationError = QString(); 74 | _result = std::make_shared(n1->number() - 75 | n2->number()); 76 | } 77 | else 78 | { 79 | modelValidationState = NodeValidationState::Warning; 80 | modelValidationError = QStringLiteral("Missing or incorrect inputs"); 81 | _result.reset(); 82 | } 83 | 84 | Q_EMIT dataUpdated(outPortIndex); 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /examples/calculator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "NumberSourceDataModel.hpp" 14 | #include "NumberDisplayDataModel.hpp" 15 | #include "AdditionModel.hpp" 16 | #include "SubtractionModel.hpp" 17 | #include "MultiplicationModel.hpp" 18 | #include "DivisionModel.hpp" 19 | #include "ModuloModel.hpp" 20 | #include "Converters.hpp" 21 | 22 | 23 | using QtNodes::DataModelRegistry; 24 | using QtNodes::FlowScene; 25 | using QtNodes::FlowView; 26 | using QtNodes::ConnectionStyle; 27 | using QtNodes::TypeConverter; 28 | using QtNodes::TypeConverterId; 29 | 30 | static std::shared_ptr 31 | registerDataModels() 32 | { 33 | auto ret = std::make_shared(); 34 | ret->registerModel("Sources"); 35 | 36 | ret->registerModel("Displays"); 37 | 38 | ret->registerModel("Operators"); 39 | 40 | ret->registerModel("Operators"); 41 | 42 | ret->registerModel("Operators"); 43 | 44 | ret->registerModel("Operators"); 45 | 46 | ret->registerModel("Operators"); 47 | 48 | ret->registerTypeConverter(std::make_pair(DecimalData().type(), 49 | IntegerData().type()), 50 | TypeConverter{DecimalToIntegerConverter()}); 51 | 52 | 53 | 54 | ret->registerTypeConverter(std::make_pair(IntegerData().type(), 55 | DecimalData().type()), 56 | TypeConverter{IntegerToDecimalConverter()}); 57 | 58 | return ret; 59 | } 60 | 61 | 62 | static 63 | void 64 | setStyle() 65 | { 66 | ConnectionStyle::setConnectionStyle( 67 | R"( 68 | { 69 | "ConnectionStyle": { 70 | "ConstructionColor": "gray", 71 | "NormalColor": "black", 72 | "SelectedColor": "gray", 73 | "SelectedHaloColor": "deepskyblue", 74 | "HoveredColor": "deepskyblue", 75 | 76 | "LineWidth": 3.0, 77 | "ConstructionLineWidth": 2.0, 78 | "PointDiameter": 10.0, 79 | 80 | "UseDataDefinedColors": true 81 | } 82 | } 83 | )"); 84 | } 85 | 86 | 87 | int 88 | main(int argc, char *argv[]) 89 | { 90 | QApplication app(argc, argv); 91 | 92 | setStyle(); 93 | 94 | QWidget mainWidget; 95 | 96 | auto menuBar = new QMenuBar(); 97 | auto saveAction = menuBar->addAction("Save.."); 98 | auto loadAction = menuBar->addAction("Load.."); 99 | 100 | QVBoxLayout *l = new QVBoxLayout(&mainWidget); 101 | 102 | l->addWidget(menuBar); 103 | auto scene = new FlowScene(registerDataModels(), &mainWidget); 104 | l->addWidget(new FlowView(scene)); 105 | l->setContentsMargins(0, 0, 0, 0); 106 | l->setSpacing(0); 107 | 108 | QObject::connect(saveAction, &QAction::triggered, 109 | scene, &FlowScene::save); 110 | 111 | QObject::connect(loadAction, &QAction::triggered, 112 | scene, &FlowScene::load); 113 | 114 | mainWidget.setWindowTitle("Dataflow tools: simplest calculator"); 115 | mainWidget.resize(800, 600); 116 | mainWidget.showNormal(); 117 | 118 | return app.exec(); 119 | } 120 | -------------------------------------------------------------------------------- /examples/connection_colors/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(connection_colors ${CPPS}) 4 | 5 | target_link_libraries(connection_colors nodes) 6 | -------------------------------------------------------------------------------- /examples/connection_colors/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "models.hpp" 10 | 11 | using QtNodes::DataModelRegistry; 12 | using QtNodes::FlowScene; 13 | using QtNodes::FlowView; 14 | using QtNodes::ConnectionStyle; 15 | 16 | static std::shared_ptr 17 | registerDataModels() 18 | { 19 | auto ret = std::make_shared(); 20 | 21 | ret->registerModel(); 22 | 23 | /* 24 | We could have more models registered. 25 | All of them become items in the context meny of the scene. 26 | 27 | ret->registerModel(); 28 | ret->registerModel(); 29 | 30 | */ 31 | 32 | return ret; 33 | } 34 | 35 | 36 | static 37 | void 38 | setStyle() 39 | { 40 | ConnectionStyle::setConnectionStyle( 41 | R"( 42 | { 43 | "ConnectionStyle": { 44 | "UseDataDefinedColors": true 45 | } 46 | } 47 | )"); 48 | } 49 | 50 | 51 | //------------------------------------------------------------------------------ 52 | 53 | int 54 | main(int argc, char* argv[]) 55 | { 56 | QApplication app(argc, argv); 57 | 58 | setStyle(); 59 | 60 | FlowScene scene(registerDataModels()); 61 | 62 | FlowView view(&scene); 63 | 64 | view.setWindowTitle("Node-based flow editor"); 65 | view.resize(800, 600); 66 | view.show(); 67 | 68 | return app.exec(); 69 | } 70 | -------------------------------------------------------------------------------- /examples/connection_colors/models.cpp: -------------------------------------------------------------------------------- 1 | #include "models.hpp" 2 | 3 | // For some reason CMake could not generate moc-files correctly 4 | // without having a cpp for an QObject from hpp. 5 | -------------------------------------------------------------------------------- /examples/connection_colors/models.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::NodeData; 11 | using QtNodes::NodeDataType; 12 | using QtNodes::NodeDataModel; 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | 16 | /// The class can potentially incapsulate any user data which 17 | /// need to be transferred within the Node Editor graph 18 | class MyNodeData : public NodeData 19 | { 20 | public: 21 | 22 | NodeDataType 23 | type() const override 24 | { 25 | return NodeDataType {"MyNodeData", 26 | "My Node Data"}; 27 | } 28 | }; 29 | 30 | class SimpleNodeData : public NodeData 31 | { 32 | public: 33 | 34 | NodeDataType 35 | type() const override 36 | { 37 | return NodeDataType {"SimpleData", 38 | "Simple Data"}; 39 | } 40 | }; 41 | 42 | //------------------------------------------------------------------------------ 43 | 44 | /// The model dictates the number of inputs and outputs for the Node. 45 | /// In this example it has no logic. 46 | class NaiveDataModel : public NodeDataModel 47 | { 48 | Q_OBJECT 49 | 50 | public: 51 | 52 | virtual 53 | ~NaiveDataModel() {} 54 | 55 | public: 56 | 57 | QString 58 | caption() const override 59 | { 60 | return QString("Naive Data Model"); 61 | } 62 | 63 | QString 64 | name() const override 65 | { return QString("NaiveDataModel"); } 66 | 67 | public: 68 | 69 | unsigned int 70 | nPorts(PortType portType) const override 71 | { 72 | unsigned int result = 1; 73 | 74 | switch (portType) 75 | { 76 | case PortType::In: 77 | result = 2; 78 | break; 79 | 80 | case PortType::Out: 81 | result = 2; 82 | break; 83 | case PortType::None: 84 | break; 85 | } 86 | 87 | return result; 88 | } 89 | 90 | NodeDataType 91 | dataType(PortType portType, 92 | PortIndex portIndex) const override 93 | { 94 | switch (portType) 95 | { 96 | case PortType::In: 97 | switch (portIndex) 98 | { 99 | case 0: 100 | return MyNodeData().type(); 101 | case 1: 102 | return SimpleNodeData().type(); 103 | } 104 | break; 105 | 106 | case PortType::Out: 107 | switch (portIndex) 108 | { 109 | case 0: 110 | return MyNodeData().type(); 111 | case 1: 112 | return SimpleNodeData().type(); 113 | } 114 | break; 115 | 116 | case PortType::None: 117 | break; 118 | } 119 | // FIXME: control may reach end of non-void function [-Wreturn-type] 120 | return NodeDataType(); 121 | } 122 | 123 | std::shared_ptr 124 | outData(PortIndex port) override 125 | { 126 | if (port < 1) 127 | return std::make_shared(); 128 | 129 | return std::make_shared(); 130 | } 131 | 132 | void 133 | setInData(std::shared_ptr, int) override 134 | { 135 | // 136 | } 137 | 138 | QWidget * 139 | embeddedWidget() override { return nullptr; } 140 | }; 141 | -------------------------------------------------------------------------------- /examples/example2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(example2 ${CPPS}) 4 | 5 | target_link_libraries(example2 nodes) 6 | -------------------------------------------------------------------------------- /examples/example2/TextData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using QtNodes::NodeData; 6 | using QtNodes::NodeDataType; 7 | 8 | /// The class can potentially incapsulate any user data which 9 | /// need to be transferred within the Node Editor graph 10 | class TextData : public NodeData 11 | { 12 | public: 13 | 14 | TextData() {} 15 | 16 | TextData(QString const &text) 17 | : _text(text) 18 | {} 19 | 20 | NodeDataType type() const override 21 | { return NodeDataType {"text", "Text"}; } 22 | 23 | QString text() const { return _text; } 24 | 25 | private: 26 | 27 | QString _text; 28 | }; 29 | -------------------------------------------------------------------------------- /examples/example2/TextDisplayDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TextDisplayDataModel.hpp" 2 | 3 | TextDisplayDataModel:: 4 | TextDisplayDataModel() 5 | : _label(new QLabel("Resulting Text")) 6 | { 7 | _label->setMargin(3); 8 | } 9 | 10 | 11 | unsigned int 12 | TextDisplayDataModel:: 13 | nPorts(PortType portType) const 14 | { 15 | unsigned int result = 1; 16 | 17 | switch (portType) 18 | { 19 | case PortType::In: 20 | result = 1; 21 | break; 22 | 23 | case PortType::Out: 24 | result = 0; 25 | 26 | default: 27 | break; 28 | } 29 | 30 | return result; 31 | } 32 | 33 | 34 | NodeDataType 35 | TextDisplayDataModel:: 36 | dataType(PortType, PortIndex) const 37 | { 38 | return TextData().type(); 39 | } 40 | 41 | 42 | std::shared_ptr 43 | TextDisplayDataModel:: 44 | outData(PortIndex) 45 | { 46 | std::shared_ptr ptr; 47 | return ptr; 48 | } 49 | -------------------------------------------------------------------------------- /examples/example2/TextDisplayDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "TextData.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | using QtNodes::PortType; 13 | using QtNodes::PortIndex; 14 | using QtNodes::NodeData; 15 | using QtNodes::NodeDataModel; 16 | 17 | /// The model dictates the number of inputs and outputs for the Node. 18 | /// In this example it has no logic. 19 | class TextDisplayDataModel : public NodeDataModel 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | TextDisplayDataModel(); 25 | 26 | virtual 27 | ~TextDisplayDataModel() {} 28 | 29 | public: 30 | 31 | QString 32 | caption() const override 33 | { return QString("Text Display"); } 34 | 35 | bool 36 | captionVisible() const override { return false; } 37 | 38 | static QString 39 | Name() 40 | { return QString("TextDisplayDataModel"); } 41 | 42 | QString 43 | name() const override 44 | { return TextDisplayDataModel::Name(); } 45 | 46 | public: 47 | 48 | unsigned int 49 | nPorts(PortType portType) const override; 50 | 51 | NodeDataType 52 | dataType(PortType portType, PortIndex portIndex) const override; 53 | 54 | std::shared_ptr 55 | outData(PortIndex port) override; 56 | 57 | void 58 | setInData(std::shared_ptr data, int) override 59 | { 60 | auto textData = std::dynamic_pointer_cast(data); 61 | 62 | if (textData) 63 | { 64 | _label->setText(textData->text()); 65 | } 66 | else 67 | { 68 | _label->clear(); 69 | } 70 | 71 | _label->adjustSize(); 72 | } 73 | 74 | QWidget * 75 | embeddedWidget() override { return _label; } 76 | 77 | private: 78 | 79 | QLabel * _label; 80 | }; 81 | -------------------------------------------------------------------------------- /examples/example2/TextSourceDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TextSourceDataModel.hpp" 2 | 3 | TextSourceDataModel:: 4 | TextSourceDataModel() 5 | : _lineEdit(new QLineEdit("Default Text")) 6 | { 7 | connect(_lineEdit, &QLineEdit::textEdited, 8 | this, &TextSourceDataModel::onTextEdited); 9 | } 10 | 11 | 12 | unsigned int 13 | TextSourceDataModel:: 14 | nPorts(PortType portType) const 15 | { 16 | unsigned int result = 1; 17 | 18 | switch (portType) 19 | { 20 | case PortType::In: 21 | result = 0; 22 | break; 23 | 24 | case PortType::Out: 25 | result = 1; 26 | 27 | default: 28 | break; 29 | } 30 | 31 | return result; 32 | } 33 | 34 | 35 | void 36 | TextSourceDataModel:: 37 | onTextEdited(QString const &string) 38 | { 39 | Q_UNUSED(string); 40 | 41 | Q_EMIT dataUpdated(0); 42 | } 43 | 44 | 45 | NodeDataType 46 | TextSourceDataModel:: 47 | dataType(PortType, PortIndex) const 48 | { 49 | return TextData().type(); 50 | } 51 | 52 | 53 | std::shared_ptr 54 | TextSourceDataModel:: 55 | outData(PortIndex) 56 | { 57 | return std::make_shared(_lineEdit->text()); 58 | } 59 | -------------------------------------------------------------------------------- /examples/example2/TextSourceDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "TextData.hpp" 7 | 8 | #include 9 | 10 | #include 11 | 12 | using QtNodes::PortType; 13 | using QtNodes::PortIndex; 14 | using QtNodes::NodeData; 15 | using QtNodes::NodeDataModel; 16 | 17 | /// The model dictates the number of inputs and outputs for the Node. 18 | /// In this example it has no logic. 19 | class TextSourceDataModel : public NodeDataModel 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | TextSourceDataModel(); 25 | 26 | virtual 27 | ~TextSourceDataModel() {} 28 | 29 | public: 30 | 31 | QString 32 | caption() const override 33 | { return QString("Text Source"); } 34 | 35 | bool 36 | captionVisible() const override { return false; } 37 | 38 | static QString 39 | Name() 40 | { return QString("TextSourceDataModel"); } 41 | 42 | QString 43 | name() const override 44 | { return TextSourceDataModel::Name(); } 45 | 46 | public: 47 | 48 | unsigned int 49 | nPorts(PortType portType) const override; 50 | 51 | NodeDataType 52 | dataType(PortType portType, PortIndex portIndex) const override; 53 | 54 | std::shared_ptr 55 | outData(PortIndex port) override; 56 | 57 | void 58 | setInData(std::shared_ptr, int) override 59 | { } 60 | 61 | QWidget * 62 | embeddedWidget() override { return _lineEdit; } 63 | 64 | private Q_SLOTS: 65 | 66 | void 67 | onTextEdited(QString const &string); 68 | 69 | private: 70 | 71 | QLineEdit * _lineEdit; 72 | }; 73 | -------------------------------------------------------------------------------- /examples/example2/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "TextSourceDataModel.hpp" 10 | #include "TextDisplayDataModel.hpp" 11 | 12 | using QtNodes::DataModelRegistry; 13 | using QtNodes::FlowView; 14 | using QtNodes::FlowScene; 15 | 16 | static std::shared_ptr 17 | registerDataModels() 18 | { 19 | auto ret = std::make_shared(); 20 | 21 | ret->registerModel(); 22 | 23 | ret->registerModel(); 24 | 25 | return ret; 26 | } 27 | 28 | 29 | int 30 | main(int argc, char *argv[]) 31 | { 32 | QApplication app(argc, argv); 33 | 34 | FlowScene scene(registerDataModels()); 35 | 36 | FlowView view(&scene); 37 | 38 | view.setWindowTitle("Node-based flow editor"); 39 | view.resize(800, 600); 40 | view.show(); 41 | 42 | return app.exec(); 43 | } 44 | -------------------------------------------------------------------------------- /examples/images/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(images ${CPPS}) 4 | 5 | target_link_libraries(images nodes) 6 | -------------------------------------------------------------------------------- /examples/images/ImageLoaderModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageLoaderModel.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | ImageLoaderModel:: 9 | ImageLoaderModel() 10 | : _label(new QLabel("Double click to load image")) 11 | { 12 | _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); 13 | 14 | QFont f = _label->font(); 15 | f.setBold(true); 16 | f.setItalic(true); 17 | 18 | _label->setFont(f); 19 | 20 | _label->setFixedSize(200, 200); 21 | 22 | _label->installEventFilter(this); 23 | } 24 | 25 | 26 | unsigned int 27 | ImageLoaderModel:: 28 | nPorts(PortType portType) const 29 | { 30 | unsigned int result = 1; 31 | 32 | switch (portType) 33 | { 34 | case PortType::In: 35 | result = 0; 36 | break; 37 | 38 | case PortType::Out: 39 | result = 1; 40 | 41 | default: 42 | break; 43 | } 44 | 45 | return result; 46 | } 47 | 48 | 49 | bool 50 | ImageLoaderModel:: 51 | eventFilter(QObject *object, QEvent *event) 52 | { 53 | if (object == _label) 54 | { 55 | int w = _label->width(); 56 | int h = _label->height(); 57 | 58 | if (event->type() == QEvent::MouseButtonPress) 59 | { 60 | 61 | QString fileName = 62 | QFileDialog::getOpenFileName(nullptr, 63 | tr("Open Image"), 64 | QDir::homePath(), 65 | tr("Image Files (*.png *.jpg *.bmp)")); 66 | 67 | _pixmap = QPixmap(fileName); 68 | 69 | _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); 70 | 71 | Q_EMIT dataUpdated(0); 72 | 73 | return true; 74 | } 75 | else if (event->type() == QEvent::Resize) 76 | { 77 | if (!_pixmap.isNull()) 78 | _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); 79 | } 80 | } 81 | 82 | return false; 83 | } 84 | 85 | 86 | NodeDataType 87 | ImageLoaderModel:: 88 | dataType(PortType, PortIndex) const 89 | { 90 | return PixmapData().type(); 91 | } 92 | 93 | 94 | std::shared_ptr 95 | ImageLoaderModel:: 96 | outData(PortIndex) 97 | { 98 | return std::make_shared(_pixmap); 99 | } 100 | -------------------------------------------------------------------------------- /examples/images/ImageLoaderModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "PixmapData.hpp" 12 | 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | using QtNodes::NodeData; 16 | using QtNodes::NodeDataType; 17 | using QtNodes::NodeDataModel; 18 | using QtNodes::NodeValidationState; 19 | 20 | /// The model dictates the number of inputs and outputs for the Node. 21 | /// In this example it has no logic. 22 | class ImageLoaderModel : public NodeDataModel 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | ImageLoaderModel(); 28 | 29 | virtual 30 | ~ImageLoaderModel() {} 31 | 32 | public: 33 | 34 | QString 35 | caption() const override 36 | { return QString("Image Source"); } 37 | 38 | QString 39 | name() const override { return QString("ImageLoaderModel"); } 40 | 41 | public: 42 | 43 | virtual QString 44 | modelName() const 45 | { return QString("Source Image"); } 46 | 47 | unsigned int 48 | nPorts(PortType portType) const override; 49 | 50 | NodeDataType 51 | dataType(PortType portType, PortIndex portIndex) const override; 52 | 53 | std::shared_ptr 54 | outData(PortIndex port) override; 55 | 56 | void 57 | setInData(std::shared_ptr, int) override 58 | { } 59 | 60 | QWidget * 61 | embeddedWidget() override { return _label; } 62 | 63 | bool 64 | resizable() const override { return true; } 65 | 66 | protected: 67 | 68 | bool 69 | eventFilter(QObject *object, QEvent *event) override; 70 | 71 | private: 72 | 73 | QLabel * _label; 74 | 75 | QPixmap _pixmap; 76 | }; 77 | -------------------------------------------------------------------------------- /examples/images/ImageShowModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageShowModel.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "PixmapData.hpp" 11 | 12 | ImageShowModel:: 13 | ImageShowModel() 14 | : _label(new QLabel("Image will appear here")) 15 | { 16 | _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); 17 | 18 | QFont f = _label->font(); 19 | f.setBold(true); 20 | f.setItalic(true); 21 | 22 | _label->setFont(f); 23 | 24 | _label->setFixedSize(200, 200); 25 | 26 | _label->installEventFilter(this); 27 | } 28 | 29 | unsigned int 30 | ImageShowModel:: 31 | nPorts(PortType portType) const 32 | { 33 | unsigned int result = 1; 34 | 35 | switch (portType) 36 | { 37 | case PortType::In: 38 | result = 1; 39 | break; 40 | 41 | case PortType::Out: 42 | result = 1; 43 | 44 | default: 45 | break; 46 | } 47 | 48 | return result; 49 | } 50 | 51 | 52 | bool 53 | ImageShowModel:: 54 | eventFilter(QObject *object, QEvent *event) 55 | { 56 | if (object == _label) 57 | { 58 | int w = _label->width(); 59 | int h = _label->height(); 60 | 61 | if (event->type() == QEvent::Resize) 62 | { 63 | auto d = std::dynamic_pointer_cast(_nodeData); 64 | if (d) 65 | { 66 | _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); 67 | } 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | 75 | NodeDataType 76 | ImageShowModel:: 77 | dataType(PortType, PortIndex) const 78 | { 79 | return PixmapData().type(); 80 | } 81 | 82 | 83 | std::shared_ptr 84 | ImageShowModel:: 85 | outData(PortIndex) 86 | { 87 | return _nodeData; 88 | } 89 | 90 | 91 | void 92 | ImageShowModel:: 93 | setInData(std::shared_ptr nodeData, PortIndex) 94 | { 95 | _nodeData = nodeData; 96 | 97 | if (_nodeData) 98 | { 99 | auto d = std::dynamic_pointer_cast(_nodeData); 100 | 101 | int w = _label->width(); 102 | int h = _label->height(); 103 | 104 | _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); 105 | } 106 | else 107 | { 108 | _label->setPixmap(QPixmap()); 109 | } 110 | 111 | Q_EMIT dataUpdated(0); 112 | } 113 | -------------------------------------------------------------------------------- /examples/images/ImageShowModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using QtNodes::PortType; 12 | using QtNodes::PortIndex; 13 | using QtNodes::NodeData; 14 | using QtNodes::NodeDataType; 15 | using QtNodes::NodeDataModel; 16 | using QtNodes::NodeValidationState; 17 | 18 | /// The model dictates the number of inputs and outputs for the Node. 19 | /// In this example it has no logic. 20 | class ImageShowModel : public NodeDataModel 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | ImageShowModel(); 26 | 27 | virtual 28 | ~ImageShowModel() {} 29 | 30 | public: 31 | 32 | QString 33 | caption() const override 34 | { return QString("Image Display"); } 35 | 36 | QString 37 | name() const override 38 | { return QString("ImageShowModel"); } 39 | 40 | public: 41 | 42 | virtual QString 43 | modelName() const 44 | { return QString("Resulting Image"); } 45 | 46 | unsigned int 47 | nPorts(PortType portType) const override; 48 | 49 | NodeDataType 50 | dataType(PortType portType, PortIndex portIndex) const override; 51 | 52 | std::shared_ptr 53 | outData(PortIndex port) override; 54 | 55 | void 56 | setInData(std::shared_ptr nodeData, PortIndex port) override; 57 | 58 | QWidget * 59 | embeddedWidget() override { return _label; } 60 | 61 | bool 62 | resizable() const override { return true; } 63 | 64 | protected: 65 | 66 | bool 67 | eventFilter(QObject *object, QEvent *event) override; 68 | 69 | private: 70 | 71 | QLabel * _label; 72 | 73 | std::shared_ptr _nodeData; 74 | }; 75 | -------------------------------------------------------------------------------- /examples/images/PixmapData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | using QtNodes::NodeData; 8 | using QtNodes::NodeDataType; 9 | 10 | /// The class can potentially incapsulate any user data which 11 | /// need to be transferred within the Node Editor graph 12 | class PixmapData : public NodeData 13 | { 14 | public: 15 | 16 | PixmapData() {} 17 | 18 | PixmapData(QPixmap const &pixmap) 19 | : _pixmap(pixmap) 20 | {} 21 | 22 | NodeDataType 23 | type() const override 24 | { 25 | // id name 26 | return {"pixmap", "P"}; 27 | } 28 | 29 | QPixmap 30 | pixmap() const { return _pixmap; } 31 | 32 | private: 33 | 34 | QPixmap _pixmap; 35 | }; 36 | -------------------------------------------------------------------------------- /examples/images/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "ImageShowModel.hpp" 8 | #include "ImageLoaderModel.hpp" 9 | 10 | using QtNodes::DataModelRegistry; 11 | using QtNodes::FlowScene; 12 | using QtNodes::FlowView; 13 | 14 | static std::shared_ptr 15 | registerDataModels() 16 | { 17 | auto ret = std::make_shared(); 18 | ret->registerModel(); 19 | 20 | ret->registerModel(); 21 | 22 | return ret; 23 | } 24 | 25 | 26 | int 27 | main(int argc, char *argv[]) 28 | { 29 | QApplication app(argc, argv); 30 | 31 | FlowScene scene(registerDataModels()); 32 | 33 | FlowView view(&scene); 34 | 35 | view.setWindowTitle("Node-based flow editor"); 36 | view.resize(800, 600); 37 | view.show(); 38 | 39 | return app.exec(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/styles/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(styles ${CPPS}) 4 | 5 | target_link_libraries(styles nodes) 6 | -------------------------------------------------------------------------------- /examples/styles/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "models.hpp" 12 | 13 | using QtNodes::DataModelRegistry; 14 | using QtNodes::FlowScene; 15 | using QtNodes::FlowView; 16 | using QtNodes::FlowViewStyle; 17 | using QtNodes::NodeStyle; 18 | using QtNodes::ConnectionStyle; 19 | 20 | static std::shared_ptr 21 | registerDataModels() 22 | { 23 | auto ret = std::make_shared(); 24 | 25 | ret->registerModel(); 26 | 27 | return ret; 28 | } 29 | 30 | 31 | static 32 | void 33 | setStyle() 34 | { 35 | FlowViewStyle::setStyle( 36 | R"( 37 | { 38 | "FlowViewStyle": { 39 | "BackgroundColor": [255, 255, 240], 40 | "FineGridColor": [245, 245, 230], 41 | "CoarseGridColor": [235, 235, 220] 42 | } 43 | } 44 | )"); 45 | 46 | NodeStyle::setNodeStyle( 47 | R"( 48 | { 49 | "NodeStyle": { 50 | "NormalBoundaryColor": "darkgray", 51 | "SelectedBoundaryColor": "deepskyblue", 52 | "GradientColor0": "mintcream", 53 | "GradientColor1": "mintcream", 54 | "GradientColor2": "mintcream", 55 | "GradientColor3": "mintcream", 56 | "ShadowColor": [200, 200, 200], 57 | "FontColor": [10, 10, 10], 58 | "FontColorFaded": [100, 100, 100], 59 | "ConnectionPointColor": "white", 60 | "PenWidth": 2.0, 61 | "HoveredPenWidth": 2.5, 62 | "ConnectionPointDiameter": 10.0, 63 | "Opacity": 1.0 64 | } 65 | } 66 | )"); 67 | 68 | ConnectionStyle::setConnectionStyle( 69 | R"( 70 | { 71 | "ConnectionStyle": { 72 | "ConstructionColor": "gray", 73 | "NormalColor": "black", 74 | "SelectedColor": "gray", 75 | "SelectedHaloColor": "deepskyblue", 76 | "HoveredColor": "deepskyblue", 77 | 78 | "LineWidth": 3.0, 79 | "ConstructionLineWidth": 2.0, 80 | "PointDiameter": 10.0, 81 | 82 | "UseDataDefinedColors": false 83 | } 84 | } 85 | )"); 86 | } 87 | 88 | 89 | //------------------------------------------------------------------------------ 90 | 91 | int 92 | main(int argc, char* argv[]) 93 | { 94 | QApplication app(argc, argv); 95 | 96 | setStyle(); 97 | 98 | FlowScene scene(registerDataModels()); 99 | 100 | FlowView view(&scene); 101 | 102 | view.setWindowTitle("Style example"); 103 | view.resize(800, 600); 104 | view.show(); 105 | 106 | return app.exec(); 107 | } 108 | -------------------------------------------------------------------------------- /examples/styles/models.cpp: -------------------------------------------------------------------------------- 1 | #include "models.hpp" 2 | 3 | // For some reason CMake could not generate moc-files correctly 4 | // without having a cpp for an QObject from hpp. 5 | -------------------------------------------------------------------------------- /examples/styles/models.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::NodeData; 13 | using QtNodes::NodeDataType; 14 | using QtNodes::NodeDataModel; 15 | using QtNodes::NodeValidationState; 16 | 17 | /// The class can potentially incapsulate any user data which 18 | /// need to be transferred within the Node Editor graph 19 | class MyNodeData : public NodeData 20 | { 21 | public: 22 | 23 | NodeDataType 24 | type() const override 25 | { return NodeDataType {"MyNodeData", "My Node Data"}; } 26 | }; 27 | 28 | //------------------------------------------------------------------------------ 29 | 30 | /// The model dictates the number of inputs and outputs for the Node. 31 | /// In this example it has no logic. 32 | class MyDataModel : public NodeDataModel 33 | { 34 | Q_OBJECT 35 | 36 | public: 37 | 38 | virtual 39 | ~MyDataModel() {} 40 | 41 | public: 42 | 43 | QString 44 | caption() const override 45 | { 46 | return QString("My Data Model"); 47 | } 48 | 49 | QString 50 | name() const override 51 | { 52 | return QString("MyDataModel"); 53 | } 54 | 55 | public: 56 | 57 | QJsonObject 58 | save() const override 59 | { 60 | QJsonObject modelJson; 61 | 62 | modelJson["name"] = name(); 63 | 64 | return modelJson; 65 | } 66 | 67 | public: 68 | 69 | unsigned int 70 | nPorts(PortType) const override 71 | { 72 | return 3; 73 | } 74 | 75 | NodeDataType 76 | dataType(PortType, PortIndex) const override 77 | { 78 | return MyNodeData().type(); 79 | } 80 | 81 | std::shared_ptr 82 | outData(PortIndex) override 83 | { 84 | return std::make_shared(); 85 | } 86 | 87 | void 88 | setInData(std::shared_ptr, int) override 89 | { 90 | // 91 | } 92 | 93 | QWidget * 94 | embeddedWidget() override { return nullptr; } 95 | }; 96 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(BUILD_TESTING) 2 | find_package(Catch2 2.13.7 QUIET) 3 | 4 | if(NOT Catch2_FOUND) 5 | add_subdirectory(Catch2) 6 | endif() 7 | endif() 8 | 9 | macro(find_package pkg) 10 | if(NOT TARGET "${pkg}") 11 | _find_package(${ARGV}) 12 | endif() 13 | endmacro() 14 | -------------------------------------------------------------------------------- /external/Catch2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp") 2 | file(DOWNLOAD https://raw.githubusercontent.com/catchorg/Catch2/v2.13.7/single_include/catch2/catch.hpp 3 | "${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp" 4 | EXPECTED_HASH SHA256=ea379c4a3cb5799027b1eb451163dff065a3d641aaba23bf4e24ee6b536bd9bc 5 | ) 6 | endif() 7 | 8 | add_library(Catch2 INTERFACE) 9 | add_library(Catch2::Catch2 ALIAS Catch2) 10 | target_include_directories(Catch2 11 | INTERFACE 12 | "${CMAKE_CURRENT_BINARY_DIR}/single_include" 13 | ) 14 | -------------------------------------------------------------------------------- /include/nodes/Connection: -------------------------------------------------------------------------------- 1 | #include "internal/Connection.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/ConnectionStyle: -------------------------------------------------------------------------------- 1 | #include "internal/ConnectionStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/DataModelRegistry: -------------------------------------------------------------------------------- 1 | #include "internal/DataModelRegistry.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/FlowScene: -------------------------------------------------------------------------------- 1 | #include "internal/FlowScene.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/FlowView: -------------------------------------------------------------------------------- 1 | #include "internal/FlowView.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/FlowViewStyle: -------------------------------------------------------------------------------- 1 | #include "internal/FlowViewStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/Node: -------------------------------------------------------------------------------- 1 | #include "internal/Node.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/NodeData: -------------------------------------------------------------------------------- 1 | #include "internal/NodeData.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/NodeDataModel: -------------------------------------------------------------------------------- 1 | #include "internal/NodeDataModel.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/NodeGeometry: -------------------------------------------------------------------------------- 1 | #include "internal/NodeGeometry.hpp" 2 | 3 | -------------------------------------------------------------------------------- /include/nodes/NodePainterDelegate: -------------------------------------------------------------------------------- 1 | #include "internal/NodePainterDelegate.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/NodeState: -------------------------------------------------------------------------------- 1 | #include "internal/NodeState.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/NodeStyle: -------------------------------------------------------------------------------- 1 | #include "internal/NodeStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/StyleCollection: -------------------------------------------------------------------------------- 1 | #include "internal/StyleCollection.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/TypeConverter: -------------------------------------------------------------------------------- 1 | #include "internal/TypeConverter.hpp" 2 | -------------------------------------------------------------------------------- /include/nodes/internal/Compiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if \ 4 | defined (__MINGW32__) || \ 5 | defined (__MINGW64__) 6 | # define NODE_EDITOR_COMPILER "MinGW" 7 | # define NODE_EDITOR_COMPILER_MINGW 8 | #elif \ 9 | defined (__GNUC__) 10 | # define NODE_EDITOR_COMPILER "GNU" 11 | # define NODE_EDITOR_COMPILER_GNU 12 | # define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ 13 | # define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ 14 | # define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ 15 | #elif \ 16 | defined (__clang__) 17 | # define NODE_EDITOR_COMPILER "Clang" 18 | # define NODE_EDITOR_COMPILER_CLANG 19 | #elif \ 20 | defined (_MSC_VER) 21 | # define NODE_EDITOR_COMPILER "Microsoft Visual C++" 22 | # define NODE_EDITOR_COMPILER_MICROSOFT 23 | #elif \ 24 | defined (__BORLANDC__) 25 | # define NODE_EDITOR_COMPILER "Borland C++ Builder" 26 | # define NODE_EDITOR_COMPILER_BORLAND 27 | #elif \ 28 | defined (__CODEGEARC__) 29 | # define NODE_EDITOR_COMPILER "CodeGear C++ Builder" 30 | # define NODE_EDITOR_COMPILER_CODEGEAR 31 | #elif \ 32 | defined (__INTEL_COMPILER) || \ 33 | defined (__ICL) 34 | # define NODE_EDITOR_COMPILER "Intel C++" 35 | # define NODE_EDITOR_COMPILER_INTEL 36 | #elif \ 37 | defined (__xlC__) || \ 38 | defined (__IBMCPP__) 39 | # define NODE_EDITOR_COMPILER "IBM XL C++" 40 | # define NODE_EDITOR_COMPILER_IBM 41 | #elif \ 42 | defined (__HP_aCC) 43 | # define NODE_EDITOR_COMPILER "HP aC++" 44 | # define NODE_EDITOR_COMPILER_HP 45 | #elif \ 46 | defined (__WATCOMC__) 47 | # define NODE_EDITOR_COMPILER "Watcom C++" 48 | # define NODE_EDITOR_COMPILER_WATCOM 49 | #endif 50 | 51 | #ifndef NODE_EDITOR_COMPILER 52 | # error "Current compiler is not supported." 53 | #endif 54 | -------------------------------------------------------------------------------- /include/nodes/internal/Connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "PortType.hpp" 8 | #include "NodeData.hpp" 9 | 10 | #include "Serializable.hpp" 11 | #include "ConnectionState.hpp" 12 | #include "ConnectionGeometry.hpp" 13 | #include "TypeConverter.hpp" 14 | #include "QUuidStdHash.hpp" 15 | #include "Export.hpp" 16 | #include "memory.hpp" 17 | 18 | class QPointF; 19 | 20 | namespace QtNodes 21 | { 22 | 23 | class Node; 24 | class NodeData; 25 | class ConnectionGraphicsObject; 26 | 27 | /// 28 | class NODE_EDITOR_PUBLIC Connection 29 | : public QObject 30 | , public Serializable 31 | { 32 | 33 | Q_OBJECT 34 | 35 | public: 36 | 37 | /// New Connection is attached to the port of the given Node. 38 | /// The port has parameters (portType, portIndex). 39 | /// The opposite connection end will require anothre port. 40 | Connection(PortType portType, 41 | Node& node, 42 | PortIndex portIndex); 43 | 44 | Connection(Node& nodeIn, 45 | PortIndex portIndexIn, 46 | Node& nodeOut, 47 | PortIndex portIndexOut, 48 | TypeConverter converter = 49 | TypeConverter{}); 50 | 51 | Connection(const Connection&) = delete; 52 | Connection operator=(const Connection&) = delete; 53 | 54 | ~Connection(); 55 | 56 | public: 57 | 58 | QJsonObject 59 | save() const override; 60 | 61 | public: 62 | 63 | QUuid 64 | id() const; 65 | 66 | /// Remembers the end being dragged. 67 | /// Invalidates Node address. 68 | /// Grabs mouse. 69 | void 70 | setRequiredPort(PortType portType); 71 | PortType 72 | requiredPort() const; 73 | 74 | void 75 | setGraphicsObject(std::unique_ptr&& graphics); 76 | 77 | /// Assigns a node to the required port. 78 | /// It is assumed that there is a required port, no extra checks 79 | void 80 | setNodeToPort(Node& node, 81 | PortType portType, 82 | PortIndex portIndex); 83 | 84 | void 85 | removeFromNodes() const; 86 | 87 | public: 88 | 89 | ConnectionGraphicsObject& 90 | getConnectionGraphicsObject() const; 91 | 92 | ConnectionState const & 93 | connectionState() const; 94 | ConnectionState& 95 | connectionState(); 96 | 97 | ConnectionGeometry& 98 | connectionGeometry(); 99 | 100 | ConnectionGeometry const& 101 | connectionGeometry() const; 102 | 103 | Node* 104 | getNode(PortType portType) const; 105 | 106 | Node*& 107 | getNode(PortType portType); 108 | 109 | PortIndex 110 | getPortIndex(PortType portType) const; 111 | 112 | void 113 | clearNode(PortType portType); 114 | 115 | NodeDataType 116 | dataType(PortType portType) const; 117 | 118 | void 119 | setTypeConverter(TypeConverter converter); 120 | 121 | bool 122 | complete() const; 123 | 124 | public: // data propagation 125 | 126 | void 127 | propagateData(std::shared_ptr nodeData) const; 128 | 129 | void 130 | propagateEmptyData() const; 131 | 132 | Q_SIGNALS: 133 | 134 | void 135 | connectionCompleted(Connection const&) const; 136 | 137 | void 138 | connectionMadeIncomplete(Connection const&) const; 139 | 140 | private: 141 | 142 | QUuid _uid; 143 | 144 | private: 145 | 146 | Node* _outNode = nullptr; 147 | Node* _inNode = nullptr; 148 | 149 | PortIndex _outPortIndex; 150 | PortIndex _inPortIndex; 151 | 152 | private: 153 | 154 | ConnectionState _connectionState; 155 | ConnectionGeometry _connectionGeometry; 156 | 157 | std::unique_ptr_connectionGraphicsObject; 158 | 159 | TypeConverter _converter; 160 | 161 | Q_SIGNALS: 162 | 163 | void 164 | updated(Connection& conn) const; 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /include/nodes/internal/ConnectionGeometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "PortType.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace QtNodes 11 | { 12 | 13 | class ConnectionGeometry 14 | { 15 | public: 16 | 17 | ConnectionGeometry(); 18 | 19 | public: 20 | 21 | QPointF const& 22 | getEndPoint(PortType portType) const; 23 | 24 | void 25 | setEndPoint(PortType portType, QPointF const& point); 26 | 27 | void 28 | moveEndPoint(PortType portType, QPointF const &offset); 29 | 30 | QRectF 31 | boundingRect() const; 32 | 33 | std::pair 34 | pointsC1C2() const; 35 | 36 | QPointF 37 | source() const { return _out; } 38 | QPointF 39 | sink() const { return _in; } 40 | 41 | double 42 | lineWidth() const { return _lineWidth; } 43 | 44 | bool 45 | hovered() const { return _hovered; } 46 | void 47 | setHovered(bool hovered) { _hovered = hovered; } 48 | 49 | private: 50 | // local object coordinates 51 | QPointF _in; 52 | QPointF _out; 53 | 54 | //int _animationPhase; 55 | 56 | double _lineWidth; 57 | 58 | bool _hovered; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /include/nodes/internal/ConnectionGraphicsObject.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class QGraphicsSceneMouseEvent; 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class FlowScene; 13 | class Connection; 14 | class ConnectionGeometry; 15 | class Node; 16 | 17 | /// Graphic Object for connection. Adds itself to scene 18 | class ConnectionGraphicsObject 19 | : public QGraphicsObject 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | 25 | ConnectionGraphicsObject(FlowScene &scene, 26 | Connection &connection); 27 | 28 | virtual 29 | ~ConnectionGraphicsObject(); 30 | 31 | enum { Type = UserType + 2 }; 32 | int 33 | type() const override { return Type; } 34 | 35 | public: 36 | 37 | Connection& 38 | connection(); 39 | 40 | QRectF 41 | boundingRect() const override; 42 | 43 | QPainterPath 44 | shape() const override; 45 | 46 | void 47 | setGeometryChanged(); 48 | 49 | /// Updates the position of both ends 50 | void 51 | move(); 52 | 53 | void 54 | lock(bool locked); 55 | 56 | protected: 57 | 58 | void 59 | paint(QPainter* painter, 60 | QStyleOptionGraphicsItem const* option, 61 | QWidget* widget = 0) override; 62 | 63 | void 64 | mousePressEvent(QGraphicsSceneMouseEvent* event) override; 65 | 66 | void 67 | mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; 68 | 69 | void 70 | mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; 71 | 72 | void 73 | hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; 74 | 75 | void 76 | hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; 77 | 78 | private: 79 | 80 | void 81 | addGraphicsEffect(); 82 | 83 | private: 84 | 85 | FlowScene & _scene; 86 | 87 | Connection& _connection; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /include/nodes/internal/ConnectionState.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "PortType.hpp" 6 | 7 | class QPointF; 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class Node; 13 | 14 | /// Stores currently draggind end. 15 | /// Remembers last hovered Node. 16 | class ConnectionState 17 | { 18 | public: 19 | 20 | ConnectionState(PortType port = PortType::None) 21 | : _requiredPort(port) 22 | {} 23 | 24 | ConnectionState(const ConnectionState&) = delete; 25 | ConnectionState operator=(const ConnectionState&) = delete; 26 | 27 | ~ConnectionState(); 28 | 29 | public: 30 | 31 | void setRequiredPort(PortType end) 32 | { _requiredPort = end; } 33 | 34 | PortType requiredPort() const 35 | { return _requiredPort; } 36 | 37 | bool requiresPort() const 38 | { return _requiredPort != PortType::None; } 39 | 40 | void setNoRequiredPort() 41 | { _requiredPort = PortType::None; } 42 | 43 | public: 44 | 45 | void interactWithNode(Node* node); 46 | 47 | void setLastHoveredNode(Node* node); 48 | 49 | Node* 50 | lastHoveredNode() const 51 | { return _lastHoveredNode; } 52 | 53 | void resetLastHoveredNode(); 54 | 55 | private: 56 | 57 | PortType _requiredPort; 58 | 59 | Node* _lastHoveredNode{nullptr}; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /include/nodes/internal/ConnectionStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC ConnectionStyle : public Style 12 | { 13 | public: 14 | 15 | ConnectionStyle(); 16 | 17 | ConnectionStyle(QString jsonText); 18 | 19 | public: 20 | 21 | static void setConnectionStyle(QString jsonText); 22 | 23 | private: 24 | 25 | void loadJsonText(QString jsonText) override; 26 | 27 | void loadJsonFile(QString fileName) override; 28 | 29 | void loadJsonFromByteArray(QByteArray const &byteArray) override; 30 | 31 | public: 32 | 33 | QColor constructionColor() const; 34 | QColor normalColor() const; 35 | QColor normalColor(QString typeId) const; 36 | QColor selectedColor() const; 37 | QColor selectedHaloColor() const; 38 | QColor hoveredColor() const; 39 | 40 | float lineWidth() const; 41 | float constructionLineWidth() const; 42 | float pointDiameter() const; 43 | 44 | bool useDataDefinedColors() const; 45 | 46 | private: 47 | 48 | QColor ConstructionColor; 49 | QColor NormalColor; 50 | QColor SelectedColor; 51 | QColor SelectedHaloColor; 52 | QColor HoveredColor; 53 | 54 | float LineWidth; 55 | float ConstructionLineWidth; 56 | float PointDiameter; 57 | 58 | bool UseDataDefinedColors; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /include/nodes/internal/DataModelRegistry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "NodeDataModel.hpp" 14 | #include "TypeConverter.hpp" 15 | #include "Export.hpp" 16 | #include "QStringStdHash.hpp" 17 | #include "memory.hpp" 18 | 19 | namespace QtNodes 20 | { 21 | 22 | inline 23 | bool 24 | operator<(QtNodes::NodeDataType const & d1, 25 | QtNodes::NodeDataType const & d2) 26 | { 27 | return d1.id < d2.id; 28 | } 29 | 30 | 31 | /// Class uses map for storing models (name, model) 32 | class NODE_EDITOR_PUBLIC DataModelRegistry 33 | { 34 | 35 | public: 36 | 37 | using RegistryItemPtr = std::unique_ptr; 38 | using RegistryItemCreator = std::function; 39 | using RegisteredModelCreatorsMap = std::unordered_map; 40 | using RegisteredModelsCategoryMap = std::unordered_map; 41 | using CategoriesSet = std::set; 42 | 43 | using RegisteredTypeConvertersMap = std::map; 44 | 45 | DataModelRegistry() = default; 46 | ~DataModelRegistry() = default; 47 | 48 | DataModelRegistry(DataModelRegistry const &) = delete; 49 | DataModelRegistry(DataModelRegistry &&) = default; 50 | 51 | DataModelRegistry&operator=(DataModelRegistry const &) = delete; 52 | DataModelRegistry&operator=(DataModelRegistry &&) = default; 53 | 54 | public: 55 | 56 | template 57 | void registerModel(RegistryItemCreator creator, 58 | QString const &category = "Nodes") 59 | { 60 | const QString name = computeName(HasStaticMethodName{}, creator); 61 | if (!_registeredItemCreators.count(name)) 62 | { 63 | _registeredItemCreators[name] = std::move(creator); 64 | _categories.insert(category); 65 | _registeredModelsCategory[name] = category; 66 | } 67 | } 68 | 69 | template 70 | void registerModel(QString const &category = "Nodes") 71 | { 72 | RegistryItemCreator creator = [](){ return std::make_unique(); }; 73 | registerModel(std::move(creator), category); 74 | } 75 | 76 | template 77 | void registerModel(QString const &category, 78 | RegistryItemCreator creator) 79 | { 80 | registerModel(std::move(creator), category); 81 | } 82 | 83 | template 84 | void registerModel(ModelCreator&& creator, QString const& category = "Nodes") 85 | { 86 | using ModelType = compute_model_type_t; 87 | registerModel(std::forward(creator), category); 88 | } 89 | 90 | template 91 | void registerModel(QString const& category, ModelCreator&& creator) 92 | { 93 | registerModel(std::forward(creator), category); 94 | } 95 | 96 | void registerTypeConverter(TypeConverterId const & id, 97 | TypeConverter typeConverter) 98 | { 99 | _registeredTypeConverters[id] = std::move(typeConverter); 100 | } 101 | 102 | std::unique_ptrcreate(QString const &modelName); 103 | 104 | RegisteredModelCreatorsMap const ®isteredModelCreators() const; 105 | 106 | RegisteredModelsCategoryMap const ®isteredModelsCategoryAssociation() const; 107 | 108 | CategoriesSet const &categories() const; 109 | 110 | TypeConverter getTypeConverter(NodeDataType const & d1, 111 | NodeDataType const & d2) const; 112 | 113 | private: 114 | 115 | RegisteredModelsCategoryMap _registeredModelsCategory; 116 | 117 | CategoriesSet _categories; 118 | 119 | RegisteredModelCreatorsMap _registeredItemCreators; 120 | 121 | RegisteredTypeConvertersMap _registeredTypeConverters; 122 | 123 | private: 124 | 125 | // If the registered ModelType class has the static member method 126 | // 127 | // static Qstring Name(); 128 | // 129 | // use it. Otherwise use the non-static method: 130 | // 131 | // virtual QString name() const; 132 | 133 | template 134 | struct HasStaticMethodName 135 | : std::false_type 136 | {}; 137 | 138 | template 139 | struct HasStaticMethodName::value>::type> 141 | : std::true_type 142 | {}; 143 | 144 | template 145 | static QString 146 | computeName(std::true_type, RegistryItemCreator const&) 147 | { 148 | return ModelType::Name(); 149 | } 150 | 151 | template 152 | static QString 153 | computeName(std::false_type, RegistryItemCreator const& creator) 154 | { 155 | return creator()->name(); 156 | } 157 | 158 | template 159 | struct UnwrapUniquePtr 160 | { 161 | // Assert always fires, but the compiler doesn't know this: 162 | static_assert(!std::is_same::value, 163 | "The ModelCreator must return a std::unique_ptr, where T " 164 | "inherits from NodeDataModel"); 165 | }; 166 | 167 | template 168 | struct UnwrapUniquePtr> 169 | { 170 | static_assert(std::is_base_of::value, 171 | "The ModelCreator must return a std::unique_ptr, where T " 172 | "inherits from NodeDataModel"); 173 | using type = T; 174 | }; 175 | 176 | template 177 | using compute_model_type_t = typename UnwrapUniquePtr::type; 178 | }; 179 | 180 | 181 | 182 | } 183 | -------------------------------------------------------------------------------- /include/nodes/internal/Export.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Compiler.hpp" 4 | #include "OperatingSystem.hpp" 5 | 6 | #ifdef NODE_EDITOR_PLATFORM_WINDOWS 7 | # define NODE_EDITOR_EXPORT __declspec(dllexport) 8 | # define NODE_EDITOR_IMPORT __declspec(dllimport) 9 | # define NODE_EDITOR_LOCAL 10 | #elif \ 11 | NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || \ 12 | defined (NODE_EDITOR_COMPILER_CLANG) 13 | # define NODE_EDITOR_EXPORT __attribute__((visibility("default"))) 14 | # define NODE_EDITOR_IMPORT __attribute__((visibility("default"))) 15 | # define NODE_EDITOR_LOCAL __attribute__((visibility("hidden"))) 16 | #else 17 | # define NODE_EDITOR_EXPORT 18 | # define NODE_EDITOR_IMPORT 19 | # define NODE_EDITOR_LOCAL 20 | #endif 21 | 22 | #ifdef __cplusplus 23 | # define NODE_EDITOR_DEMANGLED extern "C" 24 | #else 25 | # define NODE_EDITOR_DEMANGLED 26 | #endif 27 | 28 | 29 | #if defined (NODE_EDITOR_SHARED) && !defined (NODE_EDITOR_STATIC) 30 | # ifdef NODE_EDITOR_EXPORTS 31 | # define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT 32 | # else 33 | # define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT 34 | # endif 35 | # define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL 36 | #elif !defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) 37 | # define NODE_EDITOR_PUBLIC 38 | # define NODE_EDITOR_PRIVATE 39 | #elif defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) 40 | # ifdef NODE_EDITOR_EXPORTS 41 | # error "Cannot build as shared and static simultaneously." 42 | # else 43 | # error "Cannot link against shared and static simultaneously." 44 | # endif 45 | #else 46 | # ifdef NODE_EDITOR_EXPORTS 47 | # error "Choose whether to build as shared or static." 48 | # else 49 | # error "Choose whether to link against shared or static." 50 | # endif 51 | #endif 52 | -------------------------------------------------------------------------------- /include/nodes/internal/FlowScene.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "QUuidStdHash.hpp" 11 | #include "Export.hpp" 12 | #include "DataModelRegistry.hpp" 13 | #include "TypeConverter.hpp" 14 | #include "memory.hpp" 15 | 16 | namespace QtNodes 17 | { 18 | 19 | class NodeDataModel; 20 | class FlowItemInterface; 21 | class Node; 22 | class NodeGraphicsObject; 23 | class Connection; 24 | class ConnectionGraphicsObject; 25 | class NodeStyle; 26 | 27 | /// Scene holds connections and nodes. 28 | class NODE_EDITOR_PUBLIC FlowScene 29 | : public QGraphicsScene 30 | { 31 | Q_OBJECT 32 | public: 33 | 34 | FlowScene(std::shared_ptr registry, 35 | QObject * parent = Q_NULLPTR); 36 | 37 | FlowScene(QObject * parent = Q_NULLPTR); 38 | 39 | ~FlowScene(); 40 | 41 | public: 42 | 43 | std::shared_ptr 44 | createConnection(PortType connectedPort, 45 | Node& node, 46 | PortIndex portIndex); 47 | 48 | std::shared_ptr 49 | createConnection(Node& nodeIn, 50 | PortIndex portIndexIn, 51 | Node& nodeOut, 52 | PortIndex portIndexOut, 53 | TypeConverter const & converter = TypeConverter{}); 54 | 55 | std::shared_ptr restoreConnection(QJsonObject const &connectionJson); 56 | 57 | void deleteConnection(Connection const& connection); 58 | 59 | Node&createNode(std::unique_ptr && dataModel); 60 | 61 | Node&restoreNode(QJsonObject const& nodeJson); 62 | 63 | void removeNode(Node& node); 64 | 65 | DataModelRegistry®istry() const; 66 | 67 | void setRegistry(std::shared_ptr registry); 68 | 69 | void iterateOverNodes(std::function const & visitor); 70 | 71 | void iterateOverNodeData(std::function const & visitor); 72 | 73 | void iterateOverNodeDataDependentOrder(std::function const & visitor); 74 | 75 | QPointF getNodePosition(Node const& node) const; 76 | 77 | void setNodePosition(Node& node, QPointF const& pos) const; 78 | 79 | QSizeF getNodeSize(Node const& node) const; 80 | 81 | public: 82 | 83 | std::unordered_map > const & nodes() const; 84 | 85 | std::unordered_map > const & connections() const; 86 | 87 | std::vector allNodes() const; 88 | 89 | std::vector selectedNodes() const; 90 | 91 | public: 92 | 93 | void clearScene(); 94 | 95 | void save() const; 96 | 97 | void load(); 98 | 99 | QByteArray saveToMemory() const; 100 | 101 | void loadFromMemory(const QByteArray& data); 102 | 103 | Q_SIGNALS: 104 | 105 | /** 106 | * @brief Node has been created but not on the scene yet. 107 | * @see nodePlaced() 108 | */ 109 | void nodeCreated(Node &n); 110 | 111 | /** 112 | * @brief Node has been added to the scene. 113 | * @details Connect to this signal if need a correct position of node. 114 | * @see nodeCreated() 115 | */ 116 | void nodePlaced(Node &n); 117 | 118 | void nodeDeleted(Node &n); 119 | 120 | void connectionCreated(Connection const &c); 121 | void connectionDeleted(Connection const &c); 122 | 123 | void nodeMoved(Node& n, const QPointF& newLocation); 124 | 125 | void nodeDoubleClicked(Node& n); 126 | 127 | void nodeClicked(Node& n); 128 | 129 | void connectionHovered(Connection& c, QPoint screenPos); 130 | 131 | void nodeHovered(Node& n, QPoint screenPos); 132 | 133 | void connectionHoverLeft(Connection& c); 134 | 135 | void nodeHoverLeft(Node& n); 136 | 137 | void nodeContextMenu(Node& n, const QPointF& pos); 138 | 139 | private: 140 | 141 | using SharedConnection = std::shared_ptr; 142 | using UniqueNode = std::unique_ptr; 143 | 144 | // DO NOT reorder this member to go after the others. 145 | // This should outlive all the connections and nodes of 146 | // the graph, so that nodes can potentially have pointers into it, 147 | // which is why it comes first in the class. 148 | std::shared_ptr _registry; 149 | 150 | std::unordered_map _connections; 151 | std::unordered_map _nodes; 152 | 153 | private Q_SLOTS: 154 | 155 | void setupConnectionSignals(Connection const& c); 156 | 157 | void sendConnectionCreatedToNodes(Connection const& c); 158 | void sendConnectionDeletedToNodes(Connection const& c); 159 | 160 | }; 161 | 162 | Node* 163 | locateNodeAt(QPointF scenePoint, FlowScene &scene, 164 | QTransform const & viewTransform); 165 | } 166 | -------------------------------------------------------------------------------- /include/nodes/internal/FlowView.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | 7 | namespace QtNodes 8 | { 9 | 10 | class FlowScene; 11 | 12 | class NODE_EDITOR_PUBLIC FlowView 13 | : public QGraphicsView 14 | { 15 | Q_OBJECT 16 | public: 17 | 18 | FlowView(QWidget *parent = Q_NULLPTR); 19 | FlowView(FlowScene *scene, QWidget *parent = Q_NULLPTR); 20 | 21 | FlowView(const FlowView&) = delete; 22 | FlowView operator=(const FlowView&) = delete; 23 | 24 | QAction* clearSelectionAction() const; 25 | 26 | QAction* deleteSelectionAction() const; 27 | 28 | void setScene(FlowScene *scene); 29 | 30 | public Q_SLOTS: 31 | 32 | void scaleUp(); 33 | 34 | void scaleDown(); 35 | 36 | void deleteSelectedNodes(); 37 | 38 | protected: 39 | 40 | void contextMenuEvent(QContextMenuEvent *event) override; 41 | 42 | void wheelEvent(QWheelEvent *event) override; 43 | 44 | void keyPressEvent(QKeyEvent *event) override; 45 | 46 | void keyReleaseEvent(QKeyEvent *event) override; 47 | 48 | void mousePressEvent(QMouseEvent *event) override; 49 | 50 | void mouseMoveEvent(QMouseEvent *event) override; 51 | 52 | void drawBackground(QPainter* painter, const QRectF& r) override; 53 | 54 | void showEvent(QShowEvent *event) override; 55 | 56 | protected: 57 | 58 | FlowScene * scene(); 59 | 60 | private: 61 | 62 | QAction* _clearSelectionAction; 63 | QAction* _deleteSelectionAction; 64 | 65 | QPointF _clickPos; 66 | 67 | FlowScene* _scene; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /include/nodes/internal/FlowViewStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC FlowViewStyle : public Style 12 | { 13 | public: 14 | 15 | FlowViewStyle(); 16 | 17 | FlowViewStyle(QString jsonText); 18 | 19 | public: 20 | 21 | static void setStyle(QString jsonText); 22 | 23 | private: 24 | 25 | void loadJsonText(QString jsonText) override; 26 | 27 | void loadJsonFile(QString fileName) override; 28 | 29 | void loadJsonFromByteArray(QByteArray const &byteArray) override; 30 | 31 | public: 32 | 33 | QColor BackgroundColor; 34 | QColor FineGridColor; 35 | QColor CoarseGridColor; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /include/nodes/internal/Node.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "PortType.hpp" 10 | 11 | #include "Export.hpp" 12 | #include "NodeState.hpp" 13 | #include "NodeGeometry.hpp" 14 | #include "NodeData.hpp" 15 | #include "NodeGraphicsObject.hpp" 16 | #include "ConnectionGraphicsObject.hpp" 17 | #include "Serializable.hpp" 18 | #include "memory.hpp" 19 | 20 | namespace QtNodes 21 | { 22 | 23 | class Connection; 24 | class ConnectionState; 25 | class NodeGraphicsObject; 26 | class NodeDataModel; 27 | 28 | class NODE_EDITOR_PUBLIC Node 29 | : public QObject 30 | , public Serializable 31 | { 32 | Q_OBJECT 33 | 34 | public: 35 | 36 | /// NodeDataModel should be an rvalue and is moved into the Node 37 | Node(std::unique_ptr && dataModel); 38 | 39 | virtual 40 | ~Node(); 41 | 42 | public: 43 | 44 | QJsonObject 45 | save() const override; 46 | 47 | void 48 | restore(QJsonObject const &json) override; 49 | 50 | public: 51 | 52 | QUuid 53 | id() const; 54 | 55 | void reactToPossibleConnection(PortType, 56 | NodeDataType const &, 57 | QPointF const & scenePoint); 58 | 59 | void 60 | resetReactionToConnection(); 61 | 62 | public: 63 | 64 | NodeGraphicsObject const & 65 | nodeGraphicsObject() const; 66 | 67 | NodeGraphicsObject & 68 | nodeGraphicsObject(); 69 | 70 | void 71 | setGraphicsObject(std::unique_ptr&& graphics); 72 | 73 | NodeGeometry& 74 | nodeGeometry(); 75 | 76 | NodeGeometry const& 77 | nodeGeometry() const; 78 | 79 | NodeState const & 80 | nodeState() const; 81 | 82 | NodeState & 83 | nodeState(); 84 | 85 | NodeDataModel* 86 | nodeDataModel() const; 87 | 88 | public Q_SLOTS: // data propagation 89 | 90 | /// Propagates incoming data to the underlying model. 91 | void 92 | propagateData(std::shared_ptr nodeData, 93 | PortIndex inPortIndex) const; 94 | 95 | /// Fetches data from model's OUT #index port 96 | /// and propagates it to the connection 97 | void 98 | onDataUpdated(PortIndex index); 99 | 100 | /// update the graphic part if the size of the embeddedwidget changes 101 | void 102 | onNodeSizeUpdated(); 103 | 104 | private: 105 | 106 | // addressing 107 | 108 | QUuid _uid; 109 | 110 | // data 111 | 112 | std::unique_ptr _nodeDataModel; 113 | 114 | NodeState _nodeState; 115 | 116 | // painting 117 | 118 | NodeGeometry _nodeGeometry; 119 | 120 | std::unique_ptr _nodeGraphicsObject; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | 7 | namespace QtNodes 8 | { 9 | 10 | struct NodeDataType 11 | { 12 | QString id; 13 | QString name; 14 | }; 15 | 16 | /// Class represents data transferred between nodes. 17 | /// @param type is used for comparing the types 18 | /// The actual data is stored in subtypes 19 | class NODE_EDITOR_PUBLIC NodeData 20 | { 21 | public: 22 | 23 | virtual ~NodeData() = default; 24 | 25 | virtual bool sameType(NodeData const &nodeData) const 26 | { 27 | return (this->type().id == nodeData.type().id); 28 | } 29 | 30 | /// Type for inner use 31 | virtual NodeDataType type() const = 0; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | 6 | #include "PortType.hpp" 7 | #include "NodeData.hpp" 8 | #include "Serializable.hpp" 9 | #include "NodeGeometry.hpp" 10 | #include "NodeStyle.hpp" 11 | #include "NodePainterDelegate.hpp" 12 | #include "Export.hpp" 13 | #include "memory.hpp" 14 | 15 | namespace QtNodes 16 | { 17 | 18 | enum class NodeValidationState 19 | { 20 | Valid, 21 | Warning, 22 | Error 23 | }; 24 | 25 | class Connection; 26 | 27 | class StyleCollection; 28 | 29 | class NODE_EDITOR_PUBLIC NodeDataModel 30 | : public QObject 31 | , public Serializable 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | 37 | NodeDataModel(); 38 | 39 | virtual 40 | ~NodeDataModel() = default; 41 | 42 | /// Caption is used in GUI 43 | virtual QString 44 | caption() const = 0; 45 | 46 | /// It is possible to hide caption in GUI 47 | virtual bool 48 | captionVisible() const { return true; } 49 | 50 | /// Port caption is used in GUI to label individual ports 51 | virtual QString 52 | portCaption(PortType, PortIndex) const { return QString(); } 53 | 54 | /// It is possible to hide port caption in GUI 55 | virtual bool 56 | portCaptionVisible(PortType, PortIndex) const { return false; } 57 | 58 | /// Name makes this model unique 59 | virtual QString 60 | name() const = 0; 61 | 62 | public: 63 | 64 | QJsonObject 65 | save() const override; 66 | 67 | public: 68 | 69 | virtual 70 | unsigned int nPorts(PortType portType) const = 0; 71 | 72 | virtual 73 | NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0; 74 | 75 | public: 76 | 77 | enum class ConnectionPolicy 78 | { 79 | One, 80 | Many, 81 | }; 82 | 83 | virtual 84 | ConnectionPolicy 85 | portOutConnectionPolicy(PortIndex) const 86 | { 87 | return ConnectionPolicy::Many; 88 | } 89 | 90 | NodeStyle const& 91 | nodeStyle() const; 92 | 93 | void 94 | setNodeStyle(NodeStyle const& style); 95 | 96 | public: 97 | 98 | /// Triggers the algorithm 99 | virtual 100 | void 101 | setInData(std::shared_ptr nodeData, 102 | PortIndex port) = 0; 103 | 104 | virtual 105 | std::shared_ptr 106 | outData(PortIndex port) = 0; 107 | 108 | virtual 109 | QWidget * 110 | embeddedWidget() = 0; 111 | 112 | virtual 113 | bool 114 | resizable() const { return false; } 115 | 116 | virtual 117 | NodeValidationState 118 | validationState() const { return NodeValidationState::Valid; } 119 | 120 | virtual 121 | QString 122 | validationMessage() const { return QString(""); } 123 | 124 | virtual 125 | NodePainterDelegate* painterDelegate() const { return nullptr; } 126 | 127 | public Q_SLOTS: 128 | 129 | virtual void 130 | inputConnectionCreated(Connection const&) 131 | { 132 | } 133 | 134 | virtual void 135 | inputConnectionDeleted(Connection const&) 136 | { 137 | } 138 | 139 | virtual void 140 | outputConnectionCreated(Connection const&) 141 | { 142 | } 143 | 144 | virtual void 145 | outputConnectionDeleted(Connection const&) 146 | { 147 | } 148 | 149 | Q_SIGNALS: 150 | 151 | void 152 | dataUpdated(PortIndex index); 153 | 154 | void 155 | dataInvalidated(PortIndex index); 156 | 157 | void 158 | computingStarted(); 159 | 160 | void 161 | computingFinished(); 162 | 163 | void embeddedWidgetSizeUpdated(); 164 | 165 | private: 166 | 167 | NodeStyle _nodeStyle; 168 | }; 169 | } 170 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeGeometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "PortType.hpp" 9 | #include "Export.hpp" 10 | #include "memory.hpp" 11 | 12 | namespace QtNodes 13 | { 14 | 15 | class NodeState; 16 | class NodeDataModel; 17 | class Node; 18 | 19 | class NODE_EDITOR_PUBLIC NodeGeometry 20 | { 21 | public: 22 | 23 | NodeGeometry(std::unique_ptr const &dataModel); 24 | 25 | public: 26 | unsigned int 27 | height() const { return _height; } 28 | 29 | void 30 | setHeight(unsigned int h) { _height = h; } 31 | 32 | unsigned int 33 | width() const { return _width; } 34 | 35 | void 36 | setWidth(unsigned int w) { _width = w; } 37 | 38 | unsigned int 39 | entryHeight() const { return _entryHeight; } 40 | void 41 | setEntryHeight(unsigned int h) { _entryHeight = h; } 42 | 43 | unsigned int 44 | entryWidth() const { return _entryWidth; } 45 | 46 | void 47 | setEntryWidth(unsigned int w) { _entryWidth = w; } 48 | 49 | unsigned int 50 | spacing() const { return _spacing; } 51 | 52 | void 53 | setSpacing(unsigned int s) { _spacing = s; } 54 | 55 | bool 56 | hovered() const { return _hovered; } 57 | 58 | void 59 | setHovered(unsigned int h) { _hovered = h; } 60 | 61 | unsigned int 62 | nSources() const; 63 | 64 | unsigned int 65 | nSinks() const; 66 | 67 | QPointF const& 68 | draggingPos() const 69 | { return _draggingPos; } 70 | 71 | void 72 | setDraggingPosition(QPointF const& pos) 73 | { _draggingPos = pos; } 74 | 75 | public: 76 | 77 | QRectF 78 | entryBoundingRect() const; 79 | 80 | QRectF 81 | boundingRect() const; 82 | 83 | /// Updates size unconditionally 84 | void 85 | recalculateSize() const; 86 | 87 | /// Updates size if the QFontMetrics is changed 88 | void 89 | recalculateSize(QFont const &font) const; 90 | 91 | // TODO removed default QTransform() 92 | QPointF 93 | portScenePosition(PortIndex index, 94 | PortType portType, 95 | QTransform const & t = QTransform()) const; 96 | 97 | PortIndex 98 | checkHitScenePoint(PortType portType, 99 | QPointF point, 100 | QTransform const & t = QTransform()) const; 101 | 102 | QRect 103 | resizeRect() const; 104 | 105 | /// Returns the position of a widget on the Node surface 106 | QPointF 107 | widgetPosition() const; 108 | 109 | /// Returns the maximum height a widget can be without causing the node to grow. 110 | int 111 | equivalentWidgetHeight() const; 112 | 113 | unsigned int 114 | validationHeight() const; 115 | 116 | unsigned int 117 | validationWidth() const; 118 | 119 | static 120 | QPointF 121 | calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node* targetNode, 122 | PortIndex sourcePortIndex, PortType sourcePort, Node* sourceNode, 123 | Node& newNode); 124 | private: 125 | 126 | unsigned int 127 | captionHeight() const; 128 | 129 | unsigned int 130 | captionWidth() const; 131 | 132 | unsigned int 133 | portWidth(PortType portType) const; 134 | 135 | private: 136 | 137 | // some variables are mutable because 138 | // we need to change drawing metrics 139 | // corresponding to fontMetrics 140 | // but this doesn't change constness of Node 141 | 142 | mutable unsigned int _width; 143 | mutable unsigned int _height; 144 | unsigned int _entryWidth; 145 | mutable unsigned int _inputPortWidth; 146 | mutable unsigned int _outputPortWidth; 147 | mutable unsigned int _entryHeight; 148 | unsigned int _spacing; 149 | 150 | bool _hovered; 151 | 152 | unsigned int _nSources; 153 | unsigned int _nSinks; 154 | 155 | QPointF _draggingPos; 156 | 157 | std::unique_ptr const &_dataModel; 158 | 159 | mutable QFontMetrics _fontMetrics; 160 | mutable QFontMetrics _boldFontMetrics; 161 | }; 162 | } 163 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeGraphicsObject.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Connection.hpp" 7 | 8 | #include "NodeGeometry.hpp" 9 | #include "NodeState.hpp" 10 | 11 | class QGraphicsProxyWidget; 12 | 13 | namespace QtNodes 14 | { 15 | 16 | class FlowScene; 17 | class FlowItemEntry; 18 | 19 | /// Class reacts on GUI events, mouse clicks and 20 | /// forwards painting operation. 21 | class NodeGraphicsObject : public QGraphicsObject 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | NodeGraphicsObject(FlowScene &scene, 27 | Node& node); 28 | 29 | virtual 30 | ~NodeGraphicsObject(); 31 | 32 | Node& 33 | node(); 34 | 35 | Node const& 36 | node() const; 37 | 38 | QRectF 39 | boundingRect() const override; 40 | 41 | void 42 | setGeometryChanged(); 43 | 44 | /// Visits all attached connections and corrects 45 | /// their corresponding end points. 46 | void 47 | moveConnections() const; 48 | 49 | enum { Type = UserType + 1 }; 50 | 51 | int 52 | type() const override { return Type; } 53 | 54 | void 55 | lock(bool locked); 56 | 57 | protected: 58 | void 59 | paint(QPainter* painter, 60 | QStyleOptionGraphicsItem const* option, 61 | QWidget* widget = 0) override; 62 | 63 | QVariant 64 | itemChange(GraphicsItemChange change, const QVariant &value) override; 65 | 66 | void 67 | mousePressEvent(QGraphicsSceneMouseEvent* event) override; 68 | 69 | void 70 | mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; 71 | 72 | void 73 | mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; 74 | 75 | void 76 | hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; 77 | 78 | void 79 | hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; 80 | 81 | void 82 | hoverMoveEvent(QGraphicsSceneHoverEvent *) override; 83 | 84 | void 85 | mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override; 86 | 87 | void 88 | contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override; 89 | 90 | private: 91 | void 92 | embedQWidget(); 93 | 94 | private: 95 | 96 | FlowScene & _scene; 97 | 98 | Node& _node; 99 | 100 | bool _locked; 101 | 102 | // either nullptr or owned by parent QGraphicsItem 103 | QGraphicsProxyWidget * _proxyWidget; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /include/nodes/internal/NodePainterDelegate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "NodeGeometry.hpp" 6 | #include "NodeDataModel.hpp" 7 | #include "Export.hpp" 8 | 9 | namespace QtNodes { 10 | 11 | /// Class to allow for custom painting 12 | class NODE_EDITOR_PUBLIC NodePainterDelegate 13 | { 14 | 15 | public: 16 | 17 | virtual 18 | ~NodePainterDelegate() = default; 19 | 20 | virtual void 21 | paint(QPainter* painter, 22 | NodeGeometry const& geom, 23 | NodeDataModel const * model) = 0; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeState.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "Export.hpp" 9 | 10 | #include "PortType.hpp" 11 | #include "NodeData.hpp" 12 | #include "memory.hpp" 13 | 14 | namespace QtNodes 15 | { 16 | 17 | class Connection; 18 | class NodeDataModel; 19 | 20 | /// Contains vectors of connected input and output connections. 21 | /// Stores bool for reacting on hovering connections 22 | class NODE_EDITOR_PUBLIC NodeState 23 | { 24 | public: 25 | enum ReactToConnectionState 26 | { 27 | REACTING, 28 | NOT_REACTING 29 | }; 30 | 31 | public: 32 | 33 | NodeState(std::unique_ptr const &model); 34 | 35 | public: 36 | 37 | using ConnectionPtrSet = 38 | std::unordered_map; 39 | 40 | /// Returns vector of connections ID. 41 | /// Some of them can be empty (null) 42 | std::vector const& 43 | getEntries(PortType) const; 44 | 45 | std::vector & 46 | getEntries(PortType); 47 | 48 | ConnectionPtrSet 49 | connections(PortType portType, PortIndex portIndex) const; 50 | 51 | void 52 | setConnection(PortType portType, 53 | PortIndex portIndex, 54 | Connection& connection); 55 | 56 | void 57 | eraseConnection(PortType portType, 58 | PortIndex portIndex, 59 | QUuid id); 60 | 61 | ReactToConnectionState 62 | reaction() const; 63 | 64 | PortType 65 | reactingPortType() const; 66 | 67 | NodeDataType 68 | reactingDataType() const; 69 | 70 | void 71 | setReaction(ReactToConnectionState reaction, 72 | PortType reactingPortType = PortType::None, 73 | 74 | NodeDataType reactingDataType = 75 | NodeDataType()); 76 | 77 | bool 78 | isReacting() const; 79 | 80 | void 81 | setResizing(bool resizing); 82 | 83 | bool 84 | resizing() const; 85 | 86 | private: 87 | 88 | std::vector _inConnections; 89 | std::vector _outConnections; 90 | 91 | ReactToConnectionState _reaction; 92 | PortType _reactingPortType; 93 | NodeDataType _reactingDataType; 94 | 95 | bool _resizing; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /include/nodes/internal/NodeStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC NodeStyle : public Style 12 | { 13 | public: 14 | 15 | NodeStyle(); 16 | 17 | NodeStyle(QString jsonText); 18 | 19 | public: 20 | 21 | static void setNodeStyle(QString jsonText); 22 | 23 | private: 24 | 25 | void loadJsonText(QString jsonText) override; 26 | 27 | void loadJsonFile(QString fileName) override; 28 | 29 | void loadJsonFromByteArray(QByteArray const &byteArray) override; 30 | 31 | public: 32 | 33 | QColor NormalBoundaryColor; 34 | QColor SelectedBoundaryColor; 35 | QColor GradientColor0; 36 | QColor GradientColor1; 37 | QColor GradientColor2; 38 | QColor GradientColor3; 39 | QColor ShadowColor; 40 | QColor FontColor; 41 | QColor FontColorFaded; 42 | 43 | QColor ConnectionPointColor; 44 | QColor FilledConnectionPointColor; 45 | 46 | QColor WarningColor; 47 | QColor ErrorColor; 48 | 49 | float PenWidth; 50 | float HoveredPenWidth; 51 | 52 | float ConnectionPointDiameter; 53 | 54 | float Opacity; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /include/nodes/internal/OperatingSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if \ 4 | defined (__CYGWIN__) || \ 5 | defined (__CYGWIN32__) 6 | # define NODE_EDITOR_PLATFORM "Cygwin" 7 | # define NODE_EDITOR_PLATFORM_CYGWIN 8 | # define NODE_EDITOR_PLATFORM_UNIX 9 | # define NODE_EDITOR_PLATFORM_WINDOWS 10 | #elif \ 11 | defined (_WIN16) || \ 12 | defined (_WIN32) || \ 13 | defined (_WIN64) || \ 14 | defined (__WIN32__) || \ 15 | defined (__TOS_WIN__) || \ 16 | defined (__WINDOWS__) 17 | # define NODE_EDITOR_PLATFORM "Windows" 18 | # define NODE_EDITOR_PLATFORM_WINDOWS 19 | #elif \ 20 | defined (macintosh) || \ 21 | defined (Macintosh) || \ 22 | defined (__TOS_MACOS__) || \ 23 | (defined (__APPLE__) && defined (__MACH__)) 24 | # define NODE_EDITOR_PLATFORM "Mac" 25 | # define NODE_EDITOR_PLATFORM_MAC 26 | # define NODE_EDITOR_PLATFORM_UNIX 27 | #elif \ 28 | defined (linux) || \ 29 | defined (__linux) || \ 30 | defined (__linux__) || \ 31 | defined (__TOS_LINUX__) 32 | # define NODE_EDITOR_PLATFORM "Linux" 33 | # define NODE_EDITOR_PLATFORM_LINUX 34 | # define NODE_EDITOR_PLATFORM_UNIX 35 | #elif \ 36 | defined (__FreeBSD__) || \ 37 | defined (__OpenBSD__) || \ 38 | defined (__NetBSD__) || \ 39 | defined (__bsdi__) || \ 40 | defined (__DragonFly__) 41 | # define NODE_EDITOR_PLATFORM "BSD" 42 | # define NODE_EDITOR_PLATFORM_BSD 43 | # define NODE_EDITOR_PLATFORM_UNIX 44 | #elif \ 45 | defined (sun) || \ 46 | defined (__sun) 47 | # define NODE_EDITOR_PLATFORM "Solaris" 48 | # define NODE_EDITOR_PLATFORM_SOLARIS 49 | # define NODE_EDITOR_PLATFORM_UNIX 50 | #elif \ 51 | defined (_AIX) || \ 52 | defined (__TOS_AIX__) 53 | # define NODE_EDITOR_PLATFORM "AIX" 54 | # define NODE_EDITOR_PLATFORM_AIX 55 | # define NODE_EDITOR_PLATFORM_UNIX 56 | #elif \ 57 | defined (hpux) || \ 58 | defined (_hpux) || \ 59 | defined (__hpux) 60 | # define NODE_EDITOR_PLATFORM "HPUX" 61 | # define NODE_EDITOR_PLATFORM_HPUX 62 | # define NODE_EDITOR_PLATFORM_UNIX 63 | #elif \ 64 | defined (__QNX__) 65 | # define NODE_EDITOR_PLATFORM "QNX" 66 | # define NODE_EDITOR_PLATFORM_QNX 67 | # define NODE_EDITOR_PLATFORM_UNIX 68 | #elif \ 69 | defined (unix) || \ 70 | defined (__unix) || \ 71 | defined (__unix__) 72 | # define NODE_EDITOR_PLATFORM "Unix" 73 | # define NODE_EDITOR_PLATFORM_UNIX 74 | #endif 75 | 76 | #ifndef NODE_EDITOR_PLATFORM 77 | # error "Current platform is not supported." 78 | #endif 79 | -------------------------------------------------------------------------------- /include/nodes/internal/PortType.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace QtNodes 7 | { 8 | 9 | enum class PortType 10 | { 11 | None, 12 | In, 13 | Out 14 | }; 15 | 16 | static const int INVALID = -1; 17 | 18 | using PortIndex = int; 19 | 20 | struct Port 21 | { 22 | PortType type; 23 | 24 | PortIndex index; 25 | 26 | Port() 27 | : type(PortType::None) 28 | , index(INVALID) 29 | {} 30 | 31 | Port(PortType t, PortIndex i) 32 | : type(t) 33 | , index(i) 34 | {} 35 | 36 | bool 37 | indexIsValid() { return index != INVALID; } 38 | 39 | bool 40 | portTypeIsValid() { return type != PortType::None; } 41 | }; 42 | 43 | //using PortAddress = std::pair; 44 | 45 | inline 46 | PortType 47 | oppositePort(PortType port) 48 | { 49 | PortType result = PortType::None; 50 | 51 | switch (port) 52 | { 53 | case PortType::In: 54 | result = PortType::Out; 55 | break; 56 | 57 | case PortType::Out: 58 | result = PortType::In; 59 | break; 60 | 61 | default: 62 | break; 63 | } 64 | 65 | return result; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /include/nodes/internal/QStringStdHash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) 6 | 7 | // As of 5.14 there is a specialization std::hash 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace std 15 | { 16 | template<> 17 | struct hash 18 | { 19 | inline std::size_t 20 | operator()(QString const &s) const 21 | { 22 | return qHash(s); 23 | } 24 | }; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/nodes/internal/QUuidStdHash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace std 9 | { 10 | template<> 11 | struct hash 12 | { 13 | inline 14 | std::size_t 15 | operator()(QUuid const& uid) const 16 | { 17 | return qHash(uid); 18 | } 19 | }; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /include/nodes/internal/Serializable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class Serializable 9 | { 10 | public: 11 | 12 | virtual 13 | ~Serializable() = default; 14 | 15 | virtual 16 | QJsonObject 17 | save() const = 0; 18 | 19 | virtual void 20 | restore(QJsonObject const & /*p*/) {} 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /include/nodes/internal/Style.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class Style 9 | { 10 | public: 11 | 12 | virtual 13 | ~Style() = default; 14 | 15 | private: 16 | 17 | virtual void 18 | loadJsonText(QString jsonText) = 0; 19 | 20 | virtual void 21 | loadJsonFile(QString fileName) = 0; 22 | 23 | virtual void 24 | loadJsonFromByteArray(QByteArray const &byteArray) = 0; 25 | }; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /include/nodes/internal/StyleCollection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NodeStyle.hpp" 4 | #include "ConnectionStyle.hpp" 5 | #include "FlowViewStyle.hpp" 6 | #include "Export.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC StyleCollection 12 | { 13 | public: 14 | 15 | static 16 | NodeStyle const& 17 | nodeStyle(); 18 | 19 | static 20 | ConnectionStyle const& 21 | connectionStyle(); 22 | 23 | static 24 | FlowViewStyle const& 25 | flowViewStyle(); 26 | 27 | public: 28 | 29 | static 30 | void 31 | setNodeStyle(NodeStyle); 32 | 33 | static 34 | void 35 | setConnectionStyle(ConnectionStyle); 36 | 37 | static 38 | void 39 | setFlowViewStyle(FlowViewStyle); 40 | 41 | private: 42 | 43 | StyleCollection() = default; 44 | 45 | StyleCollection(StyleCollection const&) = delete; 46 | 47 | StyleCollection& 48 | operator=(StyleCollection const&) = delete; 49 | 50 | static 51 | StyleCollection& 52 | instance(); 53 | 54 | private: 55 | 56 | NodeStyle _nodeStyle; 57 | 58 | ConnectionStyle _connectionStyle; 59 | 60 | FlowViewStyle _flowViewStyle; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /include/nodes/internal/TypeConverter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NodeData.hpp" 4 | #include "memory.hpp" 5 | 6 | #include 7 | 8 | namespace QtNodes 9 | { 10 | 11 | using SharedNodeData = std::shared_ptr; 12 | 13 | // a function taking in NodeData and returning NodeData 14 | using TypeConverter = 15 | std::function; 16 | 17 | // data-type-in, data-type-out 18 | using TypeConverterId = 19 | std::pair; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /include/nodes/internal/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace QtNodes 7 | { 8 | namespace detail { 9 | #if (!defined(_MSC_VER) && (__cplusplus < 201300)) || \ 10 | ( defined(_MSC_VER) && (_MSC_VER < 1800)) 11 | //_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant, 12 | // and it has make_unique in it's standard library implementation 13 | template 14 | std::unique_ptr make_unique(Args&&... args) 15 | { 16 | return std::unique_ptr(new T(std::forward(args)...)); 17 | } 18 | #else 19 | template 20 | std::unique_ptr make_unique(Args&&... args) 21 | { 22 | return std::make_unique(std::forward(args)...); 23 | } 24 | #endif 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pictures/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/calculator.png -------------------------------------------------------------------------------- /pictures/chigraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/chigraph.png -------------------------------------------------------------------------------- /pictures/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/flow.png -------------------------------------------------------------------------------- /pictures/spkgen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/spkgen.png -------------------------------------------------------------------------------- /pictures/style_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/style_example.png -------------------------------------------------------------------------------- /pictures/vid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/pictures/vid1.png -------------------------------------------------------------------------------- /resources/DefaultStyle.json: -------------------------------------------------------------------------------- 1 | { 2 | "FlowViewStyle": { 3 | "BackgroundColor": [53, 53, 53], 4 | "FineGridColor": [60, 60, 60], 5 | "CoarseGridColor": [25, 25, 25] 6 | }, 7 | "NodeStyle": { 8 | "NormalBoundaryColor": [255, 255, 255], 9 | "SelectedBoundaryColor": [255, 165, 0], 10 | "GradientColor0": "gray", 11 | "GradientColor1": [80, 80, 80], 12 | "GradientColor2": [64, 64, 64], 13 | "GradientColor3": [58, 58, 58], 14 | "ShadowColor": [20, 20, 20], 15 | "FontColor" : "white", 16 | "FontColorFaded" : "gray", 17 | "ConnectionPointColor": [169, 169, 169], 18 | "FilledConnectionPointColor": "cyan", 19 | "ErrorColor": "red", 20 | "WarningColor": [128, 128, 0], 21 | 22 | "PenWidth": 1.0, 23 | "HoveredPenWidth": 1.5, 24 | 25 | "ConnectionPointDiameter": 8.0, 26 | 27 | "Opacity": 0.8 28 | }, 29 | "ConnectionStyle": { 30 | "ConstructionColor": "gray", 31 | "NormalColor": "darkcyan", 32 | "SelectedColor": [100, 100, 100], 33 | "SelectedHaloColor": "orange", 34 | "HoveredColor": "lightcyan", 35 | 36 | "LineWidth": 3.0, 37 | "ConstructionLineWidth": 2.0, 38 | "PointDiameter": 10.0, 39 | 40 | "UseDataDefinedColors": false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /resources/convert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facontidavide/QtNodeEditor/e786ea4ec189032eedfd85524c9c3177a4304406/resources/convert.png -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | DefaultStyle.json 4 | convert.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/ConnectionBlurEffect.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionBlurEffect.hpp" 2 | 3 | #include "ConnectionGraphicsObject.hpp" 4 | #include "ConnectionPainter.hpp" 5 | 6 | using QtNodes::ConnectionBlurEffect; 7 | using QtNodes::ConnectionGraphicsObject; 8 | 9 | ConnectionBlurEffect:: 10 | ConnectionBlurEffect(ConnectionGraphicsObject*) 11 | { 12 | // 13 | } 14 | 15 | 16 | void 17 | ConnectionBlurEffect:: 18 | draw(QPainter* painter) 19 | { 20 | QGraphicsBlurEffect::draw(painter); 21 | 22 | //ConnectionPainter::paint(painter, 23 | //_object->connectionGeometry(), 24 | //_object->connectionState()); 25 | 26 | //_item->paint(painter, nullptr, nullptr); 27 | } 28 | -------------------------------------------------------------------------------- /src/ConnectionBlurEffect.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class ConnectionGraphicsObject; 9 | 10 | class ConnectionBlurEffect : public QGraphicsBlurEffect 11 | { 12 | 13 | public: 14 | 15 | ConnectionBlurEffect(ConnectionGraphicsObject* item); 16 | 17 | void 18 | draw(QPainter* painter) override; 19 | 20 | private: 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/ConnectionGeometry.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionGeometry.hpp" 2 | 3 | #include 4 | 5 | #include "StyleCollection.hpp" 6 | 7 | using QtNodes::ConnectionGeometry; 8 | using QtNodes::PortType; 9 | 10 | ConnectionGeometry:: 11 | ConnectionGeometry() 12 | : _in(0, 0) 13 | , _out(0, 0) 14 | //, _animationPhase(0) 15 | , _lineWidth(3.0) 16 | , _hovered(false) 17 | { } 18 | 19 | QPointF const& 20 | ConnectionGeometry:: 21 | getEndPoint(PortType portType) const 22 | { 23 | Q_ASSERT(portType != PortType::None); 24 | 25 | return (portType == PortType::Out ? 26 | _out : 27 | _in); 28 | } 29 | 30 | 31 | void 32 | ConnectionGeometry:: 33 | setEndPoint(PortType portType, QPointF const& point) 34 | { 35 | switch (portType) 36 | { 37 | case PortType::Out: 38 | _out = point; 39 | break; 40 | 41 | case PortType::In: 42 | _in = point; 43 | break; 44 | 45 | default: 46 | break; 47 | } 48 | } 49 | 50 | 51 | void 52 | ConnectionGeometry:: 53 | moveEndPoint(PortType portType, QPointF const &offset) 54 | { 55 | switch (portType) 56 | { 57 | case PortType::Out: 58 | _out += offset; 59 | break; 60 | 61 | case PortType::In: 62 | _in += offset; 63 | break; 64 | 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | 71 | QRectF 72 | ConnectionGeometry:: 73 | boundingRect() const 74 | { 75 | auto points = pointsC1C2(); 76 | 77 | QRectF basicRect = QRectF(_out, _in).normalized(); 78 | 79 | QRectF c1c2Rect = QRectF(points.first, points.second).normalized(); 80 | 81 | auto const &connectionStyle = 82 | StyleCollection::connectionStyle(); 83 | 84 | float const diam = connectionStyle.pointDiameter(); 85 | 86 | QRectF commonRect = basicRect.united(c1c2Rect); 87 | 88 | QPointF const cornerOffset(diam, diam); 89 | 90 | commonRect.setTopLeft(commonRect.topLeft() - cornerOffset); 91 | commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset); 92 | 93 | return commonRect; 94 | } 95 | 96 | 97 | std::pair 98 | ConnectionGeometry:: 99 | pointsC1C2() const 100 | { 101 | const double defaultOffset = 200; 102 | 103 | double xDistance = _in.x() - _out.x(); 104 | 105 | double horizontalOffset = qMin(defaultOffset, std::abs(xDistance)); 106 | 107 | double verticalOffset = 0; 108 | 109 | double ratioX = 0.5; 110 | 111 | if (xDistance <= 0) 112 | { 113 | double yDistance = _in.y() - _out.y() + 20; 114 | 115 | double vector = yDistance < 0 ? -1.0 : 1.0; 116 | 117 | verticalOffset = qMin(defaultOffset, std::abs(yDistance)) * vector; 118 | 119 | ratioX = 1.0; 120 | } 121 | 122 | horizontalOffset *= ratioX; 123 | 124 | QPointF c1(_out.x() + horizontalOffset, 125 | _out.y() + verticalOffset); 126 | 127 | QPointF c2(_in.x() - horizontalOffset, 128 | _in.y() - verticalOffset); 129 | 130 | return std::make_pair(c1, c2); 131 | } 132 | -------------------------------------------------------------------------------- /src/ConnectionGraphicsObject.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionGraphicsObject.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "FlowScene.hpp" 10 | 11 | #include "Connection.hpp" 12 | #include "ConnectionGeometry.hpp" 13 | #include "ConnectionPainter.hpp" 14 | #include "ConnectionState.hpp" 15 | #include "ConnectionBlurEffect.hpp" 16 | 17 | #include "NodeGraphicsObject.hpp" 18 | 19 | #include "NodeConnectionInteraction.hpp" 20 | 21 | #include "Node.hpp" 22 | 23 | using QtNodes::ConnectionGraphicsObject; 24 | using QtNodes::Connection; 25 | using QtNodes::FlowScene; 26 | 27 | ConnectionGraphicsObject:: 28 | ConnectionGraphicsObject(FlowScene &scene, 29 | Connection &connection) 30 | : _scene(scene) 31 | , _connection(connection) 32 | { 33 | _scene.addItem(this); 34 | 35 | setFlag(QGraphicsItem::ItemIsMovable, true); 36 | setFlag(QGraphicsItem::ItemIsFocusable, true); 37 | setFlag(QGraphicsItem::ItemIsSelectable, true); 38 | 39 | setAcceptHoverEvents(true); 40 | 41 | // addGraphicsEffect(); 42 | 43 | setZValue(-1.0); 44 | } 45 | 46 | 47 | ConnectionGraphicsObject:: 48 | ~ConnectionGraphicsObject() 49 | { 50 | _scene.removeItem(this); 51 | } 52 | 53 | 54 | QtNodes::Connection& 55 | ConnectionGraphicsObject:: 56 | connection() 57 | { 58 | return _connection; 59 | } 60 | 61 | 62 | QRectF 63 | ConnectionGraphicsObject:: 64 | boundingRect() const 65 | { 66 | return _connection.connectionGeometry().boundingRect(); 67 | } 68 | 69 | 70 | QPainterPath 71 | ConnectionGraphicsObject:: 72 | shape() const 73 | { 74 | #ifdef DEBUG_DRAWING 75 | 76 | //QPainterPath path; 77 | 78 | //path.addRect(boundingRect()); 79 | //return path; 80 | 81 | #else 82 | auto const &geom = 83 | _connection.connectionGeometry(); 84 | 85 | return ConnectionPainter::getPainterStroke(geom); 86 | 87 | #endif 88 | } 89 | 90 | 91 | void 92 | ConnectionGraphicsObject:: 93 | setGeometryChanged() 94 | { 95 | prepareGeometryChange(); 96 | } 97 | 98 | 99 | void 100 | ConnectionGraphicsObject:: 101 | move() 102 | { 103 | for(PortType portType: { PortType::In, PortType::Out } ) 104 | { 105 | if (auto node = _connection.getNode(portType)) 106 | { 107 | auto const &nodeGraphics = node->nodeGraphicsObject(); 108 | 109 | auto const &nodeGeom = node->nodeGeometry(); 110 | 111 | QPointF scenePos = 112 | nodeGeom.portScenePosition(_connection.getPortIndex(portType), 113 | portType, 114 | nodeGraphics.sceneTransform()); 115 | 116 | QTransform sceneTransform = this->sceneTransform(); 117 | 118 | QPointF connectionPos = sceneTransform.inverted().map(scenePos); 119 | 120 | _connection.connectionGeometry().setEndPoint(portType, 121 | connectionPos); 122 | 123 | _connection.getConnectionGraphicsObject().setGeometryChanged(); 124 | _connection.getConnectionGraphicsObject().update(); 125 | } 126 | } 127 | 128 | } 129 | 130 | void ConnectionGraphicsObject::lock(bool locked) 131 | { 132 | setFlag(QGraphicsItem::ItemIsMovable, !locked); 133 | setFlag(QGraphicsItem::ItemIsFocusable, !locked); 134 | setFlag(QGraphicsItem::ItemIsSelectable, !locked); 135 | } 136 | 137 | 138 | void 139 | ConnectionGraphicsObject:: 140 | paint(QPainter* painter, 141 | QStyleOptionGraphicsItem const* option, 142 | QWidget*) 143 | { 144 | painter->setClipRect(option->exposedRect); 145 | 146 | ConnectionPainter::paint(painter, 147 | _connection); 148 | } 149 | 150 | 151 | void 152 | ConnectionGraphicsObject:: 153 | mousePressEvent(QGraphicsSceneMouseEvent* event) 154 | { 155 | QGraphicsItem::mousePressEvent(event); 156 | //event->ignore(); 157 | } 158 | 159 | 160 | void 161 | ConnectionGraphicsObject:: 162 | mouseMoveEvent(QGraphicsSceneMouseEvent* event) 163 | { 164 | prepareGeometryChange(); 165 | 166 | auto view = static_cast(event->widget()); 167 | auto node = locateNodeAt(event->scenePos(), 168 | _scene, 169 | view->transform()); 170 | 171 | auto &state = _connection.connectionState(); 172 | 173 | state.interactWithNode(node); 174 | if (node) 175 | { 176 | node->reactToPossibleConnection(state.requiredPort(), 177 | _connection.dataType(oppositePort(state.requiredPort())), 178 | event->scenePos()); 179 | } 180 | 181 | //------------------- 182 | 183 | QPointF offset = event->pos() - event->lastPos(); 184 | 185 | auto requiredPort = _connection.requiredPort(); 186 | 187 | if (requiredPort != PortType::None) 188 | { 189 | _connection.connectionGeometry().moveEndPoint(requiredPort, offset); 190 | } 191 | 192 | //------------------- 193 | 194 | update(); 195 | 196 | event->accept(); 197 | } 198 | 199 | 200 | void 201 | ConnectionGraphicsObject:: 202 | mouseReleaseEvent(QGraphicsSceneMouseEvent* event) 203 | { 204 | ungrabMouse(); 205 | event->accept(); 206 | 207 | auto node = locateNodeAt(event->scenePos(), _scene, 208 | _scene.views()[0]->transform()); 209 | 210 | NodeConnectionInteraction interaction(*node, _connection, _scene); 211 | 212 | if (node && interaction.tryConnect()) 213 | { 214 | node->resetReactionToConnection(); 215 | } 216 | 217 | if (_connection.connectionState().requiresPort()) 218 | { 219 | _scene.deleteConnection(_connection); 220 | } 221 | } 222 | 223 | 224 | void 225 | ConnectionGraphicsObject:: 226 | hoverEnterEvent(QGraphicsSceneHoverEvent* event) 227 | { 228 | _connection.connectionGeometry().setHovered(true); 229 | 230 | update(); 231 | _scene.connectionHovered(connection(), event->screenPos()); 232 | event->accept(); 233 | } 234 | 235 | 236 | void 237 | ConnectionGraphicsObject:: 238 | hoverLeaveEvent(QGraphicsSceneHoverEvent* event) 239 | { 240 | _connection.connectionGeometry().setHovered(false); 241 | 242 | update(); 243 | _scene.connectionHoverLeft(connection()); 244 | event->accept(); 245 | } 246 | 247 | 248 | void 249 | ConnectionGraphicsObject:: 250 | addGraphicsEffect() 251 | { 252 | auto effect = new QGraphicsBlurEffect; 253 | 254 | effect->setBlurRadius(5); 255 | setGraphicsEffect(effect); 256 | 257 | //auto effect = new QGraphicsDropShadowEffect; 258 | //auto effect = new ConnectionBlurEffect(this); 259 | //effect->setOffset(4, 4); 260 | //effect->setColor(QColor(Qt::gray).darker(800)); 261 | } 262 | -------------------------------------------------------------------------------- /src/ConnectionPainter.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionPainter.hpp" 2 | 3 | #include 4 | 5 | #include "ConnectionGeometry.hpp" 6 | #include "ConnectionState.hpp" 7 | #include "ConnectionGraphicsObject.hpp" 8 | #include "Connection.hpp" 9 | 10 | #include "NodeData.hpp" 11 | 12 | #include "StyleCollection.hpp" 13 | 14 | 15 | using QtNodes::ConnectionPainter; 16 | using QtNodes::ConnectionGeometry; 17 | using QtNodes::Connection; 18 | 19 | 20 | static 21 | QPainterPath 22 | cubicPath(ConnectionGeometry const& geom) 23 | { 24 | QPointF const& source = geom.source(); 25 | QPointF const& sink = geom.sink(); 26 | 27 | auto c1c2 = geom.pointsC1C2(); 28 | 29 | // cubic spline 30 | QPainterPath cubic(source); 31 | 32 | cubic.cubicTo(c1c2.first, c1c2.second, sink); 33 | 34 | return cubic; 35 | } 36 | 37 | 38 | QPainterPath 39 | ConnectionPainter:: 40 | getPainterStroke(ConnectionGeometry const& geom) 41 | { 42 | auto cubic = cubicPath(geom); 43 | 44 | QPointF const& source = geom.source(); 45 | QPainterPath result(source); 46 | 47 | unsigned segments = 20; 48 | 49 | for (auto i = 0ul; i < segments; ++i) 50 | { 51 | double ratio = double(i + 1) / segments; 52 | result.lineTo(cubic.pointAtPercent(ratio)); 53 | } 54 | 55 | QPainterPathStroker stroker; stroker.setWidth(10.0); 56 | 57 | return stroker.createStroke(result); 58 | } 59 | 60 | 61 | #ifdef NODE_DEBUG_DRAWING 62 | static 63 | void 64 | debugDrawing(QPainter * painter, 65 | Connection const & connection) 66 | { 67 | Q_UNUSED(painter); 68 | Q_UNUSED(connection); 69 | ConnectionGeometry const& geom = 70 | connection.connectionGeometry(); 71 | 72 | { 73 | QPointF const& source = geom.source(); 74 | QPointF const& sink = geom.sink(); 75 | 76 | auto points = geom.pointsC1C2(); 77 | 78 | painter->setPen(Qt::red); 79 | painter->setBrush(Qt::red); 80 | 81 | painter->drawLine(QLineF(source, points.first)); 82 | painter->drawLine(QLineF(points.first, points.second)); 83 | painter->drawLine(QLineF(points.second, sink)); 84 | painter->drawEllipse(points.first, 3, 3); 85 | painter->drawEllipse(points.second, 3, 3); 86 | 87 | painter->setBrush(Qt::NoBrush); 88 | 89 | painter->drawPath(cubicPath(geom)); 90 | } 91 | 92 | { 93 | painter->setPen(Qt::yellow); 94 | 95 | painter->drawRect(geom.boundingRect()); 96 | } 97 | } 98 | #endif 99 | 100 | static 101 | void 102 | drawSketchLine(QPainter * painter, 103 | Connection const & connection) 104 | { 105 | using QtNodes::ConnectionState; 106 | 107 | ConnectionState const& state = 108 | connection.connectionState(); 109 | 110 | if (state.requiresPort()) 111 | { 112 | auto const & connectionStyle = 113 | QtNodes::StyleCollection::connectionStyle(); 114 | 115 | QPen p; 116 | p.setWidth(connectionStyle.constructionLineWidth()); 117 | p.setColor(connectionStyle.constructionColor()); 118 | p.setStyle(Qt::DashLine); 119 | 120 | painter->setPen(p); 121 | painter->setBrush(Qt::NoBrush); 122 | 123 | using QtNodes::ConnectionGeometry; 124 | ConnectionGeometry const& geom = connection.connectionGeometry(); 125 | 126 | auto cubic = cubicPath(geom); 127 | // cubic spline 128 | painter->drawPath(cubic); 129 | } 130 | } 131 | 132 | static 133 | void 134 | drawHoveredOrSelected(QPainter * painter, 135 | Connection const & connection) 136 | { 137 | using QtNodes::ConnectionGeometry; 138 | 139 | ConnectionGeometry const& geom = connection.connectionGeometry(); 140 | bool const hovered = geom.hovered(); 141 | 142 | auto const& graphicsObject = 143 | connection.getConnectionGraphicsObject(); 144 | 145 | bool const selected = graphicsObject.isSelected(); 146 | 147 | // drawn as a fat background 148 | if (hovered || selected) 149 | { 150 | QPen p; 151 | 152 | auto const &connectionStyle = 153 | QtNodes::StyleCollection::connectionStyle(); 154 | double const lineWidth = connectionStyle.lineWidth(); 155 | 156 | p.setWidth(2 * lineWidth); 157 | p.setColor(selected ? 158 | connectionStyle.selectedHaloColor() : 159 | connectionStyle.hoveredColor()); 160 | 161 | painter->setPen(p); 162 | painter->setBrush(Qt::NoBrush); 163 | 164 | // cubic spline 165 | auto cubic = cubicPath(geom); 166 | painter->drawPath(cubic); 167 | } 168 | } 169 | 170 | 171 | static 172 | void 173 | drawNormalLine(QPainter * painter, 174 | Connection const & connection) 175 | { 176 | using QtNodes::ConnectionState; 177 | 178 | ConnectionState const& state = 179 | connection.connectionState(); 180 | 181 | if (state.requiresPort()) 182 | return; 183 | 184 | // colors 185 | 186 | auto const &connectionStyle = 187 | QtNodes::StyleCollection::connectionStyle(); 188 | 189 | QColor normalColorOut = connectionStyle.normalColor(); 190 | QColor normalColorIn = connectionStyle.normalColor(); 191 | QColor selectedColor = connectionStyle.selectedColor(); 192 | 193 | bool gradientColor = false; 194 | 195 | if (connectionStyle.useDataDefinedColors()) 196 | { 197 | using QtNodes::PortType; 198 | 199 | auto dataTypeOut = connection.dataType(PortType::Out); 200 | auto dataTypeIn = connection.dataType(PortType::In); 201 | 202 | gradientColor = (dataTypeOut.id != dataTypeIn.id); 203 | 204 | normalColorOut = connectionStyle.normalColor(dataTypeOut.id); 205 | normalColorIn = connectionStyle.normalColor(dataTypeIn.id); 206 | selectedColor = normalColorOut.darker(200); 207 | } 208 | 209 | // geometry 210 | 211 | ConnectionGeometry const& geom = connection.connectionGeometry(); 212 | 213 | double const lineWidth = connectionStyle.lineWidth(); 214 | 215 | // draw normal line 216 | QPen p; 217 | 218 | p.setWidth(lineWidth); 219 | 220 | auto const& graphicsObject = connection.getConnectionGraphicsObject(); 221 | bool const selected = graphicsObject.isSelected(); 222 | 223 | 224 | auto cubic = cubicPath(geom); 225 | if (gradientColor) 226 | { 227 | painter->setBrush(Qt::NoBrush); 228 | 229 | QColor cOut = normalColorOut; 230 | if (selected) 231 | cOut = cOut.darker(200); 232 | p.setColor(cOut); 233 | painter->setPen(p); 234 | 235 | unsigned int const segments = 60; 236 | 237 | for (unsigned int i = 0ul; i < segments; ++i) 238 | { 239 | double ratioPrev = double(i) / segments; 240 | double ratio = double(i + 1) / segments; 241 | 242 | if (i == segments / 2) 243 | { 244 | QColor cIn = normalColorIn; 245 | if (selected) 246 | cIn = cIn.darker(200); 247 | 248 | p.setColor(cIn); 249 | painter->setPen(p); 250 | } 251 | painter->drawLine(cubic.pointAtPercent(ratioPrev), 252 | cubic.pointAtPercent(ratio)); 253 | } 254 | 255 | { 256 | QIcon icon(":convert.png"); 257 | 258 | QPixmap pixmap = icon.pixmap(QSize(22, 22)); 259 | painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width()/2, 260 | pixmap.height()/2), 261 | pixmap); 262 | 263 | } 264 | } 265 | else 266 | { 267 | p.setColor(normalColorOut); 268 | 269 | if (selected) 270 | { 271 | p.setColor(selectedColor); 272 | } 273 | 274 | painter->setPen(p); 275 | painter->setBrush(Qt::NoBrush); 276 | 277 | painter->drawPath(cubic); 278 | } 279 | } 280 | 281 | 282 | void 283 | ConnectionPainter:: 284 | paint(QPainter* painter, 285 | Connection const &connection) 286 | { 287 | drawHoveredOrSelected(painter, connection); 288 | 289 | drawSketchLine(painter, connection); 290 | 291 | drawNormalLine(painter, connection); 292 | 293 | #ifdef NODE_DEBUG_DRAWING 294 | debugDrawing(painter, connection); 295 | #endif 296 | 297 | // draw end points 298 | ConnectionGeometry const& geom = connection.connectionGeometry(); 299 | 300 | QPointF const & source = geom.source(); 301 | QPointF const & sink = geom.sink(); 302 | 303 | auto const & connectionStyle = 304 | QtNodes::StyleCollection::connectionStyle(); 305 | 306 | double const pointDiameter = connectionStyle.pointDiameter(); 307 | 308 | painter->setPen(connectionStyle.constructionColor()); 309 | painter->setBrush(connectionStyle.constructionColor()); 310 | double const pointRadius = pointDiameter / 2.0; 311 | painter->drawEllipse(source, pointRadius, pointRadius); 312 | painter->drawEllipse(sink, pointRadius, pointRadius); 313 | } 314 | -------------------------------------------------------------------------------- /src/ConnectionPainter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class ConnectionGeometry; 9 | class ConnectionState; 10 | class Connection; 11 | 12 | class ConnectionPainter 13 | { 14 | public: 15 | 16 | static 17 | void 18 | paint(QPainter* painter, 19 | Connection const& connection); 20 | 21 | static 22 | QPainterPath 23 | getPainterStroke(ConnectionGeometry const& geom); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/ConnectionState.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionState.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "FlowScene.hpp" 8 | #include "Node.hpp" 9 | 10 | using QtNodes::ConnectionState; 11 | using QtNodes::Node; 12 | 13 | ConnectionState:: 14 | ~ConnectionState() 15 | { 16 | resetLastHoveredNode(); 17 | } 18 | 19 | 20 | void 21 | ConnectionState:: 22 | interactWithNode(Node* node) 23 | { 24 | if (node) 25 | { 26 | _lastHoveredNode = node; 27 | } 28 | else 29 | { 30 | resetLastHoveredNode(); 31 | } 32 | } 33 | 34 | 35 | void 36 | ConnectionState:: 37 | setLastHoveredNode(Node* node) 38 | { 39 | _lastHoveredNode = node; 40 | } 41 | 42 | 43 | void 44 | ConnectionState:: 45 | resetLastHoveredNode() 46 | { 47 | if (_lastHoveredNode) 48 | _lastHoveredNode->resetReactionToConnection(); 49 | 50 | _lastHoveredNode = nullptr; 51 | } 52 | -------------------------------------------------------------------------------- /src/ConnectionStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionStyle.hpp" 2 | 3 | #include "StyleCollection.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | using QtNodes::ConnectionStyle; 16 | 17 | inline void initResources() { Q_INIT_RESOURCE(resources); } 18 | 19 | ConnectionStyle:: 20 | ConnectionStyle() 21 | { 22 | // Explicit resources inialization for preventing the static initialization 23 | // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order 24 | initResources(); 25 | 26 | // This configuration is stored inside the compiled unit and is loaded statically 27 | loadJsonFile(":DefaultStyle.json"); 28 | } 29 | 30 | 31 | ConnectionStyle:: 32 | ConnectionStyle(QString jsonText) 33 | { 34 | loadJsonFile(":DefaultStyle.json"); 35 | loadJsonText(jsonText); 36 | } 37 | 38 | 39 | void 40 | ConnectionStyle:: 41 | setConnectionStyle(QString jsonText) 42 | { 43 | ConnectionStyle style(jsonText); 44 | 45 | StyleCollection::setConnectionStyle(style); 46 | } 47 | 48 | #ifdef STYLE_DEBUG 49 | #define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ 50 | if (v.type() == QJsonValue::Undefined || \ 51 | v.type() == QJsonValue::Null) \ 52 | qWarning() << "Undefined value for parameter:" << #variable; \ 53 | } 54 | #else 55 | #define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) 56 | #endif 57 | 58 | 59 | #define CONNECTION_VALUE_EXISTS(v) \ 60 | (v.type() != QJsonValue::Undefined && \ 61 | v.type() != QJsonValue::Null) 62 | 63 | #define CONNECTION_STYLE_READ_COLOR(values, variable) { \ 64 | auto valueRef = values[#variable]; \ 65 | CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 66 | if (CONNECTION_VALUE_EXISTS(valueRef)) {\ 67 | if (valueRef.isArray()) { \ 68 | auto colorArray = valueRef.toArray(); \ 69 | std::vector rgb; rgb.reserve(3); \ 70 | for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ 71 | rgb.push_back((*it).toInt()); \ 72 | } \ 73 | variable = QColor(rgb[0], rgb[1], rgb[2]); \ 74 | } else { \ 75 | variable = QColor(valueRef.toString()); \ 76 | } \ 77 | } \ 78 | } 79 | 80 | #define CONNECTION_STYLE_READ_FLOAT(values, variable) { \ 81 | auto valueRef = values[#variable]; \ 82 | CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 83 | if (CONNECTION_VALUE_EXISTS(valueRef)) \ 84 | variable = valueRef.toDouble(); \ 85 | } 86 | 87 | #define CONNECTION_STYLE_READ_BOOL(values, variable) { \ 88 | auto valueRef = values[#variable]; \ 89 | CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 90 | if (CONNECTION_VALUE_EXISTS(valueRef)) \ 91 | variable = valueRef.toBool(); \ 92 | } 93 | 94 | void 95 | ConnectionStyle:: 96 | loadJsonFile(QString styleFile) 97 | { 98 | QFile file(styleFile); 99 | 100 | if (!file.open(QIODevice::ReadOnly)) 101 | { 102 | qWarning() << "Couldn't open file " << styleFile; 103 | 104 | return; 105 | } 106 | 107 | loadJsonFromByteArray(file.readAll()); 108 | } 109 | 110 | 111 | void 112 | ConnectionStyle:: 113 | loadJsonText(QString jsonText) 114 | { 115 | loadJsonFromByteArray(jsonText.toUtf8()); 116 | } 117 | 118 | 119 | void 120 | ConnectionStyle:: 121 | loadJsonFromByteArray(QByteArray const &byteArray) 122 | { 123 | QJsonDocument json(QJsonDocument::fromJson(byteArray)); 124 | 125 | QJsonObject topLevelObject = json.object(); 126 | 127 | QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"]; 128 | 129 | QJsonObject obj = nodeStyleValues.toObject(); 130 | 131 | CONNECTION_STYLE_READ_COLOR(obj, ConstructionColor); 132 | CONNECTION_STYLE_READ_COLOR(obj, NormalColor); 133 | CONNECTION_STYLE_READ_COLOR(obj, SelectedColor); 134 | CONNECTION_STYLE_READ_COLOR(obj, SelectedHaloColor); 135 | CONNECTION_STYLE_READ_COLOR(obj, HoveredColor); 136 | 137 | CONNECTION_STYLE_READ_FLOAT(obj, LineWidth); 138 | CONNECTION_STYLE_READ_FLOAT(obj, ConstructionLineWidth); 139 | CONNECTION_STYLE_READ_FLOAT(obj, PointDiameter); 140 | 141 | CONNECTION_STYLE_READ_BOOL(obj, UseDataDefinedColors); 142 | } 143 | 144 | 145 | QColor 146 | ConnectionStyle:: 147 | constructionColor() const 148 | { 149 | return ConstructionColor; 150 | } 151 | 152 | 153 | QColor 154 | ConnectionStyle:: 155 | normalColor() const 156 | { 157 | return NormalColor; 158 | } 159 | 160 | 161 | QColor 162 | ConnectionStyle:: 163 | normalColor(QString typeId) const 164 | { 165 | std::size_t hash = qHash(typeId); 166 | 167 | std::size_t const hue_range = 0xFF; 168 | 169 | std::mt19937 gen(static_cast(hash)); 170 | std::uniform_int_distribution distrib(0, hue_range); 171 | 172 | int hue = distrib(gen); 173 | int sat = 120 + hash % 129; 174 | 175 | return QColor::fromHsl(hue, 176 | sat, 177 | 160); 178 | } 179 | 180 | 181 | QColor 182 | ConnectionStyle:: 183 | selectedColor() const 184 | { 185 | return SelectedColor; 186 | } 187 | 188 | 189 | QColor 190 | ConnectionStyle:: 191 | selectedHaloColor() const 192 | { 193 | return SelectedHaloColor; 194 | } 195 | 196 | 197 | QColor 198 | ConnectionStyle:: 199 | hoveredColor() const 200 | { 201 | return HoveredColor; 202 | } 203 | 204 | 205 | float 206 | ConnectionStyle:: 207 | lineWidth() const 208 | { 209 | return LineWidth; 210 | } 211 | 212 | 213 | float 214 | ConnectionStyle:: 215 | constructionLineWidth() const 216 | { 217 | return ConstructionLineWidth; 218 | } 219 | 220 | 221 | float 222 | ConnectionStyle:: 223 | pointDiameter() const 224 | { 225 | return PointDiameter; 226 | } 227 | 228 | 229 | bool 230 | ConnectionStyle:: 231 | useDataDefinedColors() const 232 | { 233 | return UseDataDefinedColors; 234 | } 235 | -------------------------------------------------------------------------------- /src/DataModelRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include "DataModelRegistry.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using QtNodes::DataModelRegistry; 7 | using QtNodes::NodeDataModel; 8 | using QtNodes::NodeDataType; 9 | using QtNodes::TypeConverter; 10 | 11 | std::unique_ptr 12 | DataModelRegistry:: 13 | create(QString const &modelName) 14 | { 15 | auto it = _registeredItemCreators.find(modelName); 16 | 17 | if (it != _registeredItemCreators.end()) 18 | { 19 | return it->second(); 20 | } 21 | 22 | return nullptr; 23 | } 24 | 25 | 26 | DataModelRegistry::RegisteredModelCreatorsMap const & 27 | DataModelRegistry:: 28 | registeredModelCreators() const 29 | { 30 | return _registeredItemCreators; 31 | } 32 | 33 | 34 | DataModelRegistry::RegisteredModelsCategoryMap const & 35 | DataModelRegistry:: 36 | registeredModelsCategoryAssociation() const 37 | { 38 | return _registeredModelsCategory; 39 | } 40 | 41 | 42 | DataModelRegistry::CategoriesSet const & 43 | DataModelRegistry:: 44 | categories() const 45 | { 46 | return _categories; 47 | } 48 | 49 | 50 | TypeConverter 51 | DataModelRegistry:: 52 | getTypeConverter(NodeDataType const & d1, 53 | NodeDataType const & d2) const 54 | { 55 | TypeConverterId converterId = std::make_pair(d1, d2); 56 | 57 | auto it = _registeredTypeConverters.find(converterId); 58 | 59 | if (it != _registeredTypeConverters.end()) 60 | { 61 | return it->second; 62 | } 63 | 64 | return TypeConverter{}; 65 | } 66 | -------------------------------------------------------------------------------- /src/FlowViewStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "FlowViewStyle.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "StyleCollection.hpp" 12 | 13 | using QtNodes::FlowViewStyle; 14 | 15 | inline void initResources() { Q_INIT_RESOURCE(resources); } 16 | 17 | FlowViewStyle:: 18 | FlowViewStyle() 19 | { 20 | // Explicit resources inialization for preventing the static initialization 21 | // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order 22 | initResources(); 23 | 24 | // This configuration is stored inside the compiled unit and is loaded statically 25 | loadJsonFile(":DefaultStyle.json"); 26 | } 27 | 28 | 29 | FlowViewStyle:: 30 | FlowViewStyle(QString jsonText) 31 | { 32 | loadJsonText(jsonText); 33 | } 34 | 35 | 36 | void 37 | FlowViewStyle:: 38 | setStyle(QString jsonText) 39 | { 40 | FlowViewStyle style(jsonText); 41 | 42 | StyleCollection::setFlowViewStyle(style); 43 | } 44 | 45 | 46 | #ifdef STYLE_DEBUG 47 | #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ 48 | if (v.type() == QJsonValue::Undefined || \ 49 | v.type() == QJsonValue::Null) \ 50 | qWarning() << "Undefined value for parameter:" << #variable; \ 51 | } 52 | #else 53 | #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) 54 | #endif 55 | 56 | #define FLOW_VIEW_STYLE_READ_COLOR(values, variable) { \ 57 | auto valueRef = values[#variable]; \ 58 | FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 59 | if (valueRef.isArray()) { \ 60 | auto colorArray = valueRef.toArray(); \ 61 | std::vector rgb; rgb.reserve(3); \ 62 | for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ 63 | rgb.push_back((*it).toInt()); \ 64 | } \ 65 | variable = QColor(rgb[0], rgb[1], rgb[2]); \ 66 | } else { \ 67 | variable = QColor(valueRef.toString()); \ 68 | } \ 69 | } 70 | 71 | void 72 | FlowViewStyle:: 73 | loadJsonFile(QString styleFile) 74 | { 75 | QFile file(styleFile); 76 | 77 | if (!file.open(QIODevice::ReadOnly)) 78 | { 79 | qWarning() << "Couldn't open file " << styleFile; 80 | 81 | return; 82 | } 83 | 84 | loadJsonFromByteArray(file.readAll()); 85 | } 86 | 87 | 88 | void 89 | FlowViewStyle:: 90 | loadJsonText(QString jsonText) 91 | { 92 | loadJsonFromByteArray(jsonText.toUtf8()); 93 | } 94 | 95 | 96 | void 97 | FlowViewStyle:: 98 | loadJsonFromByteArray(QByteArray const &byteArray) 99 | { 100 | QJsonDocument json(QJsonDocument::fromJson(byteArray)); 101 | 102 | QJsonObject topLevelObject = json.object(); 103 | 104 | QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"]; 105 | 106 | QJsonObject obj = nodeStyleValues.toObject(); 107 | 108 | FLOW_VIEW_STYLE_READ_COLOR(obj, BackgroundColor); 109 | FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor); 110 | FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor); 111 | } 112 | -------------------------------------------------------------------------------- /src/Node.cpp: -------------------------------------------------------------------------------- 1 | #include "Node.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "FlowScene.hpp" 9 | 10 | #include "NodeGraphicsObject.hpp" 11 | #include "NodeDataModel.hpp" 12 | 13 | #include "ConnectionGraphicsObject.hpp" 14 | #include "ConnectionState.hpp" 15 | 16 | using QtNodes::Node; 17 | using QtNodes::NodeGeometry; 18 | using QtNodes::NodeState; 19 | using QtNodes::NodeData; 20 | using QtNodes::NodeDataType; 21 | using QtNodes::NodeDataModel; 22 | using QtNodes::NodeGraphicsObject; 23 | using QtNodes::PortIndex; 24 | using QtNodes::PortType; 25 | 26 | Node:: 27 | Node(std::unique_ptr && dataModel) 28 | : _uid(QUuid::createUuid()) 29 | , _nodeDataModel(std::move(dataModel)) 30 | , _nodeState(_nodeDataModel) 31 | , _nodeGeometry(_nodeDataModel) 32 | , _nodeGraphicsObject(nullptr) 33 | { 34 | _nodeGeometry.recalculateSize(); 35 | 36 | // propagate data: model => node 37 | connect(_nodeDataModel.get(), &NodeDataModel::dataUpdated, 38 | this, &Node::onDataUpdated); 39 | 40 | connect(_nodeDataModel.get(), &NodeDataModel::embeddedWidgetSizeUpdated, 41 | this, &Node::onNodeSizeUpdated ); 42 | } 43 | 44 | 45 | Node:: 46 | ~Node() = default; 47 | 48 | QJsonObject 49 | Node:: 50 | save() const 51 | { 52 | QJsonObject nodeJson; 53 | 54 | nodeJson["id"] = _uid.toString(); 55 | 56 | nodeJson["model"] = _nodeDataModel->save(); 57 | 58 | QJsonObject obj; 59 | obj["x"] = _nodeGraphicsObject->pos().x(); 60 | obj["y"] = _nodeGraphicsObject->pos().y(); 61 | nodeJson["position"] = obj; 62 | 63 | return nodeJson; 64 | } 65 | 66 | 67 | void 68 | Node:: 69 | restore(QJsonObject const& json) 70 | { 71 | _uid = QUuid(json["id"].toString()); 72 | 73 | QJsonObject positionJson = json["position"].toObject(); 74 | QPointF point(positionJson["x"].toDouble(), 75 | positionJson["y"].toDouble()); 76 | _nodeGraphicsObject->setPos(point); 77 | 78 | _nodeDataModel->restore(json["model"].toObject()); 79 | } 80 | 81 | 82 | QUuid 83 | Node:: 84 | id() const 85 | { 86 | return _uid; 87 | } 88 | 89 | 90 | void 91 | Node:: 92 | reactToPossibleConnection(PortType reactingPortType, 93 | NodeDataType const &reactingDataType, 94 | QPointF const &scenePoint) 95 | { 96 | QTransform const t = _nodeGraphicsObject->sceneTransform(); 97 | 98 | QPointF p = t.inverted().map(scenePoint); 99 | 100 | _nodeGeometry.setDraggingPosition(p); 101 | 102 | _nodeGraphicsObject->update(); 103 | 104 | _nodeState.setReaction(NodeState::REACTING, 105 | reactingPortType, 106 | reactingDataType); 107 | } 108 | 109 | 110 | void 111 | Node:: 112 | resetReactionToConnection() 113 | { 114 | _nodeState.setReaction(NodeState::NOT_REACTING); 115 | _nodeGraphicsObject->update(); 116 | } 117 | 118 | 119 | NodeGraphicsObject const & 120 | Node:: 121 | nodeGraphicsObject() const 122 | { 123 | return *_nodeGraphicsObject.get(); 124 | } 125 | 126 | 127 | NodeGraphicsObject & 128 | Node:: 129 | nodeGraphicsObject() 130 | { 131 | return *_nodeGraphicsObject.get(); 132 | } 133 | 134 | 135 | void 136 | Node:: 137 | setGraphicsObject(std::unique_ptr&& graphics) 138 | { 139 | _nodeGraphicsObject = std::move(graphics); 140 | 141 | _nodeGeometry.recalculateSize(); 142 | } 143 | 144 | 145 | NodeGeometry& 146 | Node:: 147 | nodeGeometry() 148 | { 149 | return _nodeGeometry; 150 | } 151 | 152 | 153 | NodeGeometry const& 154 | Node:: 155 | nodeGeometry() const 156 | { 157 | return _nodeGeometry; 158 | } 159 | 160 | 161 | NodeState const & 162 | Node:: 163 | nodeState() const 164 | { 165 | return _nodeState; 166 | } 167 | 168 | 169 | NodeState & 170 | Node:: 171 | nodeState() 172 | { 173 | return _nodeState; 174 | } 175 | 176 | 177 | NodeDataModel* 178 | Node:: 179 | nodeDataModel() const 180 | { 181 | return _nodeDataModel.get(); 182 | } 183 | 184 | 185 | void 186 | Node:: 187 | propagateData(std::shared_ptr nodeData, 188 | PortIndex inPortIndex) const 189 | { 190 | _nodeDataModel->setInData(std::move(nodeData), inPortIndex); 191 | 192 | //Recalculate the nodes visuals. A data change can result in the node taking more space than before, so this forces a recalculate+repaint on the affected node 193 | _nodeGraphicsObject->setGeometryChanged(); 194 | _nodeGeometry.recalculateSize(); 195 | _nodeGraphicsObject->update(); 196 | _nodeGraphicsObject->moveConnections(); 197 | } 198 | 199 | 200 | void 201 | Node:: 202 | onDataUpdated(PortIndex index) 203 | { 204 | auto nodeData = _nodeDataModel->outData(index); 205 | 206 | auto connections = 207 | _nodeState.connections(PortType::Out, index); 208 | 209 | for (auto const & c : connections) 210 | c.second->propagateData(nodeData); 211 | } 212 | 213 | void 214 | Node:: 215 | onNodeSizeUpdated() 216 | { 217 | if( nodeDataModel()->embeddedWidget() ) 218 | { 219 | nodeDataModel()->embeddedWidget()->adjustSize(); 220 | } 221 | nodeGeometry().recalculateSize(); 222 | for(PortType type: {PortType::In, PortType::Out}) 223 | { 224 | for(auto& conn_set : nodeState().getEntries(type)) 225 | { 226 | for(auto& pair: conn_set) 227 | { 228 | Connection* conn = pair.second; 229 | conn->getConnectionGraphicsObject().move(); 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/NodeConnectionInteraction.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeConnectionInteraction.hpp" 2 | 3 | #include "ConnectionGraphicsObject.hpp" 4 | #include "NodeGraphicsObject.hpp" 5 | #include "NodeDataModel.hpp" 6 | #include "DataModelRegistry.hpp" 7 | #include "FlowScene.hpp" 8 | 9 | using QtNodes::NodeConnectionInteraction; 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::FlowScene; 13 | using QtNodes::Node; 14 | using QtNodes::Connection; 15 | using QtNodes::NodeDataModel; 16 | using QtNodes::TypeConverter; 17 | 18 | 19 | NodeConnectionInteraction:: 20 | NodeConnectionInteraction(Node& node, Connection& connection, FlowScene& scene) 21 | : _node(&node) 22 | , _connection(&connection) 23 | , _scene(&scene) 24 | {} 25 | 26 | 27 | bool 28 | NodeConnectionInteraction:: 29 | canConnect(PortIndex &portIndex, TypeConverter & converter) const 30 | { 31 | // 1) Connection requires a port 32 | 33 | PortType requiredPort = connectionRequiredPort(); 34 | 35 | 36 | if (requiredPort == PortType::None) 37 | { 38 | return false; 39 | } 40 | 41 | // 1.5) Forbid connecting the node to itself 42 | Node* node = _connection->getNode(oppositePort(requiredPort)); 43 | 44 | if (node == _node) 45 | return false; 46 | 47 | // 2) connection point is on top of the node port 48 | 49 | QPointF connectionPoint = connectionEndScenePosition(requiredPort); 50 | 51 | portIndex = nodePortIndexUnderScenePoint(requiredPort, 52 | connectionPoint); 53 | 54 | if (portIndex == INVALID) 55 | { 56 | return false; 57 | } 58 | 59 | // 3) Node port is vacant 60 | 61 | // port should be empty 62 | if (!nodePortIsEmpty(requiredPort, portIndex)) 63 | return false; 64 | 65 | // 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two 66 | 67 | auto connectionDataType = 68 | _connection->dataType(oppositePort(requiredPort)); 69 | 70 | auto const &modelTarget = _node->nodeDataModel(); 71 | NodeDataType candidateNodeDataType = modelTarget->dataType(requiredPort, portIndex); 72 | 73 | if (connectionDataType.id != candidateNodeDataType.id) 74 | { 75 | if (requiredPort == PortType::In) 76 | { 77 | converter = _scene->registry().getTypeConverter(connectionDataType, candidateNodeDataType); 78 | } 79 | else if (requiredPort == PortType::Out) 80 | { 81 | converter = _scene->registry().getTypeConverter(candidateNodeDataType , connectionDataType); 82 | } 83 | 84 | return (converter != nullptr); 85 | } 86 | 87 | return true; 88 | } 89 | 90 | 91 | bool 92 | NodeConnectionInteraction:: 93 | tryConnect() const 94 | { 95 | // 1) Check conditions from 'canConnect' 96 | PortIndex portIndex = INVALID; 97 | 98 | TypeConverter converter; 99 | 100 | if (!canConnect(portIndex, converter)) 101 | { 102 | return false; 103 | } 104 | 105 | // 1.5) If the connection is possible but a type conversion is needed, 106 | // assign a convertor to connection 107 | if (converter) 108 | { 109 | _connection->setTypeConverter(converter); 110 | } 111 | 112 | // 2) Assign node to required port in Connection 113 | PortType requiredPort = connectionRequiredPort(); 114 | _node->nodeState().setConnection(requiredPort, 115 | portIndex, 116 | *_connection); 117 | 118 | // 3) Assign Connection to empty port in NodeState 119 | // The port is not longer required after this function 120 | _connection->setNodeToPort(*_node, requiredPort, portIndex); 121 | 122 | // 4) Adjust Connection geometry 123 | 124 | _node->nodeGraphicsObject().moveConnections(); 125 | 126 | // 5) Poke model to intiate data transfer 127 | 128 | auto outNode = _connection->getNode(PortType::Out); 129 | if (outNode) 130 | { 131 | PortIndex outPortIndex = _connection->getPortIndex(PortType::Out); 132 | outNode->onDataUpdated(outPortIndex); 133 | } 134 | 135 | return true; 136 | } 137 | 138 | 139 | /// 1) Node and Connection should be already connected 140 | /// 2) If so, clear Connection entry in the NodeState 141 | /// 3) Set Connection end to 'requiring a port' 142 | bool 143 | NodeConnectionInteraction:: 144 | disconnect(PortType portToDisconnect) const 145 | { 146 | PortIndex portIndex = 147 | _connection->getPortIndex(portToDisconnect); 148 | 149 | NodeState &state = _node->nodeState(); 150 | 151 | // clear pointer to Connection in the NodeState 152 | state.getEntries(portToDisconnect)[portIndex].clear(); 153 | 154 | // 4) Propagate invalid data to IN node 155 | _connection->propagateEmptyData(); 156 | 157 | // clear Connection side 158 | _connection->clearNode(portToDisconnect); 159 | 160 | _connection->setRequiredPort(portToDisconnect); 161 | 162 | _connection->getConnectionGraphicsObject().grabMouse(); 163 | 164 | return true; 165 | } 166 | 167 | 168 | // ------------------ util functions below 169 | 170 | PortType 171 | NodeConnectionInteraction:: 172 | connectionRequiredPort() const 173 | { 174 | auto const &state = _connection->connectionState(); 175 | 176 | return state.requiredPort(); 177 | } 178 | 179 | 180 | QPointF 181 | NodeConnectionInteraction:: 182 | connectionEndScenePosition(PortType portType) const 183 | { 184 | auto &go = 185 | _connection->getConnectionGraphicsObject(); 186 | 187 | ConnectionGeometry& geometry = _connection->connectionGeometry(); 188 | 189 | QPointF endPoint = geometry.getEndPoint(portType); 190 | 191 | return go.mapToScene(endPoint); 192 | } 193 | 194 | 195 | QPointF 196 | NodeConnectionInteraction:: 197 | nodePortScenePosition(PortType portType, PortIndex portIndex) const 198 | { 199 | NodeGeometry const &geom = _node->nodeGeometry(); 200 | 201 | QPointF p = geom.portScenePosition(portIndex, portType); 202 | 203 | NodeGraphicsObject& ngo = _node->nodeGraphicsObject(); 204 | 205 | return ngo.sceneTransform().map(p); 206 | } 207 | 208 | 209 | PortIndex 210 | NodeConnectionInteraction:: 211 | nodePortIndexUnderScenePoint(PortType portType, 212 | QPointF const & scenePoint) const 213 | { 214 | NodeGeometry const &nodeGeom = _node->nodeGeometry(); 215 | 216 | QTransform sceneTransform = 217 | _node->nodeGraphicsObject().sceneTransform(); 218 | 219 | PortIndex portIndex = nodeGeom.checkHitScenePoint(portType, 220 | scenePoint, 221 | sceneTransform); 222 | return portIndex; 223 | } 224 | 225 | 226 | bool 227 | NodeConnectionInteraction:: 228 | nodePortIsEmpty(PortType portType, PortIndex portIndex) const 229 | { 230 | NodeState const & nodeState = _node->nodeState(); 231 | 232 | auto const & entries = nodeState.getEntries(portType); 233 | 234 | if (entries[portIndex].empty()) return true; 235 | 236 | const auto outPolicy = _node->nodeDataModel()->portOutConnectionPolicy(portIndex); 237 | return ( portType == PortType::Out && outPolicy == NodeDataModel::ConnectionPolicy::Many); 238 | } 239 | -------------------------------------------------------------------------------- /src/NodeConnectionInteraction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Node.hpp" 4 | #include "Connection.hpp" 5 | 6 | namespace QtNodes 7 | { 8 | 9 | class DataModelRegistry; 10 | class FlowScene; 11 | class NodeDataModel; 12 | 13 | /// Class performs various operations on the Node and Connection pair. 14 | /// An instance should be created on the stack and destroyed when 15 | /// the operation is completed 16 | class NodeConnectionInteraction 17 | { 18 | public: 19 | NodeConnectionInteraction(Node& node, 20 | Connection& connection, 21 | FlowScene& scene); 22 | 23 | /// Can connect when following conditions are met: 24 | /// 1) Connection 'requires' a port 25 | /// 2) Connection's vacant end is above the node port 26 | /// 3) Node port is vacant 27 | /// 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two 28 | bool canConnect(PortIndex & portIndex, 29 | TypeConverter & converter) const; 30 | 31 | /// 1) Check conditions from 'canConnect' 32 | /// 1.5) If the connection is possible but a type conversion is needed, add a converter node to the scene, and connect it properly 33 | /// 2) Assign node to required port in Connection 34 | /// 3) Assign Connection to empty port in NodeState 35 | /// 4) Adjust Connection geometry 36 | /// 5) Poke model to initiate data transfer 37 | bool tryConnect() const; 38 | 39 | 40 | /// 1) Node and Connection should be already connected 41 | /// 2) If so, clear Connection entry in the NodeState 42 | /// 3) Propagate invalid data to IN node 43 | /// 4) Set Connection end to 'requiring a port' 44 | bool disconnect(PortType portToDisconnect) const; 45 | 46 | private: 47 | 48 | PortType connectionRequiredPort() const; 49 | 50 | QPointF connectionEndScenePosition(PortType) const; 51 | 52 | QPointF nodePortScenePosition(PortType portType, 53 | PortIndex portIndex) const; 54 | 55 | PortIndex nodePortIndexUnderScenePoint(PortType portType, 56 | QPointF const &p) const; 57 | 58 | bool nodePortIsEmpty(PortType portType, PortIndex portIndex) const; 59 | 60 | private: 61 | 62 | Node* _node; 63 | 64 | Connection* _connection; 65 | 66 | FlowScene* _scene; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/NodeDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeDataModel.hpp" 2 | 3 | #include "StyleCollection.hpp" 4 | 5 | using QtNodes::NodeDataModel; 6 | using QtNodes::NodeStyle; 7 | 8 | NodeDataModel:: 9 | NodeDataModel() 10 | : _nodeStyle(StyleCollection::nodeStyle()) 11 | { 12 | // Derived classes can initialize specific style here 13 | } 14 | 15 | 16 | QJsonObject 17 | NodeDataModel:: 18 | save() const 19 | { 20 | QJsonObject modelJson; 21 | 22 | modelJson["name"] = name(); 23 | 24 | return modelJson; 25 | } 26 | 27 | 28 | NodeStyle const& 29 | NodeDataModel:: 30 | nodeStyle() const 31 | { 32 | return _nodeStyle; 33 | } 34 | 35 | 36 | void 37 | NodeDataModel:: 38 | setNodeStyle(NodeStyle const& style) 39 | { 40 | _nodeStyle = style; 41 | } 42 | -------------------------------------------------------------------------------- /src/NodePainter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class Node; 9 | class NodeState; 10 | class NodeGeometry; 11 | class NodeGraphicsObject; 12 | class NodeDataModel; 13 | class FlowItemEntry; 14 | class FlowScene; 15 | 16 | class NodePainter 17 | { 18 | public: 19 | 20 | NodePainter(); 21 | 22 | public: 23 | 24 | static 25 | void 26 | paint(QPainter* painter, 27 | Node& node, 28 | FlowScene const& scene); 29 | 30 | static 31 | void 32 | drawNodeRect(QPainter* painter, 33 | NodeGeometry const& geom, 34 | NodeDataModel const* model, 35 | NodeGraphicsObject const & graphicsObject); 36 | 37 | static 38 | void 39 | drawModelName(QPainter* painter, 40 | NodeGeometry const& geom, 41 | NodeState const& state, 42 | NodeDataModel const * model); 43 | 44 | static 45 | void 46 | drawEntryLabels(QPainter* painter, 47 | NodeGeometry const& geom, 48 | NodeState const& state, 49 | NodeDataModel const * model); 50 | 51 | static 52 | void 53 | drawConnectionPoints(QPainter* painter, 54 | NodeGeometry const& geom, 55 | NodeState const& state, 56 | NodeDataModel const * model, 57 | FlowScene const & scene); 58 | 59 | static 60 | void 61 | drawFilledConnectionPoints(QPainter* painter, 62 | NodeGeometry const& geom, 63 | NodeState const& state, 64 | NodeDataModel const * model); 65 | 66 | static 67 | void 68 | drawResizeRect(QPainter* painter, 69 | NodeGeometry const& geom, 70 | NodeDataModel const * model); 71 | 72 | static 73 | void 74 | drawValidationRect(QPainter * painter, 75 | NodeGeometry const & geom, 76 | NodeDataModel const * model, 77 | NodeGraphicsObject const & graphicsObject); 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /src/NodeState.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeState.hpp" 2 | 3 | #include "NodeDataModel.hpp" 4 | 5 | #include "Connection.hpp" 6 | 7 | using QtNodes::NodeState; 8 | using QtNodes::NodeDataType; 9 | using QtNodes::NodeDataModel; 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::Connection; 13 | 14 | NodeState:: 15 | NodeState(std::unique_ptr const &model) 16 | : _inConnections(model->nPorts(PortType::In)) 17 | , _outConnections(model->nPorts(PortType::Out)) 18 | , _reaction(NOT_REACTING) 19 | , _reactingPortType(PortType::None) 20 | , _resizing(false) 21 | {} 22 | 23 | 24 | std::vector const & 25 | NodeState:: 26 | getEntries(PortType portType) const 27 | { 28 | if (portType == PortType::In) 29 | return _inConnections; 30 | else 31 | return _outConnections; 32 | } 33 | 34 | 35 | std::vector & 36 | NodeState:: 37 | getEntries(PortType portType) 38 | { 39 | if (portType == PortType::In) 40 | return _inConnections; 41 | else 42 | return _outConnections; 43 | } 44 | 45 | 46 | NodeState::ConnectionPtrSet 47 | NodeState:: 48 | connections(PortType portType, PortIndex portIndex) const 49 | { 50 | auto const &connections = getEntries(portType); 51 | 52 | return connections[portIndex]; 53 | } 54 | 55 | 56 | void 57 | NodeState:: 58 | setConnection(PortType portType, 59 | PortIndex portIndex, 60 | Connection& connection) 61 | { 62 | auto &connections = getEntries(portType); 63 | 64 | connections.at(portIndex).insert(std::make_pair(connection.id(), 65 | &connection)); 66 | } 67 | 68 | 69 | void 70 | NodeState:: 71 | eraseConnection(PortType portType, 72 | PortIndex portIndex, 73 | QUuid id) 74 | { 75 | getEntries(portType)[portIndex].erase(id); 76 | } 77 | 78 | 79 | NodeState::ReactToConnectionState 80 | NodeState:: 81 | reaction() const 82 | { 83 | return _reaction; 84 | } 85 | 86 | 87 | PortType 88 | NodeState:: 89 | reactingPortType() const 90 | { 91 | return _reactingPortType; 92 | } 93 | 94 | 95 | NodeDataType 96 | NodeState:: 97 | reactingDataType() const 98 | { 99 | return _reactingDataType; 100 | } 101 | 102 | 103 | void 104 | NodeState:: 105 | setReaction(ReactToConnectionState reaction, 106 | PortType reactingPortType, 107 | NodeDataType reactingDataType) 108 | { 109 | _reaction = reaction; 110 | 111 | _reactingPortType = reactingPortType; 112 | 113 | _reactingDataType = std::move(reactingDataType); 114 | } 115 | 116 | 117 | bool 118 | NodeState:: 119 | isReacting() const 120 | { 121 | return _reaction == REACTING; 122 | } 123 | 124 | 125 | void 126 | NodeState:: 127 | setResizing(bool resizing) 128 | { 129 | _resizing = resizing; 130 | } 131 | 132 | 133 | bool 134 | NodeState:: 135 | resizing() const 136 | { 137 | return _resizing; 138 | } 139 | -------------------------------------------------------------------------------- /src/NodeStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeStyle.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "StyleCollection.hpp" 14 | 15 | using QtNodes::NodeStyle; 16 | 17 | inline void initResources() { Q_INIT_RESOURCE(resources); } 18 | 19 | NodeStyle:: 20 | NodeStyle() 21 | { 22 | // Explicit resources inialization for preventing the static initialization 23 | // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order 24 | initResources(); 25 | 26 | // This configuration is stored inside the compiled unit and is loaded statically 27 | loadJsonFile(":DefaultStyle.json"); 28 | } 29 | 30 | 31 | NodeStyle:: 32 | NodeStyle(QString jsonText) 33 | { 34 | loadJsonText(jsonText); 35 | } 36 | 37 | 38 | void 39 | NodeStyle:: 40 | setNodeStyle(QString jsonText) 41 | { 42 | NodeStyle style(jsonText); 43 | 44 | 45 | StyleCollection::setNodeStyle(style); 46 | } 47 | 48 | 49 | #ifdef STYLE_DEBUG 50 | #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ 51 | if (v.type() == QJsonValue::Undefined || \ 52 | v.type() == QJsonValue::Null) \ 53 | qWarning() << "Undefined value for parameter:" << #variable; \ 54 | } 55 | #else 56 | #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) 57 | #endif 58 | 59 | #define NODE_STYLE_READ_COLOR(values, variable) { \ 60 | auto valueRef = values[#variable]; \ 61 | NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 62 | if (valueRef.isArray()) { \ 63 | auto colorArray = valueRef.toArray(); \ 64 | std::vector rgb; rgb.reserve(3); \ 65 | for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ 66 | rgb.push_back((*it).toInt()); \ 67 | } \ 68 | variable = QColor(rgb[0], rgb[1], rgb[2]); \ 69 | } else { \ 70 | variable = QColor(valueRef.toString()); \ 71 | } \ 72 | } 73 | 74 | #define NODE_STYLE_READ_FLOAT(values, variable) { \ 75 | auto valueRef = values[#variable]; \ 76 | NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 77 | variable = valueRef.toDouble(); \ 78 | } 79 | 80 | void 81 | NodeStyle:: 82 | loadJsonFile(QString styleFile) 83 | { 84 | QFile file(styleFile); 85 | 86 | if (!file.open(QIODevice::ReadOnly)) 87 | { 88 | qWarning() << "Couldn't open file " << styleFile; 89 | 90 | return; 91 | } 92 | 93 | loadJsonFromByteArray(file.readAll()); 94 | } 95 | 96 | 97 | void 98 | NodeStyle:: 99 | loadJsonText(QString jsonText) 100 | { 101 | loadJsonFromByteArray(jsonText.toUtf8()); 102 | } 103 | 104 | 105 | void 106 | NodeStyle:: 107 | loadJsonFromByteArray(QByteArray const &byteArray) 108 | { 109 | QJsonDocument json(QJsonDocument::fromJson(byteArray)); 110 | 111 | QJsonObject topLevelObject = json.object(); 112 | 113 | QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"]; 114 | 115 | QJsonObject obj = nodeStyleValues.toObject(); 116 | 117 | NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor); 118 | NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor); 119 | NODE_STYLE_READ_COLOR(obj, GradientColor0); 120 | NODE_STYLE_READ_COLOR(obj, GradientColor1); 121 | NODE_STYLE_READ_COLOR(obj, GradientColor2); 122 | NODE_STYLE_READ_COLOR(obj, GradientColor3); 123 | NODE_STYLE_READ_COLOR(obj, ShadowColor); 124 | NODE_STYLE_READ_COLOR(obj, FontColor); 125 | NODE_STYLE_READ_COLOR(obj, FontColorFaded); 126 | NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); 127 | NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); 128 | NODE_STYLE_READ_COLOR(obj, WarningColor); 129 | NODE_STYLE_READ_COLOR(obj, ErrorColor); 130 | 131 | NODE_STYLE_READ_FLOAT(obj, PenWidth); 132 | NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth); 133 | NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); 134 | 135 | NODE_STYLE_READ_FLOAT(obj, Opacity); 136 | } 137 | -------------------------------------------------------------------------------- /src/Properties.cpp: -------------------------------------------------------------------------------- 1 | #include "Properties.hpp" 2 | 3 | using QtNodes::Properties; 4 | 5 | void 6 | Properties:: 7 | put(QString const &name, QVariant const &v) 8 | { 9 | _values.insert(name, v); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Properties.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "Export.hpp" 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class NODE_EDITOR_PUBLIC Properties 13 | { 14 | public: 15 | 16 | void 17 | put(QString const &name, QVariant const &v); 18 | 19 | template 20 | bool 21 | get(QString name, T* v) const 22 | { 23 | QVariant const &var = _values[name]; 24 | 25 | if (var.canConvert()) 26 | { 27 | *v = _values[name].value(); 28 | 29 | return true; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | QVariantMap const & 36 | values() const 37 | { return _values; } 38 | 39 | QVariantMap & 40 | values() 41 | { return _values; } 42 | 43 | private: 44 | 45 | QVariantMap _values; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/StyleCollection.cpp: -------------------------------------------------------------------------------- 1 | #include "StyleCollection.hpp" 2 | 3 | using QtNodes::StyleCollection; 4 | using QtNodes::NodeStyle; 5 | using QtNodes::ConnectionStyle; 6 | using QtNodes::FlowViewStyle; 7 | 8 | NodeStyle const& 9 | StyleCollection:: 10 | nodeStyle() 11 | { 12 | return instance()._nodeStyle; 13 | } 14 | 15 | 16 | ConnectionStyle const& 17 | StyleCollection:: 18 | connectionStyle() 19 | { 20 | return instance()._connectionStyle; 21 | } 22 | 23 | 24 | FlowViewStyle const& 25 | StyleCollection:: 26 | flowViewStyle() 27 | { 28 | return instance()._flowViewStyle; 29 | } 30 | 31 | 32 | void 33 | StyleCollection:: 34 | setNodeStyle(NodeStyle nodeStyle) 35 | { 36 | instance()._nodeStyle = nodeStyle; 37 | } 38 | 39 | 40 | void 41 | StyleCollection:: 42 | setConnectionStyle(ConnectionStyle connectionStyle) 43 | { 44 | instance()._connectionStyle = connectionStyle; 45 | } 46 | 47 | 48 | void 49 | StyleCollection:: 50 | setFlowViewStyle(FlowViewStyle flowViewStyle) 51 | { 52 | instance()._flowViewStyle = flowViewStyle; 53 | } 54 | 55 | 56 | 57 | StyleCollection& 58 | StyleCollection:: 59 | instance() 60 | { 61 | static StyleCollection collection; 62 | 63 | return collection; 64 | } 65 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 2.3.0 REQUIRED) 2 | 3 | if (Qt6_FOUND) 4 | find_package(Qt6 COMPONENTS Test) 5 | else() 6 | find_package(Qt5 COMPONENTS Test) 7 | endif() 8 | 9 | add_executable(test_nodes 10 | test_main.cpp 11 | src/TestDragging.cpp 12 | src/TestDataModelRegistry.cpp 13 | src/TestFlowScene.cpp 14 | src/TestNodeGraphicsObject.cpp 15 | ) 16 | 17 | target_include_directories(test_nodes 18 | PRIVATE 19 | ../src 20 | ../include/internal 21 | include 22 | ) 23 | 24 | target_link_libraries(test_nodes 25 | PRIVATE 26 | NodeEditor::nodes 27 | Catch2::Catch2 28 | Qt::Test 29 | ) 30 | 31 | add_test( 32 | NAME test_nodes 33 | COMMAND 34 | $ 35 | $<$:--use-colour=yes> 36 | ) 37 | -------------------------------------------------------------------------------- /test/include/ApplicationSetup.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | inline std::unique_ptr 9 | applicationSetup() 10 | { 11 | static int Argc = 0; 12 | static char ArgvVal = '\0'; 13 | static char* ArgvValPtr = &ArgvVal; 14 | static char** Argv = &ArgvValPtr; 15 | 16 | auto app = std::make_unique(Argc, Argv); 17 | app->setAttribute(Qt::AA_Use96Dpi, true); 18 | 19 | return app; 20 | } 21 | -------------------------------------------------------------------------------- /test/include/Stringify.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace Catch 11 | { 12 | template <> 13 | struct StringMaker 14 | { 15 | static std::string 16 | convert(QPointF const& p) 17 | { 18 | return std::string(QTest::toString(p)); 19 | } 20 | }; 21 | 22 | template <> 23 | struct StringMaker 24 | { 25 | static std::string 26 | convert(QPoint const& p) 27 | { 28 | return std::string(QTest::toString(p)); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /test/include/StubNodeDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class StubNodeDataModel : public QtNodes::NodeDataModel 8 | { 9 | public: 10 | QString 11 | name() const override 12 | { 13 | return _name; 14 | } 15 | 16 | QString 17 | caption() const override 18 | { 19 | return _caption; 20 | } 21 | 22 | unsigned int nPorts(QtNodes::PortType) const override { return 0; } 23 | 24 | QWidget* 25 | embeddedWidget() override 26 | { 27 | return nullptr; 28 | } 29 | 30 | QtNodes::NodeDataType dataType(QtNodes::PortType, QtNodes::PortIndex) const override 31 | { 32 | return QtNodes::NodeDataType(); 33 | } 34 | 35 | std::shared_ptr outData(QtNodes::PortIndex) override 36 | { 37 | return nullptr; 38 | } 39 | 40 | void setInData(std::shared_ptr, QtNodes::PortIndex) override 41 | { 42 | } 43 | 44 | void 45 | name(QString name) 46 | { 47 | _name = std::move(name); 48 | } 49 | 50 | void 51 | caption(QString caption) 52 | { 53 | _caption = std::move(caption); 54 | } 55 | 56 | private: 57 | QString _name = "name"; 58 | QString _caption = "caption"; 59 | }; 60 | -------------------------------------------------------------------------------- /test/src/TestDataModelRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "StubNodeDataModel.hpp" 6 | 7 | using QtNodes::DataModelRegistry; 8 | using QtNodes::NodeData; 9 | using QtNodes::NodeDataModel; 10 | using QtNodes::NodeDataType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::PortType; 13 | 14 | namespace 15 | { 16 | class StubModelStaticName : public StubNodeDataModel 17 | { 18 | public: 19 | static QString 20 | Name() 21 | { 22 | return "Name"; 23 | } 24 | }; 25 | } 26 | 27 | TEST_CASE("DataModelRegistry::registerModel", "[interface]") 28 | { 29 | DataModelRegistry registry; 30 | 31 | SECTION("stub model") 32 | { 33 | registry.registerModel(); 34 | auto model = registry.create("name"); 35 | 36 | CHECK(model->name() == "name"); 37 | } 38 | SECTION("stub model with static name") 39 | { 40 | registry.registerModel(); 41 | auto model = registry.create("Name"); 42 | 43 | CHECK(model->name() == "name"); 44 | } 45 | SECTION("From model creator function") 46 | { 47 | SECTION("non-static name()") 48 | { 49 | registry.registerModel([] { 50 | return std::make_unique(); 51 | }); 52 | 53 | auto model = registry.create("name"); 54 | 55 | REQUIRE(model != nullptr); 56 | CHECK(model->name() == "name"); 57 | CHECK(dynamic_cast(model.get())); 58 | } 59 | SECTION("static Name()") 60 | { 61 | registry.registerModel([] { 62 | return std::make_unique(); 63 | }); 64 | 65 | auto model = registry.create("Name"); 66 | 67 | REQUIRE(model != nullptr); 68 | CHECK(model->name() == "name"); 69 | CHECK(dynamic_cast(model.get())); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/src/TestDragging.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "ApplicationSetup.hpp" 12 | #include "Stringify.hpp" 13 | #include "StubNodeDataModel.hpp" 14 | 15 | using QtNodes::Connection; 16 | using QtNodes::DataModelRegistry; 17 | using QtNodes::FlowScene; 18 | using QtNodes::FlowView; 19 | using QtNodes::Node; 20 | using QtNodes::NodeData; 21 | using QtNodes::NodeDataModel; 22 | using QtNodes::NodeDataType; 23 | using QtNodes::PortIndex; 24 | using QtNodes::PortType; 25 | 26 | TEST_CASE("Dragging node changes position", "[gui]") 27 | { 28 | auto app = applicationSetup(); 29 | 30 | FlowScene scene; 31 | FlowView view(&scene); 32 | 33 | view.show(); 34 | REQUIRE(QTest::qWaitForWindowExposed(&view)); 35 | 36 | SECTION("just one node") 37 | { 38 | auto& node = scene.createNode(std::make_unique()); 39 | 40 | auto& ngo = node.nodeGraphicsObject(); 41 | 42 | QPointF scPosBefore = ngo.pos(); 43 | 44 | QPointF scClickPos = ngo.boundingRect().center(); 45 | scClickPos = QPointF(ngo.sceneTransform().map(scClickPos).toPoint()); 46 | 47 | QPoint vwClickPos = view.mapFromScene(scClickPos); 48 | QPoint vwDestPos = vwClickPos + QPoint(10, 20); 49 | 50 | QPointF scExpectedDelta = view.mapToScene(vwDestPos) - scClickPos; 51 | 52 | CAPTURE(scClickPos); 53 | CAPTURE(vwClickPos); 54 | CAPTURE(vwDestPos); 55 | CAPTURE(scExpectedDelta); 56 | 57 | QTest::mouseMove(view.windowHandle(), vwClickPos); 58 | QTest::mousePress(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwClickPos); 59 | QTest::mouseMove(view.windowHandle(), vwDestPos); 60 | QTest::mouseRelease(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwDestPos); 61 | 62 | QPointF scDelta = ngo.pos() - scPosBefore; 63 | QPoint roundDelta = scDelta.toPoint(); 64 | QPoint roundExpectedDelta = scExpectedDelta.toPoint(); 65 | 66 | CHECK(roundDelta == roundExpectedDelta); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/src/TestFlowScene.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "ApplicationSetup.hpp" 14 | #include "Stringify.hpp" 15 | #include "StubNodeDataModel.hpp" 16 | 17 | using QtNodes::Connection; 18 | using QtNodes::DataModelRegistry; 19 | using QtNodes::FlowScene; 20 | using QtNodes::Node; 21 | using QtNodes::NodeData; 22 | using QtNodes::NodeDataModel; 23 | using QtNodes::NodeDataType; 24 | using QtNodes::PortIndex; 25 | using QtNodes::PortType; 26 | 27 | TEST_CASE("FlowScene triggers connections created or deleted", "[gui]") 28 | { 29 | struct MockDataModel : StubNodeDataModel 30 | { 31 | unsigned int nPorts(PortType) const override { return 1; } 32 | 33 | void 34 | inputConnectionCreated(Connection const&) override 35 | { 36 | inputCreatedCalledCount++; 37 | } 38 | 39 | void 40 | inputConnectionDeleted(Connection const&) override 41 | { 42 | inputDeletedCalledCount++; 43 | } 44 | 45 | void 46 | outputConnectionCreated(Connection const&) override 47 | { 48 | outputCreatedCalledCount++; 49 | } 50 | 51 | void 52 | outputConnectionDeleted(Connection const&) override 53 | { 54 | outputDeletedCalledCount++; 55 | } 56 | 57 | int inputCreatedCalledCount = 0; 58 | int inputDeletedCalledCount = 0; 59 | int outputCreatedCalledCount = 0; 60 | int outputDeletedCalledCount = 0; 61 | 62 | void 63 | resetCallCounts() 64 | { 65 | inputCreatedCalledCount = 0; 66 | inputDeletedCalledCount = 0; 67 | outputCreatedCalledCount = 0; 68 | outputDeletedCalledCount = 0; 69 | } 70 | }; 71 | 72 | auto setup = applicationSetup(); 73 | 74 | FlowScene scene; 75 | 76 | Node& fromNode = scene.createNode(std::make_unique()); 77 | Node& toNode = scene.createNode(std::make_unique()); 78 | Node& unrelatedNode = scene.createNode(std::make_unique()); 79 | 80 | auto& fromNgo = fromNode.nodeGraphicsObject(); 81 | auto& toNgo = toNode.nodeGraphicsObject(); 82 | auto& unrelatedNgo = unrelatedNode.nodeGraphicsObject(); 83 | 84 | fromNgo.setPos(0, 0); 85 | toNgo.setPos(200, 20); 86 | unrelatedNgo.setPos(-100, -100); 87 | 88 | auto& from = dynamic_cast(*fromNode.nodeDataModel()); 89 | auto& to = dynamic_cast(*toNode.nodeDataModel()); 90 | auto& unrelated = dynamic_cast(*unrelatedNode.nodeDataModel()); 91 | 92 | 93 | SECTION("creating half a connection (not finishing the connection)") 94 | { 95 | auto connection = scene.createConnection(PortType::Out, fromNode, 0); 96 | 97 | CHECK(from.inputCreatedCalledCount == 0); 98 | CHECK(from.outputCreatedCalledCount == 0); 99 | 100 | CHECK(to.inputCreatedCalledCount == 0); 101 | CHECK(to.outputCreatedCalledCount == 0); 102 | 103 | CHECK(unrelated.inputCreatedCalledCount == 0); 104 | CHECK(unrelated.outputCreatedCalledCount == 0); 105 | 106 | scene.deleteConnection(*connection); 107 | } 108 | 109 | struct Creation 110 | { 111 | std::string name; 112 | std::function()> createConnection; 113 | }; 114 | 115 | Creation sceneCreation{"scene.createConnection", 116 | [&] { return scene.createConnection(toNode, 0, fromNode, 0); }}; 117 | 118 | Creation partialCreation{"scene.createConnection-by partial", [&] { 119 | auto connection = scene.createConnection(PortType::Out, fromNode, 0); 120 | connection->setNodeToPort(toNode, PortType::In, 0); 121 | 122 | return connection; 123 | }}; 124 | 125 | struct Deletion 126 | { 127 | std::string name; 128 | std::function deleteConnection; 129 | }; 130 | 131 | Deletion sceneDeletion{"scene.deleteConnection", 132 | [&](Connection & c) { scene.deleteConnection(c); }}; 133 | 134 | Deletion partialDragDeletion{"scene-deleteConnectionByDraggingOff", 135 | [&](Connection & c) 136 | { 137 | PortIndex portIndex = c.getPortIndex(PortType::In); 138 | Node * node = c.getNode(PortType::In); 139 | node->nodeState().getEntries(PortType::In)[portIndex].clear(); 140 | c.clearNode(PortType::In); 141 | }}; 142 | 143 | SECTION("creating a connection") 144 | { 145 | std::vector cases({sceneCreation, partialCreation}); 146 | 147 | for (Creation const& create : cases) 148 | { 149 | SECTION(create.name) 150 | { 151 | auto connection = create.createConnection(); 152 | 153 | CHECK(from.inputCreatedCalledCount == 0); 154 | CHECK(from.outputCreatedCalledCount == 1); 155 | 156 | CHECK(to.inputCreatedCalledCount == 1); 157 | CHECK(to.outputCreatedCalledCount == 0); 158 | 159 | CHECK(unrelated.inputCreatedCalledCount == 0); 160 | CHECK(unrelated.outputCreatedCalledCount == 0); 161 | 162 | scene.deleteConnection(*connection); 163 | } 164 | } 165 | } 166 | 167 | SECTION("deleting a connection") 168 | { 169 | std::vector cases({sceneDeletion, partialDragDeletion}); 170 | 171 | for (auto const& deletion : cases) 172 | { 173 | SECTION("deletion: " + deletion.name) 174 | { 175 | Connection & connection = *sceneCreation.createConnection(); 176 | 177 | from.resetCallCounts(); 178 | to.resetCallCounts(); 179 | 180 | deletion.deleteConnection(connection); 181 | 182 | // Here the Connection reference becomes dangling 183 | 184 | CHECK(from.inputDeletedCalledCount == 0); 185 | CHECK(from.outputDeletedCalledCount == 1); 186 | 187 | CHECK(to.inputDeletedCalledCount == 1); 188 | CHECK(to.outputDeletedCalledCount == 0); 189 | 190 | CHECK(unrelated.inputDeletedCalledCount == 0); 191 | CHECK(unrelated.outputDeletedCalledCount == 0); 192 | } 193 | } 194 | } 195 | } 196 | 197 | 198 | TEST_CASE("FlowScene's DataModelRegistry outlives nodes and connections", "[asan][gui]") 199 | { 200 | class MockDataModel : public StubNodeDataModel 201 | { 202 | public: 203 | MockDataModel(int* const& incrementOnDestruction) 204 | : incrementOnDestruction(incrementOnDestruction) 205 | { 206 | } 207 | 208 | ~MockDataModel() 209 | { 210 | (*incrementOnDestruction)++; 211 | } 212 | 213 | // The reference ensures that we point into the memory that would be free'd 214 | // if the DataModelRegistry doesn't outlive this node 215 | int* const& incrementOnDestruction; 216 | }; 217 | 218 | struct MockDataModelCreator 219 | { 220 | MockDataModelCreator(int* shouldBeAliveWhenAssignedTo) 221 | : shouldBeAliveWhenAssignedTo(shouldBeAliveWhenAssignedTo) 222 | { 223 | } 224 | 225 | auto 226 | operator()() const 227 | { 228 | return std::make_unique(shouldBeAliveWhenAssignedTo); 229 | } 230 | 231 | int* shouldBeAliveWhenAssignedTo; 232 | }; 233 | 234 | int modelsDestroyed = 0; 235 | 236 | // Introduce a new scope, so that modelsDestroyed will be alive even after the 237 | // FlowScene is destroyed. 238 | { 239 | auto setup = applicationSetup(); 240 | 241 | auto registry = std::make_shared(); 242 | registry->registerModel(MockDataModelCreator(&modelsDestroyed)); 243 | 244 | modelsDestroyed = 0; 245 | 246 | FlowScene scene(std::move(registry)); 247 | 248 | auto& node = scene.createNode(scene.registry().create("name")); 249 | 250 | // On destruction, if this `node` outlives its MockDataModelCreator, 251 | // (if it outlives the DataModelRegistry), then we trigger undefined 252 | // behavior through use-after-free. ASAN will catch that. 253 | } 254 | 255 | CHECK(modelsDestroyed == 1); 256 | } 257 | -------------------------------------------------------------------------------- /test/src/TestNodeGraphicsObject.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "ApplicationSetup.hpp" 11 | #include "StubNodeDataModel.hpp" 12 | 13 | using QtNodes::FlowScene; 14 | using QtNodes::FlowView; 15 | using QtNodes::Node; 16 | using QtNodes::NodeDataModel; 17 | using QtNodes::NodeGraphicsObject; 18 | using QtNodes::PortType; 19 | 20 | TEST_CASE("NodeDataModel::portOutConnectionPolicy(...) isn't called for input " 21 | "connections (issue #127)", 22 | "[gui]") 23 | { 24 | class MockModel : public StubNodeDataModel 25 | { 26 | public: 27 | unsigned int nPorts(PortType) const override { return 1; } 28 | 29 | NodeDataModel::ConnectionPolicy 30 | portOutConnectionPolicy(int index) const override 31 | { 32 | portOutConnectionPolicyCalledCount++; 33 | return NodeDataModel::ConnectionPolicy::One; 34 | } 35 | 36 | mutable int portOutConnectionPolicyCalledCount = 0; 37 | }; 38 | 39 | auto setup = applicationSetup(); 40 | 41 | FlowScene scene; 42 | FlowView view(&scene); 43 | 44 | // Ensure we have enough size to contain the node 45 | view.resize(640, 480); 46 | 47 | view.show(); 48 | REQUIRE(QTest::qWaitForWindowExposed(&view)); 49 | 50 | auto& node = scene.createNode(std::make_unique()); 51 | auto& model = dynamic_cast(*node.nodeDataModel()); 52 | auto& ngo = node.nodeGraphicsObject(); 53 | auto& ngeom = node.nodeGeometry(); 54 | 55 | // Move the node to somewhere in the middle of the screen 56 | ngo.setPos(QPointF(50, 50)); 57 | 58 | // Compute the on-screen position of the input port 59 | QPointF scInPortPos = ngeom.portScenePosition(0, PortType::In, ngo.sceneTransform()); 60 | QPoint vwInPortPos = view.mapFromScene(scInPortPos); 61 | 62 | // Create a partial connection by clicking on the input port of the node 63 | QTest::mousePress(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwInPortPos); 64 | 65 | CHECK(model.portOutConnectionPolicyCalledCount == 0); 66 | } 67 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | --------------------------------------------------------------------------------