diff --git a/.gitmodules b/.gitmodules index 95e8718..ab67074 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "3rdparty/nodeeditor"] path = 3rdparty/nodeeditor - url = https://github.com/paceholder/nodeeditor.git + url = https://github.com/alexge50/nodeeditor [submodule "3rdparty/Qt-Color-Widgets"] path = 3rdparty/Qt-Color-Widgets url = https://gitlab.com/mattia.basaglia/Qt-Color-Widgets.git diff --git a/3rdparty/nodeeditor b/3rdparty/nodeeditor index 1151b59..0eb67c1 160000 --- a/3rdparty/nodeeditor +++ b/3rdparty/nodeeditor @@ -1 +1 @@ -Subproject commit 1151b597b3370977e656bd17e675543c22fc78fa +Subproject commit 0eb67c1f52656081902e238431af432daed6c879 diff --git a/CMakeLists.txt b/CMakeLists.txt index 77513a0..b523a99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,11 @@ cmake_minimum_required(VERSION 3.9) project(GIE) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wno-deprecated-declarations") + set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") + set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wno-deprecated-declarations") elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() @@ -15,6 +17,7 @@ if(NOT TARGET Catch2) add_subdirectory(3rdparty/Catch2) endif() add_subdirectory(3rdparty/Qt-Color-Widgets) +add_subdirectory(util) add_subdirectory(gie) add_subdirectory(modules) add_subdirectory(gui) diff --git a/README.md b/README.md index b3d56ac..02a9f06 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # GIE -![](https://img.shields.io/badge/version-0.1.0-blue.svg) +![](https://img.shields.io/badge/version-0.2.0-blue.svg) -GIE (Generative Image Editor, `/jəī/`) is a node based image editor, inspired by Blender's material node editing feature. Building upon the descriptive nature of visual data flow programming, each node represent a mutation done to a source image. +GIE (Generative Image Editor, `/jəī/`) is a node based image editor, inspired by Blender's material node editing feature. Building upon the descriptive nature of visual data flow programming, each node represents a mutation done to a source image. -![gie screen shot](screenshots/screenshot-1.png) +![GIE](screenshots/gie.jpg) ## Usage The UI is intended to be relatively straight-forward. Much like in Blender, the right click context menu contains all possible nodes. In order to use pictures as sources, the user has to import them through the menu present on the top bar. @@ -19,6 +19,7 @@ Requirements: * c++17 compatible compiler (gcc recommended!) * boost.python * qt5 +* python environment + numpy and scipy ```bash git clone https://github.com/alexge50/gie.git @@ -34,7 +35,30 @@ make install ### Windows * binaries coming soon +## Screenshots + +![gie screen shot](screenshots/screenshot-4.png) + +![gie screen shot](screenshots/screenshot-1.png) + +![gie screen shot](screenshots/screenshot-2.png) + +![gie screen shot](screenshots/screenshot-3.png) + +## Change log +`version 0.2.0`: +* live code reload +* numpy integration +* moved compute work to separated thread +* added various nodes +* log console + +`version 0.1.0`: +* initial version +* load/open project +* import/export images +* node based editor + ## TODO * custom Qt UI theme -* allow user side scripting from the interface - creating nodes that invoke user code * multi-threading - allowing multiple nodes to be run at the same time diff --git a/ci/Dockerfile b/ci/Dockerfile index dd06ae0..750739b 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -8,8 +8,11 @@ RUN apt-get install -qq -y --no-install-recommends \ python3 \ libboost-dev \ libboost-python1.67-dev \ + libboost-numpy1.67-dev \ qt5-default \ libqt5opengl5-dev \ + libqt5designer5 \ + qttools5-dev \ xvfb ENTRYPOINT ["/bin/bash"] diff --git a/ci/build-app b/ci/build-app index e1aa71a..8f0a0f2 100644 --- a/ci/build-app +++ b/ci/build-app @@ -10,6 +10,6 @@ BUILD_DIR=$PWD/build mkdir $BUILD_DIR cd $BUILD_DIR cmake .. -cmake --build . --target gie_test +make cd gie ./gie_test diff --git a/gie/CMakeLists.txt b/gie/CMakeLists.txt index 6eb81d5..f0b5934 100644 --- a/gie/CMakeLists.txt +++ b/gie/CMakeLists.txt @@ -5,30 +5,14 @@ project(gie_library VERSION 0.0.0 DESCRIPTION "GenerativeImageEditor library") set(CMAKE_CXX_STANDARD 17) include(GNUInstallDirs) -set(LIBRARY_SOURCES - include/gie/Argument.h - include/gie/NodeDrawable.h - include/gie/ScriptGraph/Execute.h - include/gie/ScriptGraph/ScriptGraph.h - include/gie/NodeLogic.h - include/gie/Node.h - include/gie/Program.h - include/gie/PythonContext.h - include/gie/Value.h - src/Program.cpp - src/PythonContext.cpp - src/ScriptGraph/Execute.cpp - src/ScriptGraph/ScriptGraph.cpp - include/gie/NodeUtil.h - src/NodeUtil.cpp - include/gie/Result.h -) +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*) +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS include/*) find_package(Boost COMPONENTS python3 REQUIRED) find_package(PythonLibs REQUIRED) -add_library(gie STATIC ${LIBRARY_SOURCES}) -target_link_libraries(gie PUBLIC ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) +add_library(gie STATIC ${SOURCES} ${HEADERS} include/gie/NodeId.h) +target_link_libraries(gie PUBLIC util ${Boost_LIBRARIES} ${PYTHON_LIBRARIES}) target_include_directories(gie PUBLIC $ $) diff --git a/gie/include/gie/Argument.h b/gie/include/gie/Argument.h index faa94e1..1ba603f 100644 --- a/gie/include/gie/Argument.h +++ b/gie/include/gie/Argument.h @@ -5,8 +5,18 @@ #ifndef GIE_LIBRARY_ARGUMENT_H #define GIE_LIBRARY_ARGUMENT_H -#include #include +#include +#include + +#include + +#include +#include +#include +#include +#include + struct ArgumentMetadata { @@ -14,5 +24,11 @@ struct ArgumentMetadata Type m_argumentType; }; +struct NoArgument {}; + +using ArgumentId = StrongAlias; +using ArgumentValue = std::variant; + +using Arguments = std::vector; #endif //GIE_LIBRARY_ARGUMENT_H diff --git a/gie/include/gie/Error.h b/gie/include/gie/Error.h new file mode 100644 index 0000000..cf07212 --- /dev/null +++ b/gie/include/gie/Error.h @@ -0,0 +1,73 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_ERROR_H +#define GIE_ERROR_H + +class NodeInterfaceError +{ +public: + enum class errors + { + TypeCheckingFailed = 0, + IncorrectNodeId, + IncorrectSymbolName + }; + + explicit NodeInterfaceError(errors error): m_error{error} {} + + const char* what() + { + static const char* names[] = {"TypeCheckingFailed", "IncorrectNodeId", "IncorrectSymbolName"}; + + return names[static_cast(m_error)]; + } + +private: + errors m_error; +}; + +class ExecutionInterfaceError +{ +public: + enum class errors + { + PythonInternalError = 0, + InvalidArguments + }; + + explicit ExecutionInterfaceError(errors error, NodeId id, std::optional detail = std::nullopt): + m_error{error}, + m_id{id}, + m_detail{std::move(detail)} + {} + + std::string what() + { + static const char* names[] = {"PythonInternalError", "InvalidArguments"}; + std::string buffer = names[static_cast(error())]; + buffer += "("; + buffer += std::to_string(id().get()); + buffer += ")"; + + if(m_detail) + { + buffer += ": "; + buffer += m_detail.value(); + } + + return buffer; + } + + NodeId id() const { return m_id; } + errors error() const { return m_error; } + const std::optional& detail() const { return m_detail; } + +private: + errors m_error; + NodeId m_id; + std::optional m_detail; +}; + +#endif //GIE_ERROR_H diff --git a/gie/include/gie/Node.h b/gie/include/gie/Node.h index 6dd606a..65c3e2e 100644 --- a/gie/include/gie/Node.h +++ b/gie/include/gie/Node.h @@ -1,3 +1,7 @@ +#include + +#include + // // Created by alex on 11/17/18. // @@ -5,22 +9,34 @@ #ifndef GIE_LIBRARY_NODE_H #define GIE_LIBRARY_NODE_H -#include "NodeDrawable.h" -#include "NodeLogic.h" -#include "NodeMetadata.h" +#include +#include +#include #include #include #include -using NodeId = std::size_t; - -struct Node +class Node { - NodeDrawable m_drawable; - NodeLogic m_logic; - NodeMetadata m_metadata; +private: + Node(Arguments arguments, SymbolId symbolId): + arguments{std::move(arguments)}, + m_symbolId{symbolId} + {} + +public: + Arguments arguments; + +public: + const SymbolId& symbolId() const { return m_symbolId; } + +private: + SymbolId m_symbolId; + + friend std::optional makeNode(const PythonContext&, const std::string& name, Arguments); }; +std::optional makeNode(const PythonContext&, const std::string& name, Arguments); #endif //GIE_LIBRARY_NODE_H diff --git a/gie/include/gie/NodeDrawable.h b/gie/include/gie/NodeDrawable.h deleted file mode 100644 index 58f9b47..0000000 --- a/gie/include/gie/NodeDrawable.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by alex on 11/30/18. -// - -#ifndef GIE_LIBRARY_DRAWABLENODE_H -#define GIE_LIBRARY_DRAWABLENODE_H - - -struct NodeDrawable -{ - -}; - - -#endif //GIE_LIBRARY_DRAWABLENODE_H diff --git a/gie/include/gie/NodeId.h b/gie/include/gie/NodeId.h new file mode 100644 index 0000000..21ab7cb --- /dev/null +++ b/gie/include/gie/NodeId.h @@ -0,0 +1,29 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_NODEID_H +#define GIE_NODEID_H + +#include +#include + +using NodeId = StrongAlias; + +inline bool operator==(const NodeId& lhs, const NodeId& rhs) { return lhs.get() == rhs.get(); } + +namespace std +{ + template<> struct hash + { + using argument_type = NodeId; + using result_type = std::size_t; + + result_type operator()(const NodeId& id) const noexcept + { + return (std::hash{})(id.get()); + } + }; +} + +#endif //GIE_NODEID_H diff --git a/gie/include/gie/NodeLogic.h b/gie/include/gie/NodeLogic.h deleted file mode 100644 index 24c7368..0000000 --- a/gie/include/gie/NodeLogic.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// Created by alex on 11/30/18. -// - -#ifndef GIE_LIBRARY_LOGICNODE_H -#define GIE_LIBRARY_LOGICNODE_H - -#include "Argument.h" -#include "Value.h" - -#include -#include -#include -#include -#include - -struct NoArgument {}; - -using NodeId = std::size_t; -using ArgumentValue = std::variant; - -struct NodeLogic -{ - std::vector m_argument; -}; - -#endif //GIE_LIBRARY_LOGICNODE_H diff --git a/gie/include/gie/NodeMetadata.h b/gie/include/gie/NodeMetadata.h deleted file mode 100644 index ecae325..0000000 --- a/gie/include/gie/NodeMetadata.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Created by alex on 4/26/19. -// - -#ifndef GIE_LIBRARY_NODEMETADATA_H -#define GIE_LIBRARY_NODEMETADATA_H - -#include -#include -#include - -#include -#include -#include - -struct NodeMetadata -{ - boost::python::object m_function; - std::vector m_arguments; - Type m_returnType; - Symbol m_symbol; -}; - -#endif //GIE_LIBRARY_NODEMETADATA_H diff --git a/gie/include/gie/NodeUtil.h b/gie/include/gie/NodeUtil.h deleted file mode 100644 index d6e7f96..0000000 --- a/gie/include/gie/NodeUtil.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by alex on 4/27/19. -// - -#ifndef GIE_LIBRARY_NODEUTIL_H -#define GIE_LIBRARY_NODEUTIL_H - -#include -#include -#include -#include - -#include - -NodeMetadata fetchMetadata(PythonContext&, std::string qualifiedFunctionName); -Node makeNode(PythonContext&, std::string name, std::vector); - -#endif //GIE_LIBRARY_NODEUTIL_H diff --git a/gie/include/gie/Program.h b/gie/include/gie/Program.h index d7d621b..ef1b66f 100644 --- a/gie/include/gie/Program.h +++ b/gie/include/gie/Program.h @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -16,21 +17,38 @@ class Program { public: Program() = default; - Program(const Program &) = default; - Program(Program &&) = default; + Program(const Program&) = default; + Program(Program&&) = default; - std::vector run(); + MaybeError> run(); - NodeId addNode(const Node &node); - void editNode(NodeId, const Node &node); - void removeNode(NodeId); - const Node& getNode(NodeId id) const; + NodeId addNode(std::string name, Arguments); + + template + MaybeError editNode(NodeId id, Editor&& editor) + { + auto node = ::getNode(m_graph, id); + + if(node) + editor(*(node->node)); + else return node.error(); + + return ::updateNode(m_graph, id); + } + + MaybeError editNode(NodeId, ArgumentId argumentId, ArgumentValue); + MaybeError removeNode(NodeId); + Expected getNode(NodeId id) const; + Expected, NodeInterfaceError> getCache(NodeId id) const; void addResult(std::string tag, NodeId); void editResult(std::string tag, NodeId); void removeResult(std::string tag); - void import(const std::string &module); + auto import(const std::string& name, const std::string& path) + { + return m_pythonContext.module(name, path); + } auto& context() { return m_pythonContext; } const auto& context() const { return m_pythonContext; } diff --git a/gie/include/gie/PythonContext.h b/gie/include/gie/PythonContext.h index 462a357..2f66896 100644 --- a/gie/include/gie/PythonContext.h +++ b/gie/include/gie/PythonContext.h @@ -8,33 +8,62 @@ #include #include #include +#include -struct Symbol +#include +#include + +#include + +struct SymbolName { std::string prettyName; std::string module; std::string qualifiedName; }; -Symbol createSymbol(std::string qualifiedName); +struct Symbol +{ + SymbolName name; + std::vector arguments; + Type returnType; + boost::python::object function; + +}; + +using SymbolId = StrongAlias; class PythonContext { public: PythonContext(); - boost::python::object module(const std::string &, bool exposeSymbols = true); - boost::python::object getFunction(const std::string &) const; + boost::python::object module(const std::string&, bool exposeSymbols = true); + std::optional module(const std::string& name, const std::string& path, bool exposeSymbols = true); const std::vector& importedSymbols() const { return m_importedSymbols; } + boost::python::object inspect() const; + boost::python::object copy() const; + + const Symbol* getSymbol(SymbolId) const; + const Symbol* getSymbol(const std::string&) const; + std::optional getSymbolId(const std::string& name) const; + +private: + std::optional importAbsolute(const std::string& name, const std::string& path); + void discoverSymbols(const std::string& name, boost::python::object); + private: boost::python::object m_main; boost::python::object m_global; + boost::python::object m_importlib, m_importModule; + boost::python::object m_inspect; + boost::python::object m_copy; std::unordered_map m_importedModules; std::vector m_importedSymbols; - std::unordered_map m_functions; + std::unordered_map m_nameSymbolIdMap; }; #endif //GIE_LIBRARY_PYTHONCONTEXT_H diff --git a/gie/include/gie/ScriptGraph/Execute.h b/gie/include/gie/ScriptGraph/Execute.h index d4668fa..5c37cb9 100644 --- a/gie/include/gie/ScriptGraph/Execute.h +++ b/gie/include/gie/ScriptGraph/Execute.h @@ -10,12 +10,15 @@ #include #include #include +#include + +#include #include -std::optional executeNode(const Node& node); +Expected executeNode(const PythonContext&, const Node& node); -void executeNode(ScriptGraph& graph, NodeId nodeId); -std::vector executeGraph(ScriptGraph& graph); +MaybeError executeNode(const PythonContext&, ScriptGraph& graph, NodeId nodeId); +MaybeError> executeGraph(const PythonContext&, ScriptGraph& graph); #endif //GIE_LIBRARY_EXECUTE_H diff --git a/gie/include/gie/ScriptGraph/ScriptGraph.h b/gie/include/gie/ScriptGraph/ScriptGraph.h index b579e76..c00864c 100644 --- a/gie/include/gie/ScriptGraph/ScriptGraph.h +++ b/gie/include/gie/ScriptGraph/ScriptGraph.h @@ -6,38 +6,44 @@ #define GIE_LIBRARY_SCENEGRAPH_H #include +#include + +#include +#include +#include #include #include -#include -#include - struct ScriptGraph { std::vector> nodes; std::vector, NodeId>> cache; std::vector> results; + + Graph structure; }; struct NodeCachePair { - Node& node; - std::optional& cache; + Node* node; + std::optional* cache; }; struct ConstNodeCachePair { - const Node& node; - const std::optional& cache; + const Node* node; + const std::optional* cache; }; -NodeCachePair getNode(ScriptGraph&, NodeId); -ConstNodeCachePair getNode(const ScriptGraph&, NodeId); +Expected getNode(ScriptGraph&, NodeId); +Expected getNode(const ScriptGraph&, NodeId); NodeId addNode(ScriptGraph&, const Node&); -void editNode(ScriptGraph&, NodeId, const Node&); -void removeNode(ScriptGraph&, NodeId); +MaybeError removeNode(ScriptGraph&, NodeId); +MaybeError editNode(ScriptGraph&, NodeId, ArgumentId argumentId, ArgumentValue); +MaybeError updateNode(ScriptGraph&, NodeId); + void addResult(ScriptGraph&, std::string tag, NodeId); void editResult(ScriptGraph&, std::string tag, NodeId); void removeResult(ScriptGraph&, std::string tag); diff --git a/gie/include/gie/Value.h b/gie/include/gie/Value.h index b3d8ca1..942f0ad 100644 --- a/gie/include/gie/Value.h +++ b/gie/include/gie/Value.h @@ -14,8 +14,18 @@ struct Value { public: Value() {} - explicit Value(boost::python::object object): m_object(std::move(object)) {} + explicit Value(boost::python::object object); + const Type& type() const; + boost::python::object object() const; + + template + T extract() const + { + return boost::python::extract(m_object); + } + +private: Type m_typeName; boost::python::object m_object; }; diff --git a/gie/include/gie/detail/PythonUtils.h b/gie/include/gie/detail/PythonUtils.h new file mode 100644 index 0000000..1f0af2e --- /dev/null +++ b/gie/include/gie/detail/PythonUtils.h @@ -0,0 +1,72 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_PYTHONUTILS_H +#define GIE_PYTHONUTILS_H + +#include + +inline boost::python::object copy(const PythonContext& context, const boost::python::object& o) +{ + using namespace boost::python; + return context.copy().attr("copy")(o); +} + +inline bool hasattr(const boost::python::object& object, const char* name) +{ + return PyObject_HasAttrString(object.ptr(), name) != 0; +} + +inline std::string type(const boost::python::object& o) +{ + return {o.ptr()->ob_type->tp_name}; +} + +inline std::string fetchPythonException() +{ + namespace py = boost::python; + + PyObject *pType = nullptr, *pValue = nullptr, *pTraceback = nullptr; + PyErr_Fetch(&pType, &pValue, &pTraceback); + std::string ret("Unfetchable Python error"); + + if(pType) + { + py::handle<> type(pType); + py::extract typeString{py::str(type)}; + + if(typeString.check()) + ret = typeString(); + else + ret = "Unknown exception type"; + } + if(pValue) + { + py::handle<> value(pValue); + py::extract returned{py::str(value)}; + + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python error: "); + } + + if(pTraceback) + { + py::handle<> traceback(pTraceback); + py::object tb(py::import("traceback")); + py::object fmt_tb(tb.attr("format_tb")); + py::object tb_list(fmt_tb(traceback)); + py::object tb_str(py::str("\n").join(tb_list)); + py::extract returned(tb_str); + + if(returned.check()) + ret += ": " + returned(); + else + ret += std::string(": Unparseable Python traceback"); + } + return ret; +} + +#endif //GIE_PYTHONUTILS_H diff --git a/gie/src/Node.cpp b/gie/src/Node.cpp new file mode 100644 index 0000000..c2f55f2 --- /dev/null +++ b/gie/src/Node.cpp @@ -0,0 +1,21 @@ +// +// Created by alex on 4/27/19. +// + +#include + +std::optional makeNode(const PythonContext& context, const std::string& name, Arguments arguments) +{ + auto symbolId = context.getSymbolId(name); + + if(!symbolId) + return std::nullopt; + + arguments.resize(context.getSymbol(*symbolId)->arguments.size(), NoArgument{}); + + return Node + { + {std::move(arguments)}, + *symbolId + }; +} \ No newline at end of file diff --git a/gie/src/NodeUtil.cpp b/gie/src/NodeUtil.cpp deleted file mode 100644 index c500622..0000000 --- a/gie/src/NodeUtil.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// -// Created by alex on 4/27/19. -// - -#include - -#include - -static bool hasattr(boost::python::object object, const char* name)//TODO: move to python utils -{ - return static_cast(PyObject_HasAttrString(object.ptr(), name)); -} - -static std::vector fetchArguments(PythonContext& context, boost::python::object callable) -{ - using namespace boost::python; - - auto inspect = context.module("inspect", false); - auto signature = inspect.attr("signature")(callable); - - std::vector parameters{ - stl_input_iterator(signature.attr("parameters").attr("items")()), - stl_input_iterator{} - }; - - std::vector arguments; - arguments.reserve(parameters.size()); - - for(const auto& parameter: parameters) - { - auto p = parameter[1]; - arguments.push_back(ArgumentMetadata{ - extract(p.attr("name")), - Type{extract(p.attr("annotation").attr("__name__"))} - }); - } - - return arguments; -} - -static Type fetchReturnType(PythonContext& context, boost::python::object callable) -{ - using namespace boost::python; - - auto inspect = context.module("inspect", false); - auto signature = inspect.attr("signature")(callable); - - if(hasattr(signature, "return_annotation")) - return Type{extract(signature.attr("return_annotation").attr("__name__"))}; - else return Type{""}; -} - -NodeMetadata fetchMetadata(PythonContext& context, std::string qualifiedFunctionName) -{ - auto object = context.getFunction(qualifiedFunctionName); - - return NodeMetadata - { - object, - fetchArguments(context, object), - fetchReturnType(context, object), - createSymbol(qualifiedFunctionName) - }; -} - -Node makeNode(PythonContext& context, std::string name, std::vector arguments) -{ - NodeMetadata metadata = fetchMetadata(context, name); - - //TODO: Check parameters - - return Node - { - {}, - {std::move(arguments)}, - metadata - }; -} \ No newline at end of file diff --git a/gie/src/Program.cpp b/gie/src/Program.cpp index dd9ea54..139930f 100644 --- a/gie/src/Program.cpp +++ b/gie/src/Program.cpp @@ -12,47 +12,58 @@ #include -std::vector Program::run() +MaybeError> Program::run() { - return executeGraph(m_graph); + return ::executeGraph(m_pythonContext, m_graph); } -NodeId Program::addNode(const Node &node) +NodeId Program::addNode(std::string name, Arguments arguments) { - return ::addNode(m_graph, node); + return ::addNode(m_graph, makeNode(m_pythonContext, std::move(name), std::move(arguments)).value()); } -void Program::editNode(NodeId id, const Node &node) +MaybeError Program::editNode(NodeId id, ArgumentId argumentId, ArgumentValue argument) { - return ::editNode(m_graph, id, node); + return ::editNode(m_graph, id, argumentId, std::move(argument)); } -void Program::removeNode(NodeId id) +MaybeError Program::removeNode(NodeId id) { return ::removeNode(m_graph, id); } -const Node& Program::getNode(NodeId id) const +Expected Program::getNode(NodeId id) const { - return ::getNode(m_graph, id).node; + auto value = ::getNode(m_graph, id); + + if(!value) + return Expected{makeUnexpected(value.error())}; + + return Expected{value->node}; +} + +Expected, NodeInterfaceError> Program::getCache(NodeId id) const +{ + auto value = ::getNode(m_graph, id); + + if(!value) + return Expected, NodeInterfaceError>{makeUnexpected(value.error())}; + + return Expected, NodeInterfaceError>{*value->cache}; } + void Program::addResult(std::string tag, NodeId id) { - ::addResult(m_graph, tag, id); + ::addResult(m_graph, std::move(tag), id); } void Program::editResult(std::string tag, NodeId id) { - ::editResult(m_graph, tag, id); + ::editResult(m_graph, std::move(tag), id); } void Program::removeResult(std::string tag) { - ::removeResult(m_graph, tag); + ::removeResult(m_graph, std::move(tag)); } - -void Program::import(const std::string &module) -{ - m_pythonContext.module(module); -} \ No newline at end of file diff --git a/gie/src/PythonContext.cpp b/gie/src/PythonContext.cpp index f0e9bd3..4fdf5b2 100644 --- a/gie/src/PythonContext.cpp +++ b/gie/src/PythonContext.cpp @@ -5,16 +5,67 @@ #include #include +#include -Symbol createSymbol(std::string qualifiedName) +static std::vector fetchArguments(const PythonContext& context, boost::python::object callable) { - return Symbol{ - qualifiedName.substr(qualifiedName.find_last_of('.') + 1), - qualifiedName.substr(0, qualifiedName.find('.')), - std::move(qualifiedName) + using namespace boost::python; + + auto inspect = context.inspect(); + auto signature = inspect.attr("signature")(callable); + + std::vector parameters{ + stl_input_iterator(signature.attr("parameters").attr("items")()), + stl_input_iterator{} + }; + + std::vector arguments{}; + arguments.reserve(parameters.size()); + + for(const auto& parameter: parameters) + { + auto p = parameter[1]; + arguments.push_back(ArgumentMetadata{ + extract(p.attr("name")), + Type{extract(p.attr("annotation").attr("__name__"))} + }); + } + + return arguments; +} + +static Type fetchReturnType(const PythonContext& context, boost::python::object callable) +{ + using namespace boost::python; + + auto inspect = context.inspect(); + auto signature = inspect.attr("signature")(callable); + + if(hasattr(signature, "return_annotation")) + return Type{extract(signature.attr("return_annotation").attr("__name__"))}; + else return Type{""}; +} + +static SymbolName createSymbolName(std::string qualifiedName) +{ + return SymbolName{ + qualifiedName.substr(qualifiedName.find_last_of('.') + 1), + qualifiedName.substr(0, qualifiedName.find_last_of('.')), + std::move(qualifiedName) }; } +static Symbol createSymbol(const PythonContext& context, boost::python::object callable, std::string qualifiedFunctionName) +{ + return Symbol + { + createSymbolName(qualifiedFunctionName), + fetchArguments(context, callable), + fetchReturnType(context, callable), + callable + }; +} + PythonContext::PythonContext() { using namespace boost::python; @@ -23,6 +74,12 @@ PythonContext::PythonContext() m_main = import("__main__"); m_global = m_main.attr("__dict__"); + + m_importlib = import("importlib"); + m_importModule = m_importlib.attr("import_module"); + + m_inspect = import("inspect"); + m_copy = import("copy"); } boost::python::object PythonContext::module(const std::string& name, bool exposeSymbols) @@ -30,38 +87,111 @@ boost::python::object PythonContext::module(const std::string& name, bool expose if(auto it = m_importedModules.find(name); it != m_importedModules.end()) return it->second; - auto module = boost::python::import(name.c_str()); + auto module = m_importModule(name); m_importedModules.insert({ name, module }); if(exposeSymbols) + discoverSymbols(name, module); + + return module; +} + +std::optional PythonContext::module(const std::string& name, const std::string& path, bool exposeSymbols) +{ + if(auto it = m_importedModules.find(name); it != m_importedModules.end() && !exposeSymbols) + return it->second; + + auto module = importAbsolute(name, path); + + if(!module) + return std::nullopt; + + m_importedModules.insert({ + name, + *module + }); + + if(exposeSymbols) + discoverSymbols(name, *module); + + return module; +} + + +std::optional PythonContext::importAbsolute(const std::string& name, const std::string& path) +{ + try + { + auto importlibUtil = module("importlib.util", false); + auto spec = importlibUtil.attr("spec_from_file_location")(name, path); + + auto module = importlibUtil.attr("module_from_spec")(spec); + spec.attr("loader").attr("exec_module")(module); + + return module; + } + catch(const boost::python::error_already_set&) + { + return std::nullopt; + } +} + +void PythonContext::discoverSymbols(const std::string& name, boost::python::object module) +{ + const auto& list = (boost::python::extract(module.attr("__dict__"))()).items(); + for(int i = 0; i < boost::python::len(list); i++) { - const auto& list = (boost::python::extract(module.attr("__dict__"))()).items(); - for(int i = 0; i < boost::python::len(list); i++) + auto o = boost::python::extract(list[i][1])(); + if(PyCallable_Check(o.ptr())) { - auto o = boost::python::extract(list[i][1])(); - if(PyCallable_Check(o.ptr())) + auto extractor = boost::python::extract(o.attr("__name__")); + if(extractor.check()) { - auto extractor = boost::python::extract(o.attr("__name__")); - if(extractor.check()) + auto qualifiedName = name + '.' + extractor(); + auto id = getSymbolId(qualifiedName); + if(!id) { - auto qualifiedName = name.substr(name.find_last_of('.') + 1) + '.' + extractor(); - m_importedSymbols.push_back(createSymbol(qualifiedName)); - m_functions[qualifiedName] = o; + m_importedSymbols.push_back(createSymbol(*this, o, qualifiedName)); + m_nameSymbolIdMap[qualifiedName] = SymbolId{m_importedSymbols.size() - 1}; } + else + m_importedSymbols[id->get()] = createSymbol(*this, o, qualifiedName); } } } +} - return module; +boost::python::object PythonContext::inspect() const +{ + return m_inspect; } -boost::python::object PythonContext::getFunction(const std::string &name) const +boost::python::object PythonContext::copy() const { - if(auto it = m_functions.find(name); it != m_functions.end()) - return it->second; + return m_copy; +} - return boost::python::object(); +const Symbol* PythonContext::getSymbol(SymbolId id) const +{ + if(id.get() < m_importedSymbols.size()) + return &m_importedSymbols[id.get()]; + return nullptr; +} + +const Symbol* PythonContext::getSymbol(const std::string& name) const +{ + auto id = getSymbolId(name); + if(id) + return getSymbol(*id); + return nullptr; +} + +std::optional PythonContext::getSymbolId(const std::string& name) const +{ + if(auto it = m_nameSymbolIdMap.find(name); it != m_nameSymbolIdMap.end()) + return it->second; + return std::nullopt; } \ No newline at end of file diff --git a/gie/src/ScriptGraph/Execute.cpp b/gie/src/ScriptGraph/Execute.cpp index 575994f..8b2323d 100644 --- a/gie/src/ScriptGraph/Execute.cpp +++ b/gie/src/ScriptGraph/Execute.cpp @@ -10,6 +10,8 @@ #include +#include + #include #include @@ -17,65 +19,88 @@ #include #include -std::optional executeNode(const Node& node) + +Expected executeNode(const PythonContext& context, const Node& node) { using namespace boost::python; list arguments; - for(const auto &argument: node.m_logic.m_argument) + for(const auto &argument: node.arguments) { if(std::holds_alternative(argument)) - arguments.append(std::get(argument).m_object); - else return std::nullopt; - + arguments.append(copy(context, std::get(argument).object())); + else return Expected{makeUnexpected(ExecutionInterfaceError{ExecutionInterfaceError::errors::InvalidArguments, NodeId{-1ull}})}; } - auto p = PyEval_CallObject(node.m_metadata.m_function.ptr(), tuple{arguments}.ptr()); - return Value{object{handle(borrowed(p))}}; + try + { + auto p = PyEval_CallObject(context.getSymbol(node.symbolId())->function.ptr(), tuple{arguments}.ptr()); + return Expected{Value{object{handle(borrowed(p))}}}; + } + catch(const boost::python::error_already_set&) + { + return Expected{makeUnexpected(ExecutionInterfaceError{ExecutionInterfaceError::errors::PythonInternalError, NodeId{-1ull}, fetchPythonException()})}; + } } -void executeNode(ScriptGraph &graph, NodeId nodeId) +MaybeError executeNode(const PythonContext& context, ScriptGraph &graph, NodeId nodeId) { using namespace boost::python; auto node_ = getNode(graph, nodeId); list arguments; - for(const auto &argument: node_.node.m_logic.m_argument) + for(const auto &argument: node_->node->arguments) { if(std::holds_alternative(argument)) { - if(!getNode(graph, std::get(argument)).cache.has_value()) - executeNode(graph, std::get(argument)); - arguments.append(object{getNode(graph, std::get(argument)).cache->m_object}); + auto& cache = *getNode(graph, std::get(argument))->cache; + + if(!cache.has_value()) + { + auto error = executeNode(context, graph, std::get(argument)); + if(error) + return error; + } + arguments.append(copy(context, cache->object())); } - else - arguments.append(std::get(argument).m_object); + else if(std::holds_alternative(argument)) + arguments.append(copy(context, std::get(argument).object())); + else return ExecutionInterfaceError{ExecutionInterfaceError::errors::InvalidArguments, nodeId}; } - auto p = PyEval_CallObject(node_.node.m_metadata.m_function.ptr(), tuple{arguments}.ptr()); - object r{handle(borrowed(p))}; + try + { + auto p = PyEval_CallObject(context.getSymbol(node_->node->symbolId())->function.ptr(), tuple{arguments}.ptr()); + object r{handle(borrowed(p))}; + + *(node_->cache) = Value{r}; + + } + catch(const boost::python::error_already_set&) + { + return ExecutionInterfaceError{ExecutionInterfaceError::errors::PythonInternalError, nodeId, fetchPythonException()}; + } - node_.cache = Value{r}; + return {}; } -std::vector executeGraph(ScriptGraph &graph) +MaybeError> executeGraph(const PythonContext& context, ScriptGraph &graph) { for(auto& cache: graph.cache) cache.first = std::nullopt; + std::vector nodeErrors; + for(const auto& node: graph.nodes) { - executeNode(graph, node.second); + auto error = executeNode(context, graph, node.second); + if(error) + nodeErrors.push_back(error.error()); } - std::vector results; - - const auto& constGraph = graph; - results.reserve(graph.results.size()); - for(const auto& p: constGraph.results) - results.push_back({p.first, getNode(constGraph, p.second).cache.value()}); - - return results; + if(nodeErrors.empty()) + return {}; + else return nodeErrors; } \ No newline at end of file diff --git a/gie/src/ScriptGraph/ScriptGraph.cpp b/gie/src/ScriptGraph/ScriptGraph.cpp index 9337f2e..13f8663 100644 --- a/gie/src/ScriptGraph/ScriptGraph.cpp +++ b/gie/src/ScriptGraph/ScriptGraph.cpp @@ -9,54 +9,138 @@ #include #include -static long lookup(const ScriptGraph& graph, NodeId id) +static const unsigned long long NotFound = -1; + +static unsigned long long lookup(const ScriptGraph& graph, NodeId id) { auto it = std::lower_bound(graph.nodes.begin(), graph.nodes.end(), id, [](auto node, auto id) { - return node.second < id; + return node.second.get() < id.get(); }); - return it == graph.nodes.end() ? -1 : std::distance(graph.nodes.begin(), it); + return it == graph.nodes.end() ? NotFound : std::distance(graph.nodes.begin(), it); } -NodeCachePair getNode(ScriptGraph& graph, NodeId id) +Expected getNode(ScriptGraph& graph, NodeId id) { - auto r = lookup(graph, id); - return {graph.nodes[r].first, graph.cache[r].first}; + const auto r = lookup(graph, id); + + if(r == NotFound) + return Expected{makeUnexpected(NodeInterfaceError{NodeInterfaceError::errors::IncorrectNodeId})}; + + return Expected{{&graph.nodes[r].first, &graph.cache[r].first}}; } -ConstNodeCachePair getNode(const ScriptGraph& graph, NodeId id) +Expected getNode(const ScriptGraph& graph, NodeId id) { - auto r = lookup(graph, id); - return {graph.nodes[r].first, graph.cache[r].first}; + const auto r = lookup(graph, id); + + if(r == NotFound) + return Expected{makeUnexpected(NodeInterfaceError{NodeInterfaceError::errors::IncorrectNodeId})}; + + + return Expected{{&graph.nodes[r].first, &graph.cache[r].first}}; } NodeId addNode(ScriptGraph& graph, const Node& node) { - NodeId id = graph.nodes.empty() ? 0 : graph.nodes.back().second + 1; + NodeId id{graph.nodes.empty() ? 0 : graph.nodes.back().second.get() + 1}; graph.nodes.emplace_back(node, id); graph.cache.emplace_back(std::nullopt, id); + graph.structure.addNode(id); + + updateNode(graph, id).discard(); + return id; } -void editNode([[maybe_unused]]ScriptGraph& graph, [[maybe_unused]]NodeId id, [[maybe_unused]]const Node& newNode) +MaybeError editNode(ScriptGraph& graph, NodeId id, ArgumentId argumentId, ArgumentValue value) { auto r = lookup(graph, id); - if(r != -1) + if(r != NotFound) + { + auto& argument = graph.nodes[r].first.arguments[argumentId.get()]; + if(std::holds_alternative(argument)) + { + auto otherId = std::get(argument); + graph.structure.removeEdge(otherId, id); + } + + argument = std::move(value); + if(std::holds_alternative(argument)) + { + auto otherId = std::get(argument); + graph.structure.addEdge(otherId, id); + } + + return {}; + } + + return {NodeInterfaceError{NodeInterfaceError::errors::IncorrectNodeId}}; +} + +MaybeError updateNode(ScriptGraph& graph, NodeId id) +{ + if(lookup(graph, id) == NotFound) + return {NodeInterfaceError{NodeInterfaceError::errors::IncorrectNodeId}}; + + std::vector toRemove; + toRemove.reserve(graph.structure.inDegree(id)); + + graph.structure.iterateInNeighbours(id, [&toRemove](NodeId id) { - Node& node = graph.nodes[r].first; - node = newNode; + toRemove.push_back(id); + }); + + for(auto otherId: toRemove) + graph.structure.removeEdge(id, otherId); + + for(const auto &argument: getNode(graph, id)->node->arguments) + { + if(std::holds_alternative(argument)) + { + auto otherId = std::get(argument); + graph.structure.addEdge(otherId, id); + } } + + return {}; } -void removeNode([[maybe_unused]]ScriptGraph& graph, [[maybe_unused]]NodeId id)//BUG: boost doesn't delete the vertices +MaybeError removeNode([[maybe_unused]]ScriptGraph& graph, [[maybe_unused]]NodeId id) { auto r = lookup(graph, id); + + if(r == NotFound) + return {NodeInterfaceError{NodeInterfaceError::errors::IncorrectNodeId}}; + graph.nodes.erase(graph.nodes.begin() + r); graph.cache.erase(graph.cache.begin() + r); + + graph.structure.iterateOutNeighbours(id, [&graph](NodeId other) + { + const auto& value = getNode(graph, other); + + std::size_t argumentId = 0; + + std::size_t i = 0; + for(const auto &argument: value->node->arguments) + { + if(std::holds_alternative(argument) && std::get(argument) == other) + argumentId = i; + + i++; + } + + editNode(graph, other, ArgumentId{argumentId}, {NoArgument{}}).discard(); + }); + + graph.structure.removeNode(id); + + return {}; } void addResult(ScriptGraph& graph, std::string tag, NodeId id) diff --git a/gie/src/Value.cpp b/gie/src/Value.cpp new file mode 100644 index 0000000..011a90f --- /dev/null +++ b/gie/src/Value.cpp @@ -0,0 +1,21 @@ +// +// Created by alex on 7/19/19. +// + +#include +#include + +Value::Value(boost::python::object object): + m_typeName{::type(object)}, + m_object(std::move(object)) +{} + +const Type& Value::type() const +{ + return m_typeName; +} + +boost::python::object Value::object() const +{ + return m_object; +} \ No newline at end of file diff --git a/gie/tests/dev_test.cpp b/gie/tests/dev_test.cpp index 3a26e6f..386f422 100644 --- a/gie/tests/dev_test.cpp +++ b/gie/tests/dev_test.cpp @@ -2,9 +2,7 @@ // Created by alex on 2/2/19. // -#include #include -#include #include @@ -25,10 +23,21 @@ int main() sys.attr("path").attr("insert")(1, os.attr("getcwd")()); - program.import("test_modules.basic"); + program.context().module("test_modules.basic", "test_modules/basic"); - for(const auto& [prettyName, module, qualifiedName]: program.context().importedSymbols()) + for(const auto& symbol: program.context().importedSymbols()) { - std::cout << prettyName << " " << module << " " << qualifiedName << std::endl; + std::cout << symbol.name.qualifiedName << std::endl; } + + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.addNode("test_modules.basic.to_int", {}); + program.removeNode(NodeId{7}).discard(); + program.addNode("test_modules.basic.to_int", {}); } diff --git a/gie/tests/test.cpp b/gie/tests/test.cpp index b7a9694..953182b 100644 --- a/gie/tests/test.cpp +++ b/gie/tests/test.cpp @@ -5,9 +5,7 @@ #include -#include #include -#include #include @@ -27,42 +25,41 @@ TEST_CASE("GIE API tests", "[program]") sys.attr("path").attr("insert")(1, os.attr("getcwd")()); - program.import("test_modules.basic"); + program.import("basic", "test_modules/basic.py"); boost::python::object input(10); SECTION("a couple of nodes") { NodeId castToString, castToInt; - castToString = program.addNode(makeNode(program.context(), "basic.to_string", {ArgumentValue{Value{input}}})); + castToString = program.addNode("basic.to_string", {ArgumentValue{Value{input}}}); SECTION("1 node run") { program.addResult("1 node run", castToString); auto result = program.run(); REQUIRE(std::to_string(boost::python::extract(input)) == - std::string{boost::python::extract(result[0].value.m_object)}); + std::string{boost::python::extract(program.getCache(castToString).value()->object())}); } - castToInt = program.addNode(makeNode(program.context(), "basic.to_int", {castToString})); + castToInt = program.addNode("basic.to_int", {castToString}); SECTION("2 nodes run") { program.addResult("1 nodes run", castToInt); auto result = program.run(); - REQUIRE(boost::python::extract(input) == boost::python::extract(result[0].value.m_object)); + REQUIRE(boost::python::extract(input) == boost::python::extract(program.getCache(castToInt).value()->object())); } SECTION("removing node") { - program.removeNode(castToInt); + program.removeNode(castToInt).discard(); program.addResult("toString", castToString); auto result = program.run(); REQUIRE(std::to_string(boost::python::extract(input)) == - std::string{boost::python::extract(result[0].value.m_object)}); + std::string{boost::python::extract(program.getCache(castToString).value()->object())}); program.removeResult("toString"); - program.removeNode(castToString); + program.removeNode(castToString).discard(); auto result2 = program.run(); - REQUIRE(result2.size() == 0); } } @@ -73,12 +70,12 @@ TEST_CASE("GIE API tests", "[program]") std::vector castToInt; castToString.push_back( - program.addNode(makeNode(program.context(), "basic.to_string", {ArgumentValue{Value{input}}}))); - castToInt.push_back(program.addNode(makeNode(program.context(), "basic.to_int", {castToString.back()}))); + program.addNode("basic.to_string", {ArgumentValue{Value{input}}})); + castToInt.push_back(program.addNode("basic.to_int", {castToString.back()})); for (int i = 0; i < 100; i++) { - castToString.push_back(program.addNode(makeNode(program.context(), "basic.to_string", {castToInt.back()}))); - castToInt.push_back(program.addNode(makeNode(program.context(), "basic.to_int", {castToString.back()}))); + castToString.push_back(program.addNode("basic.to_string", {castToInt.back()})); + castToInt.push_back(program.addNode("basic.to_int", {castToString.back()})); } program.addResult("result", castToInt.back()); @@ -86,7 +83,7 @@ TEST_CASE("GIE API tests", "[program]") SECTION("run") { auto result = program.run(); - REQUIRE(boost::python::extract(input) == boost::python::extract(result[0].value.m_object)); + REQUIRE(boost::python::extract(input) == boost::python::extract(program.getCache(castToInt.back()).value()->object())); } SECTION("removing nodes in the middle") @@ -94,14 +91,14 @@ TEST_CASE("GIE API tests", "[program]") auto stringId = castToString[50]; auto intId = castToInt[50]; - program.removeNode(stringId); - program.removeNode(intId); + program.removeNode(stringId).discard(); + program.removeNode(intId).discard(); - program.editNode(castToString[51], makeNode(program.context(), "basic.to_string", {castToInt[49]})); + program.editNode(castToString[51], ArgumentId{0}, ArgumentValue{castToInt[49]}).discard(); auto result = program.run(); REQUIRE(std::to_string(boost::python::extract(input)) == - std::to_string(boost::python::extract(result[0].value.m_object))); + std::to_string(boost::python::extract(program.getCache(castToInt.back()).value()->object()))); } SECTION("removing all nodes") @@ -109,12 +106,12 @@ TEST_CASE("GIE API tests", "[program]") program.removeResult("result"); for (auto i: castToString) - program.removeNode(i); + program.removeNode(i).discard(); for (auto i: castToInt) - program.removeNode(i); + program.removeNode(i).discard(); auto result = program.run(); - REQUIRE(result.size() == 0); + REQUIRE(!result.errorSet()); } } } diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 69b10e7..bec1f1e 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -11,84 +11,10 @@ set(CMAKE_AUTORCC ON) find_package(Qt5 REQUIRED COMPONENTS Widgets) find_package(Qt5Core) -set(SOURCE - src/mainwindow.ui - src/mainwindow.cpp - src/mainwindow.h - src/main.cpp - src/editor.h - src/editor.cpp - src/gie/GieDataModelRegistry.cpp - src/gie/GieDataModelRegistry.h - src/gie/GieNodeDataModel.cpp - src/gie/GieNodeDataModel.h - src/gie/types/StringData.h - src/gie/types/NumberData.h - src/gie/types/TypeData.h - src/gie/SourceNodeDataModel/StringSourceDataModel.cpp - src/gie/SourceNodeDataModel/StringSourceDataModel.h - src/gie/SourceNodeDataModel/NumberSourceDataModel.cpp - src/gie/SourceNodeDataModel/NumberSourceDataModel.h - src/gie/SourceNodeDataModel/NumberSourceDataModel.h - src/gie/types/ColorData.h - src/serialisation/serialisation.cpp - src/serialisation/serialisation.h - src/colorpicker/colorpicker.cpp - src/colorpicker/colorpicker.h - src/gie/SourceNodeDataModel/ColorSourceDataModel.cpp - src/gie/SourceNodeDataModel/ColorSourceDataModel.h - src/serialisation/serialisation.h - src/symbolviewer/symbolviewer.cpp - src/symbolviewer/symbolviewer.h - src/gie/DisplayNodeDataModel/NumberDisplayDataModel.cpp - src/gie/DisplayNodeDataModel/NumberDisplayDataModel.h - src/colorsample/colorsample.cpp - src/colorsample/colorsample.h - src/gie/DisplayNodeDataModel/StringDisplayDataModel.cpp - src/gie/DisplayNodeDataModel/StringDisplayDataModel.h - src/gie/DisplayNodeDataModel/ColorDisplayDataModel.cpp - src/gie/DisplayNodeDataModel/ColorDisplayDataModel.h - src/gie/types/ImageData.h - src/gie/types/IntegerData.h - src/gie/SourceNodeDataModel/IntegerSourceDataModel.cpp - src/gie/SourceNodeDataModel/IntegerSourceDataModel.h - src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.cpp - src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.h - src/filepicker/filepicker.cpp - src/filepicker/filepicker.h - src/gie/SourceNodeDataModel/ImageSourceDataModel.cpp - src/gie/SourceNodeDataModel/ImageSourceDataModel.h - src/gie/DisplayNodeDataModel/ImageDisplayDataModel.cpp - src/gie/DisplayNodeDataModel/ImageDisplayDataModel.h - src/imageviewer/imageviewer.cpp - src/imageviewer/imageviewer.h - src/Project.cpp - src/Project.h - src/newproject/newproject.cpp - src/newproject/newproject.h - src/newproject/newproject.ui - src/directorypicker/directorypicker.cpp - src/directorypicker/directorypicker.h - src/importimage/importimage.cpp - src/importimage/importimage.h - src/importimage/importimage.ui - src/importedimagesviewer/importedimagesviewer.cpp - src/importedimagesviewer/importedimagesviewer.h - src/importedimagesviewer/imagecell.cpp - src/importedimagesviewer/imagecell.h - src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.cpp - src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.h - resources/gui.qrc - src/gie/DisplayNodeDataModel/TargetExportImageDataModel.cpp - src/gie/DisplayNodeDataModel/TargetExportImageDataModel.h - src/exportimage/exportimage.cpp - src/exportimage/exportimage.h - src/exportimage/exportimage.ui - src/savefilepicker/savefilepicker.cpp - src/savefilepicker/savefilepicker.h - src/gie/types/ExtractTypes.h -) +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*) +file(GLOB_RECURSE RESOURCES CONFIGURE_DEPENDS resources/*) +set(SOURCE ${SOURCES} ${RESOURCES}) add_definitions(-DQT_NO_KEYWORDS) diff --git a/gui/config b/gui/config index 3439765..d6af66b 100644 --- a/gui/config +++ b/gui/config @@ -1,3 +1,13 @@ { - "modules": ["modules.arithmetic", "modules.blur", "modules.color_correction", "modules.colors", "modules.effects", "modules.images", "modules.noise", "modules.string"] -} \ No newline at end of file + "modules": [ + {"name": "arithmetic", "path": "modules/arithmetic.py"}, + {"name": "blur", "path": "modules/blur.py"}, + {"name": "color_correction", "path": "modules/color_correction.py"}, + {"name": "colors", "path": "modules/colors.py"}, + {"name": "effects", "path": "modules/effects.py"}, + {"name": "images", "path": "modules/images.py"}, + {"name": "noise", "path": "modules/noise.py"}, + {"name": "string", "path": "modules/string.py"}, + {"name": "glitch", "path": "modules/glitch.py"} + ] +} diff --git a/gui/src/Gie.h b/gui/src/Gie.h new file mode 100644 index 0000000..f67c799 --- /dev/null +++ b/gui/src/Gie.h @@ -0,0 +1,233 @@ +// +// Created by alex on 7/15/19. +// + +#ifndef GIE_GIE_H +#define GIE_GIE_H + +#include + +#include +#include + +#include +#include + +#include "GieNodeId.h" +#include "src/nodeeditor/TypeData.h" + +#include + +struct GieSymbol +{ + SymbolName symbol; + std::vector arguments; + Type returnType; +}; + +struct GieRuntimeError +{ + std::optional nodeId; + std::string errorMessage; +}; + +Q_DECLARE_METATYPE(std::string) +Q_DECLARE_METATYPE(GieNodeId) +Q_DECLARE_METATYPE(ArgumentId) +Q_DECLARE_METATYPE(std::vector) +Q_DECLARE_METATYPE(Data) +Q_DECLARE_METATYPE(GieRuntimeError) + +class Gie: public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void init() + { + Py_Initialize(); + + auto builtins = m_program.context().module("builtins", false); + + auto sys = m_program.context().module("sys", false); + auto os = m_program.context().module("os", false); + + sys.attr("path").attr("insert")(1, os.attr("getcwd")()); + + auto internals = m_program.context().module("modules.internals", false); + + builtins.attr("Color") = internals.attr("Color"); + builtins.attr("Image") = internals.attr("Image"); + builtins.attr("to_image") = internals.attr("to_image"); + builtins.attr("to_ndarray") = internals.attr("to_ndarray"); + } + + void addModulesDirectory(const std::string& path) + { + auto sys = m_program.context().module("sys", false); + + sys.attr("path").attr("insert")(1, path); + } + + void removeModulesDirectory(const std::string& path) + { + auto sys = m_program.context().module("sys", false); + + try + { + sys.attr("path").attr("remove")(path); + } + catch(const boost::python::error_already_set&) + { + + } + } + + void addNode(GieNodeId id, const std::string& name) + { + if(auto it = m_map.find(id); it == m_map.end()) + { + m_map[id] = m_program.addNode(name, {}); + } + } + + void removeNode(GieNodeId id) + { + if(auto it = m_map.find(id); it != m_map.end()) + { + m_program.removeNode(it->second).discard(); + m_map.erase(it); + } + } + + void editNode(GieNodeId id, ArgumentId port, const Data& data) + { + if(auto it = m_map.find(id); it != m_map.end()) + { + auto error = m_program.editNode(it->second, port, std::visit([](auto x) + { + return ArgumentValue{Value{boost::python::object{x}}}; + }, data)); + + if(!error.errorSet()) + run(); + } + } + + void editNode(GieNodeId id, ArgumentId port, GieNodeId nodeId) + { + if(auto it = m_map.find(id); it != m_map.end()) + { + auto error = m_program.editNode(it->second, port, m_map[nodeId]); + + if(!error.errorSet()) + run(); + } + } + + void removeArgument(GieNodeId id, ArgumentId port) + { + if(auto it = m_map.find(id); it != m_map.end()) + { + m_program.editNode(it->second, port, NoArgument{}).discard(); + } + } + + void loadModule(const std::string& name, const std::string& path) + { + auto module = m_program.import(name, path); + + if(!module) + Q_EMIT runtimeError({std::nullopt, "coudln't load module: " + name + "(" + path + ")\n"}); + + std::vector symbols; + symbols.reserve(m_program.context().importedSymbols().size()); + for(const auto& symbol: m_program.context().importedSymbols()) + { + if(symbol.name.module == name) + { + symbols.push_back({ + symbol.name, + symbol.arguments, + symbol.returnType + }); + } + } + + Q_EMIT symbolsAdded(symbols); + + run(); + } + + void addResultNotify(QUuid nodeId, GieNodeId id) + { + m_toNotify[nodeId] = m_map[id]; + + auto cache = m_program.getCache(m_map[id]).value(); + if(cache.has_value()) + Q_EMIT resultUpdated(nodeId, toData(*cache)); + } + + void removeResultNotify(QUuid nodeId) + { + m_toNotify.erase(nodeId); + } + +Q_SIGNALS: + void symbolsAdded(std::vector); + void resultUpdated(QUuid, Data); + void finished(); + void runtimeError(GieRuntimeError); + +private: + void run() + { + auto errors = m_program.run(); + + if(errors.errorSet()) + { + for(const auto& error: errors.error()) + if(error.error() == ExecutionInterfaceError::errors::PythonInternalError) + Q_EMIT runtimeError({ + std::find_if(m_map.begin(), m_map.end(), [&error](const auto& p) + { + return error.id() == p.second; + })->first.get(), + error.detail().value() + }); + } + + for(const auto& id: m_toNotify) + { + auto cache = m_program.getCache(id.second).value(); + if(cache.has_value()) + Q_EMIT resultUpdated(id.first, toData(*cache)); + } + } + +private: + Data toData(const Value& value) + { + const std::string& type = value.type().name(); + + if(type == "int") + return Data{value.extract()}; + else if(type == "float") + return Data{value.extract()}; + else if(type == "str") + return Data{value.extract()}; + else if(type == "Color") + return Data{value.extract()}; + else if(type == "Image") + return Data{value.extract()}; + + return {}; + } + +private: + Program m_program; + std::map m_map; + std::map m_toNotify; +}; + +#endif //GIE_GIE_H diff --git a/gui/src/GieNodeId.h b/gui/src/GieNodeId.h new file mode 100644 index 0000000..80e3e91 --- /dev/null +++ b/gui/src/GieNodeId.h @@ -0,0 +1,18 @@ +// +// Created by alex on 7/16/19. +// + +#ifndef GIE_GIENODEID_H +#define GIE_GIENODEID_H + +#include +#include + +using GieNodeId = StrongAlias; + +inline bool operator< (const GieNodeId& lhs, const GieNodeId& rhs) +{ + return lhs.get() < rhs.get(); +} + +#endif //GIE_GIENODEID_H diff --git a/gui/src/Project.cpp b/gui/src/Project.cpp index 73f9d11..f586673 100644 --- a/gui/src/Project.cpp +++ b/gui/src/Project.cpp @@ -1,11 +1,11 @@ -#include - // // Created by alex on 5/25/19. // #include "Project.h" +#include + #include #include #include @@ -13,6 +13,7 @@ #include #include "src/serialisation/serialisation.h" +#include "src/editor.h" Project::Project(QtNodes::FlowScene& scene,QDir projectDirectory): m_scene{scene}, @@ -127,16 +128,28 @@ Project newProject(QDir dir, QString name, QtNodes::FlowScene& scene) return Project{scene, projectDir}; } -Project loadProject(QString directory, QtNodes::FlowScene& scene) +Project loadProject(QString directory, Editor& editor, QtNodes::FlowScene& scene) { QFile file(directory + "/gieprojectfile"); file.open(QIODevice::ReadOnly); + for(const auto& script: QDir{QDir{directory}.absoluteFilePath("scripts")}.entryInfoList(QStringList{"*.py"})) + editor.addScript(script.canonicalFilePath()); + auto data = file.readAll(); QJsonObject json = QJsonDocument::fromJson(data).object(); auto project = Project{scene, directory, json}; - deserialise(scene, project, json["scene"].toObject()); + auto deleted = deserialise(scene, project, json["scene"].toObject()); + + for(const auto& node: deleted) + editor.warning(QString("Couldn't find '") + QString::fromStdString(node.second) + QString("', deleting node.\n")); + return project; +} + +const QDir& Project::projectPath() const +{ + return m_projectDirectory; } \ No newline at end of file diff --git a/gui/src/Project.h b/gui/src/Project.h index 93028dc..31c4f22 100644 --- a/gui/src/Project.h +++ b/gui/src/Project.h @@ -22,6 +22,8 @@ struct ProjectImage QImage image; }; +class Editor; + class Project { private: @@ -38,6 +40,8 @@ class Project const std::map& importedImages() const { return m_images; }; QString projectName() const { return m_name; } + const QDir& projectPath() const; + private: QJsonValue serialiseImages(); QJsonValue serialiseScripts(); @@ -53,10 +57,10 @@ class Project QString m_name; friend Project newProject(QDir dir, QString name, QtNodes::FlowScene& scene); - friend Project loadProject(QString directory, QtNodes::FlowScene& scene); + friend Project loadProject(QString directory, Editor&, QtNodes::FlowScene& scene); }; Project newProject(QDir dir, QString name, QtNodes::FlowScene& scene); -Project loadProject(QString directory, QtNodes::FlowScene& scene); +Project loadProject(QString directory, Editor&, QtNodes::FlowScene& scene); #endif //GUI_PROJECT_H diff --git a/gui/src/editor.cpp b/gui/src/editor.cpp index cf24a43..8197f2a 100644 --- a/gui/src/editor.cpp +++ b/gui/src/editor.cpp @@ -5,33 +5,37 @@ #include #include -#include +#include #include "editor.h" #include #include -#include "gie/GieDataModelRegistry.h" -#include "gie/GieNodeDataModel.h" -#include "gie/SourceNodeDataModel/ManagedImageSourceDataModel.h" - -#include -#include -#include -#include -#include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "src/serialisation/serialisation.h" -#include "src/newproject/newproject.h" -#include "src/importimage/importimage.h" -#include "src/exportimage/exportimage.h" +#include "src/widgets/newproject/newproject.h" +#include "src/widgets/importimage/importimage.h" +#include "src/widgets/exportimage/exportimage.h" #include "Project.h" - -Editor::Editor(Program& program, QWidget* parent): QWidget(parent), m_program{program} +Editor::Editor(QWidget* parent): QWidget(parent) { QtNodes::ConnectionStyle::setConnectionStyle(R"({ "ConnectionStyle": { @@ -44,108 +48,144 @@ Editor::Editor(Program& program, QWidget* parent): QWidget(parent), m_program{pr vlayout->setMargin(0); vlayout->setSpacing(0); - m_scene = new QtNodes::FlowScene(); - - m_view = new QtNodes::FlowView(m_scene); - m_view->setSceneRect(-640000, -640000, 640000, 640000); - m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_view->hide(); + m_nodeEditor = new NodeEditor{}; - vlayout->addWidget(m_view); - - QObject::connect( - m_scene, &QtNodes::FlowScene::connectionCreated, - this, &Editor::onConnectionCreated - ); - - QObject::connect( - m_scene, &QtNodes::FlowScene::connectionDeleted, - this, &Editor::onConnectionDeleted - ); - - QObject::connect( - m_scene, &QtNodes::FlowScene::nodeCreated, - this, &Editor::nodeCreated - ); + vlayout->addWidget(m_noProjectMessage = new QLabel("No project loaded.\nNew Project: File > New Project\nOpen Project: File > Open Project")); + vlayout->addWidget(m_nodeEditor); + m_nodeEditor->hide(); + + m_nodeEditor->registerNodeType("sources"); + m_nodeEditor->registerNodeType("sources"); + m_nodeEditor->registerNodeType("sources"); + m_nodeEditor->registerNodeType("sources"); + m_nodeEditor->registerNodeType("sources"); + m_nodeEditor->registerNodeType("displays"); + m_nodeEditor->registerNodeType("displays"); + m_nodeEditor->registerNodeType("displays"); + m_nodeEditor->registerNodeType("displays"); + m_nodeEditor->registerNodeType("displays"); + m_nodeEditor->registerNodeType("displays"); + + connect(m_nodeEditor, &NodeEditor::nodeCreated, this, &Editor::nodeCreated); + connect(m_nodeEditor, &NodeEditor::nodeDeleted, this, &Editor::nodeDeleted); + connect(m_nodeEditor, &NodeEditor::argumentRemoved, this, &Editor::argumentRemoved); + connect(m_nodeEditor, &NodeEditor::argumentEdited, this, &Editor::argumentEdited); + connect(m_nodeEditor, &NodeEditor::sourceDataChanged, this, &Editor::sourceDataChanged); + + connect(m_nodeEditor, &NodeEditor::sceneChanged, this, &Editor::sceneChanged); + + auto thread = new QThread; + m_gie = new Gie{}; + + m_gie->moveToThread(thread); + connect(m_gie, &Gie::finished, thread, &QThread::quit); + connect(m_gie, &Gie::finished, m_gie, &QThread::deleteLater); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + + thread->start(); + + QMetaObject::invokeMethod( + m_gie, + "init" + ); + + connect(m_gie, &Gie::symbolsAdded, this, &Editor::reloadedSymbols); + + connect(m_gie, &Gie::symbolsAdded, [this](const std::vector& symbols) + { + for(const auto& symbol: symbols) + { + m_nodeEditor->registerNodeType([symbol]{ + return std::make_unique(symbol); + }, QString::fromStdString(symbol.symbol.module)); + } + }); - QObject::connect( - m_scene, &QtNodes::FlowScene::nodeDeleted, - this, &Editor::nodeDeleted - ); + connect(m_gie, &Gie::symbolsAdded, this, &Editor::reloadNode); - vlayout->addWidget(m_noProjectMessage = new QLabel("No project loaded.\nNew Project: File > New Project\nOpen Project: File > Open Project")); + connect(m_gie, &Gie::resultUpdated, this, &Editor::resultUpdated); - connect(m_scene, &QtNodes::FlowScene::nodePlaced, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::nodeDeleted, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::connectionCreated, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::connectionDeleted, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::nodeMoved, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::nodeDoubleClicked, this, &Editor::sceneChanged); - connect(m_scene, &QtNodes::FlowScene::connectionHovered, this, &Editor::sceneChanged); -} + connect(m_gie, &Gie::runtimeError, this, &Editor::runtimeError); -void Editor::onConnectionCreated(const QtNodes::Connection& c) -{ - auto giver = dynamic_cast(c.getNode(QtNodes::PortType::Out)->nodeDataModel()); - auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); + m_projectScriptsWatcher = new QFileSystemWatcher{this}; + connect(this, &Editor::projectLoaded, [this](const Project& project) + { + for(const auto& file: m_scripts) + removeScript(file.first); + m_scripts.clear(); + + for(const auto& dir: m_projectScriptsWatcher->directories()) + QMetaObject::invokeMethod( + m_gie, + "removeModulesDirectory", + Q_ARG(std::string, dir.toStdString()) + ); + + m_projectScriptsWatcher->removePaths(m_projectScriptsWatcher->directories()); + m_projectScriptsWatcher->removePaths(m_projectScriptsWatcher->files()); + + auto path = project.projectPath().filePath("scripts"); + m_projectScriptsWatcher->addPath(path); + QMetaObject::invokeMethod( + m_gie, + "addModulesDirectory", + Q_ARG(std::string, path.toStdString()) + ); + + for(const auto& file: QDir{path}.entryInfoList(QStringList{"*.py"})) + { + addScript(file.canonicalFilePath()); + m_scripts[file.canonicalFilePath()] = file.lastModified(); + } + }); - if(giver != nullptr && receiver != nullptr) + connect(m_projectScriptsWatcher, &QFileSystemWatcher::directoryChanged, [this](const auto& path) { - auto portIndex = c.getPortIndex(QtNodes::PortType::In); + std::map scripts; - auto node = m_program.getNode(receiver->nodeId()); - node.m_logic.m_argument[portIndex] = giver->nodeId(); + for(const auto& file: QDir{path}.entryInfoList(QStringList{"*.py"})) + scripts[file.canonicalFilePath()] = file.lastModified(); - m_program.editNode(receiver->nodeId(), node); + for(const auto& file: m_scripts) + { + if(scripts.find(file.first) == scripts.end()) + removeScript(file.first); + } - std::cout << "onConnectionCreated: connected " << giver->nodeId() << " with " << receiver->nodeId() << std::endl; - } - if(giver != nullptr) - { - if(auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); receiver != nullptr) + for(const auto& file: scripts) { - m_targets.insert({receiver->getId(), receiver->getTargetName()}); - m_program.addResult(receiver->getId().toUtf8().constData(), giver->nodeId()); + if(m_scripts.find(file.first) == m_scripts.end() || m_scripts[file.first] != file.second) + addScript(file.first); } - } -} -void Editor::onConnectionDeleted(const QtNodes::Connection& c) -{ - if(auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); receiver != nullptr) - { - auto portIndex = c.getPortIndex(QtNodes::PortType::In); + m_scripts = std::move(scripts); + }); - auto node = m_program.getNode(receiver->nodeId()); - node.m_logic.m_argument[portIndex] = NoArgument{}; - m_program.editNode(receiver->nodeId(), node); + connect(this, &Editor::beforeProjectLoading, [this]{ + m_nodeEditor->getRegistry()->removeCreatorIf( + [this](const auto& p) + { + if(p.first.startsWith("user.")) + { + QString category = p.first.left(p.first.lastIndexOf('.')); + QString name = p.first.mid(p.first.lastIndexOf('.') + 1); - std::cout << "onConnectionRemoved: removed argument from " << receiver->nodeId() << std::endl; - } - else if(auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); receiver != nullptr) - { - m_targets.erase(receiver->getId()); - m_program.removeResult(receiver->getId().toUtf8().constData()); - } -} + Q_EMIT symbolRemoved(category, name); -void Editor::nodeCreated(QtNodes::Node& node) -{ - if(auto* p = dynamic_cast(node.nodeDataModel()); p != nullptr) - { - connect(p, &TargetExportImageDataModel::targetNameChanged, this, &Editor::onTargetNameChanged); - Q_EMIT(attachDockWindow(p->dockWidget())); - } + return true; + } + return false; + }); + }); } -void Editor::nodeDeleted(QtNodes::Node& node) +void Editor::resultUpdated(QUuid displayId, Data data) { - if(auto* p = dynamic_cast(node.nodeDataModel()); p != nullptr) - { - Q_EMIT(detachDockWindow(p->dockWidget())); - } + if(auto* display = dynamic_cast( + m_nodeEditor->scene()->nodes().at(displayId)->nodeDataModel() + ); display != nullptr) + display->displayData(std::move(data)); } void Editor::onTargetNameChanged(const QUuid& id, const QString& name) @@ -156,7 +196,8 @@ void Editor::onTargetNameChanged(const QUuid& id, const QString& name) void Editor::addImageNode(const ProjectImage& projectImage) { - m_scene->createNode(std::make_unique(projectImage)); + auto& node = m_nodeEditor->scene()->createNode(std::make_unique(projectImage)); + m_values[node.id()] = dynamic_cast(node.nodeDataModel())->m_image; } void Editor::onNewProject() @@ -168,12 +209,15 @@ void Editor::onNewProject() void Editor::onNewProject_(QDir directory, QString name) { - m_project = std::make_unique(newProject(directory, std::move(name), *m_scene)); + Q_EMIT beforeProjectLoading(); + + m_project = std::make_unique(newProject(directory, std::move(name), *(m_nodeEditor->scene()))); m_noProjectMessage->hide(); - m_view->show(); + m_nodeEditor->show(); reloadImages(); Q_EMIT savedProject(); + Q_EMIT projectLoaded(*m_project); } void Editor::onOpenProject() @@ -184,13 +228,16 @@ void Editor::onOpenProject() QDir::homePath() ); - m_project = std::make_unique(loadProject(directory, *m_scene)); + Q_EMIT beforeProjectLoading(); + + m_project = std::make_unique(loadProject(directory, *this, *(m_nodeEditor->scene()))); m_noProjectMessage->hide(); - m_view->show(); + m_nodeEditor->show(); reloadImages(); Q_EMIT savedProject(); + Q_EMIT projectLoaded(*m_project); } void Editor::keyPressEvent(QKeyEvent* e) @@ -227,29 +274,8 @@ void Editor::onExportImage() void Editor::onExportImage_(const QUuid& uuid, const QString& filename) { - auto results = m_program.run(); - std::string id = uuid.toString().toUtf8().constData(); - - auto it = std::find_if(results.begin(), results.end(), [&id](const auto& x) - { - return x.tag == id; - }); - - if(it != results.end()) - { - auto image = boost::python::extract(it->value.m_object)(); - - auto imageData = new uint8_t[image.width * image.height * 3]; - std::memcpy(imageData, image.raw(), image.width * image.height * 3); - - QImage qImage(imageData, image.width, image.height, QImage::Format_RGB888, [](auto p){ delete static_cast(p); }); - qImage.save(filename); - } -} - -void Editor::setRegistry(std::shared_ptr registry) -{ - m_scene->setRegistry(std::move(registry)); + if(auto* p = dynamic_cast(m_nodeEditor->scene()->nodes().at(uuid)->nodeDataModel()); p != nullptr) + p->getImage().save(filename); } void Editor::reloadImages() @@ -268,4 +294,233 @@ QString Editor::getProjectName() if(m_project) return m_project->projectName(); return QString(""); +} + +void Editor::loadModule(const QString& name, const QString& path) +{ + QMetaObject::invokeMethod( + m_gie, + "loadModule", + Q_ARG(std::string, name.toStdString()), + Q_ARG(std::string, path.toStdString()) + ); +} + +void Editor::addScript(const QString& path) +{ + QString name = QFileInfo{path}.baseName(); + + QMetaObject::invokeMethod( + m_gie, + "loadModule", + Q_ARG(std::string, "user." + name.toStdString()), + Q_ARG(std::string, path.toStdString()) + ); + + Q_EMIT scriptAdded(QFileInfo{path}.fileName()); +} + +void Editor::removeScript(const QString& path) +{ + Q_EMIT scriptRemoved(QFileInfo{path}.fileName()); + +} + +void Editor::reloadNode(const std::vector& symbols) +{ + for (const auto &symbol: symbols) { + m_nodeEditor->registerNodeType([symbol] { + return std::make_unique(symbol); + }, QString::fromStdString(symbol.symbol.module)); + + auto toEdit = m_nodes[symbol.symbol.qualifiedName]; + for (auto nodeId: toEdit) { + if (m_nodeEditor->scene()->nodes().find(nodeId) == m_nodeEditor->scene()->nodes().end()) + continue; + + const auto &node = m_nodeEditor->scene()->nodes().at(nodeId); + + if (auto *gieNode = dynamic_cast( + node->nodeDataModel() + ); gieNode != nullptr) { + if ([&] { + if (gieNode->m_returnType != symbol.returnType) + return true; + + if (gieNode->m_arguments.size() != symbol.arguments.size()) + return true; + + for (std::size_t i = 0; i < gieNode->m_arguments.size(); i++) { + if (gieNode->m_arguments[i].m_argumentType != symbol.arguments[i].m_argumentType) + return true; + if (gieNode->m_arguments[i].m_argumentName != symbol.arguments[i].m_argumentName) + return true; + } + + return false; + }()) { + auto position = m_nodeEditor->scene()->getNodePosition(*node); + m_nodeEditor->scene()->removeNode(node.get()); + + auto &newNode = m_nodeEditor->scene()->createNode(std::make_unique(symbol)); + m_nodeEditor->scene()->setNodePosition(newNode, position); + } + else + { + gieNode->m_arguments = symbol.arguments; + gieNode->ok(); + } + } + } + } +} + +void Editor::nodeCreated(BaseNode* node) +{ + if(auto* p = dynamic_cast(node); p != nullptr) + { + m_nodes[p->symbol().qualifiedName].insert(p->id()); + QMetaObject::invokeMethod( + m_gie, + "addNode", + Q_ARG(GieNodeId, GieNodeId{node->id()}), + Q_ARG(std::string, p->symbol().qualifiedName) + ); + } + + if(auto* p = dynamic_cast(node); p != nullptr) + { + connect(p, &TargetExportImageNode::targetNameChanged, this, &Editor::onTargetNameChanged); + Q_EMIT(attachDockWindow(p->dockWidget())); + } +} + +void Editor::nodeDeleted(BaseNode* node) +{ + if(auto* p = dynamic_cast(node); p != nullptr) + { + m_nodes[p->symbol().qualifiedName].erase(p->id()); + QMetaObject::invokeMethod( + m_gie, + "removeNode", + Q_ARG(GieNodeId, GieNodeId{node->id()}) + ); + } + + if(auto* p = dynamic_cast(node); p != nullptr) + { + Q_EMIT(detachDockWindow(p->dockWidget())); + } +} + +void Editor::argumentRemoved(BaseNode* node, std::size_t port) +{ + QMetaObject::invokeMethod( + m_gie, + "removeArgument", + Q_ARG(GieNodeId, GieNodeId{node->id()}), + Q_ARG(ArgumentId, ArgumentId{port}) + ); + + if(auto* p = dynamic_cast(node); p != nullptr) + { + m_toUpdate.erase(node->id()); + m_displaysToUpdate.erase(node->id()); + + QMetaObject::invokeMethod( + m_gie, + "removeResultNotify", + Q_ARG(QUuid, p->id()) + ); + } + + if(auto* p = dynamic_cast(node); p != nullptr) + m_targets.erase(p->id()); +} + +void Editor::argumentEdited(BaseNode* node, QUuid argumentId, std::size_t port) +{ + if(auto* p = dynamic_cast(node); p != nullptr) + { + + if(auto* other = dynamic_cast( + m_nodeEditor->scene()->nodes().at(argumentId)->nodeDataModel() + ); other != nullptr) + { + m_toUpdate[argumentId].insert({node->id(), port}); + + QMetaObject::invokeMethod( + m_gie, + "editNode", + Q_ARG(GieNodeId, GieNodeId{node->id()}), + Q_ARG(ArgumentId, ArgumentId{port}), + Q_ARG(Data, other->getData()) + ); + } + else + { + QMetaObject::invokeMethod( + m_gie, + "editNode", + Q_ARG(GieNodeId, GieNodeId{node->id()}), + Q_ARG(ArgumentId, ArgumentId{port}), + Q_ARG(GieNodeId, GieNodeId{argumentId}) + ); + } + } + + if(auto* p = dynamic_cast(node); p != nullptr) + { + if(auto* other = dynamic_cast( + m_nodeEditor->scene()->nodes().at(argumentId)->nodeDataModel() + ); other != nullptr) + { + m_displaysToUpdate[argumentId].insert(p->id()); + } + else if(auto* gieNode = dynamic_cast( + m_nodeEditor->scene()->nodes().at(argumentId)->nodeDataModel() + ); gieNode != nullptr) + { + QMetaObject::invokeMethod( + m_gie, + "addResultNotify", + Q_ARG(QUuid, p->id()), + Q_ARG(GieNodeId, GieNodeId{gieNode->id()}) + ); + } + } + + if(auto* p = dynamic_cast(node); p != nullptr) + m_targets.insert({p->id(), p->getTargetName()}); +} + +void Editor::sourceDataChanged(QUuid nodeId, Data data) +{ + m_values[nodeId] = std::move(data); + + for(auto toUpdate: m_toUpdate[nodeId]) + { + QMetaObject::invokeMethod( + m_gie, + "editNode", + Q_ARG(GieNodeId, GieNodeId{toUpdate.id}), + Q_ARG(ArgumentId, ArgumentId{static_cast(toUpdate.port)}), + Q_ARG(Data, m_values[nodeId]) + ); + } + + for(auto displayId: m_displaysToUpdate[nodeId]) + { + if(auto* display = dynamic_cast( + m_nodeEditor->scene()->nodes().at(displayId)->nodeDataModel() + ); display != nullptr) + display->displayData(m_values[nodeId]); + } +} + +void Editor::runtimeError(const GieRuntimeError& error) +{ + if(error.nodeId) + m_nodeEditor->errorNode(*error.nodeId, "internal python error"); + Q_EMIT this->error(QString::fromStdString(error.errorMessage)); } \ No newline at end of file diff --git a/gui/src/editor.h b/gui/src/editor.h index 2a373e7..93e1b71 100644 --- a/gui/src/editor.h +++ b/gui/src/editor.h @@ -7,6 +7,7 @@ #include #include +#include #include @@ -14,22 +15,27 @@ #include #include #include +#include -#include -#include #include "Project.h" +#include "Gie.h" + +#include +#include "src/nodes/GieNode.h" class Editor: public QWidget { Q_OBJECT public: - explicit Editor(Program&, QWidget* parent = nullptr); + explicit Editor(QWidget* parent = nullptr); public: - void setRegistry(std::shared_ptr); - QString getProjectName(); + void loadModule(const QString&, const QString&); + + NodeEditor* nodeEditor() { return m_nodeEditor; } + Q_SIGNALS: void attachDockWindow(QDockWidget*); void detachDockWindow(QDockWidget*); @@ -37,6 +43,17 @@ class Editor: public QWidget void sceneChanged(); void savedProject(); + void reloadedSymbols(std::vector); + void beforeProjectLoading(); + void projectLoaded(const Project&); + + void symbolRemoved(const QString& category, const QString& name); + void scriptAdded(QString name); + void scriptRemoved(QString name); + + void error(const QString&); + void warning(const QString&); + public Q_SLOTS: void onNewProject(); void onOpenProject(); @@ -51,26 +68,56 @@ private Q_SLOTS: void onImportImage_(QString name); void onExportImage_(const QUuid&, const QString&); - void onConnectionCreated(const QtNodes::Connection& c); - void onConnectionDeleted(const QtNodes::Connection& c); - void nodeCreated(QtNodes::Node &node); - void nodeDeleted(QtNodes::Node &node); - void onTargetNameChanged(const QUuid& id, const QString&); + void resultUpdated(QUuid, Data); + void reloadNode(const std::vector& symbols); + + void nodeCreated(BaseNode*); + void nodeDeleted(BaseNode*); + void argumentRemoved(BaseNode* node, std::size_t port); + void argumentEdited(BaseNode* node, QUuid argumentId, std::size_t port); + void sourceDataChanged(QUuid nodeId, Data data); + + void runtimeError(const GieRuntimeError&); + private: void keyPressEvent(QKeyEvent*) override; void reloadImages(); private: - Program& m_program; + void addScript(const QString& path); + void removeScript(const QString& path); + +private: std::unique_ptr m_project; + Gie* m_gie; + NodeEditor* m_nodeEditor; + + QFileSystemWatcher* m_projectScriptsWatcher; - QtNodes::FlowScene* m_scene; - QtNodes::FlowView* m_view; QLabel* m_noProjectMessage; std::map m_targets; + std::map m_values; + + struct ToUpdate + { + QUuid id; + std::size_t port{}; + + bool operator< (const ToUpdate& other) const { return id < other.id; } + bool operator== (const ToUpdate& other) const { return id == other.id; } + }; + std::map> m_toUpdate; + + std::map> m_displaysToUpdate; + + std::map m_scripts; + + std::map> m_nodes; + + friend class Project loadProject(QString directory, Editor& editor, QtNodes::FlowScene& scene); }; diff --git a/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.cpp deleted file mode 100644 index 03007bb..0000000 --- a/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#include "ColorDisplayDataModel.h" -#include "src/gie/types/ColorData.h" - -ColorDisplayDataModel::ColorDisplayDataModel(): - m_colorSample{new ColorSample()} -{ - m_colorSample->setMaximumSize(70, 50); -} - -unsigned int ColorDisplayDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType ColorDisplayDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return ColorData().type(); -} - -void ColorDisplayDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex portIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - auto color = data->color(); - m_colorSample->setColor(QColor( - color.r, - color.g, - color.b) - ); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_colorSample->setColor(QColor(0, 0, 0)); - } -} diff --git a/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.h b/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.h deleted file mode 100644 index e197da2..0000000 --- a/gui/src/gie/DisplayNodeDataModel/ColorDisplayDataModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_COLORDISPLAYDATAMODEL_H -#define GUI_COLORDISPLAYDATAMODEL_H - -#include -#include "src/colorsample/colorsample.h" - -class ColorDisplayDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - ColorDisplayDataModel(); - virtual ~ColorDisplayDataModel() override = default; - -public: - QString caption() const override { return QString("ColorDisplay"); } - bool captionVisible() const override { return false; } - QString name() const override { return QString("ColorDisplay"); } - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_colorSample; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - ColorSample* m_colorSample; -}; - - -#endif //GUI_COLORDISPLAYDATAMODEL_H diff --git a/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.cpp deleted file mode 100644 index 48a5a7f..0000000 --- a/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#include "ImageDisplayDataModel.h" -#include "src/gie/types/ImageData.h" - -ImageDisplayDataModel::ImageDisplayDataModel(): - m_imageViewer{new ImageViewer()} -{ - m_imageViewer->setMaximumSize(70, 50); -} - -unsigned int ImageDisplayDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType ImageDisplayDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return ImageData().type(); -} - -void ImageDisplayDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex portIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - const auto& image = data->image(); - auto imageData = new uint8_t[image.width * image.height * 3]; - std::memcpy(imageData, image.raw(), image.width * image.height * 3); - - QImage qImage(imageData, image.width, image.height, QImage::Format_RGB888, [](auto p){ delete static_cast(p); }); - - m_imageViewer->setDisplayType(image.width > image.height ? ImageViewer::FixedHeight : ImageViewer::FixedWidth); - m_imageViewer->setImage(std::move(qImage)); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_imageViewer->setImage(QImage{}); - } -} diff --git a/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.h b/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.h deleted file mode 100644 index 6a28e15..0000000 --- a/gui/src/gie/DisplayNodeDataModel/ImageDisplayDataModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#ifndef GUI_IMAGEDISPLAYDATAMODEL_H -#define GUI_IMAGEDISPLAYDATAMODEL_H - -#include -#include "src/imageviewer/imageviewer.h" - -class ImageDisplayDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - ImageDisplayDataModel(); - virtual ~ImageDisplayDataModel() override = default; - -public: - QString caption() const override { return QString("ImageDisplay"); } - bool captionVisible() const override { return false; } - QString name() const override { return QString("ImageDisplay"); } - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_imageViewer; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - ImageViewer* m_imageViewer; -}; - - -#endif //GUI_IMAGEDISPLAYDATAMODEL_H diff --git a/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.cpp deleted file mode 100644 index f80b3c5..0000000 --- a/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#include "IntegerDisplayDataModel.h" -#include "src/gie/types/IntegerData.h" - -IntegerDisplayDataModel::IntegerDisplayDataModel(): - m_label{new QLabel()} -{ - m_label->setMargin(3); -} - -unsigned int IntegerDisplayDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType IntegerDisplayDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return IntegerData().type(); -} - -void IntegerDisplayDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex portIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - m_label->setText(data->asText()); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_label->clear(); - } - - m_label->adjustSize(); -} diff --git a/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.h b/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.h deleted file mode 100644 index 065e1d0..0000000 --- a/gui/src/gie/DisplayNodeDataModel/IntegerDisplayDataModel.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#ifndef GUI_INTEGERDISPLAYDATAMODEL_H -#define GUI_INTEGERDISPLAYDATAMODEL_H - -#include -#include - -class IntegerDisplayDataModel: public QtNodes::NodeDataModel -{ -Q_OBJECT -public: - IntegerDisplayDataModel(); - virtual ~IntegerDisplayDataModel() override = default; - -public: - QString caption() const override { return QString("IntegerDisplay"); } - bool captionVisible() const override { return false; } - QString name() const override { return QString("IntegerDisplay"); } - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_label; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - QLabel* m_label; -}; - -#endif //GUI_INTEGERDISPLAYDATAMODEL_H diff --git a/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.cpp deleted file mode 100644 index 759a427..0000000 --- a/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#include "NumberDisplayDataModel.h" -#include "src/gie/types/NumberData.h" - -NumberDisplayDataModel::NumberDisplayDataModel(): - m_label{new QLabel()} -{ - m_label->setMargin(3); -} - -unsigned int NumberDisplayDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType NumberDisplayDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return NumberData().type(); -} - -void NumberDisplayDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex portIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - m_label->setText(data->asText()); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_label->clear(); - } - - m_label->adjustSize(); -} diff --git a/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.h b/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.h deleted file mode 100644 index 9d31d72..0000000 --- a/gui/src/gie/DisplayNodeDataModel/NumberDisplayDataModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_NUMBERDISPLAYDATAMODEL_H -#define GUI_NUMBERDISPLAYDATAMODEL_H - -#include -#include - -class NumberDisplayDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - NumberDisplayDataModel(); - virtual ~NumberDisplayDataModel() override = default; - -public: - QString caption() const override { return QString("NumberDisplay"); } - bool captionVisible() const override { return false; } - QString name() const override { return QString("NumberDisplay"); } - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_label; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - QLabel* m_label; -}; - - -#endif //GUI_NUMBERDISPLAYDATAMODEL_H diff --git a/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.cpp deleted file mode 100644 index d1ac369..0000000 --- a/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#include "StringDisplayDataModel.h" -#include "src/gie/types/StringData.h" - -StringDisplayDataModel::StringDisplayDataModel(): - m_label{new QLabel()} -{ - m_label->setMargin(3); -} - -unsigned int StringDisplayDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType StringDisplayDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return StringData().type(); -} - -void StringDisplayDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex portIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - m_label->setText(data->asText()); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_label->clear(); - } - - m_label->adjustSize(); -} \ No newline at end of file diff --git a/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.h b/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.h deleted file mode 100644 index 030cb7f..0000000 --- a/gui/src/gie/DisplayNodeDataModel/StringDisplayDataModel.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_STRINGDISPLAYDATAMODEL_H -#define GUI_STRINGDISPLAYDATAMODEL_H - -#include -#include - -class StringDisplayDataModel: public QtNodes::NodeDataModel -{ -Q_OBJECT -public: - StringDisplayDataModel(); - virtual ~StringDisplayDataModel() override = default; - -public: - QString caption() const override { return QString("StringDisplay"); } - bool captionVisible() const override { return false; } - QString name() const override { return QString("StringDisplay"); } - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_label; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - QLabel* m_label; -}; - -#endif //GUI_STRINGDISPLAYDATAMODEL_H diff --git a/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.cpp b/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.cpp deleted file mode 100644 index 2268fe4..0000000 --- a/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// -// Created by alex on 5/28/19. -// - -#include "TargetExportImageDataModel.h" -#include "src/gie/types/ImageData.h" - -TargetExportImageDataModel::TargetExportImageDataModel(): - m_targetNameEdit{new QLineEdit()}, - m_id{QUuid::createUuid()}, - m_dock{new QDockWidget("Image Preview")}, - m_imageViewer{new ImageViewer(m_dock)} -{ - m_targetNameEdit->setText("insert target name"); - connect(m_targetNameEdit, &QLineEdit::textEdited, this, &TargetExportImageDataModel::onTargetNameChanged); - m_dock->setWidget(m_imageViewer); - m_dock->setMinimumSize(100, 100); -} - -QJsonObject TargetExportImageDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if(m_targetName != "") - modelJson["target_name"] = m_targetName; - - return modelJson; -} - - -void TargetExportImageDataModel::restore(const QJsonObject& p) -{ - QJsonValue v = p["target_name"]; - - if (!v.isUndefined()) - { - QString str = v.toString(); - - m_targetName = str; - m_targetNameEdit->setText(str); - m_dock->setWindowTitle(str); - } -} - - -unsigned int TargetExportImageDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 1; - return 0; -} - -QtNodes::NodeDataType TargetExportImageDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return ImageData().type(); -} - -void TargetExportImageDataModel::setInData(std::shared_ptr input, QtNodes::PortIndex) -{ - if(auto* data = dynamic_cast(input.get()); data) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - const auto& image = data->image(); - auto imageData = new uint8_t[image.width * image.height * 3]; - std::memcpy(imageData, image.raw(), image.width * image.height * 3); - - QImage qImage(imageData, image.width, image.height, QImage::Format_RGB888, [](auto p){ delete static_cast(p); }); - - m_imageViewer->setDisplayType(image.width > image.height ? ImageViewer::FixedHeight : ImageViewer::FixedWidth); - m_imageViewer->setImage(std::move(qImage)); - } - else - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - - m_imageViewer->setImage(QImage{}); - } -} - -void TargetExportImageDataModel::onTargetNameChanged(const QString& name) -{ - m_targetName = name; - m_dock->setWindowTitle(name); - Q_EMIT targetNameChanged(m_id, name); -} \ No newline at end of file diff --git a/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.h b/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.h deleted file mode 100644 index 75cbe5f..0000000 --- a/gui/src/gie/DisplayNodeDataModel/TargetExportImageDataModel.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// Created by alex on 5/28/19. -// - -#ifndef GUI_TARGETEXPORTIMAGEDATAMODEL_H -#define GUI_TARGETEXPORTIMAGEDATAMODEL_H - -#include -#include -#include "src/imageviewer/imageviewer.h" - -#include - -class TargetExportImageDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - TargetExportImageDataModel(); - virtual ~TargetExportImageDataModel() override = default; - -public: - QString caption() const override { return QString("export target"); } - bool captionVisible() const override { return true; } - QString name() const override { return QString("TargetExportImage"); } - -public: - QJsonObject save() const override; - void restore(const QJsonObject& p) override; - - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } - void setInData(std::shared_ptr, QtNodes::PortIndex portIndex) override; - - QWidget* embeddedWidget() override { return m_targetNameEdit; } - - QtNodes::NodeValidationState validationState() const override { return modelValidationState; } - QString validationMessage() const override { return modelValidationError; } - - const QString& getTargetName() const { return m_targetName; } - QString getId() const { return m_id.toString(); }; - QDockWidget* dockWidget() { return m_dock; } - -Q_SIGNALS: - void targetNameChanged(const QUuid& id, const QString&); - -private Q_SLOTS: - void onTargetNameChanged(const QString&); - -private: - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - QLineEdit* m_targetNameEdit; - QString m_targetName; - QUuid m_id; - - QDockWidget* m_dock; - ImageViewer* m_imageViewer; -}; - - -#endif //GUI_TARGETEXPORTIMAGEDATAMODEL_H diff --git a/gui/src/gie/GieDataModelRegistry.cpp b/gui/src/gie/GieDataModelRegistry.cpp deleted file mode 100644 index c80a92a..0000000 --- a/gui/src/gie/GieDataModelRegistry.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#include "GieDataModelRegistry.h" - -#include -#include "GieNodeDataModel.h" - -void GieDataModelRegistry::registerModel(const NodeMetadata& metadata, const QString& category) -{ - RegistryItemCreator creator = [this, metadata](){ return std::make_unique(m_program, metadata); }; - - const QString name = QString::fromStdString(metadata.m_symbol.qualifiedName); - - /* - * the members that were required to be accessed are private, but they have const qualified accessors - * since the current function is not const qualified, the const-ness of the results can be safely removed - * (const-ness can be casted away, as long as the original source wasn't const in the first place, - * which is confirmed by the non const qualification if this function) - */ - auto& registeredItemCreators = const_cast(registeredModelCreators()); - auto& categories = const_cast(this->categories()); - auto& registeredModelsCategory = const_cast(registeredModelsCategoryAssociation()); - if (registeredItemCreators.count(name) == 0) - { - registeredItemCreators[name] = std::move(creator); - categories.insert(category); - registeredModelsCategory[name] = category; - } -} diff --git a/gui/src/gie/GieDataModelRegistry.h b/gui/src/gie/GieDataModelRegistry.h deleted file mode 100644 index 46f1757..0000000 --- a/gui/src/gie/GieDataModelRegistry.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#ifndef GUI_GIEDATAMODELREGISTRY_H -#define GUI_GIEDATAMODELREGISTRY_H - -#include -#include -#include -#undef B0 - -#include - -#include "SourceNodeDataModel/NumberSourceDataModel.h" -#include "SourceNodeDataModel/IntegerSourceDataModel.h" -#include "SourceNodeDataModel/StringSourceDataModel.h" -#include "SourceNodeDataModel/ColorSourceDataModel.h" -#include "SourceNodeDataModel/ImageSourceDataModel.h" - -#include "DisplayNodeDataModel/NumberDisplayDataModel.h" -#include "DisplayNodeDataModel/IntegerDisplayDataModel.h" -#include "DisplayNodeDataModel/StringDisplayDataModel.h" -#include "DisplayNodeDataModel/ColorDisplayDataModel.h" -#include "DisplayNodeDataModel/ImageDisplayDataModel.h" -#include "DisplayNodeDataModel/TargetExportImageDataModel.h" - -class GieDataModelRegistry: public QtNodes::DataModelRegistry -{ -public: - GieDataModelRegistry(Program& program): m_program{program} {} - - void registerModel(const NodeMetadata&, const QString& category); - -private: - Program& m_program; -}; - -[[maybe_unused]] -static std::shared_ptr registerDataModels(Program& program) -{ - std::shared_ptr registry(new GieDataModelRegistry(program), [](auto p){ - delete reinterpret_cast(p); - }); - - for(const auto& symbol: program.context().importedSymbols()) - registry->registerModel(fetchMetadata(program.context(), symbol.qualifiedName), QString::fromStdString(symbol.module)); - - static_cast(registry.get())->registerModel("source"); - static_cast(registry.get())->registerModel("source"); - static_cast(registry.get())->registerModel("source"); - static_cast(registry.get())->registerModel("source"); - static_cast(registry.get())->registerModel("source"); - - static_cast(registry.get())->registerModel("display"); - static_cast(registry.get())->registerModel("display"); - static_cast(registry.get())->registerModel("display"); - static_cast(registry.get())->registerModel("display"); - static_cast(registry.get())->registerModel("display"); - static_cast(registry.get())->registerModel("display"); - - return registry; -} - - -#endif //GUI_GIEDATAMODELREGISTRY_H diff --git a/gui/src/gie/GieNodeDataModel.cpp b/gui/src/gie/GieNodeDataModel.cpp deleted file mode 100644 index 524954e..0000000 --- a/gui/src/gie/GieNodeDataModel.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#include -#include -#include -#undef B0 - -#include "GieNodeDataModel.h" -#include "src/gie/types/ExtractTypes.h" - -unsigned int GieNodeDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return static_cast(m_metadata.m_arguments.size()); - else return 1; -} - -QtNodes::NodeDataType GieNodeDataModel::dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - if(portType == QtNodes::PortType::In) - return getTypeData(m_metadata.m_arguments[portIndex].m_argumentType); - return getTypeData(m_metadata.m_returnType); -} - -QString GieNodeDataModel::caption() const -{ - return QString::fromStdString(m_metadata.m_symbol.prettyName + ":" + m_metadata.m_symbol.module); -} - -QString GieNodeDataModel::name() const -{ - return QString::fromStdString(m_metadata.m_symbol.qualifiedName); -} - -bool GieNodeDataModel::portCaptionVisible(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - return true; -} - -QString GieNodeDataModel::portCaption(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const -{ - if(portType == QtNodes::PortType::In) - return QString::fromStdString(m_metadata.m_arguments[portIndex].m_argumentName); - return QString("result"); -} - -std::shared_ptr GieNodeDataModel::outData(QtNodes::PortIndex port) -{ - return m_result; -} - -void GieNodeDataModel::setInData(std::shared_ptr data, QtNodes::PortIndex port) -{ - if(!data) - return; - - m_logic.m_argument[port] = extractGieValue(data); - - if(!std::holds_alternative(m_program.getNode(m_nodeId).m_logic.m_argument[port])) - { - auto node = m_program.getNode(m_nodeId); - node.m_logic.m_argument[port] = m_logic.m_argument[port]; - m_program.editNode(m_nodeId, node); - } - - auto allPortsAssigned = std::find_if( - m_logic.m_argument.begin(), - m_logic.m_argument.end(), - [](const auto& x) - { - return std::holds_alternative(x); - }) == m_logic.m_argument.end(); - - if(allPortsAssigned) - { - modelValidationState = QtNodes::NodeValidationState::Valid; - modelValidationError = QString(); - - auto result = executeNode({{}, m_logic, m_metadata}); - - if(result.has_value()) - m_result = extractNodeData(m_metadata.m_returnType, result.value()); - else - { - modelValidationState = QtNodes::NodeValidationState::Warning; - modelValidationError = QString("Missing or incorrect inputs"); - m_result = std::shared_ptr(); - } - } - else - { - modelValidationState = QtNodes::NodeValidationState::Warning; - modelValidationError = QString("Missing or incorrect inputs"); - } - - Q_EMIT dataUpdated(0); - -} - -QtNodes::NodeValidationState GieNodeDataModel::validationState() const -{ - return modelValidationState; -} - -QString GieNodeDataModel::validationMessage() const -{ - return modelValidationError; -} diff --git a/gui/src/gie/GieNodeDataModel.h b/gui/src/gie/GieNodeDataModel.h deleted file mode 100644 index 16651ef..0000000 --- a/gui/src/gie/GieNodeDataModel.h +++ /dev/null @@ -1,71 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#ifndef GUI_GIENODEDATAMODEL_H -#define GUI_GIENODEDATAMODEL_H - -#include -#include -#include -#undef B0 - -#include -#include - -class GieNodeDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT - -public: - explicit GieNodeDataModel(Program& program, NodeMetadata metadata): - m_program{program}, - m_metadata(std::move(metadata)), - m_logic{std::vector(m_metadata.m_arguments.size(), {NoArgument{}})} - { - m_nodeId = program.addNode({{}, m_logic, m_metadata}); - }; - - GieNodeDataModel() = delete; - - ~GieNodeDataModel() override - { - m_program.removeNode(m_nodeId); - } - - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - QString caption() const override; - QString name() const override; - - bool portCaptionVisible(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - QString portCaption(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr data, QtNodes::PortIndex port) override; - - QWidget* embeddedWidget() override { return nullptr; } - - QtNodes::NodeValidationState validationState() const override; - - QString validationMessage() const override; - - auto nodeId() const { return m_nodeId; } - -private: - Program& m_program; - NodeId m_nodeId; - NodeMetadata m_metadata; - NodeLogic m_logic; - - std::shared_ptr m_result; - - QtNodes::NodeValidationState modelValidationState = QtNodes::NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); - - friend class Editor; -}; - - -#endif //GUI_GIENODEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.cpp deleted file mode 100644 index ef12fbf..0000000 --- a/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#include "ColorSourceDataModel.h" - -ColorSourceDataModel::ColorSourceDataModel(): m_colorPicker(new ColorPicker()) -{ - m_colorPicker->setMaximumSize(m_colorPicker->sizeHint()); - - connect( - m_colorPicker, &ColorPicker::onColorChanged, - this, &ColorSourceDataModel::onColorChanged - ); -} - -QJsonObject ColorSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - { - modelJson["R"] = m_data->color().r; - modelJson["G"] = m_data->color().g; - modelJson["B"] = m_data->color().b; - } - - return modelJson; -} - -void ColorSourceDataModel::restore(QJsonObject const &p) -{ - QJsonValue r = p["R"]; - QJsonValue g = p["G"]; - QJsonValue b = p["B"]; - - if (!r.isUndefined() && !g.isUndefined() && !b.isUndefined()) - { - Color color{static_cast(r.toInt()), static_cast(g.toInt()), static_cast(b.toInt())}; - m_data = std::make_shared(color); - } -} - -unsigned int ColorSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - -void ColorSourceDataModel::onColorChanged(QColor color) -{ - m_data = std::make_shared(Color{ - static_cast(color.red()), - static_cast(color.green()), - static_cast(color.blue()) - }); - Q_EMIT dataUpdated(0); - Q_EMIT onValueChanged(m_data); -} - -QtNodes::NodeDataType ColorSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return ColorData().type(); -} - - -std::shared_ptr ColorSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.h deleted file mode 100644 index fcef6bc..0000000 --- a/gui/src/gie/SourceNodeDataModel/ColorSourceDataModel.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_COLORSOURCEDATAMODEL_H -#define GUI_COLORSOURCEDATAMODEL_H - -#include - -#include "src/colorpicker/colorpicker.h" -#include "src/gie/types/ColorData.h" - -class ColorSourceDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - ColorSourceDataModel(); - ~ColorSourceDataModel() override = default; - -public: - QString caption() const override { return QStringLiteral("color source"); } - bool captionVisible() const override { return false; } - QString name() const override { return QStringLiteral("ColorSource"); } - -public: - QJsonObject save() const override; - void restore(QJsonObject const &p) override; - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return m_colorPicker; } - -Q_SIGNALS: - void onValueChanged(std::shared_ptr); - -private Q_SLOTS: - void onColorChanged(QColor); - -private: - ColorPicker* m_colorPicker; - std::shared_ptr m_data; -}; - - -#endif //GUI_COLORSOURCEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.cpp deleted file mode 100644 index 1547473..0000000 --- a/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#include "ImageSourceDataModel.h" - -ImageSourceDataModel::ImageSourceDataModel(): m_filePicker(new FilePicker("Images (*.png *.xpm *.jpg)")) -{ - m_filePicker->setMaximumSize(m_filePicker->sizeHint()); - - connect( - m_filePicker, &FilePicker::fileChanged, - this, &ImageSourceDataModel::onFileChanged - ); -} - -QJsonObject ImageSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - { - modelJson["file"] = m_filename; - } - - return modelJson; -} - -void ImageSourceDataModel::restore(QJsonObject const &p) -{ - QJsonValue filename = p["file"]; - - if (!filename.isUndefined()) - { - m_filePicker->setFile(filename.toString()); - onFileChanged(filename.toString()); - } -} - -unsigned int ImageSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - -static std::vector imageToRawData(const QImage& image) -{ - std::vector data(static_cast(image.width() * image.height() * 3)); - const auto rowSize = static_cast(image.width()) * 3; - std::size_t index = 0; - std::size_t indexInImage = 0; - - for(int i = 0; i < image.height(); i++) - { - std::memcpy(data.data() + index, image.bits() + indexInImage, rowSize); - - indexInImage += image.bytesPerLine(); - index += rowSize; - } - - return data; -} - -void ImageSourceDataModel::onFileChanged(QString filename) -{ - m_filename = std::move(filename); - QImage image = QImage(m_filename).convertToFormat(QImage::Format_RGB888); - - m_data = std::make_shared(Image{imageToRawData(image), static_cast(image.width()), - static_cast(image.height())}); - Q_EMIT dataUpdated(0); - Q_EMIT onValueChanged(m_data); -} - -QtNodes::NodeDataType ImageSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return ImageData().type(); -} - - -std::shared_ptr ImageSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.h deleted file mode 100644 index ae7e505..0000000 --- a/gui/src/gie/SourceNodeDataModel/ImageSourceDataModel.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#ifndef GUI_IMAGESOURCEDATAMODEL_H -#define GUI_IMAGESOURCEDATAMODEL_H - -#include - -#include "src/filepicker/filepicker.h" -#include "src/gie/types/ImageData.h" - -class ImageSourceDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - ImageSourceDataModel(); - ~ImageSourceDataModel() override = default; - -public: - QString caption() const override { return QStringLiteral("image source"); } - bool captionVisible() const override { return false; } - QString name() const override { return QStringLiteral("ImageSource"); } - -public: - QJsonObject save() const override; - void restore(QJsonObject const &p) override; - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return m_filePicker; } - -Q_SIGNALS: - void onValueChanged(std::shared_ptr); - -private Q_SLOTS: - void onFileChanged(QString); - -private: - FilePicker* m_filePicker; - std::shared_ptr m_data; - QString m_filename; -}; - - -#endif //GUI_IMAGESOURCEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.cpp deleted file mode 100644 index 5a2a63a..0000000 --- a/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#include "IntegerSourceDataModel.h" - -#include -#include - -IntegerSourceDataModel::IntegerSourceDataModel(): m_lineEdit(new QLineEdit()) -{ - m_lineEdit->setValidator(new QIntValidator()); - m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); - - connect( - m_lineEdit, &QLineEdit::textChanged, - this, &IntegerSourceDataModel::onTextEdited - ); - - m_lineEdit->setText("0"); -} - - -QJsonObject IntegerSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - modelJson["number"] = QString::number(m_data->number()); - - return modelJson; -} - - -void IntegerSourceDataModel::restore(QJsonObject const &p) -{ - QJsonValue v = p["number"]; - - if (!v.isUndefined()) - { - QString str = v.toString(); - - bool ok; - long long number = str.toLongLong(&ok); - - if(ok) - { - m_data = std::make_shared(number); - m_lineEdit->setText(str); - } - } -} - - -unsigned int IntegerSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - - -void IntegerSourceDataModel::onTextEdited(QString const &string) -{ - Q_UNUSED(string); - - bool ok; - long long number = string.toLongLong(&ok); - - if(ok) - { - m_data = std::make_shared(number); - Q_EMIT dataUpdated(0); - Q_EMIT onValueChanged(m_data); - } - else Q_EMIT dataInvalidated(0); -} - - -QtNodes::NodeDataType IntegerSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return IntegerData().type(); -} - - -std::shared_ptr IntegerSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.h deleted file mode 100644 index 7218254..0000000 --- a/gui/src/gie/SourceNodeDataModel/IntegerSourceDataModel.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#ifndef GUI_INTEGERSOURCEDATAMODEL_H -#define GUI_INTEGERSOURCEDATAMODEL_H - -#include -#include - -#include - -#include "src/gie/types/IntegerData.h" - -class IntegerSourceDataModel: public QtNodes::NodeDataModel -{ -Q_OBJECT - -public: - IntegerSourceDataModel(); - - ~IntegerSourceDataModel() override {} - -public: - QString caption() const override { return QStringLiteral("integer source"); } - bool captionVisible() const override { return false; } - QString name() const override { return QStringLiteral("IntegerSource"); } - -public: - QJsonObject save() const override; - void restore(QJsonObject const &p) override; - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return m_lineEdit; } - -Q_SIGNALS: - void onValueChanged(std::shared_ptr); - -private Q_SLOTS: - void onTextEdited(QString const &string); - -private: - std::shared_ptr m_data; - QLineEdit * m_lineEdit; -}; - -#endif //GUI_INTEGERSOURCEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.cpp deleted file mode 100644 index dcdba57..0000000 --- a/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// -// Created by alex on 5/26/19. -// - -#include "ManagedImageSourceDataModel.h" - -static std::vector imageToRawData(const QImage& image) -{ - std::vector data(static_cast(image.width() * image.height() * 3)); - const auto rowSize = static_cast(image.width()) * 3; - std::size_t index = 0; - std::size_t indexInImage = 0; - - for(int i = 0; i < image.height(); i++) - { - std::memcpy(data.data() + index, image.bits() + indexInImage, rowSize); - - indexInImage += image.bytesPerLine(); - index += rowSize; - } - - return data; -} - -ManagedImageSourceDataModel::ManagedImageSourceDataModel(const ProjectImage& projectImage): - m_filename{projectImage.name}, - m_uuid{projectImage.id.toString()} -{ - QImage image = projectImage.image.convertToFormat(QImage::Format_RGB888); - - m_data = std::make_shared(Image{imageToRawData(image), static_cast(image.width()), - static_cast(image.height())}); -} - -QJsonObject ManagedImageSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - { - modelJson["uuid"] = m_uuid; - } - - return modelJson; -} - -void ManagedImageSourceDataModel::restore(QJsonObject const &p) -{} - -unsigned int ManagedImageSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - -QtNodes::NodeDataType ManagedImageSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return ImageData().type(); -} - - -std::shared_ptr ManagedImageSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.h deleted file mode 100644 index 0179f9f..0000000 --- a/gui/src/gie/SourceNodeDataModel/ManagedImageSourceDataModel.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Created by alex on 5/26/19. -// - -#ifndef GUI_MANAGEDIMAGESOURCEDATAMODEL_H -#define GUI_MANAGEDIMAGESOURCEDATAMODEL_H - -#include -#include "src/gie/types/ImageData.h" - -#include "src/Project.h" - -class ManagedImageSourceDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT -public: - ManagedImageSourceDataModel(const ProjectImage&); - ~ManagedImageSourceDataModel() override = default; - -public: - QString caption() const override { return m_filename; } - bool captionVisible() const override { return true; } - QString name() const override { return QStringLiteral("ManagedImageSource"); } - -public: - QJsonObject save() const override; - void restore(QJsonObject const &p) override; - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return nullptr; } - -private: - std::shared_ptr m_data; - QString m_filename; - QString m_uuid; -}; - - -#endif //GUI_MANAGEDIMAGESOURCEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.cpp deleted file mode 100644 index 8345d45..0000000 --- a/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// -// Created by alex on 5/20/19. -// - -#include "NumberSourceDataModel.h" - -#include -#include - -NumberSourceDataModel::NumberSourceDataModel(): m_lineEdit(new QLineEdit()) -{ - m_lineEdit->setValidator(new QDoubleValidator()); - m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); - - connect( - m_lineEdit, &QLineEdit::textChanged, - this, &NumberSourceDataModel::onTextEdited - ); - - m_lineEdit->setText("0.0"); -} - - -QJsonObject NumberSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - modelJson["number"] = QString::number(m_data->number()); - - return modelJson; -} - - -void NumberSourceDataModel::restore(QJsonObject const &p) -{ - QJsonValue v = p["number"]; - - if (!v.isUndefined()) - { - QString str = v.toString(); - - bool ok; - double number = str.toDouble(&ok); - - if(ok) - { - m_data = std::make_shared(number); - m_lineEdit->setText(str); - } - } -} - - -unsigned int NumberSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - - -void NumberSourceDataModel::onTextEdited(QString const &string) -{ - Q_UNUSED(string); - - bool ok; - double number = string.toDouble(&ok); - - if(ok) - { - m_data = std::make_shared(number); - Q_EMIT dataUpdated(0); - Q_EMIT onValueChanged(m_data); - } - else Q_EMIT dataInvalidated(0); -} - - -QtNodes::NodeDataType NumberSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return NumberData().type(); -} - - -std::shared_ptr NumberSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.h deleted file mode 100644 index 79bda59..0000000 --- a/gui/src/gie/SourceNodeDataModel/NumberSourceDataModel.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Created by alex on 5/20/19. -// - -#ifndef GUI_NUMBERSOURCEDATAMODEL_H -#define GUI_NUMBERSOURCEDATAMODEL_H - -#include -#include - -#include - -#include "src/gie/types/NumberData.h" - -class NumberSourceDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT - -public: - NumberSourceDataModel(); - - ~NumberSourceDataModel() override {} - -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(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return m_lineEdit; } - -Q_SIGNALS: - void onValueChanged(std::shared_ptr); - -private Q_SLOTS: - void onTextEdited(QString const &string); - -private: - std::shared_ptr m_data; - QLineEdit * m_lineEdit; -}; - - -#endif //GUI_NUMBERSOURCEDATAMODEL_H diff --git a/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.cpp b/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.cpp deleted file mode 100644 index 431e85f..0000000 --- a/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// -// Created by alex on 5/20/19. -// - -#include "StringSourceDataModel.h" - -#include -#include - -StringSourceDataModel::StringSourceDataModel(): m_lineEdit(new QLineEdit()) -{ - m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); - - connect( - m_lineEdit, &QLineEdit::textChanged, - this, &StringSourceDataModel::onTextEdited - ); - - m_lineEdit->setText("string"); -} - - -QJsonObject StringSourceDataModel::save() const -{ - QJsonObject modelJson = NodeDataModel::save(); - - if (m_data) - modelJson["string"] = m_data->asText(); - - return modelJson; -} - - -void StringSourceDataModel::restore(QJsonObject const &p) -{ - QJsonValue v = p["string"]; - - if (!v.isUndefined()) - { - QString str = v.toString(); - - m_data = std::make_shared(str); - m_lineEdit->setText(str); - } -} - - -unsigned int StringSourceDataModel::nPorts(QtNodes::PortType portType) const -{ - if(portType == QtNodes::PortType::In) - return 0; - else return 1; -} - - -void StringSourceDataModel::onTextEdited(QString const &string) -{ - Q_UNUSED(string); - - m_data = std::make_shared(string); - Q_EMIT dataUpdated(0); - Q_EMIT onValueChanged(m_data); -} - - -QtNodes::NodeDataType StringSourceDataModel::dataType(QtNodes::PortType, QtNodes::PortIndex) const -{ - return StringData().type(); -} - - -std::shared_ptr StringSourceDataModel::outData(QtNodes::PortIndex) -{ - return m_data; -} \ No newline at end of file diff --git a/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.h b/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.h deleted file mode 100644 index da9ceaf..0000000 --- a/gui/src/gie/SourceNodeDataModel/StringSourceDataModel.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// Created by alex on 5/20/19. -// - -#ifndef GUI_STRINGSOURCEDATAMODEL_H -#define GUI_STRINGSOURCEDATAMODEL_H - -#include -#include - -#include - -#include "src/gie/types/StringData.h" - -class StringSourceDataModel: public QtNodes::NodeDataModel -{ - Q_OBJECT - -public: - StringSourceDataModel(); - - ~StringSourceDataModel() override {} - -public: - QString caption() const override { return QStringLiteral("string source"); } - bool captionVisible() const override { return false; } - QString name() const override { return QStringLiteral("StringSource"); } - -public: - QJsonObject save() const override; - void restore(QJsonObject const &p) override; - -public: - unsigned int nPorts(QtNodes::PortType portType) const override; - QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override; - - std::shared_ptr outData(QtNodes::PortIndex port) override; - void setInData(std::shared_ptr, int) override { } - - QWidget* embeddedWidget() override { return m_lineEdit; } - -Q_SIGNALS: - void onValueChanged(std::shared_ptr); - -private Q_SLOTS: - void onTextEdited(QString const &string); - -private: - std::shared_ptr m_data; - QLineEdit * m_lineEdit; -}; - - - -#endif //GUI_STRINGSOURCEDATAMODEL_H diff --git a/gui/src/gie/types/ColorData.h b/gui/src/gie/types/ColorData.h deleted file mode 100644 index 0daa01f..0000000 --- a/gui/src/gie/types/ColorData.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by alex on 5/23/19. -// - -#ifndef GUI_COLORDATA_H -#define GUI_COLORDATA_H - -#include -#include "TypeData.h" -#include - -class ColorData: public TypeData -{ -public: - explicit ColorData() = default; - ColorData(Color data): m_data{boost::python::object(data)} {} - ColorData(const Value& data): m_data{data} {} - - ColorData(const ColorData&) = default; - ColorData(ColorData&&) = default; - - QtNodes::NodeDataType type() const override - { - return {"Color", "Color"}; - } - - const Color& color() const - { - return boost::python::extract(m_data.m_object); - } - - Value value() override { return m_data; } - -private: - Value m_data; -}; - - -#endif //GUI_COLORDATA_H diff --git a/gui/src/gie/types/ExtractTypes.h b/gui/src/gie/types/ExtractTypes.h deleted file mode 100644 index c3c0578..0000000 --- a/gui/src/gie/types/ExtractTypes.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// Created by alex on 5/28/19. -// - -#ifndef GUI_EXTRACTTYPES_H -#define GUI_EXTRACTTYPES_H - -#include -#include -#include - -#include "StringData.h" -#include "NumberData.h" -#include "IntegerData.h" -#include "ColorData.h" -#include "ImageData.h" - -#include "TypeData.h" - -#include - -Value extractGieValue(const std::shared_ptr& nodeData) -{ - if(auto p = dynamic_cast(nodeData.get()); p) - { - return p->value(); - } - - return Value{}; -} - -std::shared_ptr extractNodeData(const Value& value) -{ - if(auto x = boost::python::extract{value.m_object}; x.check() && PyLong_Check(value.m_object.ptr())) - return std::make_shared(value); - - if(auto x = boost::python::extract{value.m_object}; x.check() && PyFloat_Check(value.m_object.ptr())) - return std::make_shared(value); - - if(auto x = boost::python::extract{value.m_object}; x.check()) - return std::make_shared(value); - - if(auto x = boost::python::extract{value.m_object}; x.check()) - return std::make_shared(value); - - if(auto x = boost::python::extract{value.m_object}; x.check()) - return std::make_shared(value); - - return nullptr; -} - -std::shared_ptr extractNodeData(const Type& type, const Value& value) -{ - if(type == Type("str")) - return std::make_shared(value); - - if(type == Type("float")) - return std::make_shared(value); - - if(type == Type("int")) - return std::make_shared(value); - - if(type == Type("Color")) - return std::make_shared(value); - - if(type == Type("Image")) - return std::make_shared(value); - - return nullptr; -} - -QtNodes::NodeDataType getTypeData(const Type& type) -{ - static std::unordered_map typeMap = {{"str", "string"}, {"float", "double"}, {"int", "integer"}, {"Color", "Color"}, {"Image", "Image"}}; - - return {typeMap[type.name()], typeMap[type.name()]}; -} - -#endif //GUI_EXTRACTTYPES_H diff --git a/gui/src/gie/types/ImageData.h b/gui/src/gie/types/ImageData.h deleted file mode 100644 index 9dae20d..0000000 --- a/gui/src/gie/types/ImageData.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_IMAGEDATA_H -#define GUI_IMAGEDATA_H - -#include -#include "TypeData.h" -#include - -class ImageData: public TypeData -{ -public: - explicit ImageData(): m_data(boost::python::object(Image{0, 0})) {} - ImageData(const Image& data): m_data{boost::python::object(data)} {} - ImageData(const Value& data): m_data{data} {} - - ImageData(const ImageData&) = default; - ImageData(ImageData&&) = default; - - QtNodes::NodeDataType type() const override - { - return {"Image", "Image"}; - } - - const Image& image() const - { - return boost::python::extract(m_data.m_object); - } - - Value value() override { return m_data; } - -private: - Value m_data; -}; - -#endif //GUI_IMAGEDATA_H diff --git a/gui/src/gie/types/IntegerData.h b/gui/src/gie/types/IntegerData.h deleted file mode 100644 index 0acdfa0..0000000 --- a/gui/src/gie/types/IntegerData.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by alex on 5/24/19. -// - -#ifndef GUI_INTDATA_H -#define GUI_INTDATA_H - -#include -#include "TypeData.h" - -class IntegerData: public TypeData -{ -public: - explicit IntegerData() = default; - IntegerData(long long int data): m_data{boost::python::object(data)} {} - IntegerData(const Value& data): m_data{data} {} - - IntegerData(const IntegerData&) = default; - IntegerData(IntegerData&&) = default; - - QtNodes::NodeDataType type() const override - { - return {"integer", "integer"}; - } - - long long int number() const - { - return boost::python::extract(m_data.m_object); - } - - QString asText() const - { - return QString::number(boost::python::extract(m_data.m_object)); - } - - Value value() override { return m_data; } - -private: - Value m_data; -}; - -#endif //GUI_INTDATA_H diff --git a/gui/src/gie/types/NumberData.h b/gui/src/gie/types/NumberData.h deleted file mode 100644 index 26b03a6..0000000 --- a/gui/src/gie/types/NumberData.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#ifndef GUI_NUMBERDATA_H -#define GUI_NUMBERDATA_H - -#include -#include "TypeData.h" - -class NumberData: public TypeData -{ -public: - explicit NumberData() = default; - NumberData(double data): m_data{boost::python::object(data)} {} - NumberData(const Value& data): m_data{data} {} - - NumberData(const NumberData&) = default; - NumberData(NumberData&&) = default; - - QtNodes::NodeDataType type() const override - { - return {"double", "double"}; - } - - double number() const - { - return boost::python::extract(m_data.m_object); - } - - QString asText() const - { - return QString::number(boost::python::extract(m_data.m_object)); - } - - Value value() override { return m_data; } - -private: - Value m_data; -}; - -#endif //GUI_NUMBERDATA_H diff --git a/gui/src/gie/types/StringData.h b/gui/src/gie/types/StringData.h deleted file mode 100644 index fab05d6..0000000 --- a/gui/src/gie/types/StringData.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#ifndef GUI_STRINGDATA_H -#define GUI_STRINGDATA_H - -#include -#include "TypeData.h" - -class StringData: public TypeData -{ -public: - explicit StringData() = default; - StringData(const std::string& data): m_data{boost::python::object(data)} {} - StringData(const QString& data): m_data{boost::python::object(data.toUtf8().constData())} {} - StringData(const Value& data): m_data{data} {} - - StringData(const StringData&) = default; - StringData(StringData&&) = default; - - QtNodes::NodeDataType type() const override - { - return {"string", "string"}; - } - - const std::string& string() const - { - return boost::python::extract(m_data.m_object); - } - - QString asText() const - { - return QString::fromStdString(boost::python::extract(m_data.m_object)); - } - - Value value() override { return m_data; } - -private: - Value m_data; -}; - - -#endif //GUI_STRINGDATA_H diff --git a/gui/src/gie/types/TypeData.h b/gui/src/gie/types/TypeData.h deleted file mode 100644 index 3b1ddd6..0000000 --- a/gui/src/gie/types/TypeData.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by alex on 5/19/19. -// - -#ifndef GUI_TYPEDATA_H -#define GUI_TYPEDATA_H - -#include -#include - -class TypeData: public QtNodes::NodeData -{ -public: - virtual Value value() = 0; -}; - - -#endif //GUI_TYPEDATA_H diff --git a/gui/src/imageviewer/imageviewer.cpp b/gui/src/imageviewer/imageviewer.cpp deleted file mode 100644 index c2a2448..0000000 --- a/gui/src/imageviewer/imageviewer.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// Created by alex on 5/25/19. -// - -#include "imageviewer.h" - -#include - -ImageViewer::ImageViewer(QWidget *parent) : QWidget(parent) -{ - m_displayType = Full; -} - -void ImageViewer::setImage(QImage image) { m_image = std::move(image); Q_EMIT update(); } - -void ImageViewer::setDisplayType(DisplayType type) -{ - m_displayType = type; -} - -void ImageViewer::paintEvent(QPaintEvent*) -{ - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing); - - int width = this->width(), height = this->height(); - - if(width == 0 || height == 0 || m_image.width() == 0 || m_image.height() == 0) - return; - - if(m_displayType == FixedWidth) - { - height = m_image.height() * width / m_image.width(); - this->resize(width, height); - } - else if(m_displayType == FixedHeight) - { - width = m_image.width() * height / m_image.height(); - this->resize(width, height); - } - else if(m_displayType == Full) - { - this->resize(m_image.width(), m_image.height()); - } - - painter.drawImage(QRect{0, 0, this->width(), this->height()}, m_image); -} \ No newline at end of file diff --git a/gui/src/main.cpp b/gui/src/main.cpp index 4bc7a27..899cfec 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -8,52 +8,6 @@ #include #include -std::string fetchPythonException() -{ - namespace py = boost::python; - - PyObject *pType = nullptr, *pValue = nullptr, *pTraceback = nullptr; - PyErr_Fetch(&pType, &pValue, &pTraceback); - std::string ret("Unfetchable Python error"); - - if(pType) - { - py::handle<> type(pType); - py::extract typeString{py::str(type)}; - - if(typeString.check()) - ret = typeString(); - else - ret = "Unknown exception type"; - } - if(pValue) - { - py::handle<> value(pValue); - py::extract returned{py::str(value)}; - - if(returned.check()) - ret += ": " + returned(); - else - ret += std::string(": Unparseable Python error: "); - } - - if(pTraceback) - { - py::handle<> traceback(pTraceback); - py::object tb(py::import("traceback")); - py::object fmt_tb(tb.attr("format_tb")); - py::object tb_list(fmt_tb(traceback)); - py::object tb_str(py::str("\n").join(tb_list)); - py::extract returned(tb_str); - - if(returned.check()) - ret += ": " + returned(); - else - ret += std::string(": Unparseable Python traceback"); - } - return ret; -} - int main(int argc, char *argv[]) { int ret = -1; @@ -65,10 +19,9 @@ int main(int argc, char *argv[]) ret = QApplication::exec(); } - catch(const boost::python::error_already_set&) + catch(std::exception& e) { - std::string errorString = fetchPythonException(); - std::cout << errorString << std::endl; + std::cout << e.what() << std::endl; } diff --git a/gui/src/mainwindow.cpp b/gui/src/mainwindow.cpp index cced15d..eb1104b 100644 --- a/gui/src/mainwindow.cpp +++ b/gui/src/mainwindow.cpp @@ -8,7 +8,6 @@ #include "editor.h" #include -#include "colorpicker/colorpicker.h" #include #include @@ -18,26 +17,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); - - m_program.context().module("builtins", false); - - auto sys = m_program.context().module("sys", false); - auto os = m_program.context().module("os", false); - - sys.attr("path").attr("insert")(1, os.attr("getcwd")()); - - m_program.context().module("modules.internals", false); - - QFile file("config"); - file.open(QIODevice::ReadOnly); + qRegisterMetaType>("std::vector"); + qRegisterMetaType("GieRuntimeError"); - QJsonObject config = QJsonDocument::fromJson(file.readAll()).object(); - - for(const auto& module: config["modules"].toArray()) - m_program.import(module.toString().toUtf8().constData()); + ui->setupUi(this); - setCentralWidget(m_editor = new Editor(m_program)); + setCentralWidget(m_editor = new Editor()); QObject::connect( ui->actionNewProject, &QAction::triggered, @@ -86,8 +71,6 @@ MainWindow::MainWindow(QWidget *parent) : m_symbolViewer, &SymbolViewer::onSymbolsUpdate ); - reloadSymbols(); - QDockWidget* imageViewerDock = new QDockWidget("ImportedImages", this); m_imageViewer = new ImportedImagesViewer(imageViewerDock); @@ -109,6 +92,52 @@ MainWindow::MainWindow(QWidget *parent) : m_editor, &Editor::onExportImage ); + QObject::connect( + m_editor, &Editor::reloadedSymbols, + this, &MainWindow::reloadedSymbols + ); + + connect( + m_editor, &Editor::symbolRemoved, + m_symbolViewer, &SymbolViewer::removeSymbol); + + QDockWidget* projectScriptsDock = new QDockWidget("Project Scripts", this); + m_projectScripts = new ProjectScripts{}; + + projectScriptsDock->setWidget(m_projectScripts); + addDockWidget(Qt::RightDockWidgetArea, projectScriptsDock); + + connect(m_editor, &Editor::scriptAdded, m_projectScripts, &ProjectScripts::addFile); + connect(m_editor, &Editor::scriptRemoved, m_projectScripts, &ProjectScripts::removeFile); + connect(m_editor, &Editor::projectLoaded, [this](const Project& project) + { + m_projectScripts->setScriptsFolder(project.projectPath().filePath("scripts")); + }); + + QDockWidget* logConsoleDock = new QDockWidget("Log Console", this); + m_logConsole = new LogConsole{}; + + logConsoleDock->setWidget(m_logConsole); + addDockWidget(Qt::LeftDockWidgetArea, logConsoleDock); + + connect(m_editor, &Editor::error, m_logConsole, &LogConsole::logError); + connect(m_editor, &Editor::warning, m_logConsole, &LogConsole::logWarning); + + QFile file("config"); + file.open(QIODevice::ReadOnly); + + QJsonObject config = QJsonDocument::fromJson(file.readAll()).object(); + + QString rootPath = QFileInfo(file).absoluteDir().absolutePath(); + for(const auto& module: config["modules"].toArray()) + { + QString name = module.toObject()["name"].toString(); + QString path = rootPath + "/" + module.toObject()["path"].toString(); + + m_editor->loadModule(name, path); + } + + this->setWindowTitle("GIE"); } @@ -117,15 +146,14 @@ MainWindow::~MainWindow() delete ui; } -void MainWindow::reloadSymbols() +void MainWindow::reloadedSymbols(const std::vector& gieSymbols) { - m_modelRegistry = registerDataModels(m_program); - m_editor->setRegistry(m_modelRegistry); - std::map> symbols; - for(const auto& [name, category]: m_modelRegistry->registeredModelsCategoryAssociation()) - symbols[category].push_back(name); + for(const auto& symbol: gieSymbols) + symbols[QString::fromStdString(symbol.symbol.module)].push_back( + QString::fromStdString(symbol.symbol.prettyName) + ); Q_EMIT onSymbolsImported(symbols); } diff --git a/gui/src/mainwindow.h b/gui/src/mainwindow.h index 4a6d26f..0b03a6a 100644 --- a/gui/src/mainwindow.h +++ b/gui/src/mainwindow.h @@ -6,14 +6,14 @@ #define GUI_MAINWINDOW_H #include -#undef B0 #include #include #include "editor.h" -#include "symbolviewer/symbolviewer.h" -#include "importedimagesviewer/importedimagesviewer.h" -#include "src/gie/GieDataModelRegistry.h" +#include "widgets/symbolviewer/symbolviewer.h" +#include "widgets/importedimagesviewer/importedimagesviewer.h" +#include "widgets/projectscripts/projectscripts.h" +#include "widgets/logconsole/logconsole.h" namespace Ui { class MainWindow; @@ -36,17 +36,17 @@ private Q_SLOTS: void onChanged(); void onSaved(); -private: - void reloadSymbols(); +private Q_SLOTS: + void reloadedSymbols(const std::vector&); private: Ui::MainWindow *ui; - Program m_program; Editor* m_editor; SymbolViewer* m_symbolViewer; ImportedImagesViewer* m_imageViewer; - std::shared_ptr m_modelRegistry; + ProjectScripts* m_projectScripts; + LogConsole* m_logConsole; }; #endif //GUI_MAINWINDOW_H diff --git a/gui/src/nodeeditor/BaseNode.h b/gui/src/nodeeditor/BaseNode.h new file mode 100644 index 0000000..7427fa7 --- /dev/null +++ b/gui/src/nodeeditor/BaseNode.h @@ -0,0 +1,76 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_BASENODE_H +#define GIE_BASENODE_H + +#include +#include + +#include "TypeData.h" + +class BaseNode: public QtNodes::NodeDataModel +{ + Q_OBJECT + +public: + QUuid id() const { return m_id; } + + virtual QString caption() const override = 0; + virtual QString name() const override = 0; + + virtual unsigned int nPorts(QtNodes::PortType portType) const override = 0; + virtual QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override = 0; + + virtual QWidget* embeddedWidget() override = 0; + + virtual void setInData(std::shared_ptr, int) override = 0; + virtual std::shared_ptr outData(QtNodes::PortIndex portIndex) override = 0; + + QtNodes::NodeValidationState validationState() const override { return m_modelValidationState; } + QString validationMessage() const override { return m_modelValidationError; } + + void error(const QString& message) + { + m_modelValidationState = QtNodes::NodeValidationState::Error; + m_modelValidationError = message; + invalidateData(); + } + + void warning(const QString& message) + { + m_modelValidationState = QtNodes::NodeValidationState::Warning; + m_modelValidationError = message; + invalidateData(); + } + + void ok() + { + m_modelValidationState = QtNodes::NodeValidationState::Valid; + m_modelValidationError = ""; + updateData(); + } + +Q_SIGNALS: + void dataChanged(Data); + +public: + virtual Data getData() = 0; + +public Q_SLOTS: + virtual void displayData(Data data) = 0; + +protected: + virtual void invalidateData() = 0; + virtual void updateData() = 0; + +private: + QtNodes::NodeValidationState m_modelValidationState = QtNodes::NodeValidationState::Warning; + QString m_modelValidationError = QString("Missing or incorrect inputs"); + + QUuid m_id; + friend class NodeEditor; +}; + +#endif //GIE_BASENODE_H diff --git a/gui/src/nodeeditor/ComputeNode.h b/gui/src/nodeeditor/ComputeNode.h new file mode 100644 index 0000000..91c4d67 --- /dev/null +++ b/gui/src/nodeeditor/ComputeNode.h @@ -0,0 +1,67 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_COMPUTENODE_H +#define GIE_COMPUTENODE_H + +#include "BaseNode.h" +#include "TypeData.h" + +class ComputeNode: public BaseNode +{ +public: + + void setInData(std::shared_ptr data, QtNodes::PortIndex port) override + { + m_portAssign.resize(nPorts(QtNodes::PortType::In), PortAssigned::No); + + if(data == nullptr) + { + m_portAssign[port] = PortAssigned::No; + return; + } + + m_portAssign[port] = PortAssigned::Yes; + + if(std::find(m_portAssign.begin(), m_portAssign.end(), PortAssigned::No) == m_portAssign.end()) + { + ok(); + } + else + { + warning("Missing or incorrect inputs"); + } + + Q_EMIT dataUpdated(0); + } + +public: + Data getData() override { return {}; } + +public Q_SLOTS: + void displayData(Data data) override {} + + +protected: + void invalidateData() final + { + Q_EMIT dataInvalidated(0); + } + + void updateData() final + { + Q_EMIT dataUpdated(0); + } + +private: + enum class PortAssigned: char + { + Yes, + No + }; + + std::vector m_portAssign; +}; + +#endif //GIE_COMPUTENODE_H diff --git a/gui/src/nodeeditor/DisplayNode.h b/gui/src/nodeeditor/DisplayNode.h new file mode 100644 index 0000000..565ff6e --- /dev/null +++ b/gui/src/nodeeditor/DisplayNode.h @@ -0,0 +1,41 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_DISPLAYNODE_H +#define GIE_DISPLAYNODE_H + +#include "BaseNode.h" + +class DisplayNode: public BaseNode +{ + Q_OBJECT + +public: + std::shared_ptr outData(QtNodes::PortIndex portIndex) override { return {}; } + + unsigned int nPorts(QtNodes::PortType portType) const override + { + if(portType == QtNodes::PortType::In) + return 1; + return 0; + } + + void setInData(std::shared_ptr data, QtNodes::PortIndex portIndex) override + { + if(data) + ok(); + else + warning("Missing inputs"); + } + +public: + Data getData() override { return {}; } + +protected: + void invalidateData() final {} + + void updateData() final {} +}; + +#endif //GIE_DISPLAYNODE_H diff --git a/gui/src/nodeeditor/ExtractTypes.h b/gui/src/nodeeditor/ExtractTypes.h new file mode 100644 index 0000000..df6d996 --- /dev/null +++ b/gui/src/nodeeditor/ExtractTypes.h @@ -0,0 +1,47 @@ +// +// Created by alex on 5/28/19. +// + +#ifndef GUI_EXTRACTTYPES_H +#define GUI_EXTRACTTYPES_H + +#include +#include +#include + +#include "TypeData.h" + +#include + +inline QtNodes::NodeDataType getTypeData(const Type& type) +{ + static std::unordered_map typeMap = {{"str", "string"}, {"float", "double"}, {"int", "integer"}, {"Color", "Color"}, {"Image", "Image"}}; + + return {typeMap[type.name()], typeMap[type.name()]}; +} + +inline std::shared_ptr makeTypeData(const Type& type) +{ + using Callback = std::shared_ptr(*)(); + static std::unordered_map typeMap = { + {"str", [](){ + return std::shared_ptr(std::make_shared()); + }}, + {"float", [](){ + return std::shared_ptr(std::make_shared()); + }}, + {"int", [](){ + return std::shared_ptr(std::make_shared()); + }}, + {"Color", [](){ + return std::shared_ptr(std::make_shared()); + }}, + {"Image", [](){ + return std::shared_ptr(std::make_shared()); + }} + }; + + return typeMap[type.name()](); +} + +#endif //GUI_EXTRACTTYPES_H diff --git a/gui/src/nodeeditor/NodeEditor.h b/gui/src/nodeeditor/NodeEditor.h new file mode 100644 index 0000000..07e11c0 --- /dev/null +++ b/gui/src/nodeeditor/NodeEditor.h @@ -0,0 +1,185 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_NODEEDITOR_H +#define GIE_NODEEDITOR_H + +#include + +#include +#include +#include +#include +#include +#include + +#include "BaseNode.h" +#include "SourceNode.h" +#include "DisplayNode.h" +#include "ComputeNode.h" + +class NodeEditor: public QWidget +{ + Q_OBJECT +public: + explicit NodeEditor(QWidget* parent = nullptr): + QWidget{parent}, + m_scene{new QtNodes::FlowScene}, + m_view{new QtNodes::FlowView{m_scene}}, + m_registry{std::make_shared()} + { + auto vlayout = new QVBoxLayout(this); + + vlayout->setMargin(0); + vlayout->setSpacing(0); + + m_view->setSceneRect(-640000, -640000, 640000, 640000); + m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + vlayout->addWidget(m_view); + + m_scene->setRegistry(m_registry); + + connect( + m_scene, &QtNodes::FlowScene::connectionCreated, + this, &NodeEditor::onConnectionCreated + ); + + connect( + m_scene, &QtNodes::FlowScene::connectionDeleted, + this, &NodeEditor::onConnectionDeleted + ); + + connect( + m_scene, &QtNodes::FlowScene::nodeCreated, + this, &NodeEditor::onNodeCreated + ); + + connect( + m_scene, &QtNodes::FlowScene::nodeDeleted, + this, &NodeEditor::onNodeDeleted + ); + + connect(m_scene, &QtNodes::FlowScene::nodePlaced, this, &NodeEditor::sceneChanged); + connect(m_scene, &QtNodes::FlowScene::nodeDeleted, this, &NodeEditor::sceneChanged); + connect(m_scene, &QtNodes::FlowScene::connectionCreated, this, &NodeEditor::sceneChanged); + connect(m_scene, &QtNodes::FlowScene::connectionDeleted, this, &NodeEditor::sceneChanged); + connect(m_scene, &QtNodes::FlowScene::nodeMoved, this, &NodeEditor::sceneChanged); + } + + template + void registerNodeType(Creator&& creator, const QString& category) + { + using type = decltype(creator()); + m_registry->registerModel(std::forward(creator), category); + } + + template + void registerNodeType(const QString& category) + { + m_registry->registerModel(category); + } + + const auto& nodeTypes() const { return m_registry->registeredModelCreators(); } + + const std::shared_ptr& getRegistry() { return m_registry; } + + QtNodes::FlowScene* scene() { return m_scene; } + QtNodes::FlowView* view() { return m_view; } + + void errorNode(QUuid id, const QString& message) + { + if(auto* p = dynamic_cast(m_scene->nodes().at(id)->nodeDataModel()); p != nullptr) + { + p->error(message); + } + + } + + void warnNode(QUuid id, const QString& message) + { + if(auto* p = dynamic_cast(m_scene->nodes().at(id)->nodeDataModel()); p != nullptr) + { + p->warning(message); + } + } + +Q_SIGNALS: + void nodeCreated(BaseNode*); + void nodeDeleted(BaseNode*); + void argumentRemoved(BaseNode*, std::size_t port); + void argumentEdited(BaseNode*, QUuid argumentId, std::size_t port); + void sourceDataChanged(QUuid, Data); + + void sceneChanged(); + +public Q_SLOTS: + void changeDisplayData(QUuid id, Data data) + { + dynamic_cast(m_scene->nodes().at(id)->nodeDataModel())->displayData(std::move(data)); + } + +private Q_SLOTS: + void onNodeCreated(QtNodes::Node* node) + { + if(auto* p = dynamic_cast(node->nodeDataModel()); p != nullptr) + { + p->m_id = node->id(); + + connect(p, &BaseNode::dataChanged, [this, p](Data data) + { + Q_EMIT sourceDataChanged(p->id(), data); + }); + + Q_EMIT nodeCreated(p); + } + } + + void onNodeDeleted(QtNodes::Node* node) + { + if(auto* p = dynamic_cast(node->nodeDataModel()); p != nullptr) + { + Q_EMIT nodeDeleted(p); + } + } + + void onConnectionCreated(const QtNodes::Connection& c) + { + auto giver = dynamic_cast(c.getNode(QtNodes::PortType::Out)->nodeDataModel()); + auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); + + if(giver == nullptr || receiver == nullptr) + return; + + Q_EMIT argumentEdited( + receiver, + giver->id(), + static_cast(c.getPortIndex(QtNodes::PortType::In)) + ); + } + + void onConnectionDeleted(const QtNodes::Connection& c) + { + auto giver = dynamic_cast(c.getNode(QtNodes::PortType::Out)->nodeDataModel()); + auto receiver = dynamic_cast(c.getNode(QtNodes::PortType::In)->nodeDataModel()); + + if(giver == nullptr || receiver == nullptr) + return; + + Q_EMIT argumentRemoved( + receiver, + static_cast(c.getPortIndex(QtNodes::PortType::In)) + ); + } + +private: + QtNodes::FlowScene* m_scene; + QtNodes::FlowView* m_view; + + std::shared_ptr m_registry; +}; + + +#endif //GIE_NODEEDITOR_H diff --git a/gui/src/nodeeditor/SourceNode.h b/gui/src/nodeeditor/SourceNode.h new file mode 100644 index 0000000..523e2c0 --- /dev/null +++ b/gui/src/nodeeditor/SourceNode.h @@ -0,0 +1,42 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_SOURCENODE_H +#define GIE_SOURCENODE_H + +#include "BaseNode.h" + +class SourceNode: public BaseNode +{ + Q_OBJECT + +public: + void setInData(std::shared_ptr, int) override + { + + } + + unsigned int nPorts(QtNodes::PortType portType) const override + { + if(portType == QtNodes::PortType::In) + return 0; + else return 1; + } + +public Q_SLOTS: + virtual void displayData(Data) override {}; + +protected: + void invalidateData() final + { + Q_EMIT dataInvalidated(0); + } + + void updateData() final + { + Q_EMIT dataUpdated(0); + } +}; + +#endif //GIE_SOURCENODE_H diff --git a/gui/src/nodeeditor/TypeData.h b/gui/src/nodeeditor/TypeData.h new file mode 100644 index 0000000..0751fcd --- /dev/null +++ b/gui/src/nodeeditor/TypeData.h @@ -0,0 +1,77 @@ +// +// Created by alex on 5/19/19. +// + +#ifndef GUI_TYPEDATA_H +#define GUI_TYPEDATA_H + +#include + +#include +#include + +#include +#include + +class TypeData: public QtNodes::NodeData +{}; + +class StringTypeData: public TypeData +{ +public: + using TypeData::TypeData; + + QtNodes::NodeDataType type() const override + { + return {"string", "string"}; + } +}; + +class NumberTypeData: public TypeData +{ +public: + using TypeData::TypeData; + + QtNodes::NodeDataType type() const override + { + return {"double", "double"}; + } +}; + +class IntegerTypeData: public TypeData +{ +public: + using TypeData::TypeData; + + QtNodes::NodeDataType type() const override + { + return {"integer", "integer"}; + } +}; + +class ImageTypeData: public TypeData +{ +public: + using TypeData::TypeData; + + QtNodes::NodeDataType type() const override + { + return {"Image", "Image"}; + } +}; + +class ColorTypeData: public TypeData +{ +public: + using TypeData::TypeData; + + QtNodes::NodeDataType type() const override + { + return {"Color", "Color"}; + } +}; + +using Data = std::variant; + + +#endif //GUI_TYPEDATA_H diff --git a/gui/src/nodes/ColorDisplayNode.h b/gui/src/nodes/ColorDisplayNode.h new file mode 100644 index 0000000..b80ca78 --- /dev/null +++ b/gui/src/nodes/ColorDisplayNode.h @@ -0,0 +1,52 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_COLORDISPLAYNODE_H +#define GIE_COLORDISPLAYNODE_H + +#include "src/nodeeditor/DisplayNode.h" +#include "src/widgets/colorsample/colorsample.h" + +class ColorDisplayNode: public DisplayNode +{ +Q_OBJECT +public: + ColorDisplayNode(): + m_colorSample{new ColorSample()} + { + m_colorSample->setMaximumSize(70, 50); + } + + virtual ~ColorDisplayNode() override = default; + +public: + QString caption() const override { return QString("ColorDisplay"); } + bool captionVisible() const override { return false; } + QString name() const override { return QString("ColorDisplay"); } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ColorTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_colorSample; } + +public Q_SLOTS: + void displayData(Data data) override + { + Color color = std::get(data); + + m_colorSample->setColor(QColor( + color.r, + color.g, + color.b) + ); + }; + +private: + ColorSample* m_colorSample; +}; + +#endif //GIE_COLORDISPLAYNODE_H diff --git a/gui/src/nodes/ColorSourceNode.h b/gui/src/nodes/ColorSourceNode.h new file mode 100644 index 0000000..5505c3b --- /dev/null +++ b/gui/src/nodes/ColorSourceNode.h @@ -0,0 +1,104 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_COLORSOURCENODE_H +#define GIE_COLORSOURCENODE_H + +#include "src/nodeeditor/SourceNode.h" +#include "src/nodeeditor/TypeData.h" +#include "src/widgets/colorpicker/colorpicker.h" + +class ColorSourceNode: public SourceNode +{ +Q_OBJECT +public: + ColorSourceNode(): m_colorPicker(new ColorPicker()) + { + m_colorPicker->setMaximumSize(m_colorPicker->sizeHint()); + + connect( + m_colorPicker, &ColorPicker::onColorChanged, + this, &ColorSourceNode::onColorChanged + ); + } + + ~ColorSourceNode() override = default; + +public: + QString caption() const override { return QStringLiteral("ColorSource"); } + bool captionVisible() const override { return false; } + QString name() const override { return QStringLiteral("ColorSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + + modelJson["R"] = m_colorPicker->color().red(); + modelJson["G"] = m_colorPicker->color().green(); + modelJson["B"] = m_colorPicker->color().blue(); + + return modelJson; + } + + void restore(QJsonObject const &p) override + { + QJsonValue r = p["R"]; + QJsonValue g = p["G"]; + QJsonValue b = p["B"]; + + if (!r.isUndefined() && !g.isUndefined() && !b.isUndefined()) + { + m_colorPicker->setColor(QColor{ + r.toInt(), + g.toInt(), + b.toInt() + }); + + onColorChanged(QColor{ + r.toInt(), + g.toInt(), + b.toInt() + }); + } + } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ColorTypeData().type(); + } + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return m_colorPicker; } + + Data getData() override + { + return Data(Color{ + static_cast(m_colorPicker->color().red()), + static_cast(m_colorPicker->color().green()), + static_cast(m_colorPicker->color().blue()) + }); + } + +private Q_SLOTS: + void onColorChanged(const QColor& color) + { + Q_EMIT dataChanged(Color{ + static_cast(color.red()), + static_cast(color.green()), + static_cast(color.blue()) + }); + ok(); + } + +private: + ColorPicker* m_colorPicker; +}; + +#endif //GIE_COLORSOURCENODE_H \ No newline at end of file diff --git a/gui/src/nodes/GieNode.h b/gui/src/nodes/GieNode.h new file mode 100644 index 0000000..2cdca58 --- /dev/null +++ b/gui/src/nodes/GieNode.h @@ -0,0 +1,86 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_GIENODE_H +#define GIE_GIENODE_H + +#include +#include +#include + +class GieNode: public ComputeNode +{ +Q_OBJECT + +public: + + explicit GieNode(GieSymbol symbol): + m_arguments{std::move(symbol.arguments)}, + m_returnType{std::move(symbol.returnType)}, + m_symbol{std::move(symbol.symbol)} + { + } + + GieNode() = delete; + + ~GieNode() override + { + } + + unsigned int nPorts(QtNodes::PortType portType) const override + { + if(portType == QtNodes::PortType::In) + return static_cast(m_arguments.size()); + else return 1; + } + + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + if(portType == QtNodes::PortType::In) + return getTypeData(m_arguments[portIndex].m_argumentType); + return getTypeData(m_returnType); + } + + QString caption() const override + { + return QString::fromStdString(m_symbol.prettyName + ':' + m_symbol.module); + } + + QString name() const override + { + return QString::fromStdString(m_symbol.qualifiedName); + } + + bool portCaptionVisible(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return true; + } + + QString portCaption(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + if(portType == QtNodes::PortType::In) + return QString::fromStdString(m_arguments[portIndex].m_argumentName); + return QString("result"); + } + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return makeTypeData(m_returnType); + } + + QWidget* embeddedWidget() override { return nullptr; } + + const auto& symbol() const { return m_symbol; } + +private: + GieNodeId m_nodeId; + std::vector m_arguments; + Type m_returnType; + SymbolName m_symbol; + + friend class Editor; +}; + + +#endif //GIE_GIENODE_H diff --git a/gui/src/nodes/ImageDisplayNode.h b/gui/src/nodes/ImageDisplayNode.h new file mode 100644 index 0000000..0cf27c3 --- /dev/null +++ b/gui/src/nodes/ImageDisplayNode.h @@ -0,0 +1,60 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_IMAGEDISPLAYNODE_H +#define GIE_IMAGEDISPLAYNODE_H + +#include "src/nodeeditor/DisplayNode.h" +#include "src/widgets/imageviewer/imageviewer.h" + +class ImageDisplayNode: public DisplayNode +{ +Q_OBJECT +public: + ImageDisplayNode(): + m_imageViewer{new ImageViewer()} + { + m_imageViewer->setMaximumSize(70, 50); + } + + virtual ~ImageDisplayNode() override = default; + +public: + QString caption() const override { return QString("ImageDisplay"); } + bool captionVisible() const override { return false; } + QString name() const override { return QString("ImageDisplay"); } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ImageTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_imageViewer; } + +public Q_SLOTS: + void displayData(Data data) override + { + const auto& image = std::get(data); + + auto bufferSize = image.width() * image.height() * 3; + auto imageData = new uint8_t[bufferSize]; + std::memcpy(imageData, image.raw(), bufferSize); + + QImage qImage( + imageData, + image.width(), + image.height(), + QImage::Format_RGB888, + [](auto p){ delete static_cast(p); } + ); + + m_imageViewer->setImage(std::move(qImage)); + }; + +private: + ImageViewer* m_imageViewer; +}; + +#endif //GIE_IMAGEDISPLAYNODE_H diff --git a/gui/src/nodes/ImageSourceNode.h b/gui/src/nodes/ImageSourceNode.h new file mode 100644 index 0000000..72b8aad --- /dev/null +++ b/gui/src/nodes/ImageSourceNode.h @@ -0,0 +1,112 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_IMAGESOURCENODE_H +#define GIE_IMAGESOURCENODE_H + +#include "src/nodeeditor/SourceNode.h" +#include "src/widgets/filepicker/filepicker.h" + +class ImageSourceNode: public SourceNode +{ + Q_OBJECT +public: + ImageSourceNode(): + m_filePicker(new FilePicker("Images (*.png *.xpm *.jpg)")) + { + m_filePicker->setMaximumSize(m_filePicker->sizeHint()); + + connect( + m_filePicker, &FilePicker::fileChanged, + this, &ImageSourceNode::onFileChanged + ); + } + + ~ImageSourceNode() override = default; + +public: + QString caption() const override { return QStringLiteral("ImageSource"); } + bool captionVisible() const override { return false; } + QString name() const override { return QStringLiteral("ImageSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + + modelJson["file"] = m_filename; + + return modelJson; + } + + void restore(QJsonObject const &p) override + { + QJsonValue filename = p["file"]; + + if (!filename.isUndefined()) + { + m_filePicker->setFile(filename.toString()); + onFileChanged(filename.toString()); + } + } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ImageTypeData().type(); + } + + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return m_filePicker; } + + Data getData() override + { + QImage image = QImage(m_filename).convertToFormat(QImage::Format_RGB888); + + return Image{imageToRawData(image), static_cast(image.width()), + static_cast(image.height())}; + } + +private Q_SLOTS: + void onFileChanged(const QString& filename) + { + m_filename = filename; + QImage image = QImage(m_filename).convertToFormat(QImage::Format_RGB888); + + + Q_EMIT dataChanged(Data{Image{imageToRawData(image), static_cast(image.width()), + static_cast(image.height())}}); + Q_EMIT dataUpdated(0); + } + +private: + static std::vector imageToRawData(const QImage& image) + { + std::vector data(static_cast(image.width() * image.height() * 3)); + const auto rowSize = static_cast(image.width()) * 3; + std::size_t index = 0; + std::size_t indexInImage = 0; + + for(int i = 0; i < image.height(); i++) + { + std::memcpy(data.data() + index, image.bits() + indexInImage, rowSize); + + indexInImage += image.bytesPerLine(); + index += rowSize; + } + + return data; + } + +private: + FilePicker* m_filePicker; + QString m_filename; +}; + +#endif //GIE_IMAGESOURCENODE_H diff --git a/gui/src/nodes/IntegerDisplayNode.h b/gui/src/nodes/IntegerDisplayNode.h new file mode 100644 index 0000000..7bd4e4e --- /dev/null +++ b/gui/src/nodes/IntegerDisplayNode.h @@ -0,0 +1,47 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_INTEGERDISPLAYNODE_H +#define GIE_INTEGERDISPLAYNODE_H + +#include "src/nodeeditor/DisplayNode.h" +#include + +class IntegerDisplayNode: public DisplayNode +{ +Q_OBJECT +public: + IntegerDisplayNode(): + m_label{new QLabel()} + { + m_label->setMargin(3); + } + + virtual ~IntegerDisplayNode() override = default; + +public: + QString caption() const override { return QString("IntegerDisplay"); } + bool captionVisible() const override { return false; } + QString name() const override { return QString("IntegerDisplay"); } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return IntegerTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_label; } + +public Q_SLOTS: + void displayData(Data data) override + { + long long int number = std::get(data); + m_label->setText(QString::number(number)); + }; + +private: + QLabel* m_label; +}; + +#endif //GIE_INTEGERDISPLAYNODE_H diff --git a/gui/src/nodes/IntegerSourceNode.h b/gui/src/nodes/IntegerSourceNode.h new file mode 100644 index 0000000..6b687ad --- /dev/null +++ b/gui/src/nodes/IntegerSourceNode.h @@ -0,0 +1,103 @@ +// +// Created by alex on 7/18/19. +// + +#ifndef GIE_INTEGERSOURCENODE_H +#define GIE_INTEGERSOURCENODE_H + +#include +#include +#include "src/nodeeditor/SourceNode.h" + +class IntegerSourceNode: public SourceNode +{ + Q_OBJECT + +public: + IntegerSourceNode(): + m_lineEdit(new QLineEdit()) + { + m_lineEdit->setValidator(new QIntValidator{}); + m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); + + connect( + m_lineEdit, &QLineEdit::textChanged, + this, &IntegerSourceNode::onTextEdited + ); + + m_lineEdit->setText("0"); + } + + ~IntegerSourceNode() override = default; + +public: + QString caption() const override { return QStringLiteral("IntegerSource"); } + bool captionVisible() const override { return false; } + QString name() const override { return QStringLiteral("IntegerSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + + modelJson["number"] = m_lineEdit->text(); + + return modelJson; + } + + void restore(QJsonObject const &p) override + { + QJsonValue v = p["number"]; + + if (!v.isUndefined()) + { + QString str = v.toString(); + + bool ok{}; + str.toLongLong(&ok); + + if(ok) + { + m_lineEdit->setText(str); + } + } + } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return IntegerTypeData().type(); + } + + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return m_lineEdit; } + + Data getData() override + { + return m_lineEdit->text().toLongLong(); + } + +private Q_SLOTS: + void onTextEdited(QString const &string) + { + bool ok; + long long number = string.toLongLong(&ok); + + if(ok) + { + Q_EMIT dataChanged(number); + this->ok(); + } + else warning(""); + } + +private: + QLineEdit* m_lineEdit; +}; + +#endif //GIE_INTEGERSOURCENODE_H diff --git a/gui/src/nodes/ManagedImageSourceNode.h b/gui/src/nodes/ManagedImageSourceNode.h new file mode 100644 index 0000000..aacf2dd --- /dev/null +++ b/gui/src/nodes/ManagedImageSourceNode.h @@ -0,0 +1,88 @@ +// +// Created by alex on 7/18/19. +// + +#ifndef GIE_MANAGEDIMAGESOURCENODE_H +#define GIE_MANAGEDIMAGESOURCENODE_H + +#include +#include "src/nodeeditor/SourceNode.h" +#include "src/Project.h" + +class ManagedImageSourceNode: public SourceNode +{ + Q_OBJECT +public: + explicit ManagedImageSourceNode(const ProjectImage& projectImage): + m_filename{projectImage.name}, + m_uuid{projectImage.id.toString()} + { + QImage image = projectImage.image.convertToFormat(QImage::Format_RGB888); + auto gieImage = Image{imageToRawData(image), static_cast(image.width()), + static_cast(image.height())}; + + m_image = std::move(gieImage); + + ok(); + } + + ~ManagedImageSourceNode() override = default; + +public: + QString caption() const override { return m_filename; } + bool captionVisible() const override { return true; } + QString name() const override { return QStringLiteral("ManagedImageSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + modelJson["uuid"] = m_uuid; + return modelJson; + } + + void restore(QJsonObject const &p) override {} + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ImageTypeData().type(); + } + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return nullptr; } + + Data getData() override { return m_image; } + +private: + static std::vector imageToRawData(const QImage& image) + { + std::vector data(static_cast(image.width() * image.height() * 3)); + const auto rowSize = static_cast(image.width()) * 3; + std::size_t index = 0; + std::size_t indexInImage = 0; + + for(int i = 0; i < image.height(); i++) + { + std::memcpy(data.data() + index, image.bits() + indexInImage, rowSize); + + indexInImage += image.bytesPerLine(); + index += rowSize; + } + + return data; + } + +private: + QString m_filename; + QString m_uuid; + Image m_image; + + friend class Editor; +}; + +#endif //GIE_MANAGEDIMAGESOURCENODE_H diff --git a/gui/src/nodes/NumberDisplayNode.h b/gui/src/nodes/NumberDisplayNode.h new file mode 100644 index 0000000..b5cc931 --- /dev/null +++ b/gui/src/nodes/NumberDisplayNode.h @@ -0,0 +1,47 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_NUMBERDISPLAYNODE_H +#define GIE_NUMBERDISPLAYNODE_H + +#include "src/nodeeditor/DisplayNode.h" +#include + +class NumberDisplayNode: public DisplayNode +{ +Q_OBJECT +public: + NumberDisplayNode(): + m_label{new QLabel()} + { + m_label->setMargin(3); + } + + virtual ~NumberDisplayNode() override = default; + +public: + QString caption() const override { return QString("NumberDisplay"); } + bool captionVisible() const override { return false; } + QString name() const override { return QString("NumberDisplay"); } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return NumberTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_label; } + +public Q_SLOTS: + void displayData(Data data) override + { + double number = std::get(data); + m_label->setText(QString::number(number)); + }; + +private: + QLabel* m_label; +}; + +#endif //GIE_NUMBERDISPLAYNODE_H diff --git a/gui/src/nodes/NumberSourceNode.h b/gui/src/nodes/NumberSourceNode.h new file mode 100644 index 0000000..ee04595 --- /dev/null +++ b/gui/src/nodes/NumberSourceNode.h @@ -0,0 +1,95 @@ +// +// Created by alex on 7/18/19. +// + +#ifndef GIE_NUMBERSOURCENODE_H +#define GIE_NUMBERSOURCENODE_H + +#include "src/nodeeditor/SourceNode.h" + +class NumberSourceNode: public SourceNode +{ +Q_OBJECT + +public: + NumberSourceNode(): + m_lineEdit(new QLineEdit()) + { + m_lineEdit->setValidator(new QDoubleValidator()); + m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); + + connect( + m_lineEdit, &QLineEdit::textChanged, + this, &NumberSourceNode::onTextEdited + ); + + m_lineEdit->setText("0.0"); + } + + ~NumberSourceNode() override {} + +public: + QString caption() const override { return QStringLiteral("NumberSource"); } + bool captionVisible() const override { return false; } + QString name() const override { return QStringLiteral("NumberSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + modelJson["number"] = m_lineEdit->text(); + return modelJson; + } + + void restore(QJsonObject const &p) override + { + QJsonValue v = p["number"]; + + if (!v.isUndefined()) + { + QString str = v.toString(); + + bool ok{}; + str.toDouble(&ok); + if(ok) + m_lineEdit->setText(str); + } + } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return NumberTypeData().type(); + } + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return m_lineEdit; } + + Data getData() override + { + return m_lineEdit->text().toDouble(); + } + +private Q_SLOTS: + void onTextEdited(QString const &string) + { + bool ok; + double number = string.toDouble(&ok); + + if(ok) + { + Q_EMIT dataChanged(number); + this->ok(); + } + else warning("Missing or incorrect number of inputs"); + } + +private: + QLineEdit * m_lineEdit; +}; + +#endif //GIE_NUMBERSOURCENODE_H diff --git a/gui/src/nodes/StringDisplayNode.h b/gui/src/nodes/StringDisplayNode.h new file mode 100644 index 0000000..cdb3688 --- /dev/null +++ b/gui/src/nodes/StringDisplayNode.h @@ -0,0 +1,47 @@ +// +// Created by alex on 7/17/19. +// + +#ifndef GIE_STRINGDISPLAYNODE_H +#define GIE_STRINGDISPLAYNODE_H + +#include "src/nodeeditor/DisplayNode.h" +#include + +class StringDisplayNode: public DisplayNode +{ +Q_OBJECT +public: + StringDisplayNode(): + m_label{new QLabel()} + { + m_label->setMargin(3); + } + + virtual ~StringDisplayNode() override = default; + +public: + QString caption() const override { return QString("StringDisplay"); } + bool captionVisible() const override { return false; } + QString name() const override { return QString("StringDisplay"); } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return StringTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_label; } + +public Q_SLOTS: + void displayData(Data data) override + { + const std::string& text = std::get(data); + m_label->setText(QString::fromStdString(text)); + }; + +private: + QLabel* m_label; +}; + +#endif //GIE_STRINGDISPLAYNODE_H diff --git a/gui/src/nodes/StringSourceNode.h b/gui/src/nodes/StringSourceNode.h new file mode 100644 index 0000000..cf6d5d7 --- /dev/null +++ b/gui/src/nodes/StringSourceNode.h @@ -0,0 +1,85 @@ +// +// Created by alex on 7/18/19. +// + +#ifndef GIE_STRINGSOURCENODE_H +#define GIE_STRINGSOURCENODE_H + +#include +#include "src/nodeeditor/SourceNode.h" + +class StringSourceNode: public SourceNode +{ +Q_OBJECT + +public: + StringSourceNode(): + m_lineEdit(new QLineEdit()) + { + m_lineEdit->setMaximumSize(m_lineEdit->sizeHint()); + + connect( + m_lineEdit, &QLineEdit::textChanged, + this, &StringSourceNode::onTextEdited + ); + + m_lineEdit->setText("string"); + } + + ~StringSourceNode() override {} + +public: + QString caption() const override { return QStringLiteral("string source"); } + bool captionVisible() const override { return false; } + QString name() const override { return QStringLiteral("StringSource"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + modelJson["string"] = m_lineEdit->text(); + return modelJson; + } + + void restore(QJsonObject const &p) override + { + QJsonValue v = p["string"]; + + if (!v.isUndefined()) + { + QString str = v.toString(); + m_lineEdit->setText(str); + } + } + +public: + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return StringTypeData().type(); + } + + std::shared_ptr outData(QtNodes::PortIndex port) override + { + return std::make_shared(); + } + + QWidget* embeddedWidget() override { return m_lineEdit; } + + Data getData() override + { + return m_lineEdit->text().toStdString(); + } + +private Q_SLOTS: + void onTextEdited(QString const &string) + { + Q_EMIT dataChanged(string.toStdString()); + ok(); + } + +private: + QLineEdit * m_lineEdit; +}; + + +#endif //GIE_STRINGSOURCENODE_H diff --git a/gui/src/nodes/TargetExportImageNode.h b/gui/src/nodes/TargetExportImageNode.h new file mode 100644 index 0000000..8f63383 --- /dev/null +++ b/gui/src/nodes/TargetExportImageNode.h @@ -0,0 +1,115 @@ +// +// Created by alex on 7/18/19. +// + +#ifndef GIE_TARGETEXPORTIMAGENODE_H +#define GIE_TARGETEXPORTIMAGENODE_H + +#include +#include "src/widgets/imageviewer/imageviewer.h" +#include + +#include "src/nodeeditor/DisplayNode.h" + +class TargetExportImageNode: public DisplayNode +{ + Q_OBJECT +public: + TargetExportImageNode(): + m_targetNameEdit{new QLineEdit()}, + m_id{QUuid::createUuid()}, + m_dock{new QDockWidget("Image Preview")}, + m_imageViewer{new ImageViewer(m_dock)} + { + m_targetNameEdit->setText("insert target name"); + connect(m_targetNameEdit, &QLineEdit::textEdited, this, &TargetExportImageNode::onTargetNameChanged); + m_dock->setWidget(m_imageViewer); + m_dock->setMinimumSize(100, 100); + } + + virtual ~TargetExportImageNode() override = default; + +public: + QString caption() const override { return QString("TargetExport"); } + bool captionVisible() const override { return true; } + QString name() const override { return QString("TargetExportImage"); } + +public: + QJsonObject save() const override + { + QJsonObject modelJson = NodeDataModel::save(); + + if(m_targetName != "") + modelJson["target_name"] = m_targetName; + + return modelJson; + } + + void restore(const QJsonObject& p) override + { + QJsonValue v = p["target_name"]; + + if (!v.isUndefined()) + { + QString str = v.toString(); + + m_targetName = str; + m_targetNameEdit->setText(str); + m_dock->setWindowTitle(str); + } + } + + QtNodes::NodeDataType dataType(QtNodes::PortType portType, QtNodes::PortIndex portIndex) const override + { + return ImageTypeData().type(); + } + + QWidget* embeddedWidget() override { return m_targetNameEdit; } + + const QString& getTargetName() const { return m_targetName; } + QDockWidget* dockWidget() { return m_dock; } + + const QImage& getImage() const { return m_image; } + +public Q_SLOTS: + void displayData(Data data) override + { + const auto& image = std::get(data); + + auto bufferSize = image.width() * image.height() * 3; + auto imageData = new uint8_t[bufferSize]; + std::memcpy(imageData, image.raw(), bufferSize); + + m_image = QImage( + imageData, + image.width(), + image.height(), + QImage::Format_RGB888, + [](auto p){ delete static_cast(p); }); + + m_imageViewer->setImage(m_image); + } + +Q_SIGNALS: + void targetNameChanged(const QUuid& id, const QString&); + +private Q_SLOTS: + void onTargetNameChanged(const QString& name) + { + m_targetName = name; + m_dock->setWindowTitle(name); + Q_EMIT targetNameChanged(m_id, name); + } + +private: + QLineEdit* m_targetNameEdit; + QString m_targetName; + QUuid m_id; + + QImage m_image; + + QDockWidget* m_dock; + ImageViewer* m_imageViewer; +}; + +#endif //GIE_TARGETEXPORTIMAGENODE_H diff --git a/gui/src/serialisation/serialisation.cpp b/gui/src/serialisation/serialisation.cpp index 0d7e7c7..327daca 100644 --- a/gui/src/serialisation/serialisation.cpp +++ b/gui/src/serialisation/serialisation.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include QJsonObject serialise(const QtNodes::FlowScene& scene) { @@ -39,17 +39,24 @@ QJsonObject serialise(const QtNodes::FlowScene& scene) return json; } -void deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonObject& json) +std::map deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonObject& json) { scene.clearScene(); std::map managedNodes; + std::map deleted; auto nodes = json["nodes"].toArray(); for(QJsonValueRef node: nodes) { if(node.toObject()["model"].toObject()["name"].toString() != "ManagedImageSource") - scene.restoreNode(node.toObject()); + { + if(scene.registry().registeredModelCreators().count(node.toObject()["model"].toObject()["name"].toString())) + { + scene.restoreNode(node.toObject()); + } + else deleted.insert({QUuid{node.toObject()["id"].toString()}, node.toObject()["model"].toObject()["name"].toString().toStdString()}); + } else { QUuid id = node.toObject()["id"].toString(); @@ -59,7 +66,7 @@ void deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonO node.toObject()["position"].toObject()["y"].toDouble() }; - auto& node = scene.createNode(std::make_unique(projectImage)); + auto& node = scene.createNode(std::make_unique(projectImage)); managedNodes[id] = &node; scene.setNodePosition(node, position); } @@ -68,6 +75,10 @@ void deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonO auto connections = json["connections"].toArray(); for(QJsonValueRef connection: connections) { + if(deleted.count(connection.toObject()["out_id"].toString()) || + deleted.count(connection.toObject()["in_id"].toString()) ) + continue; + QUuid id = connection.toObject()["out_id"].toString(); if(managedNodes.find(id) == managedNodes.end()) scene.restoreConnection(connection.toObject()); @@ -77,11 +88,11 @@ void deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonO int inIndex = connection.toObject()["in_index"].toInt(); int outIndex = connection.toObject()["out_index"].toInt(); - scene.createConnection(*scene.nodes().find(inId)->second, inIndex, *managedNodes[id], outIndex); } } -} + return deleted; +} #endif //GUI_SERIALISE_H diff --git a/gui/src/serialisation/serialisation.h b/gui/src/serialisation/serialisation.h index 5686f10..c97104a 100644 --- a/gui/src/serialisation/serialisation.h +++ b/gui/src/serialisation/serialisation.h @@ -12,6 +12,6 @@ #include "src/Project.h" auto serialise(const QtNodes::FlowScene& scene) -> QJsonObject; -void deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonObject& json); +std::map deserialise(QtNodes::FlowScene& scene, const Project& project, const QJsonObject& json); #endif //GUI_SERIALISATION_H diff --git a/gui/src/symbolviewer/symbolviewer.cpp b/gui/src/symbolviewer/symbolviewer.cpp deleted file mode 100644 index 96fb545..0000000 --- a/gui/src/symbolviewer/symbolviewer.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// -// Created by alex on 5/23/19. -// - -#include "symbolviewer.h" - -#include - -SymbolViewer::SymbolViewer(QWidget* parent): - QWidget(parent) -{ - auto layout = new QGridLayout(this); - layout->setContentsMargins(QMargins()); - - m_treeWidget = new QTreeWidget{}; - m_treeWidget->setHeaderHidden(true); - - layout->addWidget(m_treeWidget, 0, 0); -} - -void SymbolViewer::onSymbolsUpdate(const std::map>& modules) -{ - m_treeWidget->reset(); - - QList list; - - for(const auto& [name, functions]: modules) - { - auto entry = new QTreeWidgetItem(QStringList() << name); - - for(const auto& function: functions) - entry->addChild(new QTreeWidgetItem(QStringList() << function)); - - list.append(entry); - } - - m_treeWidget->addTopLevelItems(list); -} \ No newline at end of file diff --git a/gui/src/colorpicker/colorpicker.cpp b/gui/src/widgets/colorpicker/colorpicker.cpp similarity index 100% rename from gui/src/colorpicker/colorpicker.cpp rename to gui/src/widgets/colorpicker/colorpicker.cpp diff --git a/gui/src/colorpicker/colorpicker.h b/gui/src/widgets/colorpicker/colorpicker.h similarity index 100% rename from gui/src/colorpicker/colorpicker.h rename to gui/src/widgets/colorpicker/colorpicker.h diff --git a/gui/src/colorsample/colorsample.cpp b/gui/src/widgets/colorsample/colorsample.cpp similarity index 100% rename from gui/src/colorsample/colorsample.cpp rename to gui/src/widgets/colorsample/colorsample.cpp diff --git a/gui/src/colorsample/colorsample.h b/gui/src/widgets/colorsample/colorsample.h similarity index 100% rename from gui/src/colorsample/colorsample.h rename to gui/src/widgets/colorsample/colorsample.h diff --git a/gui/src/directorypicker/directorypicker.cpp b/gui/src/widgets/directorypicker/directorypicker.cpp similarity index 100% rename from gui/src/directorypicker/directorypicker.cpp rename to gui/src/widgets/directorypicker/directorypicker.cpp diff --git a/gui/src/directorypicker/directorypicker.h b/gui/src/widgets/directorypicker/directorypicker.h similarity index 100% rename from gui/src/directorypicker/directorypicker.h rename to gui/src/widgets/directorypicker/directorypicker.h diff --git a/gui/src/exportimage/exportimage.cpp b/gui/src/widgets/exportimage/exportimage.cpp similarity index 100% rename from gui/src/exportimage/exportimage.cpp rename to gui/src/widgets/exportimage/exportimage.cpp diff --git a/gui/src/exportimage/exportimage.h b/gui/src/widgets/exportimage/exportimage.h similarity index 91% rename from gui/src/exportimage/exportimage.h rename to gui/src/widgets/exportimage/exportimage.h index 2cbc2fb..574736f 100644 --- a/gui/src/exportimage/exportimage.h +++ b/gui/src/widgets/exportimage/exportimage.h @@ -11,7 +11,7 @@ #include -#include "src/savefilepicker/savefilepicker.h" +#include "src/widgets/savefilepicker/savefilepicker.h" namespace Ui { diff --git a/gui/src/exportimage/exportimage.ui b/gui/src/widgets/exportimage/exportimage.ui similarity index 100% rename from gui/src/exportimage/exportimage.ui rename to gui/src/widgets/exportimage/exportimage.ui diff --git a/gui/src/filepicker/filepicker.cpp b/gui/src/widgets/filepicker/filepicker.cpp similarity index 100% rename from gui/src/filepicker/filepicker.cpp rename to gui/src/widgets/filepicker/filepicker.cpp diff --git a/gui/src/filepicker/filepicker.h b/gui/src/widgets/filepicker/filepicker.h similarity index 100% rename from gui/src/filepicker/filepicker.h rename to gui/src/widgets/filepicker/filepicker.h diff --git a/gui/src/widgets/imageviewer/imageviewer.cpp b/gui/src/widgets/imageviewer/imageviewer.cpp new file mode 100644 index 0000000..ded6499 --- /dev/null +++ b/gui/src/widgets/imageviewer/imageviewer.cpp @@ -0,0 +1,35 @@ +// +// Created by alex on 5/25/19. +// + +#include "imageviewer.h" + +#include + +ImageViewer::ImageViewer(QWidget *parent) : QWidget(parent) +{ +} + +void ImageViewer::setImage(QImage image) { m_image = std::move(image); Q_EMIT update(); } + + +void ImageViewer::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + int width = this->width(), height = this->height(); + + if(width == 0 || height == 0 || m_image.width() == 0 || m_image.height() == 0) + return; + + double screenRatio = static_cast(this->width()) / this->height(); + double imageRatio = static_cast(m_image.width()) / m_image.height(); + + QImage toPrint = imageRatio < screenRatio ? m_image.scaledToHeight(this->height()) : m_image.scaledToWidth(this->width()); + + const QSize size = toPrint.size(); + const int x = (this->rect().width() - size.width()) / 2; + const int y = (this->rect().height() - size.height()) / 2; + painter.drawImage(QRect{x, y, size.width(), size.height()}, toPrint); +} \ No newline at end of file diff --git a/gui/src/imageviewer/imageviewer.h b/gui/src/widgets/imageviewer/imageviewer.h similarity index 66% rename from gui/src/imageviewer/imageviewer.h rename to gui/src/widgets/imageviewer/imageviewer.h index 22bcc67..5c1d5e2 100644 --- a/gui/src/imageviewer/imageviewer.h +++ b/gui/src/widgets/imageviewer/imageviewer.h @@ -10,27 +10,16 @@ class ImageViewer : public QWidget { Q_OBJECT -public: - enum DisplayType - { - FixedWidth, - FixedHeight, - Fixed, - Full - }; - public: explicit ImageViewer(QWidget *parent = nullptr); void setImage(QImage image); - void setDisplayType(DisplayType type); protected: void paintEvent(QPaintEvent*) override; private: QImage m_image; - DisplayType m_displayType; }; #endif //GUI_IMAGEVIEWER_H diff --git a/gui/src/importedimagesviewer/imagecell.cpp b/gui/src/widgets/importedimagesviewer/imagecell.cpp similarity index 70% rename from gui/src/importedimagesviewer/imagecell.cpp rename to gui/src/widgets/importedimagesviewer/imagecell.cpp index 0a248a1..7ca264d 100644 --- a/gui/src/importedimagesviewer/imagecell.cpp +++ b/gui/src/widgets/importedimagesviewer/imagecell.cpp @@ -17,7 +17,6 @@ ImageCell::ImageCell(QString name, QImage image, QWidget *parent): layout->addWidget(m_label = new QLabel(m_name), 1); layout->addWidget(m_imageViewer = new ImageViewer, 5); - m_imageViewer->setDisplayType(m_image.width() > m_image.height() ? ImageViewer::FixedWidth : ImageViewer::FixedHeight); m_imageViewer->setImage(m_image); layout->setMargin(10); @@ -25,5 +24,4 @@ ImageCell::ImageCell(QString name, QImage image, QWidget *parent): void ImageCell::resizeEvent(QResizeEvent*) { - m_imageViewer->setDisplayType(width() > height() ? ImageViewer::FixedWidth : ImageViewer::FixedHeight); } \ No newline at end of file diff --git a/gui/src/importedimagesviewer/imagecell.h b/gui/src/widgets/importedimagesviewer/imagecell.h similarity index 90% rename from gui/src/importedimagesviewer/imagecell.h rename to gui/src/widgets/importedimagesviewer/imagecell.h index 3d0ec5e..948fd92 100644 --- a/gui/src/importedimagesviewer/imagecell.h +++ b/gui/src/widgets/importedimagesviewer/imagecell.h @@ -8,7 +8,7 @@ #include #include -#include "src/imageviewer/imageviewer.h" +#include "src/widgets/imageviewer/imageviewer.h" class ImageCell: public QWidget { diff --git a/gui/src/importedimagesviewer/importedimagesviewer.cpp b/gui/src/widgets/importedimagesviewer/importedimagesviewer.cpp similarity index 100% rename from gui/src/importedimagesviewer/importedimagesviewer.cpp rename to gui/src/widgets/importedimagesviewer/importedimagesviewer.cpp diff --git a/gui/src/importedimagesviewer/importedimagesviewer.h b/gui/src/widgets/importedimagesviewer/importedimagesviewer.h similarity index 100% rename from gui/src/importedimagesviewer/importedimagesviewer.h rename to gui/src/widgets/importedimagesviewer/importedimagesviewer.h diff --git a/gui/src/importimage/importimage.cpp b/gui/src/widgets/importimage/importimage.cpp similarity index 90% rename from gui/src/importimage/importimage.cpp rename to gui/src/widgets/importimage/importimage.cpp index 0dbfac1..5905226 100644 --- a/gui/src/importimage/importimage.cpp +++ b/gui/src/widgets/importimage/importimage.cpp @@ -41,7 +41,6 @@ void ImportImage::onNameChanged(const QString& name) m_filename = name; QImage image(m_filename); - m_preview->setDisplayType(image.width() > image.height() ? ImageViewer::FixedWidth : ImageViewer::FixedHeight); m_preview->setImage(std::move(image)); } diff --git a/gui/src/importimage/importimage.h b/gui/src/widgets/importimage/importimage.h similarity index 86% rename from gui/src/importimage/importimage.h rename to gui/src/widgets/importimage/importimage.h index 2215ca8..551746d 100644 --- a/gui/src/importimage/importimage.h +++ b/gui/src/widgets/importimage/importimage.h @@ -10,8 +10,8 @@ #include #include -#include "src/filepicker/filepicker.h" -#include "src/imageviewer/imageviewer.h" +#include "src/widgets/filepicker/filepicker.h" +#include "src/widgets/imageviewer/imageviewer.h" namespace Ui { diff --git a/gui/src/importimage/importimage.ui b/gui/src/widgets/importimage/importimage.ui similarity index 100% rename from gui/src/importimage/importimage.ui rename to gui/src/widgets/importimage/importimage.ui diff --git a/gui/src/widgets/logconsole/logconsole.cpp b/gui/src/widgets/logconsole/logconsole.cpp new file mode 100644 index 0000000..952a16d --- /dev/null +++ b/gui/src/widgets/logconsole/logconsole.cpp @@ -0,0 +1,28 @@ +// +// Created by alex on 7/20/19. +// + +#include "logconsole.h" + +#include + +LogConsole::LogConsole(QWidget *parent): + QWidget{parent} +{ + auto vlayout = new QVBoxLayout{this}; + vlayout->addWidget(m_logArea = new QTextEdit{}); + + m_logArea->setReadOnly(true); +} + +void LogConsole::logError(const QString& message) +{ + m_logArea->setTextColor(QColor(255, 0, 0)); + m_logArea->append(message); +} + +void LogConsole::logWarning(const QString& message) +{ + m_logArea->setTextColor(QColor(255, 255, 0)); + m_logArea->append(message); +} diff --git a/gui/src/widgets/logconsole/logconsole.h b/gui/src/widgets/logconsole/logconsole.h new file mode 100644 index 0000000..d1964d1 --- /dev/null +++ b/gui/src/widgets/logconsole/logconsole.h @@ -0,0 +1,26 @@ +// +// Created by alex on 7/20/19. +// + +#ifndef GIE_LOGCONSOLE_H +#define GIE_LOGCONSOLE_H + +#include +#include + +class LogConsole: public QWidget +{ + Q_OBJECT +public: + explicit LogConsole(QWidget* parent = nullptr); + +public Q_SLOTS: + void logError(const QString&); + void logWarning(const QString&); + +private: + QTextEdit* m_logArea; +}; + + +#endif //GIE_LOGCONSOLE_H diff --git a/gui/src/newproject/newproject.cpp b/gui/src/widgets/newproject/newproject.cpp similarity index 100% rename from gui/src/newproject/newproject.cpp rename to gui/src/widgets/newproject/newproject.cpp diff --git a/gui/src/newproject/newproject.h b/gui/src/widgets/newproject/newproject.h similarity index 92% rename from gui/src/newproject/newproject.h rename to gui/src/widgets/newproject/newproject.h index 81a9b76..965ad11 100644 --- a/gui/src/newproject/newproject.h +++ b/gui/src/widgets/newproject/newproject.h @@ -10,7 +10,7 @@ #include #include -#include "src/directorypicker/directorypicker.h" +#include "src/widgets/directorypicker/directorypicker.h" namespace Ui { diff --git a/gui/src/newproject/newproject.ui b/gui/src/widgets/newproject/newproject.ui similarity index 100% rename from gui/src/newproject/newproject.ui rename to gui/src/widgets/newproject/newproject.ui diff --git a/gui/src/widgets/projectscripts/projectscripts.cpp b/gui/src/widgets/projectscripts/projectscripts.cpp new file mode 100644 index 0000000..e3398b7 --- /dev/null +++ b/gui/src/widgets/projectscripts/projectscripts.cpp @@ -0,0 +1,71 @@ +// +// Created by alex on 7/19/19. +// + +#include "projectscripts.h" + +#include +#include + +#include +#include + +ProjectScripts::ProjectScripts(QWidget* parent): + QWidget{parent} +{ + auto vlayout = new QVBoxLayout{this}; + vlayout->addWidget(m_openDirButton = new QPushButton{"Open scripts folder"}); + vlayout->addWidget(m_loadedFiles = new QListWidget{}); + + m_openDirButton->setDisabled(true); + connect(m_openDirButton, &QPushButton::clicked, this, &ProjectScripts::openInBrowser); +} + +void ProjectScripts::addFile(const QString& name) +{ + if(m_files.count(name) == 0) + { + m_loadedFiles->addItem(name); + m_files.insert(name); + } +} + +void ProjectScripts::removeFile(const QString& name) +{ + if(m_files.count(name)) + { + for(int i = 0; i < m_loadedFiles->count(); i++) + { + if(m_loadedFiles->item(i)->text() == name) + { + delete m_loadedFiles->takeItem(i); + break; + } + } + + m_files.erase(name); + } +} + +void ProjectScripts::removeAll() +{ + m_loadedFiles->clear(); +} + +void ProjectScripts::setScriptsFolder(QString path) +{ + m_scriptsDirectory = std::move(path); + m_openDirButton->setDisabled(false); +} + +void ProjectScripts::removeScriptsFolder() +{ + m_scriptsDirectory = std::nullopt; + m_openDirButton->setDisabled(true); +} + +void ProjectScripts::openInBrowser() +{ + if(m_scriptsDirectory) + QDesktopServices::openUrl(QUrl(QString("file:///") + *m_scriptsDirectory)); +} \ No newline at end of file diff --git a/gui/src/widgets/projectscripts/projectscripts.h b/gui/src/widgets/projectscripts/projectscripts.h new file mode 100644 index 0000000..2f24cc2 --- /dev/null +++ b/gui/src/widgets/projectscripts/projectscripts.h @@ -0,0 +1,40 @@ +// +// Created by alex on 7/19/19. +// + +#ifndef GIE_PROJECTSCRIPTS_H +#define GIE_PROJECTSCRIPTS_H + +#include +#include + +#include +#include +#include + +class ProjectScripts: public QWidget +{ + Q_OBJECT +public: + explicit ProjectScripts(QWidget* parent = nullptr); + +public Q_SLOTS: + void addFile(const QString& name); + void removeFile(const QString& name); + void removeAll(); + + void setScriptsFolder(QString path); + void removeScriptsFolder(); + +private: + void openInBrowser(); + +private: + QListWidget* m_loadedFiles; + QPushButton* m_openDirButton; + std::optional m_scriptsDirectory; + std::set m_files; +}; + + +#endif //GIE_PROJECTSCRIPTS_H diff --git a/gui/src/savefilepicker/savefilepicker.cpp b/gui/src/widgets/savefilepicker/savefilepicker.cpp similarity index 100% rename from gui/src/savefilepicker/savefilepicker.cpp rename to gui/src/widgets/savefilepicker/savefilepicker.cpp diff --git a/gui/src/savefilepicker/savefilepicker.h b/gui/src/widgets/savefilepicker/savefilepicker.h similarity index 100% rename from gui/src/savefilepicker/savefilepicker.h rename to gui/src/widgets/savefilepicker/savefilepicker.h diff --git a/gui/src/widgets/symbolviewer/symbolviewer.cpp b/gui/src/widgets/symbolviewer/symbolviewer.cpp new file mode 100644 index 0000000..6332624 --- /dev/null +++ b/gui/src/widgets/symbolviewer/symbolviewer.cpp @@ -0,0 +1,69 @@ +// +// Created by alex on 5/23/19. +// + +#include "symbolviewer.h" + +#include + +SymbolViewer::SymbolViewer(QWidget* parent): + QWidget(parent) +{ + auto layout = new QGridLayout(this); + layout->setContentsMargins(QMargins()); + + m_treeWidget = new QTreeWidget{}; + m_treeWidget->setHeaderHidden(true); + + layout->addWidget(m_treeWidget, 0, 0); +} + +void SymbolViewer::onSymbolsUpdate(const std::map>& modules) +{ + QList list; + + for(const auto& [name, functions]: modules) + { + if(!m_modules.count(name)) + { + auto entry = new QTreeWidgetItem(QStringList() << name); + m_modules.insert({name, entry}); + + list.append(entry); + } + + auto entry = m_modules[name]; + for(const auto& function: functions) + { + if(!m_symbols[name].count(function)) + { + entry->addChild(new QTreeWidgetItem(QStringList() << function)); + m_symbols[name].insert(function); + } + } + + } + + m_treeWidget->addTopLevelItems(list); +} + +void SymbolViewer::removeSymbol(const QString& category, const QString& name) +{ + if(auto it = m_modules.find(category); it != m_modules.end()) + { + auto entry = it->second; + + for(int i = 0; i < entry->childCount(); i++) + { + if(name == entry->child(i)->text(0)) + entry->removeChild(entry->child(i)); + } + m_symbols[category].erase(name); + + if(m_symbols[category].empty()) + { + delete m_treeWidget->takeTopLevelItem(m_treeWidget->indexOfTopLevelItem(entry)); + m_modules.erase(category); + } + } +} \ No newline at end of file diff --git a/gui/src/symbolviewer/symbolviewer.h b/gui/src/widgets/symbolviewer/symbolviewer.h similarity index 66% rename from gui/src/symbolviewer/symbolviewer.h rename to gui/src/widgets/symbolviewer/symbolviewer.h index 300a855..6fdef49 100644 --- a/gui/src/symbolviewer/symbolviewer.h +++ b/gui/src/widgets/symbolviewer/symbolviewer.h @@ -8,6 +8,9 @@ #include #include +#include +#include + class SymbolViewer: public QWidget { Q_OBJECT @@ -16,9 +19,12 @@ class SymbolViewer: public QWidget public Q_SLOTS: void onSymbolsUpdate(const std::map>&); + void removeSymbol(const QString& category, const QString& name); private: QTreeWidget* m_treeWidget; + std::map m_modules; + std::map> m_symbols; }; diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 2efc428..8088a5e 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -5,7 +5,7 @@ project(modules CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -find_package(Boost COMPONENTS python3 REQUIRED) +find_package(Boost COMPONENTS python3 numpy3 REQUIRED) find_package(PythonLibs REQUIRED) python_add_module(internals src/internals.cpp) diff --git a/modules/include/types/Image.h b/modules/include/types/Image.h index dbcb1ba..2de2cf6 100644 --- a/modules/include/types/Image.h +++ b/modules/include/types/Image.h @@ -16,43 +16,86 @@ class Image { static const int N_CHANNELS = 3; public: + Image(): + m_width{0}, + m_height{0}, + data{nullptr} + {} + Image(unsigned width, unsigned height): - width{width}, - height{height}, + m_width{width}, + m_height{height}, data{new uint8_t[N_CHANNELS * width * height]} - {} + { + std::memset(data, 0, N_CHANNELS * width * height); + } Image(const uint8_t* data, unsigned width, unsigned height): - width{width}, - height{height}, + m_width{width}, + m_height{height}, data{new uint8_t[N_CHANNELS * width * height]} { std::memcpy(this->data, data, N_CHANNELS * width * height); } Image(const std::vector& vecdata, unsigned width, unsigned height): - width{width}, - height{height}, - data{new uint8_t[N_CHANNELS * width * height]} + m_width{width}, + m_height{height}, + data{new uint8_t[N_CHANNELS * width * height]} { std::memcpy(data, vecdata.data(), N_CHANNELS * width * height); } Image(const Image& other): - width{other.width}, - height{other.height}, - data{new uint8_t[N_CHANNELS * width * height]} + m_width{other.m_width}, + m_height{other.m_height}, + data{new uint8_t[N_CHANNELS * m_width * m_height]} { - std::memcpy(data, other.data, N_CHANNELS * width * height); + std::memcpy(data, other.data, N_CHANNELS * m_width * m_height); } - Image(Image&&) = default; + Image(Image&& other): + m_width{0}, + m_height{0}, + data{nullptr} + { + std::swap(other.m_width, m_width); + std::swap(other.m_height, m_height); + std::swap(other.data, data); + } ~Image() { delete[] data; } + Image& operator=(const Image& other) + { + delete[] data; + data = new uint8_t[N_CHANNELS * m_width * m_height]; + m_width = other.m_width; + m_height = other.m_height; + + std::memcpy(data, other.data, N_CHANNELS * m_width * m_height); + + return *this; + } + + Image& operator=(Image&& other) + { + delete[] data; + + data = nullptr; + m_width = 0; + m_height = 0; + + std::swap(other.m_width, m_width); + std::swap(other.m_height, m_height); + std::swap(other.data, data); + + return *this; + } + Color pixelAt(unsigned row, unsigned column) const { const uint8_t* pixel = &data[index(row, column)]; @@ -65,20 +108,20 @@ class Image std::tie(pixel[0], pixel[1], pixel[2]) = std::make_tuple(color.r, color.g, color.b); } - const unsigned width, height; - - auto width_() { return width; } - auto height_() { return height; } + auto width() const { return m_width; } + auto height() const { return m_height; } const uint8_t* raw() const { return data; } private: std::size_t index(unsigned row, unsigned column) const { - return (row * width + column) * N_CHANNELS; + return (row * m_width + column) * N_CHANNELS; } private: + unsigned m_width, m_height; + uint8_t* data; }; diff --git a/modules/modules/blur.py b/modules/modules/blur.py index 334b615..40fdc9a 100644 --- a/modules/modules/blur.py +++ b/modules/modules/blur.py @@ -1,5 +1,16 @@ -import modules.internals import modules.images_internal -def box_blur(source: modules.internals.Image, row_factor: float, column_factor: float)->modules.internals.Image: - return modules.images_internal.box_blur(source, row_factor, column_factor) \ No newline at end of file +import numpy as np +import scipy.ndimage as ndimage + +def box_blur(source: Image, row_factor: float, column_factor: float)->Image: + return modules.images_internal.box_blur(source, row_factor, column_factor) + +def gaussian_blur(source: Image, sigma: int) -> Image: + a = to_ndarray(source) + + channels = [] + for d in range(3): + channels.append(ndimage.gaussian_filter(a[:, :, d], sigma=sigma)) + + return to_image(np.stack(channels, axis=2)) \ No newline at end of file diff --git a/modules/modules/color_correction.py b/modules/modules/color_correction.py index b358e0e..e2195cc 100644 --- a/modules/modules/color_correction.py +++ b/modules/modules/color_correction.py @@ -1,14 +1,19 @@ import modules.internals -import modules.images_internal +import numpy as np -def lift_gain(source: modules.internals.Image, lift: float, gain: float)->modules.internals.Image: +def lift_gain(source: Image, lift: float, gain: float)->Image: return modules.images_internal.lift_gain(source, lift, gain) -def gamma(source: modules.internals.Image, gamma: float)->modules.internals.Image: +def gamma(source: Image, gamma: float)->Image: return modules.images_internal.gamma(source, gamma) -def contrast(source: modules.internals.Image, contrast: float)->modules.internals.Image: +def contrast(source: Image, contrast: float)->Image: return modules.images_internal.contrast(source, contrast) -def brightness(source: modules.internals.Image, brightness: float)->modules.internals.Image: - return modules.images_internal.brightness(source, brightness) \ No newline at end of file +def brightness(source: Image, brightness: float)->Image: + return modules.images_internal.brightness(source, brightness) + +def remap(source: Image, a: float, b: float) -> Image: + arr = to_ndarray(source) + b = 0.001 if b == 0 else b + return to_image(a + arr * (b - a) / b) diff --git a/modules/modules/colors.py b/modules/modules/colors.py index de5d550..4221501 100644 --- a/modules/modules/colors.py +++ b/modules/modules/colors.py @@ -1,20 +1,19 @@ import modules.internals -import modules.images_internal -def to_color(r: int, g: int, b: int) -> modules.internals.Color: - return modules.internals.Color(r, g, b) +def to_color(r: int, g: int, b: int) -> Color: + return Color(r, g, b) -def invert(color: modules.internals.Color) -> modules.internals.Color: - return modules.internals.Color(255 - color.r, 255 - color.g, 255 - color.b) +def invert(color: Color) -> Color: + return Color(255 - color.r, 255 - color.g, 255 - color.b) -def red_channel(color: modules.internals.Color) -> int: +def red_channel(color: Color) -> int: return color.r -def green_channel(color: modules.internals.Color) -> int: +def green_channel(color: Color) -> int: return color.g -def blue_channel(color: modules.internals.Color) -> int: +def blue_channel(color: Color) -> int: return color.b -def solid_color(source: modules.internals.Image, color: modules.internals.Color) -> modules.internals.Image: +def solid_color(source: Image, color: Color) -> Image: return modules.images_internal.solid_color(source, color) \ No newline at end of file diff --git a/modules/modules/effects.py b/modules/modules/effects.py index b3ac51b..a9e6205 100644 --- a/modules/modules/effects.py +++ b/modules/modules/effects.py @@ -1,11 +1,60 @@ import modules.internals -import modules.images_internal -def pixel_sort(source: modules.internals.Image, mask: modules.internals.Image)->modules.internals.Image: +import numpy as np +import scipy.signal as signal +import scipy.ndimage as ndimage + +def pixel_sort(source: Image, mask: Image)->Image: return modules.images_internal.pixel_sort(source, mask) -def pixel_distort_displace(source: modules.internals.Image, map: modules.internals.Image, row_factor: float, column_factor: float)->modules.internals.Image: +def pixel_distort_displace(source: Image, map: Image, row_factor: float, column_factor: float)->Image: return modules.images_internal.pixel_distort_displace(source, map, row_factor, column_factor) -def displacement(source: modules.internals.Image, map: modules.internals.Image, row_factor: float, column_factor: float)->modules.internals.Image: +def displacement(source: Image, map: Image, row_factor: float, column_factor: float)->Image: return modules.images_internal.displacement(source, map, row_factor, column_factor) + +def lens_distortion(source: Image, strength: float, zoom: float) -> Image: + return modules.images_internal.lens_distortion(source, strength, zoom) + +def sobel(source: Image) -> Image: + a = to_ndarray(source) + + channels = [] + for d in range(3): + channels.append(ndimage.sobel(a[:, :, d])) + + return to_image(np.stack(channels, axis=2)) + +def sobel_2(source: Image) -> Image: + a = to_ndarray(source) + + sobel_x = np.c_[ + [-1, 0, 1], + [-2, 0, 2], + [-1, 0, 1] + ] + + sobel_y = np.c_[ + [1, 2, 1], + [0, 0, 0], + [-1, -2, -1] + ] + + channels = [] + for d in range(3): + sx = signal.convolve2d(a[:, :, d], sobel_x, mode='same', + boundary='symm') + sy = signal.convolve2d(a[:, :, d], sobel_y, mode='same', + boundary='symm') + channels.append(np.sqrt(sx ** 2 + sy ** 2)) + + return to_image(np.stack(channels, axis=2)) + +def median_filter(source: Image, kernel_size: int) -> Image: + a = to_ndarray(source) + channels = [] + for d in range(3): + channels.append(ndimage.median_filter(a[:, :, d], + size=(kernel_size,kernel_size))) + + return to_image(np.stack(channels, axis=2)) \ No newline at end of file diff --git a/modules/modules/glitch.py b/modules/modules/glitch.py new file mode 100644 index 0000000..b05bee2 --- /dev/null +++ b/modules/modules/glitch.py @@ -0,0 +1,4 @@ +import modules.internals + +def jpeg_artifacts(source: Image, radius: int)->Image: + return modules.images_internal.jpeg_artifacts(source, radius) \ No newline at end of file diff --git a/modules/modules/images.py b/modules/modules/images.py index 17b5ae4..0c0f6bd 100644 --- a/modules/modules/images.py +++ b/modules/modules/images.py @@ -1,7 +1,7 @@ import modules.internals -import modules.images_internal +import numpy as np -def average_color(image: modules.internals.Image)->modules.internals.Color: +def average_color(image: Image)->Color: r = 0 g = 0 b = 0 @@ -18,43 +18,65 @@ def average_color(image: modules.internals.Image)->modules.internals.Color: g = g // pixel_count b = b // pixel_count - return modules.internals.Color(r, g, b) + return Color(r, g, b) -def sample(image: modules.internals.Image, row: int, column: int)->modules.internals.Color: +def sample(image: Image, row: int, column: int)->Color: return image.pixel_at(row, column) -def separate_blue_channel(image: modules.internals.Image)->modules.internals.Image: +def separate_blue_channel(image: Image)->Image: return modules.images_internal.separate_blue_channel(image) -def separate_red_channel(image: modules.internals.Image)->modules.internals.Image: +def separate_red_channel(image: Image)->Image: return modules.images_internal.separate_red_channel(image) -def separate_green_channel(image: modules.internals.Image)->modules.internals.Image: +def separate_green_channel(image: Image)->Image: return modules.images_internal.separate_green_channel(image) -def luminance_map(image: modules.internals.Image)->modules.internals.Image: +def luminance_map(image: Image)->Image: return modules.images_internal.luminance_map(image) -def mask(source: modules.internals.Image, mask: modules.internals.Image)->modules.internals.Image: +def mask(source: Image, mask: Image)->Image: return modules.images_internal.mask(source, mask) -def add(a: modules.internals.Image, b: modules.internals.Image)->modules.internals.Image: +def add(a: Image, b: Image)->Image: return modules.images_internal.add(a, b) -def strict_add(a: modules.internals.Image, b: modules.internals.Image)->modules.internals.Image: +def strict_add(a: Image, b: Image)->Image: return modules.images_internal.strict_add(a, b) -def multiply(a: modules.internals.Image, b: modules.internals.Image)->modules.internals.Image: +def multiply(a: Image, b: Image)->Image: return modules.images_internal.multiply(a, b) -def discriminate_greater_than(source: modules.internals.Image, threshold: int)->modules.internals.Image: +def discriminate_greater_than(source: Image, threshold: int)->Image: return modules.images_internal.discriminator_greater_than(source, threshold) -def discriminate_lesser_than(source: modules.internals.Image, threshold: int)->modules.internals.Image: +def discriminate_lesser_than(source: Image, threshold: int)->Image: return modules.images_internal.discriminator_lesser_than(source, threshold) -def discriminate_range(source: modules.internals.Image, a: int, b:int)->modules.internals.Image: +def discriminate_range(source: Image, a: int, b:int)->Image: return modules.images_internal.discriminator_range(source, a, b) -def proxy(source: modules.internals.Image)->modules.internals.Image: - return source \ No newline at end of file +def proxy(source: Image)->Image: + return source + +def darken(a: Image, b: Image) -> Image: + a = to_ndarray(a) + b = to_ndarray(b) + + lightmap_a = (0.21 * a[:, :, 0] + 0.72 * a[:, :, 1] + 0.07 * a[:, :, 2]) + lightmap_b = (0.21 * b[:, :, 0] + 0.72 * b[:, :, 1] + 0.07 * b[:, :, 2]) + c = lightmap_a < lightmap_b + c = np.stack([c, c, c], axis=2) + + return to_image(a * c + b * (~c)) + +def lighten(a: Image, b: Image) -> Image: + a = to_ndarray(a) + b = to_ndarray(b) + + lightmap_a = (0.21 * a[:, :, 0] + 0.72 * a[:, :, 1] + 0.07 * a[:, :, 2]) + lightmap_b = (0.21 * b[:, :, 0] + 0.72 * b[:, :, 1] + 0.07 * b[:, :, 2]) + c = lightmap_a > lightmap_b + c = np.stack([c, c, c], axis=2) + + return to_image(a * c + b * (~c)) \ No newline at end of file diff --git a/modules/modules/noise.py b/modules/modules/noise.py index 8489c91..dac9f1a 100644 --- a/modules/modules/noise.py +++ b/modules/modules/noise.py @@ -1,5 +1,7 @@ import modules.internals -import modules.images_internal -def perlin_noise(source: modules.internals.Image, seed: int, z: float, octaves: int, persistence: float)->modules.internals.Image: - return modules.images_internal.perlin_noise(source, seed, z, octaves, persistence) \ No newline at end of file +def perlin_noise(source: Image, seed: int, zoom: float, z: float, octaves: int, persistence: float)->Image: + return modules.images_internal.perlin_noise(source, seed, zoom, z, octaves, persistence) + +def noise(source: Image, seed: int) -> Image: + return modules.images_internal.noise(source, seed) \ No newline at end of file diff --git a/modules/src/images_internal.cpp b/modules/src/images_internal.cpp index 5c32983..9fa693e 100644 --- a/modules/src/images_internal.cpp +++ b/modules/src/images_internal.cpp @@ -21,13 +21,46 @@ T clamp(T x, T a, T b) return x; } +struct Coord +{ + int x, y; +}; + +Color bilinear_interpolation(std::array corners, std::array coords, double x, double y) +{ + double r = 0.0, g = 0.0, b = 0.0; + double sum = 0.0; + + for(int i = 0; i < 4; i++) + { + double d = sqrt( + (coords[i].x - x) * (coords[i].x - x) + + (coords[i].y - y) * (coords[i].y - y)); + + r += corners[i].r * d; + g += corners[i].g * d; + b += corners[i].b * d; + + sum += d; + } + + r /= sum; + g /= sum; + b /= sum; + + return Color( + static_cast(r), + static_cast(g), + static_cast(b)); +} + Image separate_blue_channel(const Image& image) { - Image new_image(image.width, image.height); + Image new_image(image.width(), image.height()); - for(int row = 0; row < static_cast(image.height); row++) - for(int column = 0; column < static_cast(image.width); column++) + for(int row = 0; row < static_cast(image.height()); row++) + for(int column = 0; column < static_cast(image.width()); column++) { auto color = image.pixelAt(row, column); new_image.setPixel(row, column, Color(0, 0, color.b)); @@ -38,10 +71,10 @@ Image separate_blue_channel(const Image& image) Image separate_red_channel(const Image& image) { - Image new_image(image.width, image.height); + Image new_image(image.width(), image.height()); - for(int row = 0; row < static_cast(image.height); row++) - for(int column = 0; column < static_cast(image.width); column++) + for(int row = 0; row < static_cast(image.height()); row++) + for(int column = 0; column < static_cast(image.width()); column++) { auto color = image.pixelAt(row, column); new_image.setPixel(row, column, Color(color.r, 0, 0)); @@ -52,10 +85,10 @@ Image separate_red_channel(const Image& image) Image separate_green_channel(const Image& image) { - Image new_image(image.width, image.height); + Image new_image(image.width(), image.height()); - for(int row = 0; row < static_cast(image.height); row++) - for(int column = 0; column < static_cast(image.width); column++) + for(int row = 0; row < static_cast(image.height()); row++) + for(int column = 0; column < static_cast(image.width()); column++) { auto color = image.pixelAt(row, column); new_image.setPixel(row, column, Color(0, color.g, 0)); @@ -66,10 +99,10 @@ Image separate_green_channel(const Image& image) Image luminance_map(const Image& image) { - Image new_image(image.width, image.height); + Image new_image(image.width(), image.height()); - for(int row = 0; row < static_cast(image.height); row++) - for(int column = 0; column < static_cast(image.width); column++) + for(int row = 0; row < static_cast(image.height()); row++) + for(int column = 0; column < static_cast(image.width()); column++) { auto color = image.pixelAt(row, column); auto luminance = static_cast(color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722); @@ -84,17 +117,17 @@ Image pixel_sort(const Image& source, const Image& mask) struct Interval {int column, row_start, row_end; }; std::vector intervals; - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) { int row = 0; - while(row < static_cast(source.height)) + while(row < static_cast(source.height())) { int row_begin = row; - while(row < static_cast(source.height) && mask.pixelAt(row, column).r == 255u) + while(row < static_cast(source.height()) && mask.pixelAt(row, column).r == 255u) row ++; intervals.push_back({column, row_begin, row - 1}); - while(row < static_cast(source.height) && mask.pixelAt(row, column).r != 255u) + while(row < static_cast(source.height()) && mask.pixelAt(row, column).r != 255u) row ++; } } @@ -126,25 +159,25 @@ Image pixel_sort(const Image& source, const Image& mask) Image pixel_distort_displace(const Image& source, const Image& map, double row_factor, double column_factor) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { double displacement = row_factor * (map.pixelAt(row, column).r / 255. * 2. - 1.); long long int column_displaced = static_cast(displacement * column_factor) + column; long long int row_displaced= static_cast(displacement * row_factor) + row; - if((row_displaced >= 0 && row_displaced < static_cast(source.height)) && - (column_displaced >= 0 && column_displaced < static_cast(source.width)) + if((row_displaced >= 0 && row_displaced < static_cast(source.height())) && + (column_displaced >= 0 && column_displaced < static_cast(source.width())) ) { new_image.setPixel(row_displaced, column_displaced, source.pixelAt(row, column)); } } - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { if(new_image.pixelAt(row, column).r == 0 && new_image.pixelAt(row, column).g == 0 && @@ -157,95 +190,84 @@ Image pixel_distort_displace(const Image& source, const Image& map, double row_f Image displacement(const Image& source, const Image& map, double row_factor, double column_factor) { - Image new_image(source.width, source.height); - Image known_pixels(source.width, source.height); + Image new_image(source.width(), source.height()); + Image known_pixels(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) { double displacement = (map.pixelAt(row, column).r / 255. * 2. - 1.); long long int column_displaced = static_cast(displacement * column_factor) + column; long long int row_displaced= static_cast(displacement * row_factor) + row; - row_displaced = (row_displaced + static_cast(source.height)) % static_cast(source.height); - column_displaced = (column_displaced + static_cast(source.width)) % static_cast(source.width); + row_displaced = (row_displaced + static_cast(source.height())) % static_cast(source.height()); + column_displaced = (column_displaced + static_cast(source.width())) % static_cast(source.width()); new_image.setPixel(row_displaced, column_displaced, source.pixelAt(row, column)); known_pixels.setPixel(row_displaced, column_displaced, Color(1, 0, 0)); } } - std::ostringstream out; + int row_radius = std::max(1, static_cast(row_factor)) * 3; + int column_radius = std::max(1, static_cast(column_factor)) * 3; - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) { - if(!known_pixels.pixelAt(row,column).r) + if(!known_pixels.pixelAt(row, column).r) { - Color samples[4]; - double weights[4]; - int nSamples = 0; + std::array samples{}; + std::array coords{}; char rowDir[] = {1, -1, 0, 0}; char colDir[] = {0, 0, -1, 1}; - out << "---\n"; for(int i = 0; i < 4; i++) { int row_ = row, column_ = column; - int dist = 0; + bool finished = false; - while(row_ >= 0 && row_ < static_cast(source.height) && column_ >= 0 && column_ < static_cast(source.width) && !known_pixels.pixelAt(row_,column_).r) + for(int l = 0; l < row_radius && !finished; l++) { row_ += rowDir[i]; - column_ += colDir[i]; - - dist ++; - } - - if(row_ >= 0 && row_ < static_cast(source.height) && column_ >= 0 && column_ < static_cast(source.width)) - { - samples[i] = source.pixelAt(row_, column_); - weights[i] = 1. / dist; - nSamples++; + column_ = column; + for(int k = 0; k < column_radius && !finished; k++) + { + column_ += colDir[i]; + + if(known_pixels.pixelAt( + (row_ + static_cast(source.height())) % static_cast(source.height()), + (column_ + static_cast(source.width())) % static_cast(source.width())).r) + { + coords[i] = { + (column_ + static_cast(source.width())) % static_cast(source.width()), + (row_ + static_cast(source.height())) % static_cast(source.height())}; + samples[i] = new_image.pixelAt( + (row_ + static_cast(source.height())) % static_cast(source.height()), + (column_ + static_cast(source.width())) % static_cast(source.width())); + finished = true; + } + } } - else weights[i] = 0; - - out << "color found: " << row_ << " " << column_ << " " << dist << '(' << int(samples[i].r) << ',' << int(samples[i].g) << ',' << int(samples[i].b) << ")\n"; - } - - double r, g, b; - - if(nSamples != 0) - { - r = (samples[0].r * weights[0] + samples[1].r * weights[1] + samples[2].r * weights[2] + samples[3].r * weights[3])/nSamples; - g = (samples[0].g * weights[0] + samples[1].g * weights[1] + samples[2].g * weights[2] + samples[3].g * weights[3])/nSamples; - b = (samples[0].b * weights[0] + samples[1].b * weights[1] + samples[2].b * weights[2] + samples[3].b * weights[3])/nSamples; } - else r = g = b = 0; - out << "computed color: " << '(' << int(r) << ',' << int(g) << ',' << int(b) << ")\n"; - - new_image.setPixel(row, column, Color(static_cast(r), static_cast(g), - static_cast(b))); + new_image.setPixel(row, column, + bilinear_interpolation(samples, coords, column, row)); + known_pixels.setPixel(row, column, Color(1, 0, 0)); } } } - std::string s = out.str(); - - std::ofstream("displacement_log") << s; - return new_image; } Image lift_gain(const Image& source, double lift, double gain) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { auto color = source.pixelAt(static_cast(row), static_cast(column)); auto r = color.r * (gain - lift) + lift; @@ -265,15 +287,20 @@ Image lift_gain(const Image& source, double lift, double gain) Image gamma_(const Image& source, double gamma) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { auto color = source.pixelAt(static_cast(row), static_cast(column)); auto r = pow(color.r, 1. / gamma); auto g = pow(color.g, 1. / gamma); auto b = pow(color.b, 1. / gamma); + + r = clamp(r, 0., 255.); + g = clamp(g, 0., 255.); + b = clamp(b, 0., 255.); + new_image.setPixel(static_cast(row), static_cast(column), Color(static_cast(r), static_cast(g), static_cast(b))); } @@ -283,10 +310,10 @@ Image gamma_(const Image& source, double gamma) Image contrast(const Image& source, double contrast) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { auto color = source.pixelAt(static_cast(row), static_cast(column)); auto r = color.r * (1. + contrast) * (-1.) - contrast / 2.; @@ -306,10 +333,10 @@ Image contrast(const Image& source, double contrast) Image brightness(const Image& source, double brightness) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { auto color = source.pixelAt(static_cast(row), static_cast(column)); auto r = color.r + brightness * 255; @@ -331,17 +358,17 @@ Image box_blur(const Image& source, double row_factor, double column_factor) { static auto get_pixel_or_black = [](const Image& image, int row, int column) { - if(row < static_cast(image.height) && column < static_cast(image.width) && row >= 0 && column >= 0) + if(row < static_cast(image.height()) && column < static_cast(image.width()) && row >= 0 && column >= 0) return image.pixelAt(row, column); return Color(); }; - Image tmp(source.width, source.height); + Image tmp(source.width(), source.height()); { double weight = 1.0f / column_factor; int half = static_cast(column_factor / 2); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { double r = 0., g = 0., b = 0.; for(int i = -half; i <= half; i++) @@ -360,13 +387,13 @@ Image box_blur(const Image& source, double row_factor, double column_factor) } } - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); { double weight = 1.0f / row_factor; int half = static_cast(column_factor / 2); - for(int row = 0; row < static_cast(source.height); row++) - for(int column = 0; column < static_cast(source.width); column++) + for(int row = 0; row < static_cast(source.height()); row++) + for(int column = 0; column < static_cast(source.width()); column++) { double r = 0., g = 0., b = 0.; for(int i = -half; i <= half; i++) @@ -390,11 +417,11 @@ Image box_blur(const Image& source, double row_factor, double column_factor) Image mask(const Image& source, const Image& mask) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) if(mask.pixelAt(row, column).r == 255u) new_image.setPixel(row, column, source.pixelAt(row, column)); else new_image.setPixel(row, column, Color(0, 0, 0)); @@ -405,11 +432,11 @@ Image mask(const Image& source, const Image& mask) Image add(const Image& a, const Image& b) { - Image new_image(a.width, a.height); + Image new_image(a.width(), a.height()); - for(int row = 0; row < static_cast(a.height); row++) + for(int row = 0; row < static_cast(a.height()); row++) { - for(int column = 0; column < static_cast(a.width); column++) + for(int column = 0; column < static_cast(a.width()); column++) { uint8_t r_ = a.pixelAt(row, column).r + b.pixelAt(row, column).r; uint8_t g_ = a.pixelAt(row, column).g + b.pixelAt(row, column).g; @@ -424,11 +451,11 @@ Image add(const Image& a, const Image& b) Image strict_add(const Image& a, const Image& b) { - Image new_image(a.width, a.height); + Image new_image(a.width(), a.height()); - for(int row = 0; row < static_cast(a.height); row++) + for(int row = 0; row < static_cast(a.height()); row++) { - for(int column = 0; column < static_cast(a.width); column++) + for(int column = 0; column < static_cast(a.width()); column++) { int r_ = static_cast(a.pixelAt(row, column).r) + static_cast(b.pixelAt(row, column).r); int g_ = static_cast(a.pixelAt(row, column).g) + static_cast(b.pixelAt(row, column).g); @@ -447,11 +474,11 @@ Image strict_add(const Image& a, const Image& b) Image multiply(const Image& a, const Image& b) { - Image new_image(a.width, a.height); + Image new_image(a.width(), a.height()); - for(int row = 0; row < static_cast(a.height); row++) + for(int row = 0; row < static_cast(a.height()); row++) { - for (int column = 0; column < static_cast(a.width); column++) + for (int column = 0; column < static_cast(a.width()); column++) { int r_ = static_cast(a.pixelAt(row, column).r) * static_cast(b.pixelAt(row, column).r); int g_ = static_cast(a.pixelAt(row, column).g) * static_cast(b.pixelAt(row, column).g); @@ -544,9 +571,9 @@ namespace PerlinNoise return (lerp(y1, y2, w) + 1) / 2; } - Image perlin_noise(const Image& source, long long int seed, double z, long long int octaves, double persistence) + Image perlin_noise(const Image& source, long long int seed, double zoom, double z, long long int octaves, double persistence) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); std::mt19937 engine(seed); @@ -557,30 +584,51 @@ namespace PerlinNoise permutation.insert(permutation.end(), permutation.begin(), permutation.end()); - for(int row = 0; row < static_cast(source.height); row++) + std::vector noise_array; + + noise_array.reserve(source.width() * source.height()); + double min = 1000; + double max = -1000; + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) { double frequency = 1; double amplitude = 1; double max_value = 0; double total = 0; - const double x = static_cast(row) / static_cast(source.height); - const double y = static_cast(column) / static_cast(source.width); + const double x = static_cast(row) / static_cast(source.height()) * zoom; + const double y = static_cast(column) / static_cast(source.width()) * zoom; for(int i = 0; i < octaves; i++) { - total += perlin(permutation, x, y, z); + total += amplitude * perlin(permutation, x * frequency, y * frequency, z * frequency); max_value += amplitude; amplitude *= persistence; frequency *= 2; } - auto a = static_cast(total / max_value * 255); + noise_array.push_back(total); + min = std::min(min, total); + max = std::max(max, total); + } + } + + int i = 0; + for(int row = 0; row < static_cast(source.height()); row++) + { + for (int column = 0; column < static_cast(source.width()); column++) + { + double noise = noise_array[i++]; + noise = -1. + 2. * (noise - min) / (max - min); + noise += 1.; + noise /= 2.; + + uint8_t sample = static_cast(noise * 255); - new_image.setPixel(row, column, Color(a, a, a)); + new_image.setPixel(row, column, Color(sample, sample, sample)); } } @@ -588,13 +636,32 @@ namespace PerlinNoise } } +Image noise(const Image& source, long long int seed) +{ + Image new_image(source.width(), source.height()); + + std::mt19937 engine(seed); + std::uniform_int_distribution distribution; + + for(int row = 0; row < static_cast(source.height()); row++) + { + for (int column = 0; column < static_cast(source.width()); column++) + { + uint8_t sample = distribution(engine); + new_image.setPixel(row, column, Color(sample, sample, sample)); + } + } + + return new_image; +} + Image discriminator_greater_than(const Image& source, long long int threshold) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) if(source.pixelAt(row, column).r >= threshold) new_image.setPixel(row, column, Color(255, 255, 255)); else new_image.setPixel(row, column, Color(0, 0, 0)); @@ -605,11 +672,11 @@ Image discriminator_greater_than(const Image& source, long long int threshold) Image discriminator_lesser_than(const Image& source, long long int threshold) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) if(source.pixelAt(row, column).r <= threshold) new_image.setPixel(row, column, Color(255, 255, 255)); else new_image.setPixel(row, column, Color(0, 0, 0)); @@ -620,11 +687,11 @@ Image discriminator_lesser_than(const Image& source, long long int threshold) Image discriminator_range(const Image& source, long long int a, long long int b) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for(int column = 0; column < static_cast(source.width); column++) + for(int column = 0; column < static_cast(source.width()); column++) if(source.pixelAt(row, column).r >= a && source.pixelAt(row, column).r <= b) new_image.setPixel(row, column, Color(255, 255, 255)); else new_image.setPixel(row, column, Color(0, 0, 0)); @@ -636,16 +703,136 @@ Image discriminator_range(const Image& source, long long int a, long long int b) Image solid_color(const Image& source, Color color) { - Image new_image(source.width, source.height); + Image new_image(source.width(), source.height()); - for(int row = 0; row < static_cast(source.height); row++) + for(int row = 0; row < static_cast(source.height()); row++) { - for (int column = 0; column < static_cast(source.width); column++) + for (int column = 0; column < static_cast(source.width()); column++) new_image.setPixel(row, column, color); } return new_image; } + +Image lens_distortion(const Image& source, double strength, double zoom) +{ + Image new_image(source.width(), source.height()); + + int half_width = source.width() / 2; + int half_height = source.height() / 2; + + strength = strength == 0 ? 0.0001 : strength; + double correction_radius = sqrt( + source.width() * source.width() + + source.height() * source.height()) / strength; + + for(int row = 0; row < static_cast(source.height()); row++) + { + for (int column = 0; column < static_cast(source.width()); column++) { + int new_row = row - half_height; + int new_column = column - half_width; + + double distance = sqrt(new_row * new_row + new_column * new_column); + double r = distance / correction_radius; + double theta = r == 0 ? 1 : atan(r) / r; + + double source_row = half_height + theta * new_row * zoom; + double source_column = half_width + theta * new_column * zoom; + + if (0 < source_row && source_row < source.height() && + 0 < source_column && source_column < source.width()) + { + double x = source_column - floor(source_column); + double y = source_row - floor(source_row); + + new_image.setPixel( + row, + column, + bilinear_interpolation({ + source.pixelAt(static_cast(floor(source_row)), static_cast(floor(source_column))), //0, 0 + source.pixelAt(static_cast(floor(source_row)), static_cast(ceil(source_column))), //1, 0 + source.pixelAt(static_cast(ceil(source_row)), static_cast(ceil(source_column))), //1, 1 + source.pixelAt(static_cast(ceil(source_row)), static_cast(floor(source_column))) //0, 1 + },{ + Coord{0, 0}, + Coord{1, 0}, + Coord{1, 1}, + Coord{0, 1} + }, x, y)); + } + else new_image.setPixel(row, column, Color(0, 0, 0)); + } + } + + return new_image; +} + +int hue(Color color) +{ + int max = std::max({color.r, color.g, color.b}); + int min = std::min({color.r, color.g, color.b}); + + min = max == min ? min - 1 : min; + + int hue; + if(max == color.r) + hue = (static_cast(color.g ) - color.b) / (max - min); + else if(max == color.g) + hue = 2 + (static_cast(color.b) - color.r) / (max - min); + else hue = 4 + (static_cast(color.r) - color.g) / (max - min); + + hue *= 60; + hue = hue < 0 ? hue + 360 : hue; + + return hue; +} + +Image jpeg_artifacts(const Image& source, int radius) +{ + Image new_image = source; + + for(int row = 0; row < static_cast(new_image.height()); row++) + { + for (int column = 0; column < static_cast(new_image.width()); column++) + { + int row_swap = -1; + int column_swap = -1; + int max = hue(new_image.pixelAt(row, column)); + + int row_ = row - radius; + for(int i = -radius; i < radius; i++) + { + if(row_ < 0 || row_ >= static_cast(new_image.height())) + continue; + + int column_ = column - radius; + for(int j = -radius; j < radius; j++) + { + if(column_ < 0 || column_ >= static_cast(new_image.width())) + continue; + + if(max < hue(new_image.pixelAt(row_, column_))) + { + max = hue(new_image.pixelAt(row_, column_)); + row_swap = row_; + column_swap = column_; + } + + column_++; + } + } + + if(row_swap != -1) + { + new_image.setPixel(row, column, new_image.pixelAt(row_swap, column_swap)); + new_image.setPixel(row_swap, column_swap, source.pixelAt(row, column)); + } + } + } + + return new_image; +} + BOOST_PYTHON_MODULE(images_internal) { using namespace boost::python; @@ -667,8 +854,11 @@ BOOST_PYTHON_MODULE(images_internal) def("add", add); def("multiply", multiply); def("perlin_noise", PerlinNoise::perlin_noise); + def("noise", noise); def("discriminator_greater_than", discriminator_greater_than); def("discriminator_lesser_than", discriminator_lesser_than); def("discriminator_range", discriminator_range); def("solid_color", solid_color); + def("lens_distortion", lens_distortion); + def("jpeg_artifacts", jpeg_artifacts); } \ No newline at end of file diff --git a/modules/src/internals.cpp b/modules/src/internals.cpp index b0edb5b..7b82698 100644 --- a/modules/src/internals.cpp +++ b/modules/src/internals.cpp @@ -3,10 +3,15 @@ // #include +#include + #include #include -using namespace boost::python; +#include + +namespace py = boost::python; +namespace np = boost::python::numpy; template const T copyObject(const T& v) @@ -14,18 +19,73 @@ const T copyObject(const T& v) return v; } +Image to_image(const np::ndarray& np_image) +{ + np::initialize(); + + std::size_t width = np_image.shape(1); + std::size_t height = np_image.shape(0); + + Image image{static_cast(width), static_cast(height)}; + std::size_t offset = 0; + for(unsigned int j = 0; j < image.height(); j++) + { + for (unsigned int i = 0; i < image.width(); i++) + { + Color pixel; + + pixel.r = static_cast((*reinterpret_cast(np_image.get_data() + offset + sizeof(float) * 0)) * 255.f); + pixel.g = static_cast((*reinterpret_cast(np_image.get_data() + offset + sizeof(float) * 1)) * 255.f); + pixel.b = static_cast((*reinterpret_cast(np_image.get_data() + offset + sizeof(float) * 2)) * 255.f); + + image.setPixel(j, i, pixel); + offset += sizeof(float) * 3; + } + } + + return image; +} + +np::ndarray to_ndarray(const Image& image) +{ + np::initialize(); + + py::tuple shape = py::make_tuple(image.height(), image.width(), 3); + auto np_image = np::zeros(shape, np::dtype::get_builtin()); + + std::size_t offset = 0; + for(unsigned int j = 0; j < image.height(); j++) + { + for(unsigned int i = 0; i < image.width(); i++) + { + Color pixel = image.pixelAt(j, i); + + *reinterpret_cast(np_image.get_data() + offset) = static_cast(pixel.r) / 255.; + *reinterpret_cast(np_image.get_data() + offset + sizeof(float)) = static_cast(pixel.g) / 255.; + *reinterpret_cast(np_image.get_data() + offset + sizeof(float) * 2) = static_cast(pixel.b) / 255.; + + offset += sizeof(float) * 3; + } + } + + return np_image; +} + BOOST_PYTHON_MODULE(internals) { - class_("Color", init()) + py::class_("Color", py::init()) .def("__copy__", copyObject) .def_readwrite("r", &Color::r) .def_readwrite("g", &Color::g) .def_readwrite("b", &Color::b); - class_("Image", init()) + py::class_("Image", py::init()) .def("__copy__", copyObject) .def("pixel_at", &Image::pixelAt) .def("set_pixel", &Image::setPixel) - .def("width", &Image::width_) - .def("height", &Image::height_); + .def("width", &Image::width) + .def("height", &Image::height); + + py::def("to_ndarray", to_ndarray); + py::def("to_image", to_image); } \ No newline at end of file diff --git a/screenshots/gie.jpg b/screenshots/gie.jpg new file mode 100644 index 0000000..aa9c2ad Binary files /dev/null and b/screenshots/gie.jpg differ diff --git a/screenshots/screenshot-2.png b/screenshots/screenshot-2.png new file mode 100644 index 0000000..7a0547e Binary files /dev/null and b/screenshots/screenshot-2.png differ diff --git a/screenshots/screenshot-3.png b/screenshots/screenshot-3.png new file mode 100644 index 0000000..663cf7c Binary files /dev/null and b/screenshots/screenshot-3.png differ diff --git a/screenshots/screenshot-4.png b/screenshots/screenshot-4.png new file mode 100644 index 0000000..8599603 Binary files /dev/null and b/screenshots/screenshot-4.png differ diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000..8093926 --- /dev/null +++ b/util/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.9) + +project(util) + +set(CMAKE_CXX_STANDARD 17) + +file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*) +file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS include/*) + +add_library(util INTERFACE) +target_include_directories(util INTERFACE + $ + $) + +install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) +install( + TARGETS util + EXPORT util-config + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install( + EXPORT util-config + NAMESPACE util:: + DESTINATION lib/cmake/util) diff --git a/util/include/Expected.h b/util/include/Expected.h new file mode 100644 index 0000000..c85d833 --- /dev/null +++ b/util/include/Expected.h @@ -0,0 +1,136 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_EXPECTED_H +#define GIE_EXPECTED_H + +#include +#include +#include + +class bad_expected_access: std::exception +{ +public: + const char* what() const noexcept override + { + return "bad_expected_access"; + } +}; + +template +class Unexpected +{ +public: + ErrorType error; + + ErrorType* operator->() { return error; } + const ErrorType* operator->() const { return error; } +}; + +template +auto makeUnexpected(T&& t) +{ + using CleanT = std::remove_cv_t>; + return Unexpected{std::forward(t)}; +} + +template +class Expected +{ +public: + explicit Expected(): m_value{ExpectedType{}} {} + explicit Expected(ExpectedType value): m_value{std::move(value)} {} + explicit Expected(Unexpected error): m_value{std::move(error)} {} + + void swap(Expected& other) noexcept + { + std::swap(m_value, other.m_value); + } + + const ExpectedType* operator->() const + { + if(*this) + return &std::get(m_value); + else throwUnhandled(); + } + + ExpectedType* operator->() + { + if(*this) + return &std::get(m_value); + else throwUnhandled(); + } + + ExpectedType& operator*() + { + if(*this) + return &std::get(m_value); + else throwUnhandled(); + } + + const ExpectedType& operator*() const + { + if(*this) + return &std::get(m_value); + else throwUnhandled(); + } + + explicit operator bool() const noexcept { return std::holds_alternative(m_value); } + + bool hasValue() const { return std::holds_alternative(m_value); } + + ExpectedType& value() + { + if(*this) + return std::get(m_value); + else throwUnhandled(); + } + + const ExpectedType& value() const + { + if(*this) + return std::get(m_value); + else throwUnhandled(); + } + + ErrorType& error() + { + if(!*this) + return std::get>(m_value).error; + else throwUnhandled(); + } + + const ErrorType& error() const + { + if(!*this) + return std::get>(m_value).error; + else throwUnhandled(); + } + + ExpectedType valueOr(ExpectedType&& value) + { + if(*this) + return std::get(m_value); + else return std::move(value); + } + + ExpectedType valueOr(ExpectedType&& value) const + { + if(*this) + return std::get(m_value); + else return std::move(value); + } + +private: + [[noreturn]] + void throwUnhandled() const + { + throw bad_expected_access{}; + } + +private: + std::variant> m_value; +}; + +#endif //GIE_EXPECTED_H diff --git a/util/include/Graph.h b/util/include/Graph.h new file mode 100644 index 0000000..4253a9b --- /dev/null +++ b/util/include/Graph.h @@ -0,0 +1,177 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_GRAPH_H +#define GIE_GRAPH_H + +#include +#include + +template +class Graph +{ +private: + using NodeId = std::size_t; + static const std::size_t InvalidNode = 0; + +public: + void addNode(const NodeType& key) + { + if(auto it = m_map.find(key); it == m_map.end()) + { + m_graph.push_back({m_nodeIdCount, {}, {}}); + m_idMap.insert({m_nodeIdCount, key}); + m_map.insert({key, m_nodeIdCount}); + + m_nodeIdCount++; + } + } + + void removeNode(const NodeType& key) + { + auto id = m_map[key]; + auto index = graphLookup(id); + + for(auto& neighbour: m_graph[index].in) + { + auto neighbourIndex = graphLookup(neighbour); + removeNeighbour(m_graph[neighbourIndex].out, id); + } + + for(auto& neighbour: m_graph[index].out) + { + auto neighbourIndex = graphLookup(neighbour); + removeNeighbour(m_graph[neighbourIndex].in, id); + } + + removeNode(id); + } + + void addEdge(const NodeType& a, const NodeType& b) + { + addNode(a); + addNode(b); + + auto aId = m_map[a]; + auto bId = m_map[b]; + auto& aNode = m_graph[graphLookup(aId)]; + auto& bNode = m_graph[graphLookup(bId)]; + + addNeighbour(aNode.out, bId); + addNeighbour(bNode.in, aId); + } + + void removeEdge(const NodeType& a, const NodeType& b) + { + auto aIt = m_map.find(a); + auto bIt = m_map.find(b); + + if(aIt == m_map.end() || bIt == m_map.end()) + return ; + + auto& aNode = m_graph[graphLookup(aIt->second)]; + auto& bNode = m_graph[graphLookup(bIt->second)]; + + removeNeighbour(aNode.in, bIt->second); + removeNeighbour(aNode.out, bIt->second); + removeNeighbour(bNode.in, aIt->second); + removeNeighbour(bNode.out, aIt->second); + } + + template + void iterateInNeighbours(const NodeType& key, Callback&& callback) + { + for(auto neighbour: m_graph[graphLookup(m_map[key])].in) + callback(m_idMap[neighbour]); + } + + template + void iterateOutNeighbours(const NodeType& key, Callback&& callback) + { + for(auto neighbour: m_graph[graphLookup(m_map[key])].out) + callback(m_idMap[neighbour]); + } + + std::size_t inDegree(const NodeType& key) + { + return m_graph[graphLookup(m_map[key])].in.size(); + } + + std::size_t outDegree(const NodeType& key) + { + return m_graph[graphLookup(m_map[key])].out.size(); + } + +private: + void addNeighbour(std::vector& neighbour, NodeId id) + { + auto it = neighbour.begin(); + + while(it != neighbour.end() && *it < id) + it++; + + if(it == neighbour.end() || *it != id) + neighbour.insert(it, id); + } + + void removeNeighbour(std::vector& neighbour, NodeId id) + { + auto r = findInNeighbours(neighbour, id); + + if(r != NotFound) + neighbour.erase(neighbour.begin() + r); + } + + void removeNode(NodeId id) + { + auto r = graphLookup(id); + + if(r != NotFound) + { + m_graph.erase(m_graph.begin() + r); + m_map.erase(m_idMap[id]); + m_idMap.erase(id); + } + } + +private: + static const std::size_t NotFound = -1; + + std::size_t graphLookup(NodeId id) + { + struct + { + std::size_t id; + } shadowId{id}; + + auto it = std::lower_bound(m_graph.begin(), m_graph.end(), shadowId, [](const auto& lhs, const auto& rhs) + { + return lhs.id < rhs.id; + }); + + return it == m_graph.end() ? NotFound : std::distance(m_graph.begin(), it); + } + + std::size_t findInNeighbours(const std::vector& neighbours, NodeId id) + { + auto it = std::lower_bound(neighbours.begin(), neighbours.end(), id); + return it == neighbours.end() ? NotFound : std::distance(neighbours.begin(), it); + } + +private: + std::size_t m_nodeIdCount = 0; + std::unordered_map m_map; + std::unordered_map m_idMap; + + struct Node + { + NodeId id{}; + std::vector in; + std::vector out; + }; + + std::vector m_graph; +}; + +#endif //GIE_GRAPH_H \ No newline at end of file diff --git a/util/include/MaybeError.h b/util/include/MaybeError.h new file mode 100644 index 0000000..8e054f4 --- /dev/null +++ b/util/include/MaybeError.h @@ -0,0 +1,60 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_MAYBEERROR_H +#define GIE_MAYBEERROR_H + +#include +#include +#include + +class maybe_error_bad_access: std::exception +{ +public: + const char* what() const noexcept override + { + return "maybe_error_bad_access"; + } +}; + +template +class [[nodiscard]] MaybeError +{ +public: + MaybeError(): m_error{std::nullopt} {} + MaybeError(ErrorType error): m_error{std::move(error)} {} + + operator bool() const { return m_error.has_value(); } + bool errorSet() const { return m_error.has_value(); } + + [[nodiscard]] ErrorType& error() + { + if(errorSet()) + return m_error.value(); + else throw maybe_error_bad_access{}; + } + + + [[nodiscard]] const ErrorType& error() const + { + if(errorSet()) + return m_error.value(); + else throw maybe_error_bad_access{}; + } + + template + void handle(Handler&& handler) + { + if(errorSet()) + handler(m_error); + } + + void discard() { } + +private: + std::optional m_error; +}; + + +#endif //GIE_MAYBEERROR_H diff --git a/util/include/StrongAlias.h b/util/include/StrongAlias.h new file mode 100644 index 0000000..62b38e5 --- /dev/null +++ b/util/include/StrongAlias.h @@ -0,0 +1,42 @@ +// +// Created by alex on 7/14/19. +// + +#ifndef GIE_STRONGALIAS_H +#define GIE_STRONGALIAS_H + +#include + +template +class StrongAlias +{ +public: + explicit StrongAlias(const T& value): m_value{value} {}; + explicit StrongAlias(T&& value): m_value{std::move(value)} {}; + explicit StrongAlias(): m_value{} {} + + T& get() { return m_value; } + const T& get() const { return m_value; } + + explicit operator T() { return m_value; } + explicit operator T() const { return m_value; } + + auto& operator=(const T& t) + { + m_value = t; + + return *this; + } + + auto& operator=(T&& t) + { + m_value = std::move(t); + + return *this; + } + +private: + T m_value; +}; + +#endif //GIE_STRONGALIAS_H