add NodeEditor library (modified by me, do not submodule later!!!)

This commit is contained in:
sshumakov3
2020-11-28 01:37:38 +03:00
parent 68d0400e3a
commit 18cd05afa2
133 changed files with 10824 additions and 0 deletions

189
src/external/NodeEditor/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,189 @@
cmake_minimum_required(VERSION 3.2)
# version 3.4 is required as other do not work with C++14 and clang
project(NodeEditor CXX)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
get_directory_property(_has_parent PARENT_DIRECTORY)
if(_has_parent)
set(is_root_project OFF)
else()
set(is_root_project ON)
endif()
set(NE_DEVELOPER_DEFAULTS "${is_root_project}" CACHE BOOL "Turns on default settings for development of NodeEditor")
option(BUILD_TESTING "Build tests" "${NE_DEVELOPER_DEFAULTS}")
option(BUILD_EXAMPLES "Build Examples" "${NE_DEVELOPER_DEFAULTS}")
option(BUILD_SHARED_LIBS "Build as shared library" OFF)
option(NE_FORCE_TEST_COLOR "Force colorized unit test output" OFF)
enable_testing()
if(NE_DEVELOPER_DEFAULTS)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
endif()
add_subdirectory(external)
# Find the QtWidgets library
find_package(Qt5 5.10 COMPONENTS
Core
Widgets
Gui
OpenGL)
qt5_add_resources(RESOURCES ./resources/resources.qrc)
# Unfortunately, as we have a split include/src, AUTOMOC doesn't work.
# We'll have to manually specify some files
set(CMAKE_AUTOMOC ON)
set(CPP_SOURCE_FILES
src/Connection.cpp
src/ConnectionBlurEffect.cpp
src/ConnectionGeometry.cpp
src/ConnectionGraphicsObject.cpp
src/ConnectionPainter.cpp
src/ConnectionState.cpp
src/ConnectionStyle.cpp
src/DataModelRegistry.cpp
src/FlowScene.cpp
src/FlowView.cpp
src/FlowViewStyle.cpp
src/Node.cpp
src/NodeConnectionInteraction.cpp
src/NodeDataModel.cpp
src/NodeGeometry.cpp
src/NodeGraphicsObject.cpp
src/NodePainter.cpp
src/NodeState.cpp
src/NodeStyle.cpp
src/Properties.cpp
src/StyleCollection.cpp
)
# If we want to give the option to build a static library,
# set BUILD_SHARED_LIBS option to OFF
add_library(nodes
${CPP_SOURCE_FILES}
${RESOURCES}
)
add_library(NodeEditor::nodes ALIAS nodes)
target_include_directories(nodes
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/nodes/internal>
)
target_link_libraries(nodes
PUBLIC
Qt5::Core
Qt5::Widgets
Qt5::Gui
Qt5::OpenGL
)
target_compile_definitions(nodes
PUBLIC
${Qt5Widgets_DEFINITIONS}
NODE_EDITOR_SHARED
PRIVATE
NODE_EDITOR_EXPORTS
#NODE_DEBUG_DRAWING
QT_NO_KEYWORDS
)
target_compile_options(nodes
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /wd4127 /EHsc>
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
)
target_compile_features(nodes
PUBLIC
cxx_generic_lambdas # Require C++14
)
set_target_properties(nodes
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
######
# Moc
##
file(GLOB_RECURSE HEADERS_TO_MOC include/nodes/internal/*.hpp)
qt5_wrap_cpp(nodes_moc
${HEADERS_TO_MOC}
TARGET nodes
OPTIONS --no-notes # Don't display a note for the headers which don't produce a moc_*.cpp
)
target_sources(nodes PRIVATE ${nodes_moc})
###########
# Examples
##
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
##################
# Automated Tests
##
if(BUILD_TESTING)
add_subdirectory(test)
endif()
###############
# Installation
##
include(GNUInstallDirs)
set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/NodeEditor)
install(TARGETS nodes
EXPORT NodeEditorTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT NodeEditorTargets
FILE NodeEditorTargets.cmake
NAMESPACE NodeEditor::
DESTINATION ${INSTALL_CONFIGDIR}
)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/NodeEditorConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake
INSTALL_DESTINATION ${INSTALL_CONFIGDIR}
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake
DESTINATION ${INSTALL_CONFIGDIR}
)

28
src/external/NodeEditor/LICENSE vendored Normal file
View File

@@ -0,0 +1,28 @@
Copyright (c) 2017, Dmitry Pinaev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of copyright holder, nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

122
src/external/NodeEditor/README.md vendored Normal file
View File

@@ -0,0 +1,122 @@
## About this Fork
This repository keeps master branch aligned with the original repository [paceholder/nodeeditor](https://github.com/paceholder/nodeeditor). In the develop branch can be found some fix or feature never merged in the original repository and some additional feature.
## Purpose
**NodeEditor** is conceived as a general-purpose Qt-based library aimed at
graph-controlled data processing. Nodes represent algorithms with certain inputs
and outputs. Connections transfer data from the output (source) of the first node
to the input (sink) of the second one.
**NodeEditor** framework is a Visual [Dataflow
Programming](https://en.wikipedia.org/wiki/Dataflow_programming) tool. A library
client defines models and registers them in the data model registry. Further
work is driven by events taking place in DataModels and Nodes. The model
computing is triggered upon arriving of any new input data. The computed result
is propagated to the output connections. Each new connection fetches available
data and propagates is further.
Each change in the source node is immediately propagated through all the
connections updating the whole graph.
### Platforms
* 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)
* Windows (Win32, x64, msvc2017, MinGW 5.3): [![Build status](https://ci.appveyor.com/api/projects/status/wxp47wv3uyyiujjw/branch/master?svg=true)](https://ci.appveyor.com/project/paceholder/nodeeditor/branch/master)
### Dependencies
* Qt >5.2
* CMake 3.2
* Catch2
### Current state
* Model-based nodes
* Automatic data propagation
* Datatype-aware connections
* Embedded Qt widgets
* One-output to many-input connections
* JSON-based interface styles
* Saving scenes to JSON files
### Building
#### Linux
~~~
git clone git@github.com:paceholder/nodeeditor.git
cd nodeeditor
mkdir build
cd build
cmake ..
make -j && make install
~~~
#### Qt Creator
1. Open `CMakeLists.txt` as project.
2. If you don't have the `Catch2` library installed, go to `Build Settings`, disable the checkbox `BUILD_TESTING`.
3. `Build -> Run CMake`
4. `Build -> Build All`
5. Click the button `Run`
### Roadmap
1. Extend set of examples
2. GUI: fix scrolling for scene view window scrolling
3. Implement grouping nodes
4. Split graph and GUI parts
5. Build data propagation on top of the graph code
### Citing
Dmitry Pinaev et al, Qt5 Node Editor, (2017), GitHub repository, https://github.com/paceholder/nodeeditor
BibTeX
@misc{Pinaev2017,
author = {Dmitry Pinaev et al},
title = {Qt5 Node Editor},
year = {2017},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/paceholder/nodeeditor}},
commit = {1d1757d09b03cea0e4921bc19659465fe6e65b9b}
}
### Youtube video:
[![Youtube demonstration](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/vid1.png)](https://www.youtube.com/watch?v=pxMXjSvlOFw)
### Now with styles
[![Styles](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/style_example.png)](https://www.youtube.com/watch?v=i_pB-Y0hCYQ)
### Buy me a beer
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/DmitryPinaev)
### Showcase
#### [Chigraph](https://github.com/chigraph/chigraph)
Chigraph is a programming language for beginners that is unique in that it is an
intuitive flow graph:
![chigraph screenshot](pictures/chigraph.png)
It features easy bindings to C/C++, package management, and a cool interface.
#### [Spkgen particle engine editor](https://github.com/fredakilla/spkgen)
![spkgen screenshot](pictures/spkgen.png)
Spkgen is an editor for the SPARK particles engine using a node-based interface
to create particles effects for games.

View File

@@ -0,0 +1,17 @@
get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(CMakeFindDependencyMacro)
# NOTE Had to use find_package because find_dependency does not support COMPONENTS or MODULE until 3.8.0
find_package(Qt5 REQUIRED COMPONENTS
Core
Widgets
Gui
OpenGL)
if(NOT TARGET NodeEditor::nodes)
include("${NodeEditor_CMAKE_DIR}/NodeEditorTargets.cmake")
endif()
set(NodeEditor_LIBRARIES NodeEditor::nodes)

View File

@@ -0,0 +1,9 @@
add_subdirectory(connection_colors)
add_subdirectory(example2)
add_subdirectory(calculator)
add_subdirectory(images)
add_subdirectory(styles)

View File

@@ -0,0 +1,57 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include "MathOperationDataModel.hpp"
#include "DecimalData.hpp"
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class AdditionModel : public MathOperationDataModel
{
public:
virtual
~AdditionModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Addition"); }
QString
name() const override
{ return QStringLiteral("Addition"); }
private:
void
compute() override
{
PortIndex const outPortIndex = 0;
auto n1 = _number1.lock();
auto n2 = _number2.lock();
if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_result = std::make_shared<DecimalData>(n1->number() +
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_result.reset();
}
Q_EMIT dataUpdated(outPortIndex);
}
};

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE CPPS ./*.cpp )
add_executable(calculator ${CPPS})
target_link_libraries(calculator nodes)

View File

@@ -0,0 +1,46 @@
#include "Converters.hpp"
#include <QtGui/QDoubleValidator>
#include "DecimalData.hpp"
#include "IntegerData.hpp"
std::shared_ptr<NodeData>
DecimalToIntegerConverter::
operator()(std::shared_ptr<NodeData> data)
{
auto numberData =
std::dynamic_pointer_cast<DecimalData>(data);
if (numberData)
{
_integer = std::make_shared<IntegerData>(numberData->number());
}
else
{
_integer.reset();
}
return _integer;
}
std::shared_ptr<NodeData>
IntegerToDecimalConverter::
operator()(std::shared_ptr<NodeData> data)
{
auto numberData =
std::dynamic_pointer_cast<IntegerData>(data);
if (numberData)
{
_decimal = std::make_shared<DecimalData>(numberData->number());
}
else
{
_decimal.reset();
}
return _decimal;
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include "DecimalData.hpp"
#include "IntegerData.hpp"
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
class DecimalData;
class IntegerData;
class DecimalToIntegerConverter
{
public:
std::shared_ptr<NodeData>
operator()(std::shared_ptr<NodeData> data);
private:
std::shared_ptr<NodeData> _integer;
};
class IntegerToDecimalConverter
{
public:
std::shared_ptr<NodeData>
operator()(std::shared_ptr<NodeData> data);
private:
std::shared_ptr<NodeData> _decimal;
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include <nodes/NodeDataModel>
using QtNodes::NodeDataType;
using QtNodes::NodeData;
/// The class can potentially incapsulate any user data which
/// need to be transferred within the Node Editor graph
class DecimalData : public NodeData
{
public:
DecimalData()
: _number(0.0)
{}
DecimalData(double const number)
: _number(number)
{}
NodeDataType type() const override
{
return NodeDataType {"decimal",
"Decimal"};
}
double number() const
{ return _number; }
QString numberAsText() const
{ return QString::number(_number, 'f'); }
private:
double _number;
};

View File

@@ -0,0 +1,91 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include "MathOperationDataModel.hpp"
#include "DecimalData.hpp"
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class DivisionModel : public MathOperationDataModel
{
public:
virtual
~DivisionModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Division"); }
bool
portCaptionVisible(PortType portType, PortIndex portIndex) const override
{
Q_UNUSED(portType); Q_UNUSED(portIndex);
return true;
}
QString
portCaption(PortType portType, PortIndex portIndex) const override
{
switch (portType)
{
case PortType::In:
if (portIndex == 0)
return QStringLiteral("Dividend");
else if (portIndex == 1)
return QStringLiteral("Divisor");
break;
case PortType::Out:
return QStringLiteral("Result");
default:
break;
}
return QString();
}
QString
name() const override
{ return QStringLiteral("Division"); }
private:
void
compute() override
{
PortIndex const outPortIndex = 0;
auto n1 = _number1.lock();
auto n2 = _number2.lock();
if (n2 && (n2->number() == 0.0))
{
modelValidationState = NodeValidationState::Error;
modelValidationError = QStringLiteral("Division by zero error");
_result.reset();
}
else if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_result = std::make_shared<DecimalData>(n1->number() /
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_result.reset();
}
Q_EMIT dataUpdated(outPortIndex);
}
};

View File

@@ -0,0 +1,36 @@
#pragma once
#include <nodes/NodeDataModel>
using QtNodes::NodeDataType;
using QtNodes::NodeData;
class IntegerData : public NodeData
{
public:
IntegerData()
: _number(0.0)
{}
IntegerData(int const number)
: _number(number)
{}
NodeDataType type() const override
{
return NodeDataType {"integer",
"Integer"};
}
int number() const
{ return _number; }
QString numberAsText() const
{ return QString::number(_number); }
private:
int _number;
};

View File

@@ -0,0 +1,69 @@
#include "MathOperationDataModel.hpp"
#include "DecimalData.hpp"
unsigned int
MathOperationDataModel::
nPorts(PortType portType) const
{
unsigned int result;
if (portType == PortType::In)
result = 2;
else
result = 1;
return result;
}
NodeDataType
MathOperationDataModel::
dataType(PortType, PortIndex) const
{
return DecimalData().type();
}
std::shared_ptr<NodeData>
MathOperationDataModel::
outData(PortIndex)
{
return std::static_pointer_cast<NodeData>(_result);
}
void
MathOperationDataModel::
setInData(std::shared_ptr<NodeData> data, PortIndex portIndex)
{
auto numberData =
std::dynamic_pointer_cast<DecimalData>(data);
if (portIndex == 0)
{
_number1 = numberData;
}
else
{
_number2 = numberData;
}
compute();
}
NodeValidationState
MathOperationDataModel::
validationState() const
{
return modelValidationState;
}
QString
MathOperationDataModel::
validationMessage() const
{
return modelValidationError;
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QJsonObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include <iostream>
class DecimalData;
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class MathOperationDataModel : public NodeDataModel
{
Q_OBJECT
public:
virtual
~MathOperationDataModel() {}
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType,
PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData> data, PortIndex portIndex) override;
QWidget *
embeddedWidget() override { return nullptr; }
NodeValidationState
validationState() const override;
QString
validationMessage() const override;
protected:
virtual void
compute() = 0;
protected:
std::weak_ptr<DecimalData> _number1;
std::weak_ptr<DecimalData> _number2;
std::shared_ptr<DecimalData> _result;
NodeValidationState modelValidationState = NodeValidationState::Warning;
QString modelValidationError = QString("Missing or incorrect inputs");
};

View File

@@ -0,0 +1,118 @@
#include "ModuloModel.hpp"
#include <QtGui/QDoubleValidator>
#include "IntegerData.hpp"
QJsonObject
ModuloModel::
save() const
{
QJsonObject modelJson;
modelJson["name"] = name();
return modelJson;
}
unsigned int
ModuloModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 2;
break;
case PortType::Out:
result = 1;
default:
break;
}
return result;
}
NodeDataType
ModuloModel::
dataType(PortType, PortIndex) const
{
return IntegerData().type();
}
std::shared_ptr<NodeData>
ModuloModel::
outData(PortIndex)
{
return _result;
}
void
ModuloModel::
setInData(std::shared_ptr<NodeData> data, PortIndex portIndex)
{
auto numberData =
std::dynamic_pointer_cast<IntegerData>(data);
if (portIndex == 0)
{
_number1 = numberData;
}
else
{
_number2 = numberData;
}
{
PortIndex const outPortIndex = 0;
auto n1 = _number1.lock();
auto n2 = _number2.lock();
if (n2 && (n2->number() == 0.0))
{
modelValidationState = NodeValidationState::Error;
modelValidationError = QStringLiteral("Division by zero error");
_result.reset();
}
else if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_result = std::make_shared<IntegerData>(n1->number() %
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_result.reset();
}
Q_EMIT dataUpdated(outPortIndex);
}
}
NodeValidationState
ModuloModel::
validationState() const
{
return modelValidationState;
}
QString
ModuloModel::
validationMessage() const
{
return modelValidationError;
}

View File

@@ -0,0 +1,107 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLineEdit>
#include <nodes/NodeDataModel>
#include <iostream>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
class IntegerData;
class ModuloModel
: public NodeDataModel
{
Q_OBJECT
public:
ModuloModel() = default;
virtual
~ModuloModel() = default;
public:
QString
caption() const override
{ return QStringLiteral("Modulo"); }
bool
captionVisible() const override
{ return true; }
bool
portCaptionVisible(PortType, PortIndex ) const override
{ return true; }
QString
portCaption(PortType portType, PortIndex portIndex) const override
{
switch (portType)
{
case PortType::In:
if (portIndex == 0)
return QStringLiteral("Dividend");
else if (portIndex == 1)
return QStringLiteral("Divisor");
break;
case PortType::Out:
return QStringLiteral("Result");
default:
break;
}
return QString();
}
QString
name() const override
{ return QStringLiteral("Modulo"); }
public:
QJsonObject
save() const override;
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData>, int) override;
QWidget *
embeddedWidget() override { return nullptr; }
NodeValidationState
validationState() const override;
QString
validationMessage() const override;
private:
std::weak_ptr<IntegerData> _number1;
std::weak_ptr<IntegerData> _number2;
std::shared_ptr<IntegerData> _result;
NodeValidationState modelValidationState = NodeValidationState::Warning;
QString modelValidationError = QStringLiteral("Missing or incorrect inputs");
};

View File

@@ -0,0 +1,57 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include "MathOperationDataModel.hpp"
#include "DecimalData.hpp"
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class MultiplicationModel : public MathOperationDataModel
{
public:
virtual
~MultiplicationModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Multiplication"); }
QString
name() const override
{ return QStringLiteral("Multiplication"); }
private:
void
compute() override
{
PortIndex const outPortIndex = 0;
auto n1 = _number1.lock();
auto n2 = _number2.lock();
if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_result = std::make_shared<DecimalData>(n1->number() *
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_result.reset();
}
Q_EMIT dataUpdated(outPortIndex);
}
};

View File

@@ -0,0 +1,89 @@
#include "NumberDisplayDataModel.hpp"
#include "DecimalData.hpp"
NumberDisplayDataModel::
NumberDisplayDataModel()
: _label(new QLabel())
{
_label->setMargin(3);
}
unsigned int
NumberDisplayDataModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 1;
break;
case PortType::Out:
result = 0;
default:
break;
}
return result;
}
NodeDataType
NumberDisplayDataModel::
dataType(PortType, PortIndex) const
{
return DecimalData().type();
}
std::shared_ptr<NodeData>
NumberDisplayDataModel::
outData(PortIndex)
{
std::shared_ptr<NodeData> ptr;
return ptr;
}
void
NumberDisplayDataModel::
setInData(std::shared_ptr<NodeData> data, int)
{
auto numberData = std::dynamic_pointer_cast<DecimalData>(data);
if (numberData)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_label->setText(numberData->numberAsText());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_label->clear();
}
_label->adjustSize();
}
NodeValidationState
NumberDisplayDataModel::
validationState() const
{
return modelValidationState;
}
QString
NumberDisplayDataModel::
validationMessage() const
{
return modelValidationError;
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include <iostream>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class NumberDisplayDataModel : public NodeDataModel
{
Q_OBJECT
public:
NumberDisplayDataModel();
virtual
~NumberDisplayDataModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Result"); }
bool
captionVisible() const override
{ return false; }
QString
name() const override
{ return QStringLiteral("Result"); }
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType,
PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData> data, int) override;
QWidget *
embeddedWidget() override { return _label; }
NodeValidationState
validationState() const override;
QString
validationMessage() const override;
private:
NodeValidationState modelValidationState = NodeValidationState::Warning;
QString modelValidationError = QStringLiteral("Missing or incorrect inputs");
QLabel * _label;
};

View File

@@ -0,0 +1,116 @@
#include "NumberSourceDataModel.hpp"
#include <QtCore/QJsonValue>
#include <QtGui/QDoubleValidator>
#include "DecimalData.hpp"
NumberSourceDataModel::
NumberSourceDataModel()
: _lineEdit(new QLineEdit())
{
_lineEdit->setValidator(new QDoubleValidator());
_lineEdit->setMaximumSize(_lineEdit->sizeHint());
connect(_lineEdit, &QLineEdit::textChanged,
this, &NumberSourceDataModel::onTextEdited);
_lineEdit->setText("0.0");
}
QJsonObject
NumberSourceDataModel::
save() const
{
QJsonObject modelJson = NodeDataModel::save();
if (_number)
modelJson["number"] = QString::number(_number->number());
return modelJson;
}
void
NumberSourceDataModel::
restore(QJsonObject const &p)
{
QJsonValue v = p["number"];
if (!v.isUndefined())
{
QString strNum = v.toString();
bool ok;
double d = strNum.toDouble(&ok);
if (ok)
{
_number = std::make_shared<DecimalData>(d);
_lineEdit->setText(strNum);
}
}
}
unsigned int
NumberSourceDataModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 0;
break;
case PortType::Out:
result = 1;
default:
break;
}
return result;
}
void
NumberSourceDataModel::
onTextEdited(QString const &string)
{
Q_UNUSED(string);
bool ok = false;
double number = _lineEdit->text().toDouble(&ok);
if (ok)
{
_number = std::make_shared<DecimalData>(number);
Q_EMIT dataUpdated(0);
}
else
{
Q_EMIT dataInvalidated(0);
}
}
NodeDataType
NumberSourceDataModel::
dataType(PortType, PortIndex) const
{
return DecimalData().type();
}
std::shared_ptr<NodeData>
NumberSourceDataModel::
outData(PortIndex)
{
return _number;
}

View File

@@ -0,0 +1,82 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLineEdit>
#include <nodes/NodeDataModel>
#include <iostream>
class DecimalData;
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class NumberSourceDataModel
: public NodeDataModel
{
Q_OBJECT
public:
NumberSourceDataModel();
virtual
~NumberSourceDataModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Number Source"); }
bool
captionVisible() const override
{ return false; }
QString
name() const override
{ return QStringLiteral("NumberSource"); }
public:
QJsonObject
save() const override;
void
restore(QJsonObject const &p) override;
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData>, int) override
{ }
QWidget *
embeddedWidget() override { return _lineEdit; }
private Q_SLOTS:
void
onTextEdited(QString const &string);
private:
std::shared_ptr<DecimalData> _number;
QLineEdit * _lineEdit;
};

View File

@@ -0,0 +1,86 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/NodeDataModel>
#include "MathOperationDataModel.hpp"
#include "DecimalData.hpp"
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class SubtractionModel : public MathOperationDataModel
{
public:
virtual
~SubtractionModel() {}
public:
QString
caption() const override
{ return QStringLiteral("Subtraction"); }
virtual bool
portCaptionVisible(PortType portType, PortIndex portIndex) const override
{
Q_UNUSED(portType); Q_UNUSED(portIndex);
return true;
}
virtual QString
portCaption(PortType portType, PortIndex portIndex) const override
{
switch (portType)
{
case PortType::In:
if (portIndex == 0)
return QStringLiteral("Minuend");
else if (portIndex == 1)
return QStringLiteral("Subtrahend");
break;
case PortType::Out:
return QStringLiteral("Result");
default:
break;
}
return QString();
}
QString
name() const override
{ return QStringLiteral("Subtraction"); }
private:
void
compute() override
{
PortIndex const outPortIndex = 0;
auto n1 = _number1.lock();
auto n2 = _number2.lock();
if (n1 && n2)
{
modelValidationState = NodeValidationState::Valid;
modelValidationError = QString();
_result = std::make_shared<DecimalData>(n1->number() -
n2->number());
}
else
{
modelValidationState = NodeValidationState::Warning;
modelValidationError = QStringLiteral("Missing or incorrect inputs");
_result.reset();
}
Q_EMIT dataUpdated(outPortIndex);
}
};

View File

@@ -0,0 +1,119 @@
#include <nodes/NodeData>
#include <nodes/FlowScene>
#include <nodes/FlowView>
#include <nodes/ConnectionStyle>
#include <nodes/TypeConverter>
#include <QtWidgets/QApplication>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QMenuBar>
#include <nodes/DataModelRegistry>
#include "NumberSourceDataModel.hpp"
#include "NumberDisplayDataModel.hpp"
#include "AdditionModel.hpp"
#include "SubtractionModel.hpp"
#include "MultiplicationModel.hpp"
#include "DivisionModel.hpp"
#include "ModuloModel.hpp"
#include "Converters.hpp"
using QtNodes::DataModelRegistry;
using QtNodes::FlowScene;
using QtNodes::FlowView;
using QtNodes::ConnectionStyle;
using QtNodes::TypeConverter;
using QtNodes::TypeConverterId;
static std::shared_ptr<DataModelRegistry>
registerDataModels()
{
auto ret = std::make_shared<DataModelRegistry>();
ret->registerModel<NumberSourceDataModel>("I/O//Sources");
ret->registerModel<NumberDisplayDataModel>("I/O//Displays");
ret->registerModel<AdditionModel>("Operators");
ret->registerModel<SubtractionModel>("Operators");
ret->registerModel<MultiplicationModel>("Operators");
ret->registerModel<DivisionModel>("Operators");
ret->registerModel<ModuloModel>("Operators");
ret->registerTypeConverter(std::make_pair(DecimalData().type(),
IntegerData().type()),
TypeConverter{DecimalToIntegerConverter()});
ret->registerTypeConverter(std::make_pair(IntegerData().type(),
DecimalData().type()),
TypeConverter{IntegerToDecimalConverter()});
return ret;
}
static
void
setStyle()
{
ConnectionStyle::setConnectionStyle(
R"(
{
"ConnectionStyle": {
"ConstructionColor": "gray",
"NormalColor": "black",
"SelectedColor": "gray",
"SelectedHaloColor": "deepskyblue",
"HoveredColor": "deepskyblue",
"LineWidth": 3.0,
"ConstructionLineWidth": 2.0,
"PointDiameter": 10.0,
"UseDataDefinedColors": true
}
}
)");
}
int
main(int argc, char *argv[])
{
QApplication app(argc, argv);
setStyle();
QWidget mainWidget;
auto menuBar = new QMenuBar();
auto saveAction = menuBar->addAction("Save..");
auto loadAction = menuBar->addAction("Load..");
QVBoxLayout *l = new QVBoxLayout(&mainWidget);
l->addWidget(menuBar);
auto scene = new FlowScene(registerDataModels(), &mainWidget);
l->addWidget(new FlowView(scene));
l->setContentsMargins(0, 0, 0, 0);
l->setSpacing(0);
QObject::connect(saveAction, &QAction::triggered,
scene, &FlowScene::save);
QObject::connect(loadAction, &QAction::triggered,
scene, &FlowScene::load);
mainWidget.setWindowTitle("Dataflow tools: simplest calculator");
mainWidget.resize(800, 600);
mainWidget.showNormal();
return app.exec();
}

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE CPPS ./*.cpp )
add_executable(connection_colors ${CPPS})
target_link_libraries(connection_colors nodes)

View File

@@ -0,0 +1,69 @@
#include <QtWidgets/QApplication>
#include <nodes/NodeData>
#include <nodes/FlowScene>
#include <nodes/FlowView>
#include <nodes/DataModelRegistry>
#include <nodes/ConnectionStyle>
#include "models.hpp"
using QtNodes::DataModelRegistry;
using QtNodes::FlowScene;
using QtNodes::FlowView;
using QtNodes::ConnectionStyle;
static std::shared_ptr<DataModelRegistry>
registerDataModels()
{
auto ret = std::make_shared<DataModelRegistry>();
ret->registerModel<NaiveDataModel>();
/*
We could have more models registered.
All of them become items in the context meny of the scene.
ret->registerModel<AnotherDataModel>();
ret->registerModel<OneMoreDataModel>();
*/
return ret;
}
static
void
setStyle()
{
ConnectionStyle::setConnectionStyle(
R"(
{
"ConnectionStyle": {
"UseDataDefinedColors": true
}
}
)");
}
//------------------------------------------------------------------------------
int
main(int argc, char* argv[])
{
QApplication app(argc, argv);
setStyle();
FlowScene scene(registerDataModels());
FlowView view(&scene);
view.setWindowTitle("Node-based flow editor");
view.resize(800, 600);
view.show();
return app.exec();
}

View File

@@ -0,0 +1,4 @@
#include "models.hpp"
// For some reason CMake could not generate moc-files correctly
// without having a cpp for an QObject from hpp.

View File

@@ -0,0 +1,140 @@
#pragma once
#include <QtCore/QObject>
#include <nodes/NodeData>
#include <nodes/NodeDataModel>
#include <memory>
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::PortType;
using QtNodes::PortIndex;
/// The class can potentially incapsulate any user data which
/// need to be transferred within the Node Editor graph
class MyNodeData : public NodeData
{
public:
NodeDataType
type() const override
{
return NodeDataType {"MyNodeData",
"My Node Data"};
}
};
class SimpleNodeData : public NodeData
{
public:
NodeDataType
type() const override
{
return NodeDataType {"SimpleData",
"Simple Data"};
}
};
//------------------------------------------------------------------------------
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class NaiveDataModel : public NodeDataModel
{
Q_OBJECT
public:
virtual
~NaiveDataModel() {}
public:
QString
caption() const override
{
return QString("Naive Data Model");
}
QString
name() const override
{ return QString("NaiveDataModel"); }
public:
unsigned int
nPorts(PortType portType) const override
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 2;
break;
case PortType::Out:
result = 2;
break;
case PortType::None:
break;
}
return result;
}
NodeDataType
dataType(PortType portType,
PortIndex portIndex) const override
{
switch (portType)
{
case PortType::In:
switch (portIndex)
{
case 0:
return MyNodeData().type();
case 1:
return SimpleNodeData().type();
}
break;
case PortType::Out:
switch (portIndex)
{
case 0:
return MyNodeData().type();
case 1:
return SimpleNodeData().type();
}
break;
case PortType::None:
break;
}
// FIXME: control may reach end of non-void function [-Wreturn-type]
return NodeDataType();
}
std::shared_ptr<NodeData>
outData(PortIndex port) override
{
if (port < 1)
return std::make_shared<MyNodeData>();
return std::make_shared<SimpleNodeData>();
}
void
setInData(std::shared_ptr<NodeData>, int) override
{
//
}
QWidget *
embeddedWidget() override { return nullptr; }
};

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE CPPS ./*.cpp )
add_executable(example2 ${CPPS})
target_link_libraries(example2 nodes)

View File

@@ -0,0 +1,28 @@
#pragma once
#include <nodes/NodeDataModel>
using QtNodes::NodeData;
using QtNodes::NodeDataType;
/// The class can potentially incapsulate any user data which
/// need to be transferred within the Node Editor graph
class TextData : public NodeData
{
public:
TextData() {}
TextData(QString const &text)
: _text(text)
{}
NodeDataType type() const override
{ return NodeDataType {"text", "Text"}; }
QString text() const { return _text; }
private:
QString _text;
};

View File

@@ -0,0 +1,48 @@
#include "TextDisplayDataModel.hpp"
TextDisplayDataModel::
TextDisplayDataModel()
: _label(new QLabel("Resulting Text"))
{
_label->setMargin(3);
}
unsigned int
TextDisplayDataModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 1;
break;
case PortType::Out:
result = 0;
default:
break;
}
return result;
}
NodeDataType
TextDisplayDataModel::
dataType(PortType, PortIndex) const
{
return TextData().type();
}
std::shared_ptr<NodeData>
TextDisplayDataModel::
outData(PortIndex)
{
std::shared_ptr<NodeData> ptr;
return ptr;
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include "TextData.hpp"
#include <nodes/NodeDataModel>
#include <iostream>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataModel;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class TextDisplayDataModel : public NodeDataModel
{
Q_OBJECT
public:
TextDisplayDataModel();
virtual
~TextDisplayDataModel() {}
public:
QString
caption() const override
{ return QString("Text Display"); }
bool
captionVisible() const override { return false; }
static QString
Name()
{ return QString("TextDisplayDataModel"); }
QString
name() const override
{ return TextDisplayDataModel::Name(); }
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData> data, int) override
{
auto textData = std::dynamic_pointer_cast<TextData>(data);
if (textData)
{
_label->setText(textData->text());
}
else
{
_label->clear();
}
_label->adjustSize();
}
QWidget *
embeddedWidget() override { return _label; }
private:
QLabel * _label;
};

View File

@@ -0,0 +1,58 @@
#include "TextSourceDataModel.hpp"
TextSourceDataModel::
TextSourceDataModel()
: _lineEdit(new QLineEdit("Default Text"))
{
connect(_lineEdit, &QLineEdit::textEdited,
this, &TextSourceDataModel::onTextEdited);
}
unsigned int
TextSourceDataModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 0;
break;
case PortType::Out:
result = 1;
default:
break;
}
return result;
}
void
TextSourceDataModel::
onTextEdited(QString const &string)
{
Q_UNUSED(string);
Q_EMIT dataUpdated(0);
}
NodeDataType
TextSourceDataModel::
dataType(PortType, PortIndex) const
{
return TextData().type();
}
std::shared_ptr<NodeData>
TextSourceDataModel::
outData(PortIndex)
{
return std::make_shared<TextData>(_lineEdit->text());
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <QtCore/QObject>
#include <QtWidgets/QLineEdit>
#include "TextData.hpp"
#include <nodes/NodeDataModel>
#include <iostream>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataModel;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class TextSourceDataModel : public NodeDataModel
{
Q_OBJECT
public:
TextSourceDataModel();
virtual
~TextSourceDataModel() {}
public:
QString
caption() const override
{ return QString("Text Source"); }
bool
captionVisible() const override { return false; }
static QString
Name()
{ return QString("TextSourceDataModel"); }
QString
name() const override
{ return TextSourceDataModel::Name(); }
public:
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData>, int) override
{ }
QWidget *
embeddedWidget() override { return _lineEdit; }
private Q_SLOTS:
void
onTextEdited(QString const &string);
private:
QLineEdit * _lineEdit;
};

View File

@@ -0,0 +1,43 @@
#include <nodes/NodeData>
#include <nodes/FlowScene>
#include <nodes/FlowView>
#include <QtWidgets/QApplication>
#include <nodes/DataModelRegistry>
#include "TextSourceDataModel.hpp"
#include "TextDisplayDataModel.hpp"
using QtNodes::DataModelRegistry;
using QtNodes::FlowView;
using QtNodes::FlowScene;
static std::shared_ptr<DataModelRegistry>
registerDataModels()
{
auto ret = std::make_shared<DataModelRegistry>();
ret->registerModel<TextSourceDataModel>();
ret->registerModel<TextDisplayDataModel>();
return ret;
}
int
main(int argc, char *argv[])
{
QApplication app(argc, argv);
FlowScene scene(registerDataModels());
FlowView view(&scene);
view.setWindowTitle("Node-based flow editor");
view.resize(800, 600);
view.show();
return app.exec();
}

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE CPPS ./*.cpp )
add_executable(images ${CPPS})
target_link_libraries(images nodes)

View File

@@ -0,0 +1,99 @@
#include "ImageLoaderModel.hpp"
#include <QtCore/QEvent>
#include <QtCore/QDir>
#include <QtWidgets/QFileDialog>
ImageLoaderModel::
ImageLoaderModel()
: _label(new QLabel("Double click to load image"))
{
_label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
QFont f = _label->font();
f.setBold(true);
f.setItalic(true);
_label->setFont(f);
_label->setFixedSize(200, 200);
_label->installEventFilter(this);
}
unsigned int
ImageLoaderModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 0;
break;
case PortType::Out:
result = 1;
default:
break;
}
return result;
}
bool
ImageLoaderModel::
eventFilter(QObject *object, QEvent *event)
{
if (object == _label)
{
int w = _label->width();
int h = _label->height();
if (event->type() == QEvent::MouseButtonPress)
{
QString fileName =
QFileDialog::getOpenFileName(nullptr,
tr("Open Image"),
QDir::homePath(),
tr("Image Files (*.png *.jpg *.bmp)"));
_pixmap = QPixmap(fileName);
_label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio));
Q_EMIT dataUpdated(0);
return true;
}
else if (event->type() == QEvent::Resize)
{
if (!_pixmap.isNull())
_label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio));
}
}
return false;
}
NodeDataType
ImageLoaderModel::
dataType(PortType, PortIndex) const
{
return PixmapData().type();
}
std::shared_ptr<NodeData>
ImageLoaderModel::
outData(PortIndex)
{
return std::make_shared<PixmapData>(_pixmap);
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include <iostream>
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/DataModelRegistry>
#include <nodes/NodeDataModel>
#include "PixmapData.hpp"
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class ImageLoaderModel : public NodeDataModel
{
Q_OBJECT
public:
ImageLoaderModel();
virtual
~ImageLoaderModel() {}
public:
QString
caption() const override
{ return QString("Image Source"); }
QString
name() const override { return QString("ImageLoaderModel"); }
public:
virtual QString
modelName() const
{ return QString("Source Image"); }
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData>, int) override
{ }
QWidget *
embeddedWidget() override { return _label; }
bool
resizable() const override { return true; }
protected:
bool
eventFilter(QObject *object, QEvent *event) override;
private:
QLabel * _label;
QPixmap _pixmap;
};

View File

@@ -0,0 +1,112 @@
#include "ImageShowModel.hpp"
#include <QtCore/QEvent>
#include <QtCore/QDir>
#include <QtWidgets/QFileDialog>
#include <nodes/DataModelRegistry>
#include "PixmapData.hpp"
ImageShowModel::
ImageShowModel()
: _label(new QLabel("Image will appear here"))
{
_label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
QFont f = _label->font();
f.setBold(true);
f.setItalic(true);
_label->setFont(f);
_label->setFixedSize(200, 200);
_label->installEventFilter(this);
}
unsigned int
ImageShowModel::
nPorts(PortType portType) const
{
unsigned int result = 1;
switch (portType)
{
case PortType::In:
result = 1;
break;
case PortType::Out:
result = 1;
default:
break;
}
return result;
}
bool
ImageShowModel::
eventFilter(QObject *object, QEvent *event)
{
if (object == _label)
{
int w = _label->width();
int h = _label->height();
if (event->type() == QEvent::Resize)
{
auto d = std::dynamic_pointer_cast<PixmapData>(_nodeData);
if (d)
{
_label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio));
}
}
}
return false;
}
NodeDataType
ImageShowModel::
dataType(PortType, PortIndex) const
{
return PixmapData().type();
}
std::shared_ptr<NodeData>
ImageShowModel::
outData(PortIndex)
{
return _nodeData;
}
void
ImageShowModel::
setInData(std::shared_ptr<NodeData> nodeData, PortIndex)
{
_nodeData = nodeData;
if (_nodeData)
{
auto d = std::dynamic_pointer_cast<PixmapData>(_nodeData);
int w = _label->width();
int h = _label->height();
_label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio));
}
else
{
_label->setPixmap(QPixmap());
}
Q_EMIT dataUpdated(0);
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <iostream>
#include <QtCore/QObject>
#include <QtWidgets/QLabel>
#include <nodes/DataModelRegistry>
#include <nodes/NodeDataModel>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class ImageShowModel : public NodeDataModel
{
Q_OBJECT
public:
ImageShowModel();
virtual
~ImageShowModel() {}
public:
QString
caption() const override
{ return QString("Image Display"); }
QString
name() const override
{ return QString("ImageShowModel"); }
public:
virtual QString
modelName() const
{ return QString("Resulting Image"); }
unsigned int
nPorts(PortType portType) const override;
NodeDataType
dataType(PortType portType, PortIndex portIndex) const override;
std::shared_ptr<NodeData>
outData(PortIndex port) override;
void
setInData(std::shared_ptr<NodeData> nodeData, PortIndex port) override;
QWidget *
embeddedWidget() override { return _label; }
bool
resizable() const override { return true; }
protected:
bool
eventFilter(QObject *object, QEvent *event) override;
private:
QLabel * _label;
std::shared_ptr<NodeData> _nodeData;
};

View File

@@ -0,0 +1,35 @@
#pragma once
#include <QtGui/QPixmap>
#include <nodes/NodeDataModel>
using QtNodes::NodeData;
using QtNodes::NodeDataType;
/// The class can potentially incapsulate any user data which
/// need to be transferred within the Node Editor graph
class PixmapData : public NodeData
{
public:
PixmapData() {}
PixmapData(QPixmap const &pixmap)
: _pixmap(pixmap)
{}
NodeDataType
type() const override
{
// id name
return {"pixmap", "P"};
}
QPixmap
pixmap() const { return _pixmap; }
private:
QPixmap _pixmap;
};

View File

@@ -0,0 +1,40 @@
#include <nodes/NodeData>
#include <nodes/FlowScene>
#include <nodes/FlowView>
#include <QtWidgets/QApplication>
#include "ImageShowModel.hpp"
#include "ImageLoaderModel.hpp"
using QtNodes::DataModelRegistry;
using QtNodes::FlowScene;
using QtNodes::FlowView;
static std::shared_ptr<DataModelRegistry>
registerDataModels()
{
auto ret = std::make_shared<DataModelRegistry>();
ret->registerModel<ImageShowModel>();
ret->registerModel<ImageLoaderModel>();
return ret;
}
int
main(int argc, char *argv[])
{
QApplication app(argc, argv);
FlowScene scene(registerDataModels());
FlowView view(&scene);
view.setWindowTitle("Node-based flow editor");
view.resize(800, 600);
view.show();
return app.exec();
}

View File

@@ -0,0 +1,5 @@
file(GLOB_RECURSE CPPS ./*.cpp )
add_executable(styles ${CPPS})
target_link_libraries(styles nodes)

View File

@@ -0,0 +1,107 @@
#include <QtWidgets/QApplication>
#include <nodes/NodeData>
#include <nodes/FlowScene>
#include <nodes/FlowView>
#include <nodes/DataModelRegistry>
#include <nodes/NodeStyle>
#include <nodes/FlowViewStyle>
#include <nodes/ConnectionStyle>
#include "models.hpp"
using QtNodes::DataModelRegistry;
using QtNodes::FlowScene;
using QtNodes::FlowView;
using QtNodes::FlowViewStyle;
using QtNodes::NodeStyle;
using QtNodes::ConnectionStyle;
static std::shared_ptr<DataModelRegistry>
registerDataModels()
{
auto ret = std::make_shared<DataModelRegistry>();
ret->registerModel<MyDataModel>();
return ret;
}
static
void
setStyle()
{
FlowViewStyle::setStyle(
R"(
{
"FlowViewStyle": {
"BackgroundColor": [255, 255, 240],
"FineGridColor": [245, 245, 230],
"CoarseGridColor": [235, 235, 220]
}
}
)");
NodeStyle::setNodeStyle(
R"(
{
"NodeStyle": {
"NormalBoundaryColor": "darkgray",
"SelectedBoundaryColor": "deepskyblue",
"GradientColor0": "mintcream",
"GradientColor1": "mintcream",
"GradientColor2": "mintcream",
"GradientColor3": "mintcream",
"ShadowColor": [200, 200, 200],
"FontColor": [10, 10, 10],
"FontColorFaded": [100, 100, 100],
"ConnectionPointColor": "white",
"PenWidth": 2.0,
"HoveredPenWidth": 2.5,
"ConnectionPointDiameter": 10.0,
"Opacity": 1.0
}
}
)");
ConnectionStyle::setConnectionStyle(
R"(
{
"ConnectionStyle": {
"ConstructionColor": "gray",
"NormalColor": "black",
"SelectedColor": "gray",
"SelectedHaloColor": "deepskyblue",
"HoveredColor": "deepskyblue",
"LineWidth": 3.0,
"ConstructionLineWidth": 2.0,
"PointDiameter": 10.0,
"UseDataDefinedColors": false
}
}
)");
}
//------------------------------------------------------------------------------
int
main(int argc, char* argv[])
{
QApplication app(argc, argv);
setStyle();
FlowScene scene(registerDataModels());
FlowView view(&scene);
view.setWindowTitle("Style example");
view.resize(800, 600);
view.show();
return app.exec();
}

View File

@@ -0,0 +1,4 @@
#include "models.hpp"
// For some reason CMake could not generate moc-files correctly
// without having a cpp for an QObject from hpp.

View File

@@ -0,0 +1,95 @@
#pragma once
#include <QtCore/QObject>
#include <nodes/NodeData>
#include <nodes/NodeDataModel>
#include <memory>
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::NodeDataModel;
using QtNodes::NodeValidationState;
/// The class can potentially incapsulate any user data which
/// need to be transferred within the Node Editor graph
class MyNodeData : public NodeData
{
public:
NodeDataType
type() const override
{ return NodeDataType {"MyNodeData", "My Node Data"}; }
};
//------------------------------------------------------------------------------
/// The model dictates the number of inputs and outputs for the Node.
/// In this example it has no logic.
class MyDataModel : public NodeDataModel
{
Q_OBJECT
public:
virtual
~MyDataModel() {}
public:
QString
caption() const override
{
return QString("My Data Model");
}
QString
name() const override
{
return QString("MyDataModel");
}
public:
QJsonObject
save() const override
{
QJsonObject modelJson;
modelJson["name"] = name();
return modelJson;
}
public:
unsigned int
nPorts(PortType) const override
{
return 3;
}
NodeDataType
dataType(PortType, PortIndex) const override
{
return MyNodeData().type();
}
std::shared_ptr<NodeData>
outData(PortIndex) override
{
return std::make_shared<MyNodeData>();
}
void
setInData(std::shared_ptr<NodeData>, int) override
{
//
}
QWidget *
embeddedWidget() override { return nullptr; }
};

View File

@@ -0,0 +1,13 @@
if(BUILD_TESTING)
find_package(Catch2 2.3.0 QUIET)
if(NOT Catch2_FOUND)
add_subdirectory(Catch2)
endif()
endif()
macro(find_package pkg)
if(NOT TARGET "${pkg}")
_find_package(${ARGV})
endif()
endmacro()

View File

@@ -0,0 +1,13 @@
if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp")
file(DOWNLOAD https://raw.githubusercontent.com/catchorg/Catch2/v2.4.1/single_include/catch2/catch.hpp
"${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp"
EXPECTED_HASH SHA256=a4b90030cb813f0452bb00e97c92ca6c2ecf9386a2f000b6effb8e265a53959e
)
endif()
add_library(Catch2 INTERFACE)
add_library(Catch2::Catch2 ALIAS Catch2)
target_include_directories(Catch2
INTERFACE
"${CMAKE_CURRENT_BINARY_DIR}/single_include"
)

View File

@@ -0,0 +1 @@
#include "internal/Connection.hpp"

View File

@@ -0,0 +1 @@
#include "internal/ConnectionStyle.hpp"

View File

@@ -0,0 +1 @@
#include "internal/DataModelRegistry.hpp"

View File

@@ -0,0 +1 @@
#include "internal/FlowScene.hpp"

View File

@@ -0,0 +1 @@
#include "internal/FlowView.hpp"

View File

@@ -0,0 +1 @@
#include "internal/FlowViewStyle.hpp"

View File

@@ -0,0 +1 @@
#include "internal/Node.hpp"

View File

@@ -0,0 +1 @@
#include "internal/NodeData.hpp"

View File

@@ -0,0 +1 @@
#include "internal/NodeDataModel.hpp"

View File

@@ -0,0 +1,2 @@
#include "internal/NodeGeometry.hpp"

View File

@@ -0,0 +1 @@
#include "internal/NodePainterDelegate.hpp"

View File

@@ -0,0 +1 @@
#include "internal/NodeState.hpp"

View File

@@ -0,0 +1 @@
#include "internal/NodeStyle.hpp"

View File

@@ -0,0 +1 @@
#include "internal/TypeConverter.hpp"

View File

@@ -0,0 +1,53 @@
#pragma once
#if \
defined (__MINGW32__) || \
defined (__MINGW64__)
# define NODE_EDITOR_COMPILER "MinGW"
# define NODE_EDITOR_COMPILER_MINGW
#elif \
defined (__GNUC__)
# define NODE_EDITOR_COMPILER "GNU"
# define NODE_EDITOR_COMPILER_GNU
# define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__
# define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__
# define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__
#elif \
defined (__clang__)
# define NODE_EDITOR_COMPILER "Clang"
# define NODE_EDITOR_COMPILER_CLANG
#elif \
defined (_MSC_VER)
# define NODE_EDITOR_COMPILER "Microsoft Visual C++"
# define NODE_EDITOR_COMPILER_MICROSOFT
#elif \
defined (__BORLANDC__)
# define NODE_EDITOR_COMPILER "Borland C++ Builder"
# define NODE_EDITOR_COMPILER_BORLAND
#elif \
defined (__CODEGEARC__)
# define NODE_EDITOR_COMPILER "CodeGear C++ Builder"
# define NODE_EDITOR_COMPILER_CODEGEAR
#elif \
defined (__INTEL_COMPILER) || \
defined (__ICL)
# define NODE_EDITOR_COMPILER "Intel C++"
# define NODE_EDITOR_COMPILER_INTEL
#elif \
defined (__xlC__) || \
defined (__IBMCPP__)
# define NODE_EDITOR_COMPILER "IBM XL C++"
# define NODE_EDITOR_COMPILER_IBM
#elif \
defined (__HP_aCC)
# define NODE_EDITOR_COMPILER "HP aC++"
# define NODE_EDITOR_COMPILER_HP
#elif \
defined (__WATCOMC__)
# define NODE_EDITOR_COMPILER "Watcom C++"
# define NODE_EDITOR_COMPILER_WATCOM
#endif
#ifndef NODE_EDITOR_COMPILER
# error "Current compiler is not supported."
#endif

View File

@@ -0,0 +1,166 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QVariant>
#include "PortType.hpp"
#include "NodeData.hpp"
#include "Serializable.hpp"
#include "ConnectionState.hpp"
#include "ConnectionGeometry.hpp"
#include "TypeConverter.hpp"
#include "QUuidStdHash.hpp"
#include "Export.hpp"
#include "memory.hpp"
class QPointF;
namespace QtNodes
{
class Node;
class NodeData;
class ConnectionGraphicsObject;
///
class NODE_EDITOR_PUBLIC Connection
: public QObject
, public Serializable
{
Q_OBJECT
public:
/// New Connection is attached to the port of the given Node.
/// The port has parameters (portType, portIndex).
/// The opposite connection end will require anothre port.
Connection(PortType portType,
Node& node,
PortIndex portIndex);
Connection(Node& nodeIn,
PortIndex portIndexIn,
Node& nodeOut,
PortIndex portIndexOut,
TypeConverter converter =
TypeConverter{});
Connection(const Connection&) = delete;
Connection operator=(const Connection&) = delete;
~Connection();
public:
QJsonObject
save() const override;
public:
QUuid
id() const;
/// Remembers the end being dragged.
/// Invalidates Node address.
/// Grabs mouse.
void
setRequiredPort(PortType portType);
PortType
requiredPort() const;
void
setGraphicsObject(std::unique_ptr<ConnectionGraphicsObject>&& graphics);
/// Assigns a node to the required port.
/// It is assumed that there is a required port, no extra checks
void
setNodeToPort(Node& node,
PortType portType,
PortIndex portIndex);
void
removeFromNodes() const;
public:
ConnectionGraphicsObject&
getConnectionGraphicsObject() const;
ConnectionState const &
connectionState() const;
ConnectionState&
connectionState();
ConnectionGeometry&
connectionGeometry();
ConnectionGeometry const&
connectionGeometry() const;
Node*
getNode(PortType portType) const;
Node*&
getNode(PortType portType);
PortIndex
getPortIndex(PortType portType) const;
void
clearNode(PortType portType);
NodeDataType
dataType(PortType portType) const;
void
setTypeConverter(TypeConverter converter);
bool
complete() const;
public: // data propagation
void
propagateData(std::shared_ptr<NodeData> nodeData) const;
void
propagateEmptyData() const;
Q_SIGNALS:
void
connectionCompleted(Connection const&) const;
void
connectionMadeIncomplete(Connection const&) const;
private:
QUuid _uid;
private:
Node* _outNode = nullptr;
Node* _inNode = nullptr;
PortIndex _outPortIndex;
PortIndex _inPortIndex;
private:
ConnectionState _connectionState;
ConnectionGeometry _connectionGeometry;
std::unique_ptr<ConnectionGraphicsObject>_connectionGraphicsObject;
TypeConverter _converter;
Q_SIGNALS:
void
updated(Connection& conn) const;
};
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include "PortType.hpp"
#include <QtCore/QPointF>
#include <QtCore/QRectF>
#include <iostream>
namespace QtNodes
{
class ConnectionGeometry
{
public:
ConnectionGeometry();
public:
QPointF const&
getEndPoint(PortType portType) const;
void
setEndPoint(PortType portType, QPointF const& point);
void
moveEndPoint(PortType portType, QPointF const &offset);
QRectF
boundingRect() const;
std::pair<QPointF, QPointF>
pointsC1C2() const;
QPointF
source() const { return _out; }
QPointF
sink() const { return _in; }
double
lineWidth() const { return _lineWidth; }
bool
hovered() const { return _hovered; }
void
setHovered(bool hovered) { _hovered = hovered; }
private:
// local object coordinates
QPointF _in;
QPointF _out;
//int _animationPhase;
double _lineWidth;
bool _hovered;
};
}

View File

@@ -0,0 +1,89 @@
#pragma once
#include <QtCore/QUuid>
#include <QtWidgets/QGraphicsObject>
class QGraphicsSceneMouseEvent;
namespace QtNodes
{
class FlowScene;
class Connection;
class ConnectionGeometry;
class Node;
/// Graphic Object for connection. Adds itself to scene
class ConnectionGraphicsObject
: public QGraphicsObject
{
Q_OBJECT
public:
ConnectionGraphicsObject(FlowScene &scene,
Connection &connection);
virtual
~ConnectionGraphicsObject();
enum { Type = UserType + 2 };
int
type() const override { return Type; }
public:
Connection&
connection();
QRectF
boundingRect() const override;
QPainterPath
shape() const override;
void
setGeometryChanged();
/// Updates the position of both ends
void
move();
void
lock(bool locked);
protected:
void
paint(QPainter* painter,
QStyleOptionGraphicsItem const* option,
QWidget* widget = 0) override;
void
mousePressEvent(QGraphicsSceneMouseEvent* event) override;
void
mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
void
mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
void
hoverEnterEvent(QGraphicsSceneHoverEvent* event) override;
void
hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override;
private:
void
addGraphicsEffect();
private:
FlowScene & _scene;
Connection& _connection;
};
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <QtCore/QUuid>
#include "PortType.hpp"
class QPointF;
namespace QtNodes
{
class Node;
/// Stores currently draggind end.
/// Remembers last hovered Node.
class ConnectionState
{
public:
ConnectionState(PortType port = PortType::None)
: _requiredPort(port)
{}
ConnectionState(const ConnectionState&) = delete;
ConnectionState operator=(const ConnectionState&) = delete;
~ConnectionState();
public:
void setRequiredPort(PortType end)
{ _requiredPort = end; }
PortType requiredPort() const
{ return _requiredPort; }
bool requiresPort() const
{ return _requiredPort != PortType::None; }
void setNoRequiredPort()
{ _requiredPort = PortType::None; }
public:
void interactWithNode(Node* node);
void setLastHoveredNode(Node* node);
Node*
lastHoveredNode() const
{ return _lastHoveredNode; }
void resetLastHoveredNode();
private:
PortType _requiredPort;
Node* _lastHoveredNode{nullptr};
};
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <QtGui/QColor>
#include "Export.hpp"
#include "Style.hpp"
namespace QtNodes
{
class NODE_EDITOR_PUBLIC ConnectionStyle : public Style
{
public:
ConnectionStyle();
ConnectionStyle(QString jsonText);
public:
static void setConnectionStyle(QString jsonText);
private:
void loadJsonText(QString jsonText) override;
void loadJsonFile(QString fileName) override;
void loadJsonFromByteArray(QByteArray const &byteArray) override;
public:
QColor constructionColor() const;
QColor normalColor() const;
QColor normalColor(QString typeId) const;
QColor selectedColor() const;
QColor selectedHaloColor() const;
QColor hoveredColor() const;
float lineWidth() const;
float constructionLineWidth() const;
float pointDiameter() const;
bool useDataDefinedColors() const;
private:
QColor ConstructionColor;
QColor NormalColor;
QColor SelectedColor;
QColor SelectedHaloColor;
QColor HoveredColor;
float LineWidth;
float ConstructionLineWidth;
float PointDiameter;
bool UseDataDefinedColors;
};
}

View File

@@ -0,0 +1,153 @@
#pragma once
#include <set>
#include <memory>
#include <functional>
#include <unordered_map>
#include <vector>
#include <QtCore/QString>
#include "NodeDataModel.hpp"
#include "TypeConverter.hpp"
#include "Export.hpp"
#include "QStringStdHash.hpp"
#include "memory.hpp"
namespace QtNodes
{
inline
bool
operator<(QtNodes::NodeDataType const & d1,
QtNodes::NodeDataType const & d2)
{
return d1.id < d2.id;
}
/// Class uses map for storing models (name, model)
class NODE_EDITOR_PUBLIC DataModelRegistry
{
public:
using RegistryItemPtr = std::unique_ptr<NodeDataModel>;
using RegistryItemCreator = std::function<RegistryItemPtr()>;
using RegisteredModelCreatorsMap = std::unordered_map<QString, RegistryItemCreator>;
using RegisteredModelsCategoryMap = std::unordered_map<QString, QString>;
using CategoriesSet = std::set<QString>;
using RegisteredTypeConvertersMap = std::map<TypeConverterId, TypeConverter>;
DataModelRegistry() = default;
~DataModelRegistry() = default;
DataModelRegistry(DataModelRegistry const &) = delete;
DataModelRegistry(DataModelRegistry &&) = default;
DataModelRegistry&operator=(DataModelRegistry const &) = delete;
DataModelRegistry&operator=(DataModelRegistry &&) = default;
public:
template<typename ModelType>
void registerModel(RegistryItemCreator creator,
QString const &category = "Nodes")
{
registerModelImpl<ModelType>(std::move(creator), category);
}
template<typename ModelType>
void registerModel(QString const &category = "Nodes")
{
RegistryItemCreator creator = [](){ return std::make_unique<ModelType>(); };
registerModelImpl<ModelType>(std::move(creator), category);
}
template<typename ModelType>
void registerModel(QString const &category,
RegistryItemCreator creator)
{
registerModelImpl<ModelType>(std::move(creator), category);
}
void registerTypeConverter(TypeConverterId const & id,
TypeConverter typeConverter)
{
_registeredTypeConverters[id] = std::move(typeConverter);
}
std::unique_ptr<NodeDataModel>create(QString const &modelName);
RegisteredModelCreatorsMap const &registeredModelCreators() const;
RegisteredModelsCategoryMap const &registeredModelsCategoryAssociation() const;
CategoriesSet const &categories() const;
TypeConverter getTypeConverter(NodeDataType const & d1,
NodeDataType const & d2) const;
private:
RegisteredModelsCategoryMap _registeredModelsCategory;
CategoriesSet _categories;
RegisteredModelCreatorsMap _registeredItemCreators;
RegisteredTypeConvertersMap _registeredTypeConverters;
private:
// If the registered ModelType class has the static member method
//
// static Qstring Name();
//
// use it. Otherwise use the non-static method:
//
// virtual QString name() const;
template <typename T, typename = void>
struct HasStaticMethodName
: std::false_type
{};
template <typename T>
struct HasStaticMethodName<T,
typename std::enable_if<std::is_same<decltype(T::Name()), QString>::value>::type>
: std::true_type
{};
template<typename ModelType>
typename std::enable_if< HasStaticMethodName<ModelType>::value>::type
registerModelImpl(RegistryItemCreator creator, QString const &category )
{
const QString name = ModelType::Name();
if (_registeredItemCreators.count(name) == 0)
{
_registeredItemCreators[name] = std::move(creator);
_categories.insert(category);
_registeredModelsCategory[name] = category;
}
}
template<typename ModelType>
typename std::enable_if< !HasStaticMethodName<ModelType>::value>::type
registerModelImpl(RegistryItemCreator creator, QString const &category )
{
const QString name = creator()->name();
if (_registeredItemCreators.count(name) == 0)
{
_registeredItemCreators[name] = std::move(creator);
_categories.insert(category);
_registeredModelsCategory[name] = category;
}
}
};
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include "Compiler.hpp"
#include "OperatingSystem.hpp"
#ifdef NODE_EDITOR_PLATFORM_WINDOWS
# define NODE_EDITOR_EXPORT __declspec(dllexport)
# define NODE_EDITOR_IMPORT __declspec(dllimport)
# define NODE_EDITOR_LOCAL
#elif \
NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || \
defined (NODE_EDITOR_COMPILER_CLANG)
# define NODE_EDITOR_EXPORT __attribute__((visibility("default")))
# define NODE_EDITOR_IMPORT __attribute__((visibility("default")))
# define NODE_EDITOR_LOCAL __attribute__((visibility("hidden")))
#else
# define NODE_EDITOR_EXPORT
# define NODE_EDITOR_IMPORT
# define NODE_EDITOR_LOCAL
#endif
#ifdef __cplusplus
# define NODE_EDITOR_DEMANGLED extern "C"
#else
# define NODE_EDITOR_DEMANGLED
#endif
#if defined (NODE_EDITOR_SHARED) && !defined (NODE_EDITOR_STATIC)
# ifdef NODE_EDITOR_EXPORTS
# define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT
# else
# define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT
# endif
# define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL
#elif !defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC)
# define NODE_EDITOR_PUBLIC
# define NODE_EDITOR_PRIVATE
#elif defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC)
# ifdef NODE_EDITOR_EXPORTS
# error "Cannot build as shared and static simultaneously."
# else
# error "Cannot link against shared and static simultaneously."
# endif
#else
# ifdef NODE_EDITOR_EXPORTS
# error "Choose whether to build as shared or static."
# else
# error "Choose whether to link against shared or static."
# endif
#endif

View File

@@ -0,0 +1,171 @@
#pragma once
#include <QtCore/QUuid>
#include <QtCore/QJsonDocument>
#include <QtWidgets/QGraphicsScene>
#include <unordered_map>
#include <tuple>
#include <functional>
#include "QUuidStdHash.hpp"
#include "Export.hpp"
#include "DataModelRegistry.hpp"
#include "TypeConverter.hpp"
#include "memory.hpp"
namespace QtNodes
{
class NodeDataModel;
class FlowItemInterface;
class Node;
class NodeGraphicsObject;
class Connection;
class ConnectionGraphicsObject;
class NodeStyle;
/// Scene holds connections and nodes.
class NODE_EDITOR_PUBLIC FlowScene
: public QGraphicsScene
{
Q_OBJECT
public:
FlowScene(std::shared_ptr<DataModelRegistry> registry,
QObject * parent = Q_NULLPTR);
FlowScene(QObject * parent = Q_NULLPTR);
~FlowScene() override;
public:
std::shared_ptr<Connection>
createConnection(PortType connectedPort,
Node& node,
PortIndex portIndex);
std::shared_ptr<Connection>
createConnection(Node& nodeIn,
PortIndex portIndexIn,
Node& nodeOut,
PortIndex portIndexOut,
TypeConverter const & converter = TypeConverter{});
std::shared_ptr<Connection> restoreConnection(QJsonObject const &connectionJson);
void deleteConnection(Connection& connection);
Node&createNode(std::unique_ptr<NodeDataModel> && dataModel);
Node&restoreNode(QJsonObject const& nodeJson);
void removeNode(Node& node);
DataModelRegistry&registry() const;
void setRegistry(std::shared_ptr<DataModelRegistry> registry);
void iterateOverNodes(std::function<void(Node*)> const & visitor);
void iterateOverNodeData(std::function<void(NodeDataModel*)> const & visitor);
void iterateOverNodeDataDependentOrder(std::function<void(NodeDataModel*)> const & visitor);
QPointF getNodePosition(Node const& node) const;
void setNodePosition(Node& node, QPointF const& pos) const;
QSizeF getNodeSize(Node const& node) const;
public:
std::unordered_map<QUuid, std::unique_ptr<Node> > const & nodes() const;
std::unordered_map<QUuid, std::shared_ptr<Connection> > const & connections() const;
std::vector<Node*> allNodes() const;
std::vector<Node*> selectedNodes() const;
public:
void clearScene();
/// Open a FileDialog to save the scene in a .flow file
void save() const;
/// Load a FileDialog to open a scene from a .flow file
void load();
/// Dump the scene on a JSON QByteArray
QByteArray saveToMemory(QJsonDocument::JsonFormat format = QJsonDocument::Indented) const;
/// Load a scene from a JSON QByteArray
void loadFromMemory(const QByteArray& data);
/// Load a scene from a JSON Object
void loadFromMemory(const QJsonObject& data);
/// Save only a subset of the nodes to memory, as well as the connections that link two nodes lying within this subset.
QByteArray copyNodes(const std::vector<Node*> & nodes) const;
//! Paste selected nodes to the scene replacing uuids with new ones with a certain offset from the original position.
void pasteNodes(const QByteArray& data, const QPointF& pointOffset = QPointF(0,0));
Q_SIGNALS:
/**
* @brief Node has been created but not on the scene yet.
* @see nodePlaced()
*/
void nodeCreated(Node &n);
/**
* @brief Node has been added to the scene.
* @details Connect to this signal if need a correct position of node.
* @see nodeCreated()
*/
void nodePlaced(Node &n);
void nodeDeleted(Node &n);
void connectionCreated(Connection const &c);
void connectionDeleted(Connection const &c);
void nodeMoved(Node& n, const QPointF& newLocation);
void nodeDoubleClicked(Node& n);
void connectionHovered(Connection& c, QPoint screenPos);
void nodeHovered(Node& n, QPoint screenPos);
void connectionHoverLeft(Connection& c);
void nodeHoverLeft(Node& n);
void nodeContextMenu(Node& n, const QPointF& pos);
private:
using SharedConnection = std::shared_ptr<Connection>;
using UniqueNode = std::unique_ptr<Node>;
std::unordered_map<QUuid, SharedConnection> _connections;
std::unordered_map<QUuid, UniqueNode> _nodes;
std::shared_ptr<DataModelRegistry> _registry;
private Q_SLOTS:
void setupConnectionSignals(Connection const& c);
void sendConnectionCreatedToNodes(Connection const& c);
void sendConnectionDeletedToNodes(Connection const& c);
};
Node*
locateNodeAt(QPointF scenePoint, FlowScene &scene,
QTransform const & viewTransform);
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include <QtWidgets/QGraphicsView>
#include "Export.hpp"
namespace QtNodes
{
class FlowScene;
class NODE_EDITOR_PUBLIC FlowView
: public QGraphicsView
{
Q_OBJECT
public:
FlowView(QWidget *parent = Q_NULLPTR);
FlowView(FlowScene *scene, QWidget *parent = Q_NULLPTR);
FlowView(const FlowView&) = delete;
FlowView operator=(const FlowView&) = delete;
QAction* clearSelectionAction() const;
QAction* deleteSelectionAction() const;
void setScene(FlowScene *scene);
public Q_SLOTS:
void scaleUp();
void scaleDown();
void deleteSelectedNodes();
protected:
void contextMenuEvent(QContextMenuEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void drawBackground(QPainter* painter, const QRectF& r) override;
void showEvent(QShowEvent *event) override;
protected:
FlowScene * scene();
/**
* MIME type used to copy/paste and drag n drop nodes.
* You will likely want to override it with your own type.
* This defaults to 'application/x-nodeeditor-nodes'.
*/
virtual QString nodeMimeType() const;
private:
void copy();
void paste();
private:
QAction* _clearSelectionAction;
QAction* _deleteSelectionAction;
QPointF _clickPos;
FlowScene* _scene;
};
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <QtGui/QColor>
#include "Export.hpp"
#include "Style.hpp"
namespace QtNodes
{
class NODE_EDITOR_PUBLIC FlowViewStyle : public Style
{
public:
FlowViewStyle();
FlowViewStyle(QString jsonText);
public:
static void setStyle(QString jsonText);
private:
void loadJsonText(QString jsonText) override;
void loadJsonFile(QString fileName) override;
void loadJsonFromByteArray(QByteArray const &byteArray) override;
public:
QColor BackgroundColor;
QColor FineGridColor;
QColor CoarseGridColor;
};
}

View File

@@ -0,0 +1,122 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QJsonObject>
#include "PortType.hpp"
#include "Export.hpp"
#include "NodeState.hpp"
#include "NodeGeometry.hpp"
#include "NodeData.hpp"
#include "NodeGraphicsObject.hpp"
#include "ConnectionGraphicsObject.hpp"
#include "Serializable.hpp"
#include "memory.hpp"
namespace QtNodes
{
class Connection;
class ConnectionState;
class NodeGraphicsObject;
class NodeDataModel;
class NODE_EDITOR_PUBLIC Node
: public QObject
, public Serializable
{
Q_OBJECT
public:
/// NodeDataModel should be an rvalue and is moved into the Node
Node(std::unique_ptr<NodeDataModel> && dataModel);
virtual
~Node();
public:
QJsonObject
save() const override;
void
restore(QJsonObject const &json) override;
public:
QUuid
id() const;
void reactToPossibleConnection(PortType,
NodeDataType const &,
QPointF const & scenePoint);
void
resetReactionToConnection();
public:
NodeGraphicsObject const &
nodeGraphicsObject() const;
NodeGraphicsObject &
nodeGraphicsObject();
void
setGraphicsObject(std::unique_ptr<NodeGraphicsObject>&& graphics);
NodeGeometry&
nodeGeometry();
NodeGeometry const&
nodeGeometry() const;
NodeState const &
nodeState() const;
NodeState &
nodeState();
NodeDataModel*
nodeDataModel() const;
public Q_SLOTS: // data propagation
/// Propagates incoming data to the underlying model.
void
propagateData(std::shared_ptr<NodeData> nodeData,
PortIndex inPortIndex) const;
/// Fetches data from model's OUT #index port
/// and propagates it to the connection
void
onDataUpdated(PortIndex index);
/// update the graphic part if the size of the embeddedwidget changes
void
onNodeSizeUpdated();
private:
// addressing
QUuid _uid;
// data
std::unique_ptr<NodeDataModel> _nodeDataModel;
NodeState _nodeState;
// painting
NodeGeometry _nodeGeometry;
std::unique_ptr<NodeGraphicsObject> _nodeGraphicsObject;
};
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include <QtCore/QString>
#include "Export.hpp"
namespace QtNodes
{
struct NodeDataType
{
QString id;
QString name;
};
/// Class represents data transferred between nodes.
/// @param type is used for comparing the types
/// The actual data is stored in subtypes
class NODE_EDITOR_PUBLIC NodeData
{
public:
virtual ~NodeData() = default;
virtual bool sameType(NodeData const &nodeData) const
{
return (this->type().id == nodeData.type().id);
}
/// Type for inner use
virtual NodeDataType type() const = 0;
};
}

View File

@@ -0,0 +1,171 @@
#pragma once
#include <QtWidgets/QWidget>
#include "PortType.hpp"
#include "NodeData.hpp"
#include "Serializable.hpp"
#include "NodeGeometry.hpp"
#include "NodeStyle.hpp"
#include "NodePainterDelegate.hpp"
#include "Export.hpp"
#include "memory.hpp"
namespace QtNodes
{
enum class NodeValidationState
{
Valid,
Warning,
Error
};
class Connection;
class StyleCollection;
class NODE_EDITOR_PUBLIC NodeDataModel
: public QObject
, public Serializable
{
Q_OBJECT
public:
NodeDataModel();
virtual
~NodeDataModel() = default;
/// Caption is used in GUI
virtual QString
caption() const = 0;
/// It is possible to hide caption in GUI
virtual bool
captionVisible() const { return true; }
/// Port caption is used in GUI to label individual ports
virtual QString
portCaption(PortType, PortIndex) const { return QString(); }
/// It is possible to hide port caption in GUI
virtual bool
portCaptionVisible(PortType, PortIndex) const { return false; }
/// Name makes this model unique
virtual QString
name() const = 0;
virtual QWidget* portDefaultValueWidget(PortIndex port_index) { return nullptr; }
public:
QJsonObject
save() const override;
public:
virtual
unsigned int nPorts(PortType portType) const = 0;
virtual
NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0;
public:
enum class ConnectionPolicy
{
One,
Many,
};
virtual
ConnectionPolicy
portOutConnectionPolicy(PortIndex) const
{
return ConnectionPolicy::Many;
}
NodeStyle const&
nodeStyle() const;
void
setNodeStyle(NodeStyle const& style);
public:
/// Triggers the algorithm
virtual
void
setInData(std::shared_ptr<NodeData> nodeData,
PortIndex port) = 0;
virtual
std::shared_ptr<NodeData>
outData(PortIndex port) = 0;
virtual
QWidget *
embeddedWidget() = 0;
virtual
bool
resizable() const { return false; }
virtual
NodeValidationState
validationState() const { return NodeValidationState::Valid; }
virtual
QString
validationMessage() const { return QString(""); }
virtual
NodePainterDelegate* painterDelegate() const { return nullptr; }
public Q_SLOTS:
virtual void
inputConnectionCreated(Connection const&)
{
}
virtual void
inputConnectionDeleted(Connection const&)
{
}
virtual void
outputConnectionCreated(Connection const&)
{
}
virtual void
outputConnectionDeleted(Connection const&)
{
}
Q_SIGNALS:
void
dataUpdated(PortIndex index);
void
dataInvalidated(PortIndex index);
void
computingStarted();
void
computingFinished();
void embeddedWidgetSizeUpdated();
private:
NodeStyle _nodeStyle;
};
}

View File

@@ -0,0 +1,162 @@
#pragma once
#include <QtCore/QRectF>
#include <QtCore/QPointF>
#include <QtGui/QTransform>
#include <QtGui/QFontMetrics>
#include "PortType.hpp"
#include "Export.hpp"
#include "memory.hpp"
namespace QtNodes
{
class NodeState;
class NodeDataModel;
class Node;
class NODE_EDITOR_PUBLIC NodeGeometry
{
public:
NodeGeometry(std::unique_ptr<NodeDataModel> const &dataModel);
public:
unsigned int
height() const { return _height; }
void
setHeight(unsigned int h) { _height = h; }
unsigned int
width() const { return _width; }
void
setWidth(unsigned int w) { _width = w; }
unsigned int
entryHeight() const { return _entryHeight; }
void
setEntryHeight(unsigned int h) { _entryHeight = h; }
unsigned int
entryWidth() const { return _entryWidth; }
void
setEntryWidth(unsigned int w) { _entryWidth = w; }
unsigned int
spacing() const { return _spacing; }
void
setSpacing(unsigned int s) { _spacing = s; }
bool
hovered() const { return _hovered; }
void
setHovered(unsigned int h) { _hovered = h; }
unsigned int
nSources() const;
unsigned int
nSinks() const;
QPointF const&
draggingPos() const
{ return _draggingPos; }
void
setDraggingPosition(QPointF const& pos)
{ _draggingPos = pos; }
public:
QRectF
entryBoundingRect() const;
QRectF
boundingRect() const;
/// Updates size unconditionally
void
recalculateSize() const;
/// Updates size if the QFontMetrics is changed
void
recalculateSize(QFont const &font) const;
// TODO removed default QTransform()
QPointF
portScenePosition(PortIndex index,
PortType portType,
QTransform const & t = QTransform()) const;
PortIndex
checkHitScenePoint(PortType portType,
QPointF point,
QTransform const & t = QTransform()) const;
QRect
resizeRect() const;
/// Returns the position of a widget on the Node surface
QPointF
widgetPosition() const;
/// Returns the maximum height a widget can be without causing the node to grow.
int
equivalentWidgetHeight() const;
unsigned int
validationHeight() const;
unsigned int
validationWidth() const;
static
QPointF
calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node* targetNode,
PortIndex sourcePortIndex, PortType sourcePort, Node* sourceNode,
Node& newNode);
private:
unsigned int
captionHeight() const;
unsigned int
captionWidth() const;
unsigned int
portWidth(PortType portType) const;
private:
// some variables are mutable because
// we need to change drawing metrics
// corresponding to fontMetrics
// but this doesn't change constness of Node
mutable unsigned int _width;
mutable unsigned int _height;
unsigned int _entryWidth;
mutable unsigned int _inputPortWidth;
mutable unsigned int _outputPortWidth;
mutable unsigned int _entryHeight;
unsigned int _spacing;
bool _hovered;
unsigned int _nSources;
unsigned int _nSinks;
QPointF _draggingPos;
std::unique_ptr<NodeDataModel> const &_dataModel;
mutable QFontMetrics _fontMetrics;
mutable QFontMetrics _boldFontMetrics;
};
}

View File

@@ -0,0 +1,105 @@
#pragma once
#include <QtCore/QUuid>
#include <QtWidgets/QGraphicsObject>
#include "Connection.hpp"
#include "NodeGeometry.hpp"
#include "NodeState.hpp"
class QGraphicsProxyWidget;
namespace QtNodes
{
class FlowScene;
class FlowItemEntry;
/// Class reacts on GUI events, mouse clicks and
/// forwards painting operation.
class NodeGraphicsObject : public QGraphicsObject
{
Q_OBJECT
public:
NodeGraphicsObject(FlowScene &scene,
Node& node);
virtual
~NodeGraphicsObject();
Node&
node();
Node const&
node() const;
QRectF
boundingRect() const override;
void
setGeometryChanged();
/// Visits all attached connections and corrects
/// their corresponding end points.
void
moveConnections() const;
enum { Type = UserType + 1 };
int
type() const override { return Type; }
void
lock(bool locked);
protected:
void
paint(QPainter* painter,
QStyleOptionGraphicsItem const* option,
QWidget* widget = 0) override;
QVariant
itemChange(GraphicsItemChange change, const QVariant &value) override;
void
mousePressEvent(QGraphicsSceneMouseEvent* event) override;
void
mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
void
mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
void
hoverEnterEvent(QGraphicsSceneHoverEvent* event) override;
void
hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override;
void
hoverMoveEvent(QGraphicsSceneHoverEvent *) override;
void
mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override;
void
contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
private:
void
embedQWidget();
private:
FlowScene & _scene;
Node& _node;
bool _locked;
// either nullptr or owned by parent QGraphicsItem
QGraphicsProxyWidget * _proxyWidget;
};
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <QPainter>
#include "NodeGeometry.hpp"
#include "NodeDataModel.hpp"
#include "Export.hpp"
namespace QtNodes {
/// Class to allow for custom painting
class NODE_EDITOR_PUBLIC NodePainterDelegate
{
public:
virtual
~NodePainterDelegate() = default;
virtual void
paint(QPainter* painter,
NodeGeometry const& geom,
NodeDataModel const * model) = 0;
};
}

View File

@@ -0,0 +1,97 @@
#pragma once
#include <vector>
#include <unordered_map>
#include <QtCore/QUuid>
#include "Export.hpp"
#include "PortType.hpp"
#include "NodeData.hpp"
#include "memory.hpp"
namespace QtNodes
{
class Connection;
class NodeDataModel;
/// Contains vectors of connected input and output connections.
/// Stores bool for reacting on hovering connections
class NODE_EDITOR_PUBLIC NodeState
{
public:
enum ReactToConnectionState
{
REACTING,
NOT_REACTING
};
public:
NodeState(std::unique_ptr<NodeDataModel> const &model);
public:
using ConnectionPtrSet =
std::unordered_map<QUuid, Connection*>;
/// Returns vector of connections ID.
/// Some of them can be empty (null)
std::vector<ConnectionPtrSet> const&
getEntries(PortType) const;
std::vector<ConnectionPtrSet> &
getEntries(PortType);
ConnectionPtrSet
connections(PortType portType, PortIndex portIndex) const;
void
setConnection(PortType portType,
PortIndex portIndex,
Connection& connection);
void
eraseConnection(PortType portType,
PortIndex portIndex,
QUuid id);
ReactToConnectionState
reaction() const;
PortType
reactingPortType() const;
NodeDataType
reactingDataType() const;
void
setReaction(ReactToConnectionState reaction,
PortType reactingPortType = PortType::None,
NodeDataType reactingDataType =
NodeDataType());
bool
isReacting() const;
void
setResizing(bool resizing);
bool
resizing() const;
private:
std::vector<ConnectionPtrSet> _inConnections;
std::vector<ConnectionPtrSet> _outConnections;
ReactToConnectionState _reaction;
PortType _reactingPortType;
NodeDataType _reactingDataType;
bool _resizing;
};
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <QtGui/QColor>
#include "Export.hpp"
#include "Style.hpp"
namespace QtNodes
{
class NODE_EDITOR_PUBLIC NodeStyle : public Style
{
public:
NodeStyle();
NodeStyle(QString jsonText);
public:
static void setNodeStyle(QString jsonText);
private:
void loadJsonText(QString jsonText) override;
void loadJsonFile(QString fileName) override;
void loadJsonFromByteArray(QByteArray const &byteArray) override;
public:
QColor NormalBoundaryColor;
QColor SelectedBoundaryColor;
QColor GradientColor0;
QColor GradientColor1;
QColor GradientColor2;
QColor GradientColor3;
QColor ShadowColor;
QColor FontColor;
QColor FontColorFaded;
QColor ConnectionPointColor;
QColor FilledConnectionPointColor;
QColor WarningColor;
QColor ErrorColor;
float PenWidth;
float HoveredPenWidth;
float ConnectionPointDiameter;
float Opacity;
};
}

View File

@@ -0,0 +1,78 @@
#pragma once
#if \
defined (__CYGWIN__) || \
defined (__CYGWIN32__)
# define NODE_EDITOR_PLATFORM "Cygwin"
# define NODE_EDITOR_PLATFORM_CYGWIN
# define NODE_EDITOR_PLATFORM_UNIX
# define NODE_EDITOR_PLATFORM_WINDOWS
#elif \
defined (_WIN16) || \
defined (_WIN32) || \
defined (_WIN64) || \
defined (__WIN32__) || \
defined (__TOS_WIN__) || \
defined (__WINDOWS__)
# define NODE_EDITOR_PLATFORM "Windows"
# define NODE_EDITOR_PLATFORM_WINDOWS
#elif \
defined (macintosh) || \
defined (Macintosh) || \
defined (__TOS_MACOS__) || \
(defined (__APPLE__) && defined (__MACH__))
# define NODE_EDITOR_PLATFORM "Mac"
# define NODE_EDITOR_PLATFORM_MAC
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (linux) || \
defined (__linux) || \
defined (__linux__) || \
defined (__TOS_LINUX__)
# define NODE_EDITOR_PLATFORM "Linux"
# define NODE_EDITOR_PLATFORM_LINUX
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (__FreeBSD__) || \
defined (__OpenBSD__) || \
defined (__NetBSD__) || \
defined (__bsdi__) || \
defined (__DragonFly__)
# define NODE_EDITOR_PLATFORM "BSD"
# define NODE_EDITOR_PLATFORM_BSD
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (sun) || \
defined (__sun)
# define NODE_EDITOR_PLATFORM "Solaris"
# define NODE_EDITOR_PLATFORM_SOLARIS
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (_AIX) || \
defined (__TOS_AIX__)
# define NODE_EDITOR_PLATFORM "AIX"
# define NODE_EDITOR_PLATFORM_AIX
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (hpux) || \
defined (_hpux) || \
defined (__hpux)
# define NODE_EDITOR_PLATFORM "HPUX"
# define NODE_EDITOR_PLATFORM_HPUX
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (__QNX__)
# define NODE_EDITOR_PLATFORM "QNX"
# define NODE_EDITOR_PLATFORM_QNX
# define NODE_EDITOR_PLATFORM_UNIX
#elif \
defined (unix) || \
defined (__unix) || \
defined (__unix__)
# define NODE_EDITOR_PLATFORM "Unix"
# define NODE_EDITOR_PLATFORM_UNIX
#endif
#ifndef NODE_EDITOR_PLATFORM
# error "Current platform is not supported."
#endif

View File

@@ -0,0 +1,67 @@
#pragma once
#include <utility>
#include <QtCore/QUuid>
namespace QtNodes
{
enum class PortType
{
None,
In,
Out
};
static const int INVALID = -1;
using PortIndex = int;
struct Port
{
PortType type;
PortIndex index;
Port()
: type(PortType::None)
, index(INVALID)
{}
Port(PortType t, PortIndex i)
: type(t)
, index(i)
{}
bool
indexIsValid() { return index != INVALID; }
bool
portTypeIsValid() { return type != PortType::None; }
};
//using PortAddress = std::pair<QUuid, PortIndex>;
inline
PortType
oppositePort(PortType port)
{
PortType result = PortType::None;
switch (port)
{
case PortType::In:
result = PortType::Out;
break;
case PortType::Out:
result = PortType::In;
break;
default:
break;
}
return result;
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <QtGlobal>
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
// As of 5.14 there is a specialization std::hash<QString>
#include <functional>
#include <QtCore/QString>
#include <QtCore/QVariant>
namespace std
{
template<>
struct hash<QString>
{
inline std::size_t
operator()(QString const &s) const
{
return qHash(s);
}
};
}
#endif

View File

@@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include <QtCore/QUuid>
#include <QtCore/QVariant>
namespace std
{
template<>
struct hash<QUuid>
{
inline
std::size_t
operator()(QUuid const& uid) const
{
return qHash(uid);
}
};
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <QtCore/QJsonObject>
namespace QtNodes
{
class Serializable
{
public:
virtual
~Serializable() = default;
virtual
QJsonObject
save() const = 0;
virtual void
restore(QJsonObject const & /*p*/) {}
};
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <QtCore/QString>
namespace QtNodes
{
class Style
{
public:
virtual
~Style() = default;
private:
virtual void
loadJsonText(QString jsonText) = 0;
virtual void
loadJsonFile(QString fileName) = 0;
virtual void
loadJsonFromByteArray(QByteArray const &byteArray) = 0;
};
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "NodeData.hpp"
#include "memory.hpp"
#include <functional>
namespace QtNodes
{
using SharedNodeData = std::shared_ptr<NodeData>;
// a function taking in NodeData and returning NodeData
using TypeConverter =
std::function<SharedNodeData(SharedNodeData)>;
// data-type-in, data-type-out
using TypeConverterId =
std::pair<NodeDataType, NodeDataType>;
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include <memory>
#include <utility>
namespace QtNodes
{
namespace detail {
#if (!defined(_MSC_VER) && (__cplusplus < 201300)) || \
( defined(_MSC_VER) && (_MSC_VER < 1800))
//_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant,
// and it has make_unique in it's standard library implementation
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
#else
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::make_unique<T>(std::forward<Args>(args)...);
}
#endif
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View File

@@ -0,0 +1,42 @@
{
"FlowViewStyle": {
"BackgroundColor": [53, 53, 53],
"FineGridColor": [60, 60, 60],
"CoarseGridColor": [25, 25, 25]
},
"NodeStyle": {
"NormalBoundaryColor": [255, 255, 255],
"SelectedBoundaryColor": [255, 165, 0],
"GradientColor0": "gray",
"GradientColor1": [80, 80, 80],
"GradientColor2": [64, 64, 64],
"GradientColor3": [58, 58, 58],
"ShadowColor": [20, 20, 20],
"FontColor" : "white",
"FontColorFaded" : "gray",
"ConnectionPointColor": [169, 169, 169],
"FilledConnectionPointColor": "cyan",
"ErrorColor": "red",
"WarningColor": [128, 128, 0],
"PenWidth": 1.0,
"HoveredPenWidth": 1.5,
"ConnectionPointDiameter": 8.0,
"Opacity": 0.8
},
"ConnectionStyle": {
"ConstructionColor": "gray",
"NormalColor": "darkcyan",
"SelectedColor": [100, 100, 100],
"SelectedHaloColor": "orange",
"HoveredColor": "lightcyan",
"LineWidth": 3.0,
"ConstructionLineWidth": 2.0,
"PointDiameter": 10.0,
"UseDataDefinedColors": false
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,6 @@
<RCC version="1.0">
<qresource>
<file>DefaultStyle.json</file>
<file>convert.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,450 @@
#include "Connection.hpp"
#include <cmath>
#include <utility>
#include <QtWidgets/QtWidgets>
#include <QtGlobal>
#include "Node.hpp"
#include "FlowScene.hpp"
#include "FlowView.hpp"
#include "NodeGeometry.hpp"
#include "NodeGraphicsObject.hpp"
#include "NodeDataModel.hpp"
#include "ConnectionState.hpp"
#include "ConnectionGeometry.hpp"
#include "ConnectionGraphicsObject.hpp"
using QtNodes::Connection;
using QtNodes::PortType;
using QtNodes::PortIndex;
using QtNodes::ConnectionState;
using QtNodes::Node;
using QtNodes::NodeData;
using QtNodes::NodeDataType;
using QtNodes::ConnectionGraphicsObject;
using QtNodes::ConnectionGeometry;
using QtNodes::TypeConverter;
Connection::
Connection(PortType portType,
Node& node,
PortIndex portIndex)
: _uid(QUuid::createUuid())
, _outPortIndex(INVALID)
, _inPortIndex(INVALID)
, _connectionState()
{
setNodeToPort(node, portType, portIndex);
setRequiredPort(oppositePort(portType));
}
Connection::
Connection(Node& nodeIn,
PortIndex portIndexIn,
Node& nodeOut,
PortIndex portIndexOut,
TypeConverter typeConverter)
: _uid(QUuid::createUuid())
, _outNode(&nodeOut)
, _inNode(&nodeIn)
, _outPortIndex(portIndexOut)
, _inPortIndex(portIndexIn)
, _connectionState()
, _converter(std::move(typeConverter))
{
setNodeToPort(nodeIn, PortType::In, portIndexIn);
setNodeToPort(nodeOut, PortType::Out, portIndexOut);
}
Connection::
~Connection()
{
if (complete()) connectionMadeIncomplete(*this);
propagateEmptyData();
if (_inNode)
{
_inNode->nodeGraphicsObject().update();
}
if (_outNode)
{
_outNode->nodeGraphicsObject().update();
}
}
QJsonObject
Connection::
save() const
{
QJsonObject connectionJson;
if (_inNode && _outNode)
{
connectionJson["in_id"] = _inNode->id().toString();
connectionJson["in_index"] = _inPortIndex;
connectionJson["in_type"] = _inNode->nodeDataModel()->dataType(PortType::In, _inPortIndex).id;
connectionJson["out_id"] = _outNode->id().toString();
connectionJson["out_index"] = _outPortIndex;
connectionJson["out_type"] = _outNode->nodeDataModel()->dataType(PortType::Out, _outPortIndex).id;
if (_converter)
{
auto getTypeJson = [this](PortType type)
{
QJsonObject typeJson;
NodeDataType nodeType = this->dataType(type);
typeJson["id"] = nodeType.id;
typeJson["name"] = nodeType.name;
return typeJson;
};
QJsonObject converterTypeJson;
converterTypeJson["in"] = getTypeJson(PortType::In);
converterTypeJson["out"] = getTypeJson(PortType::Out);
connectionJson["converter"] = converterTypeJson;
}
}
return connectionJson;
}
QUuid
Connection::
id() const
{
return _uid;
}
bool
Connection::
complete() const
{
return _inNode != nullptr && _outNode != nullptr;
}
void
Connection::
setRequiredPort(PortType dragging)
{
_connectionState.setRequiredPort(dragging);
switch (dragging)
{
case PortType::Out:
_outNode = nullptr;
_outPortIndex = INVALID;
break;
case PortType::In:
_inNode = nullptr;
_inPortIndex = INVALID;
break;
default:
break;
}
}
PortType
Connection::
requiredPort() const
{
return _connectionState.requiredPort();
}
void
Connection::
setGraphicsObject(std::unique_ptr<ConnectionGraphicsObject>&& graphics)
{
_connectionGraphicsObject = std::move(graphics);
// This function is only called when the ConnectionGraphicsObject
// is newly created. At this moment both end coordinates are (0, 0)
// in Connection G.O. coordinates. The position of the whole
// Connection G. O. in scene coordinate system is also (0, 0).
// By moving the whole object to the Node Port position
// we position both connection ends correctly.
if (requiredPort() != PortType::None)
{
PortType attachedPort = oppositePort(requiredPort());
PortIndex attachedPortIndex = getPortIndex(attachedPort);
auto node = getNode(attachedPort);
QTransform nodeSceneTransform =
node->nodeGraphicsObject().sceneTransform();
QPointF pos = node->nodeGeometry().portScenePosition(attachedPortIndex,
attachedPort,
nodeSceneTransform);
_connectionGraphicsObject->setPos(pos);
}
_connectionGraphicsObject->move();
}
PortIndex
Connection::
getPortIndex(PortType portType) const
{
PortIndex result = INVALID;
switch (portType)
{
case PortType::In:
result = _inPortIndex;
break;
case PortType::Out:
result = _outPortIndex;
break;
default:
break;
}
return result;
}
void
Connection::
setNodeToPort(Node& node,
PortType portType,
PortIndex portIndex)
{
bool wasIncomplete = !complete();
auto& nodeWeak = getNode(portType);
nodeWeak = &node;
if (portType == PortType::Out)
_outPortIndex = portIndex;
else
_inPortIndex = portIndex;
_connectionState.setNoRequiredPort();
updated(*this);
if (complete() && wasIncomplete) {
connectionCompleted(*this);
}
}
void
Connection::
removeFromNodes() const
{
if (_inNode)
_inNode->nodeState().eraseConnection(PortType::In, _inPortIndex, id());
if (_outNode)
_outNode->nodeState().eraseConnection(PortType::Out, _outPortIndex, id());
}
ConnectionGraphicsObject&
Connection::
getConnectionGraphicsObject() const
{
return *_connectionGraphicsObject;
}
ConnectionState&
Connection::
connectionState()
{
return _connectionState;
}
ConnectionState const&
Connection::
connectionState() const
{
return _connectionState;
}
ConnectionGeometry&
Connection::
connectionGeometry()
{
return _connectionGeometry;
}
ConnectionGeometry const&
Connection::
connectionGeometry() const
{
return _connectionGeometry;
}
Node*
Connection::
getNode(PortType portType) const
{
switch (portType)
{
case PortType::In:
return _inNode;
break;
case PortType::Out:
return _outNode;
break;
default:
// not possible
break;
}
return nullptr;
}
Node*&
Connection::
getNode(PortType portType)
{
switch (portType)
{
case PortType::In:
return _inNode;
break;
case PortType::Out:
return _outNode;
break;
default:
// not possible
break;
}
Q_UNREACHABLE();
}
void
Connection::
clearNode(PortType portType)
{
if (complete()) {
connectionMadeIncomplete(*this);
}
getNode(portType) = nullptr;
if (portType == PortType::In)
_inPortIndex = INVALID;
else
_outPortIndex = INVALID;
}
NodeDataType
Connection::
dataType(PortType portType) const
{
if (_inNode && _outNode)
{
auto const & model = (portType == PortType::In) ?
_inNode->nodeDataModel() :
_outNode->nodeDataModel();
PortIndex index = (portType == PortType::In) ?
_inPortIndex :
_outPortIndex;
return model->dataType(portType, index);
}
else
{
Node* validNode;
PortIndex index = INVALID;
if ((validNode = _inNode))
{
index = _inPortIndex;
portType = PortType::In;
}
else if ((validNode = _outNode))
{
index = _outPortIndex;
portType = PortType::Out;
}
if (validNode)
{
auto const &model = validNode->nodeDataModel();
return model->dataType(portType, index);
}
}
Q_UNREACHABLE();
}
void
Connection::
setTypeConverter(TypeConverter converter)
{
_converter = std::move(converter);
}
void
Connection::
propagateData(std::shared_ptr<NodeData> nodeData) const
{
if (_inNode)
{
if (_converter)
{
nodeData = _converter(nodeData);
}
_inNode->propagateData(nodeData, _inPortIndex);
}
}
void
Connection::
propagateEmptyData() const
{
std::shared_ptr<NodeData> emptyData;
propagateData(emptyData);
}

View File

@@ -0,0 +1,27 @@
#include "ConnectionBlurEffect.hpp"
#include "ConnectionGraphicsObject.hpp"
#include "ConnectionPainter.hpp"
using QtNodes::ConnectionBlurEffect;
using QtNodes::ConnectionGraphicsObject;
ConnectionBlurEffect::
ConnectionBlurEffect(ConnectionGraphicsObject*)
{
//
}
void
ConnectionBlurEffect::
draw(QPainter* painter)
{
QGraphicsBlurEffect::draw(painter);
//ConnectionPainter::paint(painter,
//_object->connectionGeometry(),
//_object->connectionState());
//_item->paint(painter, nullptr, nullptr);
}

View File

@@ -0,0 +1,22 @@
#include <QtWidgets/QGraphicsBlurEffect>
#include <QtWidgets/QGraphicsItem>
namespace QtNodes
{
class ConnectionGraphicsObject;
class ConnectionBlurEffect : public QGraphicsBlurEffect
{
public:
ConnectionBlurEffect(ConnectionGraphicsObject* item);
void
draw(QPainter* painter) override;
private:
};
}

Some files were not shown because too many files have changed in this diff Show More