diff --git a/.clang-format b/.clang-format
index 4897bcf18..ee0c924b3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -18,7 +18,6 @@ AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
-AllowShortCaseLabelsOnASingleLine: true
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
diff --git a/.gitignore b/.gitignore
index 25ba3af2e..256c24fc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
/build/
/Submodules/
+/.vscode/
# C++ objects and libs
*.slo
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3be56113f..2a4295745 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,25 +1,85 @@
+###################################################################################
+## ##
+## Copyright (C) 2024 Saif Kandil (k0T0z) ##
+## ##
+## This file is a part of the ENIGMA Development Environment. ##
+## ##
+## ##
+## ENIGMA is free software: you can redistribute it and/or modify it under the ##
+## terms of the GNU General Public License as published by the Free Software ##
+## Foundation, version 3 of the license or any later version. ##
+## ##
+## This application and its source code is distributed AS-IS, WITHOUT ANY ##
+## WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ##
+## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ##
+## details. ##
+## ##
+## You should have recieved a copy of the GNU General Public License along ##
+## with this code. If not, see ##
+## ##
+## ENIGMA is an environment designed to create games and other programs with a ##
+## high-level, fully compilable language. Developers of ENIGMA or anything ##
+## associated with ENIGMA are in no way responsible for its users or ##
+## applications created by its users, or damages caused by the environment ##
+## or programs made in the environment. ##
+## ##
+###################################################################################
+
cmake_minimum_required(VERSION 3.14)
project(RadialGM)
+# Uncomment to give priority to the local CMake modules
+# set(CMAKE_PREFIX_PATH "/usr/local/lib")
+
include(CMakeDependentOption)
option(RGM_BUILD_EMAKE "Build Emake and the compiler." ON)
+option(RGM_BUILD_TESTS "Build tests." OFF)
# FIXME: MSVC dynamic linking requires US TO DLLEXPORT our funcs
# since we currently don't, I'm force disabling the option on MSVC
cmake_dependent_option(RGM_BUILD_STATIC "Build static libs." ON "MSVC" OFF)
+# Check https://stackoverflow.com/q/33062728/14629018 for more information.
+# if(MSVC)
+# set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
+# endif()
+
if (RGM_BUILD_STATIC)
- set(LIB_TYPE STATIC)
+ set(LIB_TYPE STATIC CACHE STRING "Static Library type")
+else()
+ set(LIB_TYPE SHARED CACHE STRING "Shared Library type")
+endif()
+
+# Set default build type
+if(NOT CMAKE_BUILD_TYPE)
+ message(STATUS "Build type not set - defaulting to Debug")
+ set(
+ CMAKE_BUILD_TYPE "Debug"
+ CACHE
+ STRING
+ "Choose the type of build from: Debug Release MinSizeRel RelWithDebInfo."
+ FORCE)
else()
- set(LIB_TYPE SHARED)
+ if (NOT CMAKE_BUILD_TYPE MATCHES "Debug" AND NOT CMAKE_BUILD_TYPE MATCHES "Release" AND NOT CMAKE_BUILD_TYPE MATCHES "MinSizeRel" AND NOT CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
+ message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}")
+ endif()
endif()
+set(CMAKE_DEBUG_POSTFIX "d")
+set(CMAKE_RELEASE_POSTFIX "")
+set(CMAKE_MINSIZEREL_POSTFIX "s")
+set(CMAKE_RELWITHDEBINFO_POSTFIX "rd")
+
if (CMAKE_BUILD_TYPE MATCHES "Debug")
- set(EXE "RadialGM-Debug")
+ set(EXE "RadialGM-Debug" CACHE STRING "RGM Executable name")
add_definitions(-DRGM_DEBUG)
-else()
- set(EXE "RadialGM")
+elseif(CMAKE_BUILD_TYPE MATCHES "MinSizeRel")
+ set(EXE "RadialGM-MinSizeRel" CACHE STRING "RGM Executable name")
+elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
+ set(EXE "RadialGM-RelWithDebInfo" CACHE STRING "RGM Executable name")
+else() # Release
+ set(EXE "RadialGM" CACHE STRING "RGM Executable name")
endif()
set(EXE_DESCRIPTION "ENIGMA IDE")
@@ -40,10 +100,30 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(CMAKE_AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/Dialogs")
+# TODO: Do we need to disable exceptions in RGM like we do in ENIGMA?
+# if(MSVC)
+# string(APPEND CMAKE_CXX_FLAGS " /EHsc /wd26812")
+# string(APPEND CMAKE_C_FLAGS " /EHsc /wd26812")
+# endif()
+
+# # Disable C++ exceptions.
+# if(MSVC)
+# string(REGEX REPLACE "/EHsc" "/EHs-c-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+# add_definitions(-D_HAS_EXCEPTIONS=0)
+# else()
+# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables")
+# endif()
+set(CMAKE_AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/" "${CMAKE_CURRENT_SOURCE_DIR}/Dialogs" "${CMAKE_CURRENT_SOURCE_DIR}/Editors")
+
+# Uncomment to be able to use local grpc_cpp_plugin
+# set(GRPC_EXE "/usr/local/bin/grpc_cpp_plugin")
+
+set(RGM_ROOTDIR "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# The ENIGMA_DIR is sent to the C++ code as a define
# Include ENIGMA things
-set(ENIGMA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/enigma-dev)
+set(ENIGMA_DIR ${RGM_ROOTDIR}/Submodules/enigma-dev CACHE PATH "ENIGMA directory")
include_directories("${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/protos/" "${ENIGMA_DIR}/CommandLine/libEGM" "${ENIGMA_DIR}/shared")
# Populate a CMake variable with the sources
@@ -81,12 +161,14 @@ set(RGM_SOURCES
Editors/ScriptEditor.cpp
Editors/CodeEditor.cpp
Editors/TimelineEditor.cpp
+ Editors/VisualShaderEditor.cpp
main.cpp
Plugins/RGMPlugin.cpp
- Plugins/ServerPlugin.cpp
Dialogs/EventArgumentsDialog.cpp
Dialogs/TimelineChangeMoment.cpp
Dialogs/PreferencesDialog.cpp
+ Dialogs/PreferencesKeys.cpp
+ Dialogs/KeyBindingPreferences.cpp
Utils/ProtoManip.cpp
Utils/FieldPath.cpp
MainWindow.cpp
@@ -117,7 +199,6 @@ set(RGM_HEADERS
Models/ModelMapper.h
Models/RepeatedSortFilterProxyModel.h
Models/TreeSortFilterProxyModel.h
- main.h
Components/RecentFiles.h
Components/QMenuView_p.h
Components/Utility.h
@@ -139,13 +220,14 @@ set(RGM_HEADERS
Editors/FontEditor.h
Editors/SpriteEditor.h
Editors/BackgroundEditor.h
- Plugins/ServerPlugin.h
+ Editors/VisualShaderEditor.h
Plugins/RGMPlugin.h
MainWindow.h
Dialogs/EventArgumentsDialog.h
Dialogs/PreferencesDialog.h
Dialogs/PreferencesKeys.h
Dialogs/TimelineChangeMoment.h
+ Dialogs/KeyBindingPreferences.h
Utils/SafeCasts.h
Utils/ProtoManip.h
Utils/FieldPath.h
@@ -196,14 +278,14 @@ else()
set(EDITOR_SOURCES Widgets/CodeWidgetScintilla.cpp)
endif()
- set(RGM_SOURCES ${RGM_SOURCES} Plugins/ServerPlugin.cpp)
- set(RGM_HEADERS ${RGM_HEADERS} Plugins/ServerPlugin.h)
+set(RGM_SOURCES ${RGM_SOURCES} Plugins/ServerPlugin.cpp)
+set(RGM_HEADERS ${RGM_HEADERS} Plugins/ServerPlugin.h)
# Tell CMake to create the RadialGM executable
add_executable(${EXE} WIN32 ${RGM_UI} ${RGM_HEADERS} ${RGM_SOURCES} ${EDITOR_SOURCES} ${RGM_RC})
# we do this even in release mode for "Editor Diagnostics"
-target_compile_definitions(${EXE} PUBLIC QT_MESSAGELOGCONTEXT)
+target_compile_definitions(${EXE} PUBLIC QT_MESSAGELOGCONTEXT ENIGMA_DIR="${ENIGMA_DIR}")
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
@@ -244,14 +326,13 @@ include_directories(${EXE} PRIVATE ${RAPIDJSON_INCLUDE_DIRS})
find_package(yaml-cpp CONFIG REQUIRED)
target_link_libraries(${EXE} PRIVATE yaml-cpp)
- #Find gRPC
- find_package(gRPC CONFIG REQUIRED)
- target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++)
+# Find gRPC
+find_package(gRPC CONFIG REQUIRED)
+target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++)
# Find Protobuf
-include(FindProtobuf)
-include_directories(${Protobuf_INCLUDE_DIRS})
-target_link_libraries(${EXE} PRIVATE ${Protobuf_LIBRARIES})
+find_package(Protobuf CONFIG REQUIRED)
+target_link_libraries(${EXE} PRIVATE protobuf::libprotobuf)
# Find OpenSSL
find_package(OpenSSL REQUIRED)
@@ -262,11 +343,13 @@ find_package(Qt5 COMPONENTS Core Widgets Gui PrintSupport Multimedia REQUIRED)
target_link_libraries(${EXE} PRIVATE Qt5::Core Qt5::Widgets Qt5::Gui Qt5::PrintSupport Qt5::Multimedia)
# LibProto
-add_subdirectory(Submodules/enigma-dev/shared)
-add_subdirectory(Submodules/enigma-dev/shared/protos)
-add_subdirectory(Submodules/enigma-dev/CommandLine/libEGM)
-add_dependencies(${EXE} "EGM")
-target_link_libraries(${EXE} PRIVATE "EGM" "Protocols" "ENIGMAShared")
+# Arrangement of these is important: shared depends on proto and emake depends on all of them
+# We need to cache the library names first so we can build on top of them
+add_subdirectory(${ENIGMA_DIR}/shared/protos)
+add_subdirectory(${ENIGMA_DIR}/shared)
+add_subdirectory(${ENIGMA_DIR}/CommandLine/libEGM)
+add_dependencies(${EXE} ${LIB_EGM})
+target_link_libraries(${EXE} PRIVATE ${LIB_EGM} ${LIB_PROTO} ${SHARED_LIB})
# Find FreeType
find_package(Freetype REQUIRED)
@@ -289,6 +372,10 @@ target_link_libraries(${EXE} PRIVATE ${LIB_PCRE2})
find_library(LIB_DOUBLE_CONVERSION NAMES double-conversion)
target_link_libraries(${EXE} PRIVATE ${LIB_DOUBLE_CONVERSION})
+if(RGM_BUILD_TESTS)
+ add_subdirectory(Tests)
+endif()
+
if(WIN32)
# Windows is a turd
target_link_libraries(${EXE} PRIVATE Ws2_32 Wtsapi32 Wldap32 Crypt32 Winmm Userenv Netapi32 version Dwmapi Imm32)
@@ -300,16 +387,24 @@ if(MSVC)
endif()
if (RGM_BUILD_EMAKE)
- add_subdirectory(Submodules/enigma-dev/CompilerSource)
- add_subdirectory(Submodules/enigma-dev/CommandLine/emake)
- if (CMAKE_BUILD_TYPE MATCHES "Debug")
- set(CLI_TARGET "emake-debug")
- else()
- set(CLI_TARGET "emake")
- endif()
+ add_subdirectory(${ENIGMA_DIR}/CompilerSource)
+ add_subdirectory(${ENIGMA_DIR}/CommandLine/emake)
add_dependencies(${EXE} ${CLI_TARGET})
endif()
+add_custom_command(
+ TARGET ${EXE}
+ POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CommandLine/emake/${CLI_TARGET}${CMAKE_EXECUTABLE_SUFFIX}
+ ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CommandLine/libEGM/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_EGM}$,${CMAKE_DEBUG_POSTFIX},$,${CMAKE_MINSIZEREL_POSTFIX},$,${CMAKE_RELWITHDEBINFO_POSTFIX},>>>${CMAKE_SHARED_LIBRARY_SUFFIX}
+ ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CompilerSource/${CMAKE_SHARED_LIBRARY_PREFIX}${COMPILER_LIB}$,${CMAKE_DEBUG_POSTFIX},$,${CMAKE_MINSIZEREL_POSTFIX},$,${CMAKE_RELWITHDEBINFO_POSTFIX},>>>${CMAKE_SHARED_LIBRARY_SUFFIX}
+ ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/protos/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_PROTO}$,${CMAKE_DEBUG_POSTFIX},$,${CMAKE_MINSIZEREL_POSTFIX},$,${CMAKE_RELWITHDEBINFO_POSTFIX},>>>${CMAKE_SHARED_LIBRARY_SUFFIX}
+ ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/${CMAKE_SHARED_LIBRARY_PREFIX}${SHARED_LIB}$,${CMAKE_DEBUG_POSTFIX},$,${CMAKE_MINSIZEREL_POSTFIX},$,${CMAKE_RELWITHDEBINFO_POSTFIX},>>>${CMAKE_SHARED_LIBRARY_SUFFIX}
+ ${ENIGMA_DIR}
+ COMMENT "Copying exes to ${ENIGMA_DIR}"
+)
+
install(TARGETS ${EXE} RUNTIME DESTINATION .)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${EXE}.dir/Debug/${EXE}.pdb" DESTINATION . OPTIONAL)
@@ -324,9 +419,9 @@ file(TO_CMAKE_PATH ${VCPKG_ROOT} VCPKG_ROOT)
set(SEARCH_PATHS "${VCPKG_ROOT}/installed/x64-windows/bin/")
endif()
else()
- set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}EGM${CMAKE_SHARED_LIBRARY_SUFFIX}"
- "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}Protocols${CMAKE_SHARED_LIBRARY_SUFFIX}"
- "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}ENIGMAShared${CMAKE_SHARED_LIBRARY_SUFFIX}")
+ set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_EGM}${CMAKE_SHARED_LIBRARY_SUFFIX}"
+ "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_PROTO}${CMAKE_SHARED_LIBRARY_SUFFIX}"
+ "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${SHARED_LIB}${CMAKE_SHARED_LIBRARY_SUFFIX}")
endif()
if (WIN32)
diff --git a/Dialogs/keybindingpreferences.cpp b/Dialogs/KeyBindingPreferences.cpp
similarity index 94%
rename from Dialogs/keybindingpreferences.cpp
rename to Dialogs/KeyBindingPreferences.cpp
index 61b4e313d..957eac93d 100644
--- a/Dialogs/keybindingpreferences.cpp
+++ b/Dialogs/KeyBindingPreferences.cpp
@@ -1,4 +1,4 @@
-#include "KeybindingPreferences.h"
+#include "KeyBindingPreferences.h"
#include "ui_MainWindow.h"
#include "ui_SpriteEditor.h"
diff --git a/Dialogs/keybindingpreferences.h b/Dialogs/KeyBindingPreferences.h
similarity index 100%
rename from Dialogs/keybindingpreferences.h
rename to Dialogs/KeyBindingPreferences.h
diff --git a/Dialogs/PreferencesDialog.cpp b/Dialogs/PreferencesDialog.cpp
index 489c8f8c2..a25cacec0 100644
--- a/Dialogs/PreferencesDialog.cpp
+++ b/Dialogs/PreferencesDialog.cpp
@@ -2,8 +2,7 @@
#include "ui_PreferencesDialog.h"
#include "PreferencesKeys.h"
-#include "KeybindingPreferences.h"
-#include "main.h"
+#include "KeyBindingPreferences.h"
#include "Components/Logger.h"
#include
diff --git a/Dialogs/PreferencesKeys.cpp b/Dialogs/PreferencesKeys.cpp
new file mode 100644
index 000000000..f023935d4
--- /dev/null
+++ b/Dialogs/PreferencesKeys.cpp
@@ -0,0 +1,30 @@
+/*********************************************************************************/
+/* */
+/* Copyright (C) 2024 Saif Kandil (k0T0z) */
+/* */
+/* This file is a part of the ENIGMA Development Environment. */
+/* */
+/* */
+/* ENIGMA is free software: you can redistribute it and/or modify it under the */
+/* terms of the GNU General Public License as published by the Free Software */
+/* Foundation, version 3 of the license or any later version. */
+/* */
+/* This application and its source code is distributed AS-IS, WITHOUT ANY */
+/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */
+/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */
+/* details. */
+/* */
+/* You should have recieved a copy of the GNU General Public License along */
+/* with this code. If not, see */
+/* */
+/* ENIGMA is an environment designed to create games and other programs with a */
+/* high-level, fully compilable language. Developers of ENIGMA or anything */
+/* associated with ENIGMA are in no way responsible for its users or */
+/* applications created by its users, or damages caused by the environment */
+/* or programs made in the environment. */
+/* */
+/*********************************************************************************/
+
+#include "Dialogs/PreferencesKeys.h"
+
+QString defaultStyle = "";
diff --git a/Dialogs/PreferencesKeys.h b/Dialogs/PreferencesKeys.h
index 46e574c97..4ab196514 100644
--- a/Dialogs/PreferencesKeys.h
+++ b/Dialogs/PreferencesKeys.h
@@ -3,6 +3,11 @@
#include
+// Qt doesn't have a way of getting the default style
+// so we store it for later when the user restores
+// default settings so we can apply it again
+extern QString defaultStyle;
+
// these are settings key helpers to prevent typos and promote
// consistency, or at least turn such mistakes into compile-time
// errors and not difficult-to-diagnose issues at runtime
diff --git a/Editors/BaseEditor.cpp b/Editors/BaseEditor.cpp
index a411d3666..1ac76e056 100644
--- a/Editors/BaseEditor.cpp
+++ b/Editors/BaseEditor.cpp
@@ -5,6 +5,8 @@
#include
#include
+BaseEditor::BaseEditor(QWidget* parent) : QWidget(parent) {}
+
BaseEditor::BaseEditor(MessageModel* resource_model, QWidget* parent)
: QWidget(parent), _model(resource_model->GetParentModel()), _nodeMapper(new ModelMapper(_model, this)) {
_resMapper = new ModelMapper(resource_model, this);
diff --git a/Editors/BaseEditor.h b/Editors/BaseEditor.h
index 0cf6de9db..7c4165b2d 100644
--- a/Editors/BaseEditor.h
+++ b/Editors/BaseEditor.h
@@ -13,12 +13,14 @@ static const QHash ResTypeFields = {
{TypeCase::kPath, TreeNode::kPathFieldNumber}, {TypeCase::kFont, TreeNode::kFontFieldNumber},
{TypeCase::kScript, TreeNode::kScriptFieldNumber}, {TypeCase::kTimeline, TreeNode::kTimelineFieldNumber},
{TypeCase::kObject, TreeNode::kObjectFieldNumber}, {TypeCase::kRoom, TreeNode::kRoomFieldNumber},
- {TypeCase::kSettings, TreeNode::kSettingsFieldNumber}, {TypeCase::kShader, TreeNode::kShaderFieldNumber}};
+ {TypeCase::kSettings, TreeNode::kSettingsFieldNumber}, {TypeCase::kShader, TreeNode::kShaderFieldNumber},
+ {TypeCase::kVisualShader, TreeNode::kVisualShaderFieldNumber}};
class BaseEditor : public QWidget {
Q_OBJECT
public:
+ explicit BaseEditor(QWidget *parent = nullptr);
explicit BaseEditor(MessageModel *treeNodeModel, QWidget *parent);
~BaseEditor();
void ReplaceBuffer(google::protobuf::Message *buffer);
diff --git a/Editors/RoomEditor.cpp b/Editors/RoomEditor.cpp
index cd0eda3d8..e941a6502 100644
--- a/Editors/RoomEditor.cpp
+++ b/Editors/RoomEditor.cpp
@@ -90,15 +90,15 @@ RoomEditor::RoomEditor(MessageModel* model, QWidget* parent) : BaseEditor(model,
_ui->objectSelectButton->setMenu(objMenu);
_ui->objectSelectButton->setPopupMode(QToolButton::MenuButtonPopup);
- auto objects = treeProxy
- ->match(treeProxy->index(0, 0), TreeModel::UserRoles::TypeCaseRole,
- TypeCase::kObject, 1, Qt::MatchRecursive);
- if (!objects.empty()) {
- QModelIndex firstObjIdx = objects.first();
- QString firstObj = firstObjIdx.data(Qt::DisplayRole).toString();
- _ui->objectSelectButton->setIcon(firstObjIdx.data(Qt::DecorationRole).value());
- _ui->currentObject->setText(firstObj);
- }
+ // auto objects = treeProxy
+ // ->match(treeProxy->index(0, 0), TreeModel::UserRoles::TypeCaseRole,
+ // TypeCase::kObject, 1, Qt::MatchRecursive);
+ // if (!objects.empty()) {
+ // QModelIndex firstObjIdx = objects.first();
+ // QString firstObj = firstObjIdx.data(Qt::DisplayRole).toString();
+ // _ui->objectSelectButton->setIcon(firstObjIdx.data(Qt::DecorationRole).value());
+ // _ui->currentObject->setText(firstObj);
+ // }
connect(objMenu, &QMenuView::triggered, [=](const QModelIndex &index) {
_ui->currentObject->setText(treeProxy->data(index, Qt::DisplayRole).toString());
@@ -116,9 +116,10 @@ RoomEditor::RoomEditor(MessageModel* model, QWidget* parent) : BaseEditor(model,
// This updates all the model views in the event of a sprite is changed
connect(MainWindow::resourceMap, &ResourceModelMap::DataChanged, this, [this]() {
- _ui->instancesListView->reset();
- _ui->tilesListView->reset();
- _ui->layersPropertiesView->reset();
+ // _ui->entitiesListView->reset();
+ _ui->elementsListView->reset();
+ _ui->layersListView->reset();
+ _ui->propertiesView->reset();
});
RoomEditor::RebindSubModels();
@@ -133,35 +134,35 @@ void RoomEditor::RebindSubModels() {
RepeatedMessageModel* im = _roomModel->GetSubModel(Room::kInstancesFieldNumber);
RepeatedSortFilterProxyModel* imp = new RepeatedSortFilterProxyModel(this);
imp->SetSourceModel(im);
- _ui->instancesListView->setModel(imp);
+ // _ui->instancesListView->setModel(imp);
- for (int c = 0; c < im->columnCount(); ++c) {
- if (c != im->FieldToColumn(Room::Instance::kNameFieldNumber) &&
- c != im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber) &&
- c != im->FieldToColumn(Room::Instance::kIdFieldNumber))
- _ui->instancesListView->hideColumn(c);
- else
- _ui->instancesListView->resizeColumnToContents(c);
- }
+ // for (int c = 0; c < im->columnCount(); ++c) {
+ // if (c != im->FieldToColumn(Room::Instance::kNameFieldNumber) &&
+ // c != im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber) &&
+ // c != im->FieldToColumn(Room::Instance::kIdFieldNumber))
+ // _ui->instancesListView->hideColumn(c);
+ // else
+ // _ui->instancesListView->resizeColumnToContents(c);
+ // }
- _ui->instancesListView->header()->swapSections(im->FieldToColumn(Room::Instance::kNameFieldNumber),
- im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber));
+ // _ui->instancesListView->header()->swapSections(im->FieldToColumn(Room::Instance::kNameFieldNumber),
+ // im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber));
RepeatedMessageModel* tm = _roomModel->GetSubModel(Room::kTilesFieldNumber);
RepeatedSortFilterProxyModel* tmp = new RepeatedSortFilterProxyModel(this);
tmp->SetSourceModel(tm);
- _ui->tilesListView->setModel(tmp);
+ _ui->layersListView->setModel(tmp);
for (int c = 0; c < tm->columnCount(); ++c) {
if (c != tm->FieldToColumn(Room::Tile::kBackgroundNameFieldNumber) &&
c != tm->FieldToColumn(Room::Tile::kIdFieldNumber) && c != tm->FieldToColumn(Room::Tile::kDepthFieldNumber) &&
c != tm->FieldToColumn(Room::Tile::kNameFieldNumber))
- _ui->tilesListView->hideColumn(c);
+ _ui->layersListView->hideColumn(c);
else
- _ui->tilesListView->resizeColumnToContents(c);
+ _ui->layersListView->resizeColumnToContents(c);
}
- _ui->tilesListView->header()->swapSections(tm->FieldToColumn(Room::Tile::kNameFieldNumber),
+ _ui->layersListView->header()->swapSections(tm->FieldToColumn(Room::Tile::kNameFieldNumber),
tm->FieldToColumn(Room::Tile::kBackgroundNameFieldNumber));
RepeatedMessageModel* vm = _roomModel->GetSubModel(Room::kViewsFieldNumber);
@@ -170,19 +171,19 @@ void RoomEditor::RebindSubModels() {
connect(_ui->elementsListView->selectionModel(), &QItemSelectionModel::selectionChanged,
[=](const QItemSelection& selected, const QItemSelection& /*deselected*/) {
if (selected.empty()) return;
- _ui->tilesListView->clearSelection();
+ _ui->layersListView->clearSelection();
auto selectedIndex = selected.indexes().first();
auto currentInstanceModel = imp->GetSubModel(selectedIndex.row());
- _ui->layersPropertiesView->setModel(currentInstanceModel);
+ _ui->propertiesView->setModel(currentInstanceModel);
});
- connect(_ui->tilesListView->selectionModel(), &QItemSelectionModel::selectionChanged,
+ connect(_ui->layersListView->selectionModel(), &QItemSelectionModel::selectionChanged,
[=](const QItemSelection& selected, const QItemSelection& /*deselected*/) {
if (selected.empty()) return;
- _ui->instancesListView->clearSelection();
+ // _ui->instancesListView->clearSelection();
auto selectedIndex = selected.indexes().first();
auto currentInstanceModel = tmp->GetSubModel(selectedIndex.row());
- _ui->layersPropertiesView->setModel(currentInstanceModel);
+ _ui->propertiesView->setModel(currentInstanceModel);
});
BaseEditor::RebindSubModels();
@@ -201,7 +202,7 @@ void RoomEditor::MousePressed(Qt::MouseButton button) {
if (button == Qt::MouseButton::LeftButton) {
auto index = layerElements->rowCount();
layerElements->insertRow(index);
- layerElements->SetData(_ui->currentObject->text(), index, Room::Instance::kObjectTypeFieldNumber);
+ // layerElements->SetData(_ui->currentObject->text(), index, Room::Instance::kObjectTypeFieldNumber);
}
}
@@ -218,3 +219,7 @@ void RoomEditor::on_actionZoom_triggered() { _ui->roomPreviewBackground->ResetZo
void RoomEditor::on_actionShowHideGrid_triggered() {
_ui->roomPreviewBackground->SetGridVisible(_ui->actionShowHideGrid->isChecked());
}
+
+void RoomEditor::updateCursorPositionLabel(const QPoint& pos) {
+
+}
diff --git a/Editors/RoomEditor.h b/Editors/RoomEditor.h
index 7847f3ac4..8f399f775 100644
--- a/Editors/RoomEditor.h
+++ b/Editors/RoomEditor.h
@@ -21,6 +21,12 @@ class RoomEditor : public BaseEditor {
void setZoom(qreal zoom);
+ void MouseMoved(int x, int y);
+
+ void MousePressed(Qt::MouseButton button);
+
+ void MouseReleased(Qt::MouseButton button);
+
public slots:
void RebindSubModels() override;
diff --git a/Editors/VisualShaderEditor.cpp b/Editors/VisualShaderEditor.cpp
new file mode 100644
index 000000000..39120004d
--- /dev/null
+++ b/Editors/VisualShaderEditor.cpp
@@ -0,0 +1,3957 @@
+/*********************************************************************************/
+/* */
+/* Copyright (C) 2024 Saif Kandil (k0T0z) */
+/* */
+/* This file is a part of the ENIGMA Development Environment. */
+/* */
+/* */
+/* ENIGMA is free software: you can redistribute it and/or modify it under the */
+/* terms of the GNU General Public License as published by the Free Software */
+/* Foundation, version 3 of the license or any later version. */
+/* */
+/* This application and its source code is distributed AS-IS, WITHOUT ANY */
+/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */
+/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */
+/* details. */
+/* */
+/* You should have recieved a copy of the GNU General Public License along */
+/* with this code. If not, see */
+/* */
+/* ENIGMA is an environment designed to create games and other programs with a */
+/* high-level, fully compilable language. Developers of ENIGMA or anything */
+/* associated with ENIGMA are in no way responsible for its users or */
+/* applications created by its users, or damages caused by the environment */
+/* or programs made in the environment. */
+/* */
+/*********************************************************************************/
+
+#include "Editors/VisualShaderEditor.h"
+
+#include
+
+#include "EVisualShader.pb.h"
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderEditor *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+VisualShaderEditor::VisualShaderEditor(QWidget* parent)
+ : BaseEditor(parent),
+ visual_shader(nullptr),
+ layout(nullptr),
+ side_widget(nullptr),
+ side_outer_layout(nullptr),
+ side_layout(nullptr),
+ name_edit(nullptr),
+ save_button(nullptr),
+ scene_layer_layout(nullptr),
+ scene_layer(nullptr),
+ scene(nullptr),
+ view(nullptr),
+ top_layer(nullptr),
+ menu_bar(nullptr),
+ menu_button(nullptr),
+ create_node_button(nullptr),
+ preview_shader_button(nullptr),
+ create_node_action(nullptr),
+ zoom_in_button(nullptr),
+ reset_zoom_button(nullptr),
+ zoom_out_button(nullptr),
+ load_image_button(nullptr),
+ match_image_button(nullptr),
+ create_node_dialog(nullptr),
+ code_previewer_dialog(nullptr),
+ code_previewer_layout(nullptr),
+ code_previewer(nullptr) {
+ VisualShaderEditor::init();
+}
+
+VisualShaderEditor::VisualShaderEditor(MessageModel* model, QWidget* parent)
+ : BaseEditor(model, parent),
+ visual_shader(nullptr),
+ layout(nullptr),
+ side_widget(nullptr),
+ side_outer_layout(nullptr),
+ side_layout(nullptr),
+ name_edit(nullptr),
+ save_button(nullptr),
+ scene_layer_layout(nullptr),
+ scene_layer(nullptr),
+ scene(nullptr),
+ view(nullptr),
+ top_layer(nullptr),
+ menu_bar(nullptr),
+ menu_button(nullptr),
+ create_node_button(nullptr),
+ preview_shader_button(nullptr),
+ create_node_action(nullptr),
+ zoom_in_button(nullptr),
+ reset_zoom_button(nullptr),
+ zoom_out_button(nullptr),
+ load_image_button(nullptr),
+ match_image_button(nullptr),
+ create_node_dialog(nullptr),
+ code_previewer_dialog(nullptr),
+ code_previewer_layout(nullptr),
+ code_previewer(nullptr) {
+ VisualShaderEditor::init();
+
+ _nodeMapper->addMapping(name_edit, TreeNode::kNameFieldNumber);
+ QObject::connect(save_button, &QAbstractButton::pressed, this, &BaseEditor::OnSave);
+ RebindSubModels();
+ visual_shader_model = _model->GetSubModel(TreeNode::kVisualShaderFieldNumber);
+}
+
+VisualShaderEditor::~VisualShaderEditor() {
+ if (visual_shader) delete visual_shader;
+}
+
+void VisualShaderEditor::init() {
+ visual_shader = new VisualShader();
+
+ // Create the main layout.
+ layout = new QHBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ layout->setSizeConstraint(QLayout::SetNoConstraint);
+ layout->setSpacing(5);
+ layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ //////////////// End of Header ////////////////
+
+ // Create the side widget
+ side_widget = new QWidget();
+ side_widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ side_widget->setContentsMargins(10, 10, 10, 10); // Left, top, right, bottom
+ side_widget->setVisible(false);
+
+ // Create the side outer layout
+ side_outer_layout = new QVBoxLayout(side_widget);
+ side_outer_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ side_outer_layout->setSpacing(5);
+ side_outer_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ side_outer_layout->setSizeConstraint(QLayout::SetNoConstraint);
+
+ // Add the side inner layout
+ side_layout = new QVBoxLayout();
+ side_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ side_layout->setSpacing(5);
+ side_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ side_layout->setSizeConstraint(QLayout::SetNoConstraint);
+
+ // Fill in the left layout
+ QHBoxLayout* name_layout = new QHBoxLayout();
+ name_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ name_layout->setSpacing(5);
+ name_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ name_layout->setSizeConstraint(QLayout::SetNoConstraint);
+
+ QLabel* name_label = new QLabel("Name");
+ name_layout->addWidget(name_label, 1);
+
+ name_edit = new QLineEdit();
+ name_layout->addWidget(name_edit, 4);
+
+ side_layout->addLayout(name_layout);
+
+ side_outer_layout->addLayout(side_layout);
+
+ save_button = new QPushButton("Save");
+ save_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ save_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ save_button->setToolTip("Save editor changes including the graph");
+ save_button->setIcon(QIcon(":/actions/accept.png"));
+ side_outer_layout->addWidget(save_button);
+
+ side_widget->setLayout(side_outer_layout);
+
+ // Create the scene layer.
+ scene_layer = new QWidget();
+ scene_layer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ scene_layer->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ // Create the scene layer layout.
+ scene_layer_layout = new QHBoxLayout(scene_layer);
+ scene_layer_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ scene_layer_layout->setSpacing(0);
+ scene_layer_layout->setSizeConstraint(QLayout::SetNoConstraint);
+ scene_layer_layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ scene = new VisualShaderGraphicsScene(visual_shader);
+ scene->set_editor(this);
+
+ view = new VisualShaderGraphicsView(scene, scene_layer);
+ view->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ VisualShaderEditor::init_graph(); // Must be called after a scene and a view are created.
+
+ scene_layer_layout->addWidget(view);
+
+ // Set the scene layer layout.
+ scene_layer->setLayout(scene_layer_layout);
+
+ // Create the menu bar layer on top of the scene layer.
+ top_layer = new QWidget(view);
+ top_layer->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ top_layer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ // Create the menu bar layout.
+ menu_bar = new QHBoxLayout(top_layer);
+ menu_bar->setContentsMargins(10, 10, 10, 10); // Left, top, right, bottom
+ menu_bar->setSpacing(5); // Adjust spacing as needed
+ menu_bar->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ menu_bar->setSizeConstraint(QLayout::SetMinimumSize);
+
+ // Create the menu button
+ menu_button = new QPushButton("Show Menu", top_layer);
+ menu_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ menu_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ menu_button->setToolTip("Toggle Menu");
+ menu_bar->addWidget(menu_button);
+ QObject::connect(menu_button, &QPushButton::pressed, this, &VisualShaderEditor::on_menu_button_pressed);
+
+ // Create the create node button.
+ create_node_button = new QPushButton("Create Node", top_layer);
+ create_node_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ create_node_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_node_button->setToolTip("Create a new node");
+ menu_bar->addWidget(create_node_button);
+ QObject::connect(create_node_button, &QPushButton::pressed, this, &VisualShaderEditor::on_create_node_button_pressed);
+
+ this->connect(this, &VisualShaderEditor::create_node_dialog_requested, this,
+ &VisualShaderEditor::show_create_node_dialog);
+
+ // Create the preview shader button.
+ preview_shader_button = new QPushButton("Preview Shader", top_layer);
+ preview_shader_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ preview_shader_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ preview_shader_button->setToolTip("Preview the expected generated shader code");
+ menu_bar->addWidget(preview_shader_button);
+ QObject::connect(preview_shader_button, &QPushButton::pressed, this,
+ &VisualShaderEditor::on_preview_shader_button_pressed);
+
+ zoom_in_button = new QPushButton("Zoom In", scene_layer);
+ zoom_in_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ zoom_in_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ zoom_in_button->setToolTip("Zoom In");
+ menu_bar->addWidget(zoom_in_button);
+ QObject::connect(zoom_in_button, &QPushButton::pressed, view, &VisualShaderGraphicsView::zoom_in);
+
+ reset_zoom_button = new QPushButton("Reset Zoom", scene_layer);
+ reset_zoom_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ reset_zoom_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ reset_zoom_button->setToolTip("Reset Zoom");
+ menu_bar->addWidget(reset_zoom_button);
+ QObject::connect(reset_zoom_button, &QPushButton::pressed, view, &VisualShaderGraphicsView::reset_zoom);
+
+ zoom_out_button = new QPushButton("Zoom Out", scene_layer);
+ zoom_out_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ zoom_out_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ zoom_out_button->setToolTip("Zoom Out");
+ menu_bar->addWidget(zoom_out_button);
+ QObject::connect(zoom_out_button, &QPushButton::pressed, view, &VisualShaderGraphicsView::zoom_out);
+
+ load_image_button = new QPushButton("Load Image", scene_layer);
+ load_image_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ load_image_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ load_image_button->setToolTip("Load an image to match");
+ menu_bar->addWidget(load_image_button);
+ QObject::connect(load_image_button, &QPushButton::pressed, this, &VisualShaderEditor::on_load_image_button_pressed);
+
+ match_image_button = new QPushButton("Match Image", scene_layer);
+ match_image_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ match_image_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ match_image_button->setToolTip("Match the shader to the loaded image");
+ menu_bar->addWidget(match_image_button);
+ QObject::connect(match_image_button, &QPushButton::pressed, this, &VisualShaderEditor::on_match_image_button_pressed);
+
+ // Set the top layer layout.
+ top_layer->setLayout(menu_bar);
+
+ // Add the left layout
+ layout->addWidget(side_widget, 1);
+
+ // Add the scene layer to the main layout.
+ layout->addWidget(scene_layer, 4);
+
+ ////////////////////////////////////
+ // Code Previewer
+ ////////////////////////////////////
+
+ code_previewer_dialog = new QDialog(this);
+ code_previewer_dialog->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ code_previewer_dialog->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ code_previewer_layout = new QVBoxLayout(code_previewer_dialog);
+ code_previewer_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ code_previewer_layout->setSpacing(0); // Adjust spacing as needed
+ code_previewer_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ code_previewer_layout->setSizeConstraint(QLayout::SetMinimumSize);
+
+ code_previewer = new QPlainTextEdit(code_previewer_dialog);
+ code_previewer->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ code_previewer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ code_previewer->setReadOnly(true);
+ code_previewer->setLineWrapMode(QPlainTextEdit::NoWrap);
+ code_previewer->setWordWrapMode(QTextOption::NoWrap);
+ code_previewer->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ code_previewer->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ code_previewer->setTabChangesFocus(true);
+ code_previewer->setMinimumSize(800, 600);
+
+ code_previewer_layout->addWidget(code_previewer);
+
+ code_previewer_dialog->setLayout(code_previewer_layout);
+
+ ////////////////////////////////////
+ // CreateNodeDialog Nodes Tree
+ ////////////////////////////////////
+
+ // Create the create node dialog under the main layout.
+ create_node_dialog = new CreateNodeDialog(this);
+ create_node_dialog->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_node_dialog->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ //////////////////////////////////////////
+ // CreateNodeDialog Nodes Tree Children
+ //////////////////////////////////////////
+
+ const VisualShaderEditor::CreateNodeDialogNodesTreeItem* items{
+ VisualShaderEditor::create_node_dialog_nodes_tree_items};
+
+ // Map to store category items
+ std::unordered_map category_path_map;
+
+ int i{0};
+
+ while (!items[i].type.empty()) {
+ const CreateNodeDialogNodesTreeItem& item{items[i]};
+
+ // Parse the category string into a vector of strings
+ std::vector categories{parse_node_category_path(item.category_path)};
+ QTreeWidgetItem* parent{nullptr}; // Start from the root
+
+ std::string current_category_path;
+ // Create/find each level of categories
+ for (const std::string& category : categories) {
+ if (!current_category_path.empty()) {
+ current_category_path += "/";
+ }
+
+ current_category_path += category;
+
+ parent = find_or_create_category_item(parent, category, current_category_path,
+ create_node_dialog->get_nodes_tree(), category_path_map);
+ }
+
+ // Now add the item to its corresponding parent category
+ QTreeWidgetItem* node_item = new QTreeWidgetItem(parent);
+ node_item->setText(0, QString::fromStdString(item.name));
+ node_item->setData(0, Qt::UserRole, QString::fromStdString(item.type));
+ node_item->setData(0, Qt::UserRole + 1, QString::fromStdString(item.description));
+
+ i++;
+ }
+
+ //////////////// Start of Footer ////////////////
+
+ this->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ // this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ // Set the window title and icon.
+ this->setWindowTitle("Visual Shader Editor");
+ // this->setWindowIcon(QIcon(":/resources/visual_shader.png"));
+ this->setLayout(layout);
+}
+
+void VisualShaderEditor::init_graph() {
+ // Load the nodes and connections from the VisualShader
+ std::vector ns{visual_shader->get_nodes()};
+ for (const int& n_id : ns) {
+ const std::shared_ptr n{visual_shader->get_node(n_id)};
+
+ if (!n) {
+ continue;
+ }
+
+ TVector2 c{visual_shader->get_node_coordinate(n_id)};
+
+ scene->add_node(n_id, n, {c.x, c.y});
+ }
+
+ std::vector cs{visual_shader->get_connections()};
+ for (const VisualShader::Connection& c : cs) {
+ scene->add_connection(c.from_node, c.from_port, c.to_node, c.to_port);
+ }
+}
+
+const VisualShaderEditor::CreateNodeDialogNodesTreeItem VisualShaderEditor::create_node_dialog_nodes_tree_items[] = {
+
+ // Input
+
+ {"Input", "Input/Basic", "VisualShaderNodeInput", "Input parameter."},
+
+ {"ColorConstant", "Input/Basic", "VisualShaderNodeColorConstant", "Color constant."},
+ {"BooleanConstant", "Input/Basic", "VisualShaderNodeBooleanConstant", "Boolean constant."},
+ {"FloatConstant", "Input/Basic", "VisualShaderNodeFloatConstant", "Scalar floating-point constant."},
+ {"IntConstant", "Input/Basic", "VisualShaderNodeIntConstant", "Scalar integer constant."},
+ {"UIntConstant", "Input/Basic", "VisualShaderNodeUIntConstant", "Scalar unsigned integer constant."},
+ {"Vector2Constant", "Input/Basic", "VisualShaderNodeVec2Constant", "2D vector constant."},
+ {"Vector3Constant", "Input/Basic", "VisualShaderNodeVec3Constant", "3D vector constant."},
+ {"Vector4Constant", "Input/Basic", "VisualShaderNodeVec4Constant", "4D vector constant."},
+
+ // Functions
+
+ {"FloatFunc", "Functions/Scalar", "VisualShaderNodeFloatFunc", "Float function."},
+ {"IntFunc", "Functions/Scalar", "VisualShaderNodeIntFunc", "Integer function."},
+ {"UIntFunc", "Functions/Scalar", "VisualShaderNodeUIntFunc", "Unsigned integer function."},
+ {"VectorFunc", "Functions/Vector", "VisualShaderNodeVectorFunc", "Vector function."},
+ {"DerivativeFunc", "Functions/Others", "VisualShaderNodeDerivativeFunc", "Derivative function."},
+ {"Step", "Functions/Others", "VisualShaderNodeStep",
+ "Step function( scalar(edge), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."},
+ {"SmoothStep", "Functions/Others", "VisualShaderNodeSmoothStep",
+ "SmoothStep function( scalar(edge0), scalar(edge1), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' "
+ "and 1.0 if x is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using "
+ "Hermite polynomials."},
+ {"Dot", "Functions/Others", "VisualShaderNodeDotProduct", "Calculates the dot product of two vectors."},
+
+ // Operators
+
+ {"FloatOp", "Operators/Scalar", "VisualShaderNodeFloatOp", "Float operator."},
+ {"IntOp", "Operators/Scalar", "VisualShaderNodeIntOp", "Integer operator."},
+ {"UIntOp", "Operators/Scalar", "VisualShaderNodeUIntOp", "Unsigned integer operator."},
+ {"VectorOp", "Operators/Vector", "VisualShaderNodeVectorOp", "Vector operator."},
+ {"VectorCompose", "Operators/Vector", "VisualShaderNodeVectorCompose", "Composes vector from scalars."},
+ {"VectorDecompose", "Operators/Vector", "VisualShaderNodeVectorDecompose", "Decomposes vector to scalars."},
+
+ // Procedural
+
+ {"ValueNoise", "Procedural/Noise", "VisualShaderNodeValueNoise",
+ "Generates a simple, or Value, noise based on input 'UV'. The scale of the generated noise is controlled by input "
+ "'Scale'."},
+ {"PerlinNoise", "Procedural/Noise", "VisualShaderNodePerlinNoise",
+ "Generates a gradient, or Perlin, noise based on input 'UV'. The scale of the generated noise is controlled by "
+ "input 'Scale'."},
+ {"VoronoiNoise", "Procedural/Noise", "VisualShaderNodeVoronoiNoise",
+ "Generates a Voronoi, or Worley, noise based on input 'UV'. Voronoi noise is generated by calculating distances "
+ "between a pixel and a lattice of points. By offsetting these points by a pseudo-random number, controlled by "
+ "input 'Angle Offset', a cluster of cells can be generated. The scale of these cells, and the resulting noise, is "
+ "controlled by input 'Cell Density'. The output 'Cells' contains the raw cell data."},
+
+ // Utility
+
+ {"Compare", "Utility/Logic", "VisualShaderNodeCompare",
+ "Returns the boolean result of the comparison between two parameters."},
+ {"If", "Utility/Logic", "VisualShaderNodeIf",
+ "Returns the value of the 'True' or 'False' input based on the value of the 'Condition' input."},
+ {"Switch", "Utility/Logic", "VisualShaderNodeSwitch",
+ "Returns an associated scalar if the provided boolean value is true or false."},
+ {"Is", "Utility/Logic", "VisualShaderNodeIs",
+ "Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."},
+
+ {"", "", "", ""},
+};
+
+void VisualShaderEditor::create_node(const QPointF& coordinate) {
+ QTreeWidgetItem* selected_item{create_node_dialog->get_selected_item()};
+
+ if (!selected_item) {
+ return;
+ }
+
+ VisualShaderEditor::add_node(selected_item, coordinate);
+}
+
+void VisualShaderEditor::add_node(QTreeWidgetItem* selected_item, const QPointF& coordinate) {
+ std::string type{selected_item->data(0, Qt::UserRole).toString().toStdString()};
+
+ if (type.empty()) {
+ return;
+ }
+
+ scene->add_node(type, coordinate);
+}
+
+void VisualShaderEditor::show_create_node_dialog(const QPointF& coordinate) {
+ int status{create_node_dialog->exec()};
+ switch (status) {
+ case QDialog::Accepted:
+ std::cout << "Create node dialog accepted" << std::endl;
+ VisualShaderEditor::create_node(coordinate);
+ break;
+ case QDialog::Rejected:
+ std::cout << "Create node dialog rejected" << std::endl;
+ break;
+ default:
+ std::cout << "Create node dialog unknown status" << std::endl;
+ break;
+ }
+}
+
+void VisualShaderEditor::on_create_node_button_pressed() {
+ // Send the center of the current view port
+ QPointF coordinate{view->mapToScene(view->viewport()->rect().center())};
+
+ Q_EMIT create_node_dialog_requested(coordinate);
+}
+
+void VisualShaderEditor::on_preview_shader_button_pressed() {
+ bool result{visual_shader->generate_shader()};
+ if (!result) {
+ std::cout << "Failed to generate shader" << std::endl;
+ return;
+ }
+ code_previewer->setPlainText(QString::fromStdString(visual_shader->get_code()));
+ code_previewer_dialog->exec();
+}
+
+void VisualShaderEditor::on_menu_button_pressed() {
+ bool is_visible{side_widget->isVisible()};
+ side_widget->setVisible(!is_visible);
+ menu_button->setText(!is_visible ? "Hide Menu" : "Show Menu");
+}
+
+void VisualShaderEditor::on_load_image_button_pressed() {
+ // TODO: Decide on how to load an image
+ // For example, use QFileDialog to open an image file or
+ // load an existing sprite or background from the project.
+ // Then, send the image to OriginalMatchingImageWidget widget to display it.
+ // R0bert — 27/09/2024 at 22:10
+ // i would use resource picker and let user pick a sprite or background that exists in the project
+ // Josh — 27/09/2024 at 22:13
+ // sprites have multiple frames, which is a headache for this project because it's a lot more behavior we need to define
+}
+
+void VisualShaderEditor::on_match_image_button_pressed() {}
+
+std::vector VisualShaderEditor::parse_node_category_path(const std::string& node_category_path) {
+ std::vector tokens;
+ std::stringstream ss(node_category_path);
+ std::string token;
+ while (std::getline(ss, token, '/')) {
+ tokens.push_back(token);
+ }
+ return tokens;
+}
+
+QTreeWidgetItem* VisualShaderEditor::find_or_create_category_item(
+ QTreeWidgetItem* parent, const std::string& category, const std::string& category_path,
+ QTreeWidget* create_node_dialog_nodes_tree, std::unordered_map& category_path_map) {
+ // Check if category already exists under parent
+ if (category_path_map.find(category_path) != category_path_map.end()) {
+ return category_path_map[category_path];
+ }
+
+ // Create a new QTreeWidgetItem
+ QTreeWidgetItem* new_item;
+
+ if (parent) {
+ new_item = new QTreeWidgetItem(parent);
+ } else {
+ new_item = new QTreeWidgetItem(create_node_dialog_nodes_tree);
+ }
+
+ new_item->setText(0, QString::fromStdString(category));
+
+ // Add the new category to the map
+ category_path_map[category_path] = new_item;
+
+ return new_item;
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** CreateNodeDialog *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+CreateNodeDialog::CreateNodeDialog(QWidget* parent)
+ : QDialog(parent),
+ layout(nullptr),
+ create_node_dialog_nodes_tree_layout(nullptr),
+ create_node_dialog_nodes_tree(nullptr),
+ create_node_dialog_nodes_description(nullptr),
+ buttons_layout(nullptr),
+ create_button(nullptr),
+ cancel_button(nullptr),
+ selected_item(nullptr) {
+ layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ layout->setSizeConstraint(QLayout::SetNoConstraint);
+ layout->setSpacing(0);
+ layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ //////////////// End of Header ////////////////
+
+ // Create the nodes tree layout.
+ create_node_dialog_nodes_tree_layout = new QVBoxLayout();
+ create_node_dialog_nodes_tree_layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_node_dialog_nodes_tree_layout->setSpacing(0); // Adjust spacing as needed
+ create_node_dialog_nodes_tree_layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+ create_node_dialog_nodes_tree_layout->setSizeConstraint(QLayout::SetMinimumSize);
+
+ // Add the nodes tree layout to the main layout.
+ layout->addLayout(create_node_dialog_nodes_tree_layout);
+
+ // Create the nodes tree.
+ create_node_dialog_nodes_tree = new QTreeWidget();
+ create_node_dialog_nodes_tree->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ create_node_dialog_nodes_tree->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_node_dialog_nodes_tree->setColumnCount(1);
+ create_node_dialog_nodes_tree->setHeaderHidden(true);
+ this->connect(create_node_dialog_nodes_tree, &QTreeWidget::itemSelectionChanged, this,
+ &CreateNodeDialog::update_selected_item);
+
+ // Add the nodes tree to the nodes tree layout.
+ create_node_dialog_nodes_tree_layout->addWidget(create_node_dialog_nodes_tree,
+ 2); // 2x the size of the nodes description.
+
+ // Create the nodes description.
+ create_node_dialog_nodes_description = new QTextEdit();
+ create_node_dialog_nodes_description->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ create_node_dialog_nodes_description->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_node_dialog_nodes_description->setReadOnly(true);
+ create_node_dialog_nodes_description->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+
+ // Add the nodes description to the nodes tree layout.
+ create_node_dialog_nodes_tree_layout->addWidget(create_node_dialog_nodes_description, 1);
+
+ // Create the buttons layout.
+ buttons_layout = new QHBoxLayout();
+ layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ layout->setSizeConstraint(QLayout::SetNoConstraint);
+ layout->setSpacing(0);
+ layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ create_button = new QPushButton("Create");
+ QObject::connect(create_button, &QPushButton::pressed, this, &CreateNodeDialog::on_create_node_button_pressed);
+ create_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ create_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ create_button->setToolTip("Create the selected node.");
+
+ cancel_button = new QPushButton("Cancel");
+ QObject::connect(cancel_button, &QPushButton::pressed, this,
+ &CreateNodeDialog::on_cancel_node_creation_button_pressed);
+ cancel_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ cancel_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ cancel_button->setToolTip("Cancel the node creation.");
+
+ // Add the buttons to the buttons layout.
+ buttons_layout->addWidget(create_button);
+ buttons_layout->addWidget(cancel_button);
+
+ // Add the buttons layout to the main layout.
+ layout->addLayout(buttons_layout);
+
+ //////////////// Start of Footer ////////////////
+
+ this->setWindowTitle("Create Shader Node");
+
+ this->setLayout(layout);
+}
+
+CreateNodeDialog::~CreateNodeDialog() {}
+
+void CreateNodeDialog::on_create_node_button_pressed() { this->accept(); }
+
+void CreateNodeDialog::on_cancel_node_creation_button_pressed() { this->reject(); }
+
+void CreateNodeDialog::update_selected_item() {
+ QTreeWidgetItem* item{create_node_dialog_nodes_tree->currentItem()};
+ if (item) {
+ selected_item = item;
+ create_node_dialog_nodes_description->setText(item->data(0, Qt::UserRole + 1).toString());
+ } else {
+ selected_item = nullptr;
+ create_node_dialog_nodes_description->setText("");
+ }
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** ShaderPreviewerWidget *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+ShaderPreviewerWidget::ShaderPreviewerWidget(QWidget* parent)
+ : QOpenGLWidget(parent), shader_program(nullptr), VAO(0), VBO(0) {}
+
+ShaderPreviewerWidget::~ShaderPreviewerWidget() {}
+
+void ShaderPreviewerWidget::set_code(const std::string& new_code) {
+ if (new_code == code) return;
+
+ code = new_code;
+ shader_needs_update = true;
+ if (isVisible()) {
+ update_shader_program();
+ timer.restart();
+ }
+}
+
+void ShaderPreviewerWidget::initializeGL() {
+ QOpenGLFunctions_4_3_Core* f{QOpenGLContext::currentContext()->versionFunctions()};
+
+ if (!f) {
+ qWarning() << "Failed to get OpenGL 4.3 functions";
+ return;
+ }
+
+ if (!f->initializeOpenGLFunctions()) {
+ qWarning() << "Failed to initialize OpenGL functions";
+ return;
+ }
+
+ f->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black background
+ init_buffers();
+ init_shaders();
+
+ timer.start();
+
+ connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ShaderPreviewerWidget::cleanup);
+}
+
+void ShaderPreviewerWidget::resizeGL(int w, int h) {
+ QOpenGLFunctions_4_3_Core* f{QOpenGLContext::currentContext()->versionFunctions()};
+
+ if (!f) {
+ qWarning() << "Failed to get OpenGL 4.3 functions";
+ return;
+ }
+
+ f->glViewport(0, 0, w, h);
+}
+
+void ShaderPreviewerWidget::paintGL() {
+ // Check https://doc.qt.io/qt-5/qopenglwidget.html#isValid
+ // At start, the widget is hidden so this call returns false which results
+ // in returning (no painting happens).
+ if (!isValid()) return;
+
+ QOpenGLFunctions_4_3_Core* f{QOpenGLContext::currentContext()->versionFunctions()};
+
+ if (!f) {
+ qWarning() << "Failed to get OpenGL 4.3 functions";
+ return;
+ }
+
+ if (shader_needs_update) {
+ update_shader_program();
+ }
+
+ if (!shader_program || !shader_program->isLinked()) {
+ qWarning() << "Shader program is not linked.";
+ return;
+ }
+
+ float time_value{timer.elapsed() * 0.001f};
+
+ f->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ f->glClear(GL_COLOR_BUFFER_BIT);
+
+ shader_program->bind();
+ shader_program->setUniformValue("uTime", time_value);
+
+ f->glBindVertexArray(VAO);
+ f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ f->glBindVertexArray(0);
+
+ shader_program->release();
+
+ update(); // Request a repaint
+ Q_EMIT scene_update_requested();
+}
+
+void ShaderPreviewerWidget::cleanup() {
+ makeCurrent();
+
+ QOpenGLFunctions_4_3_Core* f{QOpenGLContext::currentContext()->versionFunctions()};
+
+ if (!f) {
+ qWarning() << "Failed to get OpenGL 4.3 functions";
+ return;
+ }
+
+ f->glDeleteVertexArrays(1, &VAO);
+ f->glDeleteBuffers(1, &VBO);
+}
+
+void ShaderPreviewerWidget::init_buffers() {
+ QOpenGLFunctions_4_3_Core* f{QOpenGLContext::currentContext()->versionFunctions()};
+
+ if (!f) {
+ qWarning() << "Failed to get OpenGL 4.3 functions";
+ return;
+ }
+
+ float vertices[] = {
+ // coordinates // frag coords
+ -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f};
+
+ f->glGenVertexArrays(1, &VAO);
+ f->glGenBuffers(1, &VBO);
+
+ f->glBindVertexArray(VAO);
+
+ f->glBindBuffer(GL_ARRAY_BUFFER, VBO);
+ f->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
+ f->glEnableVertexAttribArray(0);
+
+ f->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
+ f->glEnableVertexAttribArray(1);
+
+ f->glBindVertexArray(0);
+}
+
+void ShaderPreviewerWidget::update_shader_program() {
+ shader_program.reset(new QOpenGLShaderProgram());
+
+ const char* vertex_shader_source = R"(
+ #version 330 core
+ layout(location = 0) in vec2 aPos;
+ layout(location = 1) in vec2 aFragCoord;
+
+ out vec2 FragCoord;
+
+ void main() {
+ gl_Position = vec4(aPos, 0.0, 1.0);
+ FragCoord = aFragCoord;
+ }
+ )";
+
+ std::string fragment_shader_source{code.empty() ? R"(
+ #version 330 core
+ out vec4 FragColor;
+ in vec2 FragCoord;
+
+ uniform float uTime;
+
+ void main() {
+ FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+ }
+ )"
+ : "#version 330 core\n\n" + code};
+
+ if (!shader_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex_shader_source)) {
+ qWarning() << "Vertex shader compilation failed:" << shader_program->log();
+ }
+
+ if (!shader_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment_shader_source.c_str())) {
+ qWarning() << "Fragment shader compilation failed:" << shader_program->log();
+ }
+
+ if (!shader_program->link()) {
+ qWarning() << "Shader program linking failed:" << shader_program->log();
+ }
+
+ shader_needs_update = false;
+}
+
+void ShaderPreviewerWidget::init_shaders() { update_shader_program(); }
+
+void ShaderPreviewerWidget::showEvent(QShowEvent* event) {
+ QOpenGLWidget::showEvent(event);
+ if (!timer.isValid()) {
+ // See https://doc.qt.io/qt-5/qelapsedtimer.html#start.
+ timer.start(); // Start the timer on first show
+ }
+}
+
+void ShaderPreviewerWidget::hideEvent(QHideEvent* event) {
+ QOpenGLWidget::hideEvent(event);
+ // See https://doc.qt.io/qt-5/qelapsedtimer.html#invalidate.
+ timer.invalidate();
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderGraphicsScene *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+//////////////////////////////
+// Public functions
+//////////////////////////////
+
+VisualShaderGraphicsScene::VisualShaderGraphicsScene(VisualShader* vs, QObject* parent)
+ : QGraphicsScene(parent), vs(vs), temporary_connection_graphics_object(nullptr) {
+ setItemIndexMethod(QGraphicsScene::NoIndex); // https://doc.qt.io/qt-6/qgraphicsscene.html#ItemIndexMethod-enum
+}
+
+VisualShaderGraphicsScene::~VisualShaderGraphicsScene() {}
+
+bool VisualShaderGraphicsScene::add_node(const std::string& type, const QPointF& coordinate) {
+ // Instantiate the node based on the type
+ std::shared_ptr n;
+
+ if (type == "VisualShaderNodeInput") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeColorConstant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeBooleanConstant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeFloatConstant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeIntConstant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeUIntConstant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVec2Constant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVec3Constant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVec4Constant") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeFloatFunc") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeIntFunc") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeUIntFunc") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeDerivativeFunc") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeFloatOp") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeIntOp") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeUIntOp") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeValueNoise") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodePerlinNoise") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVoronoiNoise") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVectorFunc") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVectorOp") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVectorCompose") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeVectorDecompose") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeCompare") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeIf") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeIs") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeSwitch") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeStep") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeSmoothStep") {
+ n = std::make_shared();
+ } else if (type == "VisualShaderNodeDotProduct") {
+ n = std::make_shared();
+ } else {
+ std::cout << "Unknown node type: " << type << std::endl;
+ }
+
+ if (!n) {
+ std::cout << "Failed to create node of type: " << type << std::endl;
+ return false;
+ }
+
+ int n_id{vs->get_valid_node_id()};
+
+ if (n_id == (int)VisualShader::NODE_ID_INVALID) {
+ return false;
+ }
+
+ return VisualShaderGraphicsScene::add_node(n_id, n, coordinate);
+}
+
+bool VisualShaderGraphicsScene::add_node(const int& n_id, const std::shared_ptr& n,
+ const QPointF& coordinate) {
+ // Make sure the node doesn't already exist, we don't want to overwrite a node.
+ if (node_graphics_objects.find(n_id) != node_graphics_objects.end()) {
+ return false;
+ }
+
+ QList views{this->views()};
+ if (views.isEmpty()) {
+ std::cout << "No views available" << std::endl;
+ return false;
+ }
+
+ // The output node cannot be removed or added by the user
+ if (n_id >= (int)VisualShader::NODE_ID_OUTPUT + 1) {
+ bool result{vs->add_node(n, {(float)coordinate.x(), (float)coordinate.y()}, n_id)};
+
+ if (!result) {
+ return false;
+ }
+ }
+
+ VisualShaderGraphicsView* view{dynamic_cast(views.first())};
+
+ if (vs->get_node_coordinate(n_id).x < view->get_x() ||
+ vs->get_node_coordinate(n_id).x > view->get_x() + view->get_width() ||
+ vs->get_node_coordinate(n_id).y < view->get_y() ||
+ vs->get_node_coordinate(n_id).y > view->get_y() + view->get_height()) {
+ std::cout << "Node is out of view bounds" << std::endl;
+ }
+
+ VisualShaderNodeGraphicsObject* n_o{new VisualShaderNodeGraphicsObject(n_id, coordinate, n)};
+
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::node_moved, this, &VisualShaderGraphicsScene::on_node_moved);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::in_port_pressed, this,
+ &VisualShaderGraphicsScene::on_port_pressed);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::in_port_dragged, this,
+ &VisualShaderGraphicsScene::on_port_dragged);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::in_port_dropped, this,
+ &VisualShaderGraphicsScene::on_port_dropped);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::out_port_pressed, this,
+ &VisualShaderGraphicsScene::on_port_pressed);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::out_port_dragged, this,
+ &VisualShaderGraphicsScene::on_port_dragged);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::out_port_dropped, this,
+ &VisualShaderGraphicsScene::on_port_dropped);
+
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::scene_update_requested, this,
+ &VisualShaderGraphicsScene::on_scene_update_requested);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::in_port_remove_requested, this,
+ &VisualShaderGraphicsScene::on_in_port_remove_requested);
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::out_port_remove_requested, this,
+ &VisualShaderGraphicsScene::on_out_port_remove_requested);
+
+ if (n_id != (int)VisualShader::NODE_ID_OUTPUT) {
+ VisualShaderNodeEmbedWidget* embed_widget{new VisualShaderNodeEmbedWidget(n)};
+ QGraphicsProxyWidget* embed_widget_proxy{new QGraphicsProxyWidget(n_o)};
+ embed_widget_proxy->setWidget(embed_widget);
+ n_o->set_embed_widget(embed_widget);
+ QObject::connect(embed_widget, &VisualShaderNodeEmbedWidget::shader_preview_update_requested, this,
+ &VisualShaderGraphicsScene::on_update_shader_previewer_widgets_requested);
+
+ QObject::connect(embed_widget, &VisualShaderNodeEmbedWidget::node_update_requested, n_o,
+ &VisualShaderNodeGraphicsObject::on_node_update_requested);
+
+ // Send the shader previewer widget
+ embed_widget->set_shader_previewer_widget(n_o->get_shader_previewer_widget());
+ }
+
+ if (ShaderPreviewerWidget * spw{n_o->get_shader_previewer_widget()}) {
+ QObject::connect(spw, &ShaderPreviewerWidget::scene_update_requested, this,
+ &VisualShaderGraphicsScene::on_scene_update_requested);
+ }
+
+ QObject::connect(n_o, &VisualShaderNodeGraphicsObject::node_deleted, this,
+ &VisualShaderGraphicsScene::on_node_deleted);
+
+ node_graphics_objects[n_id] = n_o;
+
+ addItem(n_o);
+
+ return true;
+}
+
+bool VisualShaderGraphicsScene::delete_node(const int& n_id) {
+ const std::shared_ptr n{vs->get_node(n_id)};
+
+ if (!n) {
+ return false;
+ }
+
+ VisualShaderNodeGraphicsObject* n_o{this->get_node_graphics_object(n_id)};
+
+ if (!n_o) {
+ return false;
+ }
+
+ // Remove all connections to the node
+ for (int i{0}; i < n->get_input_port_count(); i++) {
+ VisualShaderInputPortGraphicsObject* i_port{n_o->get_input_port_graphics_object(i)};
+
+ if (!i_port || !i_port->is_connected()) {
+ continue;
+ }
+
+ // Get the output port of the connection
+ VisualShaderConnectionGraphicsObject* c_o{i_port->get_connection_graphics_object()};
+
+ if (!c_o) {
+ continue;
+ }
+
+ bool result{this->delete_connection(c_o->get_from_node_id(), c_o->get_from_port_index(), n_id, i)};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ continue;
+ }
+ }
+
+ for (int i{0}; i < n->get_output_port_count(); i++) {
+ VisualShaderOutputPortGraphicsObject* o_port{n_o->get_output_port_graphics_object(i)};
+
+ if (!o_port || !o_port->is_connected()) {
+ continue;
+ }
+
+ std::vector c_os{o_port->get_connection_graphics_objects()};
+
+ for (VisualShaderConnectionGraphicsObject* c_o : c_os) {
+ if (!c_o) {
+ continue;
+ }
+
+ bool result{this->delete_connection(n_id, i, c_o->get_to_node_id(), c_o->get_to_port_index())};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ continue;
+ }
+ }
+ }
+
+ bool result{vs->remove_node(n_id)};
+
+ if (!result) {
+ return false;
+ }
+
+ // Remove the node from the scene
+ // TODO: Why if we exchange the order of these two lines, the program crashes?
+ this->node_graphics_objects.erase(n_id);
+ remove_item(n_o);
+
+ return true;
+}
+
+void VisualShaderGraphicsScene::on_update_shader_previewer_widgets_requested() {
+ for (auto& [n_id, n_o] : node_graphics_objects) {
+ if (n_id == (int)VisualShader::NODE_ID_OUTPUT) {
+ continue;
+ }
+
+ ShaderPreviewerWidget* spw{n_o->get_shader_previewer_widget()};
+ if (!spw) {
+ continue;
+ }
+
+ spw->set_code(vs->generate_preview_shader(n_id, 0)); // 0 is the output port index
+ }
+
+ on_scene_update_requested();
+}
+
+void VisualShaderGraphicsScene::on_scene_update_requested() { update(); }
+
+void VisualShaderGraphicsScene::on_in_port_remove_requested(VisualShaderInputPortGraphicsObject* in_port) {
+ if (in_port->is_connected()) {
+ VisualShaderConnectionGraphicsObject* c_o{in_port->get_connection_graphics_object()};
+ delete_connection(c_o->get_from_node_id(), c_o->get_from_port_index(), c_o->get_to_node_id(),
+ c_o->get_to_port_index());
+ }
+
+ remove_item(in_port);
+}
+
+void VisualShaderGraphicsScene::on_out_port_remove_requested(VisualShaderOutputPortGraphicsObject* out_port) {
+ if (out_port->is_connected()) {
+ std::vector c_os{out_port->get_connection_graphics_objects()};
+ for (VisualShaderConnectionGraphicsObject* c_o : c_os) {
+ delete_connection(c_o->get_from_node_id(), c_o->get_from_port_index(), c_o->get_to_node_id(),
+ c_o->get_to_port_index());
+ }
+ }
+
+ remove_item(out_port);
+}
+
+bool VisualShaderGraphicsScene::add_connection(const int& from_node_id, const int& from_port_index,
+ const int& to_node_id, const int& to_port_index) {
+ QList views{this->views()};
+ if (views.isEmpty()) {
+ std::cout << "No views available" << std::endl;
+ return false;
+ }
+
+ // Create the connection and set its start
+ VisualShaderNodeGraphicsObject* from_n_o{this->get_node_graphics_object(from_node_id)};
+
+ if (!from_n_o) {
+ return false;
+ }
+
+ VisualShaderOutputPortGraphicsObject* from_o_port{from_n_o->get_output_port_graphics_object(from_port_index)};
+
+ if (!from_o_port) {
+ return false;
+ }
+
+ VisualShaderGraphicsView* view{dynamic_cast(views.first())};
+
+ if (from_o_port->get_global_coordinate().x() < view->get_x() ||
+ from_o_port->get_global_coordinate().x() > view->get_x() + view->get_width()) {
+ std::cout << "Start of connection is out of view bounds" << std::endl;
+ }
+
+ if (!this->temporary_connection_graphics_object) {
+ this->temporary_connection_graphics_object =
+ new VisualShaderConnectionGraphicsObject(from_node_id, from_port_index, from_o_port->get_global_coordinate());
+ from_o_port->connect(this->temporary_connection_graphics_object);
+ addItem(this->temporary_connection_graphics_object);
+ return true;
+ }
+
+ if (to_node_id != (int)VisualShader::NODE_ID_INVALID && to_port_index != (int)VisualShader::PORT_INDEX_INVALID) {
+ // Set the end of the connection
+ VisualShaderNodeGraphicsObject* to_n_o{this->get_node_graphics_object(to_node_id)};
+
+ if (!to_n_o) {
+ return false;
+ }
+
+ VisualShaderInputPortGraphicsObject* to_i_port{to_n_o->get_input_port_graphics_object(to_port_index)};
+
+ if (!to_i_port) {
+ return false;
+ }
+
+ if (to_i_port->get_global_coordinate().y() < view->get_y() ||
+ to_i_port->get_global_coordinate().y() > view->get_y() + view->get_height()) {
+ std::cout << "End of connection is out of view bounds" << std::endl;
+ }
+
+ // Connect the nodes in the VisualShader
+ bool result{vs->can_connect_nodes(from_node_id, from_port_index, to_node_id, to_port_index)};
+ if (!result) {
+ std::cout << "Can't connect nodes" << std::endl;
+ return false;
+ }
+
+ result = vs->connect_nodes(from_node_id, from_port_index, to_node_id, to_port_index);
+ if (!result) {
+ std::cout << "Failed to connect nodes" << std::endl;
+ return false;
+ }
+
+ this->temporary_connection_graphics_object->set_end_coordinate(to_i_port->get_global_coordinate());
+ to_i_port->connect(this->temporary_connection_graphics_object);
+ this->temporary_connection_graphics_object->set_to_node_id(to_node_id);
+ this->temporary_connection_graphics_object->set_to_port_index(to_port_index);
+ this->temporary_connection_graphics_object = nullptr; // Make sure to reset the temporary connection object
+
+ on_update_shader_previewer_widgets_requested();
+
+ return true;
+ }
+
+ return false;
+}
+
+bool VisualShaderGraphicsScene::delete_connection(const int& from_node_id, const int& from_port_index,
+ const int& to_node_id, const int& to_port_index) {
+ VisualShaderNodeGraphicsObject* from_n_o{this->get_node_graphics_object(from_node_id)};
+
+ if (!from_n_o) {
+ return false;
+ }
+
+ VisualShaderOutputPortGraphicsObject* from_o_port{from_n_o->get_output_port_graphics_object(from_port_index)};
+
+ if (!from_o_port) {
+ return false;
+ }
+
+ if (this->temporary_connection_graphics_object) {
+ from_o_port->detach_connection(this->temporary_connection_graphics_object);
+ remove_item(this->temporary_connection_graphics_object);
+ this->temporary_connection_graphics_object = nullptr;
+ return true;
+ }
+
+ // If we have a complete connection, then we can disconnect the nodes
+ if (to_node_id != (int)VisualShader::NODE_ID_INVALID && to_port_index != (int)VisualShader::PORT_INDEX_INVALID) {
+ VisualShaderConnectionGraphicsObject* c_o{from_o_port->get_connection_graphics_object(to_node_id, to_port_index)};
+
+ if (!c_o) {
+ return false;
+ }
+
+ VisualShaderNodeGraphicsObject* to_n_o{this->get_node_graphics_object(to_node_id)};
+
+ if (!to_n_o) {
+ return false;
+ }
+
+ VisualShaderInputPortGraphicsObject* to_i_port{to_n_o->get_input_port_graphics_object(to_port_index)};
+
+ if (!to_i_port) {
+ return false;
+ }
+
+ bool result{vs->disconnect_nodes(from_node_id, from_port_index, to_node_id, to_port_index)};
+
+ if (!result) {
+ return false;
+ }
+
+ to_i_port->detach_connection();
+ from_o_port->detach_connection(c_o);
+ remove_item(c_o);
+
+ on_update_shader_previewer_widgets_requested();
+
+ return true;
+ }
+
+ return false;
+}
+
+VisualShaderNodeGraphicsObject* VisualShaderGraphicsScene::get_node_graphics_object(const int& n_id) const {
+ VisualShaderNodeGraphicsObject* n_o{nullptr};
+
+ auto it{node_graphics_objects.find(n_id)};
+ if (it != node_graphics_objects.end()) {
+ n_o = it->second;
+ }
+
+ return n_o;
+}
+
+void VisualShaderGraphicsScene::on_node_moved(const int& n_id, const QPointF& new_coordinate) {
+ const std::shared_ptr n{vs->get_node(n_id)};
+
+ if (!n) {
+ return;
+ }
+
+ // Update the node's coordinate in the VisualShader
+ vs->set_node_coordinate(n_id, {(float)new_coordinate.x(), (float)new_coordinate.y()});
+
+ // Update coordinates of all connected connections
+ VisualShaderNodeGraphicsObject* n_o{this->get_node_graphics_object(n_id)};
+
+ for (int i{0}; i < n->get_input_port_count(); i++) {
+ VisualShaderInputPortGraphicsObject* i_port{n_o->get_input_port_graphics_object(i)};
+
+ if (!i_port || !i_port->is_connected()) {
+ continue;
+ }
+
+ VisualShaderConnectionGraphicsObject* c_o{i_port->get_connection_graphics_object()};
+
+ if (!c_o) {
+ continue;
+ }
+
+ c_o->set_end_coordinate(i_port->get_global_coordinate());
+ }
+
+ for (int i{0}; i < n->get_output_port_count(); i++) {
+ VisualShaderOutputPortGraphicsObject* o_port{n_o->get_output_port_graphics_object(i)};
+
+ if (!o_port || !o_port->is_connected()) {
+ continue;
+ }
+
+ std::vector c_os{o_port->get_connection_graphics_objects()};
+
+ for (VisualShaderConnectionGraphicsObject* c_o : c_os) {
+ if (!c_o) {
+ continue;
+ }
+
+ c_o->set_start_coordinate(o_port->get_global_coordinate());
+ }
+ }
+}
+
+void VisualShaderGraphicsScene::on_node_deleted(const int& n_id) {
+ if (n_id == (int)VisualShader::NODE_ID_OUTPUT) {
+ return;
+ }
+
+ bool result{this->delete_node(n_id)};
+
+ if (!result) {
+ std::cout << "Failed to delete node" << std::endl;
+ }
+}
+
+void VisualShaderGraphicsScene::on_port_pressed(QGraphicsObject* port, const QPointF& coordinate) {
+ this->temporary_connection_graphics_object = nullptr; // Reset the temporary connection object
+}
+
+void VisualShaderGraphicsScene::on_port_dragged(QGraphicsObject* port, const QPointF& coordinate) {
+ VisualShaderConnectionGraphicsObject* c_o{nullptr};
+
+ VisualShaderOutputPortGraphicsObject* o_port{dynamic_cast(port)};
+
+ if (!o_port) {
+ VisualShaderInputPortGraphicsObject* i_port{dynamic_cast(port)};
+
+ if (!i_port) {
+ return;
+ }
+
+ if (i_port->is_connected() && !temporary_connection_graphics_object) {
+ c_o = i_port->get_connection_graphics_object();
+ temporary_connection_graphics_object = c_o; // Store the connection object for access in the next drag call
+ bool result{vs->disconnect_nodes(c_o->get_from_node_id(), c_o->get_from_port_index(), c_o->get_to_node_id(),
+ c_o->get_to_port_index())};
+ if (!result) {
+ std::cout << "Failed to disconnect nodes" << std::endl;
+ }
+ i_port->detach_connection();
+ c_o->detach_end();
+
+ on_update_shader_previewer_widgets_requested();
+ } else if (!i_port->is_connected() && temporary_connection_graphics_object) {
+ c_o = temporary_connection_graphics_object;
+ on_scene_update_requested();
+ } else {
+ return;
+ }
+
+ c_o->set_end_coordinate(coordinate);
+
+ return;
+ }
+
+ if (o_port->is_connected() && temporary_connection_graphics_object) {
+ c_o = temporary_connection_graphics_object;
+ } else if (!temporary_connection_graphics_object) {
+ bool result{this->add_connection(o_port->get_node_id(), o_port->get_port_index())};
+ if (!result) {
+ std::cout << "Failed to add connection" << std::endl;
+ return;
+ }
+ c_o = temporary_connection_graphics_object;
+ } else {
+ return;
+ }
+
+ c_o->set_end_coordinate(coordinate);
+}
+
+void VisualShaderGraphicsScene::on_port_dropped(QGraphicsObject* port, const QPointF& coordinate) {
+ if (!temporary_connection_graphics_object) {
+ return;
+ }
+
+ // Find all items under the coordinate
+ QList items_at_coordinate{this->items(coordinate)};
+
+ // Iterate through the items and check if an input port is under the mouse
+ VisualShaderInputPortGraphicsObject* in_p_o{nullptr};
+ for (QGraphicsItem* item : items_at_coordinate) {
+ // Check if the item is an input port
+ in_p_o = dynamic_cast(item);
+
+ if (in_p_o) {
+ break;
+ }
+ }
+
+ VisualShaderOutputPortGraphicsObject* o_port{dynamic_cast(port)};
+
+ if (!o_port) {
+ VisualShaderInputPortGraphicsObject* i_port{dynamic_cast(port)};
+
+ if (!i_port) {
+ return;
+ }
+
+ if (!in_p_o) {
+ bool result{this->delete_connection(temporary_connection_graphics_object->get_from_node_id(),
+ temporary_connection_graphics_object->get_from_port_index())};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ }
+
+ return; // Return because we dragging an input port and dropped on nothing
+ }
+
+ bool result{add_connection(temporary_connection_graphics_object->get_from_node_id(),
+ temporary_connection_graphics_object->get_from_port_index(), in_p_o->get_node_id(),
+ in_p_o->get_port_index())};
+
+ if (!result) {
+ bool result{this->delete_connection(temporary_connection_graphics_object->get_from_node_id(),
+ temporary_connection_graphics_object->get_from_port_index())};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ }
+
+ return; // Return because we dragging an input port and dropped on nothing
+ }
+
+ return;
+ }
+
+ if (!in_p_o) {
+ bool result{this->delete_connection(temporary_connection_graphics_object->get_from_node_id(),
+ temporary_connection_graphics_object->get_from_port_index())};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ }
+
+ return; // Return because we dragging an output port and dropped on nothing
+ }
+
+ bool result{
+ add_connection(o_port->get_node_id(), o_port->get_port_index(), in_p_o->get_node_id(), in_p_o->get_port_index())};
+
+ if (!result) {
+ bool result{this->delete_connection(temporary_connection_graphics_object->get_from_node_id(),
+ temporary_connection_graphics_object->get_from_port_index())};
+
+ if (!result) {
+ std::cout << "Failed to delete connection" << std::endl;
+ }
+
+ return;
+ }
+}
+
+void VisualShaderGraphicsScene::remove_item(QGraphicsItem* item) {
+ removeItem(item);
+ delete item;
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderGraphicsView *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+//////////////////////////////
+// Public functions
+//////////////////////////////
+
+VisualShaderGraphicsView::VisualShaderGraphicsView(VisualShaderGraphicsScene* scene, QWidget* parent)
+ : QGraphicsView(scene, parent),
+ scene(scene),
+ context_menu(nullptr),
+ create_node_action(nullptr),
+ zoom_in_action(nullptr),
+ reset_zoom_action(nullptr),
+ zoom_out_action(nullptr) {
+ setDragMode(QGraphicsView::ScrollHandDrag);
+ setRenderHint(QPainter::Antialiasing);
+
+ setBackgroundBrush(this->background_color);
+
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
+
+ setCacheMode(QGraphicsView::CacheBackground);
+ setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
+
+ // Allow dezooming 8 times from the default zoom level.
+ zoom_min = (1.0f / std::pow(zoom_step, 8.0f));
+ // Allow zooming 4 times from the default zoom level.
+ zoom_max = (1.0f * std::pow(zoom_step, 4.0f));
+
+ setSceneRect(rect_x, rect_y, rect_width, rect_height);
+
+ reset_zoom();
+
+ // Set the context menu
+ context_menu = new QMenu(this);
+ create_node_action = new QAction(QStringLiteral("Create Node"), context_menu);
+ QObject::connect(create_node_action, &QAction::triggered, this,
+ &VisualShaderGraphicsView::on_create_node_action_triggered);
+ context_menu->addAction(create_node_action);
+
+ zoom_in_action = new QAction(QStringLiteral("Zoom In"), context_menu);
+ zoom_in_action->setShortcutContext(Qt::ShortcutContext::WidgetShortcut);
+ zoom_in_action->setShortcut(QKeySequence(QKeySequence::ZoomIn));
+ QObject::connect(zoom_in_action, &QAction::triggered, this, &VisualShaderGraphicsView::zoom_in);
+ context_menu->addAction(zoom_in_action);
+
+ reset_zoom_action = new QAction(QStringLiteral("Reset Zoom"), context_menu);
+ QObject::connect(reset_zoom_action, &QAction::triggered, this, &VisualShaderGraphicsView::reset_zoom);
+ context_menu->addAction(reset_zoom_action);
+
+ zoom_out_action = new QAction(QStringLiteral("Zoom Out"), context_menu);
+ zoom_out_action->setShortcutContext(Qt::ShortcutContext::WidgetShortcut);
+ zoom_out_action->setShortcut(QKeySequence(QKeySequence::ZoomOut));
+ QObject::connect(zoom_out_action, &QAction::triggered, this, &VisualShaderGraphicsView::zoom_out);
+ context_menu->addAction(zoom_out_action);
+}
+
+VisualShaderGraphicsView::~VisualShaderGraphicsView() {}
+
+//////////////////////////////
+// Private slots
+//////////////////////////////
+
+void VisualShaderGraphicsView::on_create_node_action_triggered() {
+ VisualShaderEditor* editor{scene->get_editor()};
+
+ Q_EMIT editor->create_node_dialog_requested(this->last_context_menu_coordinate);
+}
+
+void VisualShaderGraphicsView::zoom_in() {
+ const float factor{std::pow(zoom_step, zoom)};
+
+ QTransform t{transform()};
+ t.scale(factor, factor);
+ if (t.m11() >= zoom_max) {
+ return;
+ }
+
+ scale(factor, factor);
+ Q_EMIT zoom_changed(transform().m11());
+}
+
+void VisualShaderGraphicsView::reset_zoom() {
+ if ((float)transform().m11() == zoom) {
+ return;
+ }
+
+ resetTransform(); // Reset the zoom level to 1.0f
+ Q_EMIT zoom_changed(transform().m11());
+}
+
+void VisualShaderGraphicsView::zoom_out() {
+ const float factor{std::pow(zoom_step, -1.0f * zoom)};
+
+ QTransform t{transform()};
+ t.scale(factor, factor);
+ if (t.m11() <= zoom_min) {
+ return;
+ }
+
+ scale(factor, factor);
+ Q_EMIT zoom_changed(transform().m11());
+}
+
+//////////////////////////////
+// Private functions
+//////////////////////////////
+
+void VisualShaderGraphicsView::drawBackground(QPainter* painter, const QRectF& r) {
+ QGraphicsView::drawBackground(painter, r);
+
+ std::function draw_grid = [&](float grid_step) {
+ QRect window_rect{this->rect()};
+
+ QPointF tl{mapToScene(window_rect.topLeft())};
+ QPointF br{mapToScene(window_rect.bottomRight())};
+
+ float left{std::floor((float)tl.x() / grid_step)};
+ float right{std::ceil((float)br.x() / grid_step)};
+ float bottom{std::floor((float)tl.y() / grid_step)};
+ float top{std::ceil((float)br.y() / grid_step)};
+
+ // Vertical lines
+ for (int xi{(int)left}; xi <= (int)right; ++xi) {
+ QLineF line(xi * grid_step, bottom * grid_step, xi * grid_step, top * grid_step);
+ painter->drawLine(line);
+ }
+
+ // Horizontal lines
+ for (int yi{(int)bottom}; yi <= (int)top; ++yi) {
+ QLineF line(left * grid_step, yi * grid_step, right * grid_step, yi * grid_step);
+ painter->drawLine(line);
+ }
+ };
+
+ QPen fine_pen(this->fine_grid_color, 1.0f);
+ painter->setPen(fine_pen);
+ draw_grid(15.0f);
+
+ QPen coarse_pen(this->coarse_grid_color, 1.0f);
+ painter->setPen(coarse_pen);
+ draw_grid(150.0f);
+}
+
+void VisualShaderGraphicsView::contextMenuEvent(QContextMenuEvent* event) {
+ QGraphicsItem* item{itemAt(event->pos())};
+
+ // If there is an item and this item is a node object, pass the event to the
+ // children
+ if (item && dynamic_cast(item)) {
+ QGraphicsView::contextMenuEvent(event);
+ return;
+ } else if (item) {
+ return;
+ }
+
+ this->last_context_menu_coordinate = mapToScene(event->pos());
+
+ context_menu->exec(event->globalPos());
+}
+
+void VisualShaderGraphicsView::wheelEvent(QWheelEvent* event) {
+ float t_zoom{(float)transform().m11()};
+
+ const QPoint delta{event->angleDelta()};
+
+ if (delta.y() == 0) {
+ event->ignore();
+ return;
+ }
+
+ if (delta.y() > 0 && (std::abs(t_zoom - zoom_max) > std::numeric_limits::epsilon())) {
+ zoom_in();
+ } else if (delta.y() < 0 && (std::abs(t_zoom - zoom_min) > std::numeric_limits::epsilon())) {
+ zoom_out();
+ } else {
+ event->ignore();
+ }
+}
+
+void VisualShaderGraphicsView::mousePressEvent(QMouseEvent* event) {
+ QGraphicsView::mousePressEvent(event);
+
+ switch (event->button()) {
+ case Qt::LeftButton:
+ last_click_coordinate = mapToScene(event->pos());
+ break;
+ default:
+ break;
+ }
+}
+
+void VisualShaderGraphicsView::mouseMoveEvent(QMouseEvent* event) {
+ QGraphicsView::mouseMoveEvent(event);
+
+ switch (event->buttons()) {
+ case Qt::LeftButton: {
+ QPointF current_coordinate{mapToScene(event->pos())};
+ QPointF difference{last_click_coordinate - current_coordinate};
+ setSceneRect(sceneRect().translated(difference.x(), difference.y()));
+ last_click_coordinate = current_coordinate;
+ } break;
+ default:
+ break;
+ }
+}
+
+void VisualShaderGraphicsView::mouseReleaseEvent(QMouseEvent* event) { QGraphicsView::mouseReleaseEvent(event); }
+
+void VisualShaderGraphicsView::showEvent(QShowEvent* event) {
+ QGraphicsView::showEvent(event);
+
+ move_view_to_fit_items();
+}
+
+void VisualShaderGraphicsView::move_view_to_fit_items() {
+ if (!scene) {
+ return;
+ }
+
+ if (scene->items().isEmpty()) {
+ return;
+ }
+
+ std::cout << "Changing view port to fit items..." << std::endl;
+
+ QRectF items_bounding_rect{scene->itemsBoundingRect()};
+ items_bounding_rect.adjust(-fit_in_view_margin, -fit_in_view_margin, fit_in_view_margin, fit_in_view_margin);
+
+ QPointF scene_tl{scene->sceneRect().topLeft()};
+ QPointF scene_br{scene->sceneRect().bottomRight()};
+ QPointF items_tl{items_bounding_rect.topLeft()};
+ QPointF items_br{items_bounding_rect.bottomRight()};
+
+ // Make sure the items bounding rect is inside the scene rect
+ if (items_tl.x() < scene_tl.x()) {
+ items_bounding_rect.setLeft(scene_tl.x());
+ }
+
+ if (items_tl.y() > scene_tl.y()) {
+ items_bounding_rect.setTop(scene_tl.y());
+ }
+
+ if (items_br.x() > scene_br.x()) {
+ items_bounding_rect.setRight(scene_br.x());
+ }
+
+ if (items_br.y() < scene_br.y()) {
+ items_bounding_rect.setBottom(scene_br.y());
+ }
+
+ fitInView(items_bounding_rect, Qt::KeepAspectRatio);
+
+ centerOn(items_bounding_rect.center());
+
+ if ((float)transform().m11() > zoom_max) {
+ std::cout << "Current zoom level is greater than the maximum zoom level." << std::endl;
+ std::cout << "Maybe due to having a very large distance between the nodes." << std::endl;
+ }
+
+ if ((float)transform().m11() < zoom_min) {
+ std::cout << "Current zoom level is less than the minimum zoom level." << std::endl;
+ std::cout << "Maybe due to having all the nodes outside the scene bounds." << std::endl;
+ }
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderNodeGraphicsObject *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+VisualShaderNodeGraphicsObject::VisualShaderNodeGraphicsObject(const int& n_id, const QPointF& coordinate,
+ const std::shared_ptr& node,
+ QGraphicsItem* parent)
+ : QGraphicsObject(parent),
+ n_id(n_id),
+ coordinate(coordinate),
+ node(node),
+ context_menu(nullptr),
+ delete_node_action(nullptr),
+ rect_width(0.0f),
+ caption_rect_height(0.0f),
+ rect_height(0.0f),
+ rect_margin(0.0f),
+ rect_padding(0.0f),
+ embed_widget(nullptr),
+ matching_image_widget(nullptr),
+ shader_previewer_widget(nullptr) {
+ setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true);
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setFlag(QGraphicsItem::ItemIsMovable, true);
+ setFlag(QGraphicsItem::ItemIsSelectable, true);
+ setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true);
+
+ setCacheMode(QGraphicsItem::DeviceCoordinateCache);
+
+ setVisible(true);
+ setOpacity(this->opacity);
+
+ setZValue(0);
+
+ setPos(coordinate.x(), coordinate.y());
+
+ // Output node should have a matching image widget
+ if (n_id == (int)VisualShader::NODE_ID_OUTPUT) {
+ QGraphicsProxyWidget* matching_image_widget_proxy{new QGraphicsProxyWidget(this)};
+ matching_image_widget = new OriginalMatchingImageWidget();
+ matching_image_widget_proxy->setWidget(matching_image_widget);
+ } else {
+ // Create the shader previewer widget
+ QGraphicsProxyWidget* shader_previewer_widget_proxy{new QGraphicsProxyWidget(this)};
+ shader_previewer_widget = new ShaderPreviewerWidget();
+ shader_previewer_widget->setVisible(false);
+ shader_previewer_widget_proxy->setWidget(shader_previewer_widget);
+ }
+
+ // Set the context menu
+ context_menu = new QMenu();
+ delete_node_action = new QAction(QStringLiteral("Delete Node"), context_menu);
+ delete_node_action->setShortcutContext(Qt::ShortcutContext::WidgetShortcut);
+ delete_node_action->setShortcut(QKeySequence(QKeySequence::Delete));
+ QObject::connect(delete_node_action, &QAction::triggered, this,
+ &VisualShaderNodeGraphicsObject::on_delete_node_action_triggered);
+ context_menu->addAction(delete_node_action);
+}
+
+VisualShaderNodeGraphicsObject::~VisualShaderNodeGraphicsObject() {
+ if (context_menu) delete context_menu;
+}
+
+void VisualShaderNodeGraphicsObject::on_delete_node_action_triggered() { Q_EMIT node_deleted(n_id); }
+
+VisualShaderInputPortGraphicsObject* VisualShaderNodeGraphicsObject::get_input_port_graphics_object(
+ const int& p_index) const {
+ if (in_port_graphics_objects.find(p_index) != in_port_graphics_objects.end()) {
+ return in_port_graphics_objects.at(p_index);
+ }
+
+ return nullptr;
+}
+
+VisualShaderOutputPortGraphicsObject* VisualShaderNodeGraphicsObject::get_output_port_graphics_object(
+ const int& p_index) const {
+ if (out_port_graphics_objects.find(p_index) != out_port_graphics_objects.end()) {
+ return out_port_graphics_objects.at(p_index);
+ }
+
+ return nullptr;
+}
+
+void VisualShaderNodeGraphicsObject::on_node_update_requested() {
+ // Here if the number of ports changed, for example, changing the operation type in a decompose node,
+ // we need to remove any extra ports that are not needed anymore. Don't forget to remove the connections
+ // as well.
+ if (in_port_graphics_objects.size() > node->get_input_port_count()) {
+ int p_index{node->get_input_port_count()};
+ int size{(int)in_port_graphics_objects.size() - p_index};
+ for (int i{0}; i < size; ++i) {
+ Q_EMIT in_port_remove_requested(in_port_graphics_objects.at(p_index));
+ in_port_graphics_objects.erase(p_index);
+ p_index++;
+ }
+ }
+
+ if (out_port_graphics_objects.size() > node->get_output_port_count()) {
+ int p_index{node->get_output_port_count()};
+ int size{(int)out_port_graphics_objects.size() - p_index};
+ for (int i{0}; i < size; ++i) {
+ Q_EMIT out_port_remove_requested(out_port_graphics_objects.at(p_index));
+ out_port_graphics_objects.erase(p_index);
+ p_index++;
+ }
+ }
+
+ update();
+ Q_EMIT scene_update_requested();
+}
+
+QRectF VisualShaderNodeGraphicsObject::boundingRect() const {
+ QFont f("Arial", caption_font_size);
+ f.setBold(true);
+ QFontMetrics fm(f);
+
+ QString caption{QString::fromStdString(node->get_caption())};
+
+ rect_width = (float)(fm.horizontalAdvance(caption, caption.length()) + caption_h_padding * 2.0f);
+
+ caption_rect_height = (float)((fm.height()) + caption_v_padding * 2.0f);
+
+ int max_num_ports{qMax(node->get_input_port_count(), node->get_output_port_count())};
+
+ // Calculate the height of the node
+ float t_rect_h{caption_rect_height};
+
+ t_rect_h += body_rect_header_height; // Header
+ if (max_num_ports >= 0) {
+ t_rect_h += (float)(max_num_ports - 1) * body_rect_port_step; // Ports
+ }
+ t_rect_h += body_rect_footer_height; // Footer
+
+ rect_height = t_rect_h;
+
+ // Correct node rect if it has an embed widget (if needed).
+ if (embed_widget) {
+ float embed_widget_width{(float)embed_widget->width()};
+
+ // Find biggest horizontal length of input port names
+ int in_p_count{node->get_input_port_count()};
+ float max_in_p_width{0.0f};
+ for (unsigned i{0}; i < in_p_count; ++i) {
+ QString p_n{QString::fromStdString(node->get_input_port_name(i))};
+
+ QFont f("Arial", port_caption_font_size);
+ QFontMetrics fm(f);
+
+ float w{0.0f};
+
+ if (!p_n.isEmpty()) {
+ w = (float)fm.horizontalAdvance(p_n);
+ } else {
+ // If the port name is empty, use a default name
+ // This is because the horizontal advance of an empty string is 0 and
+ // this will wrong the calculation of the rect width
+ w = (float)fm.horizontalAdvance("Input");
+ }
+
+ if ((w + port_caption_spacing) > max_in_p_width) {
+ max_in_p_width = w + port_caption_spacing;
+ }
+ }
+
+ // Find biggest horizontal length of output port names
+ int out_p_count{node->get_output_port_count()};
+ float max_out_p_width{0.0f};
+ for (unsigned i{0}; i < out_p_count; ++i) {
+ QString p_n{QString::fromStdString(node->get_output_port_name(i))};
+
+ QFont f("Arial", port_caption_font_size);
+ QFontMetrics fm(f);
+
+ float w{0.0f};
+
+ if (!p_n.isEmpty()) {
+ w = (float)fm.horizontalAdvance(p_n);
+ } else {
+ // If the port name is empty, use a default name
+ // This is because the horizontal advance of an empty string is 0 and
+ // this will wrong the calculation of the rect width
+ w = (float)fm.horizontalAdvance("Output");
+ }
+
+ if ((w + port_caption_spacing) > max_out_p_width) {
+ max_out_p_width = w + port_caption_spacing;
+ }
+ }
+
+ float calculated_rect{max_in_p_width + embed_widget_width + max_out_p_width + embed_widget_h_padding * 2.0f};
+
+ if (calculated_rect > rect_width) {
+ rect_width = calculated_rect;
+ }
+
+ // Check the height
+ float calculated_height{caption_rect_height + body_rect_header_height + embed_widget->height() +
+ body_rect_footer_height + embed_widget_v_padding * 2.0f};
+
+ if (calculated_height > rect_height) {
+ rect_height = calculated_height;
+ }
+ }
+
+ QRectF r({0.0f, 0.0f}, QSizeF(rect_width, rect_height));
+
+ // Calculate the margin
+ this->rect_margin = port_diameter * 0.5f;
+
+ // Calculate the rect padding
+ // We add a safe area around the rect to prevent the ports from being cut off
+ this->rect_padding = port_diameter * 0.5f;
+
+ r.adjust(-rect_margin - rect_padding, -rect_margin - rect_padding, rect_margin + rect_padding,
+ rect_margin + rect_padding);
+
+ return r;
+}
+
+void VisualShaderNodeGraphicsObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
+ painter->setClipRect(option->exposedRect);
+
+ // Get the rect without the padding
+ QRectF r{this->boundingRect()};
+
+ // {
+ // // Draw Node Rect
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(r);
+ // }
+
+ // Add the padding to the rect
+ r.adjust(rect_padding, rect_padding, -rect_padding, -rect_padding);
+
+ {
+ // Draw Node Rect
+ QColor rect_color;
+ if (isSelected()) {
+ rect_color = this->normal_boundary_color;
+ } else {
+ rect_color = this->selected_boundary_color;
+ }
+
+ QPen p(rect_color, this->pen_width);
+ painter->setPen(p);
+
+ painter->setBrush(this->fill_color);
+
+ painter->drawRoundedRect(r, this->corner_radius, this->corner_radius);
+ }
+
+ // Draw Matching Image Widget
+ if (n_id == (int)VisualShader::NODE_ID_OUTPUT) {
+ float matching_image_widget_x{(float)r.x() + (float)r.width() + spacing_between_output_node_and_matching_image};
+ float matching_image_widget_y{(float)r.y()};
+
+ matching_image_widget->setGeometry(matching_image_widget_x, matching_image_widget_y, r.height(), r.height());
+ } else {
+ // Draw Shader Previewer Widget
+ float shader_previewer_widget_x{(float)r.x()};
+ float shader_previewer_widget_y{(float)r.y() + (float)r.height() +
+ spacing_between_current_node_and_shader_previewer};
+ shader_previewer_widget->setGeometry(shader_previewer_widget_x, shader_previewer_widget_y, r.width(), r.width());
+ }
+
+ // Add the margin to the rect
+ r.adjust(rect_margin, rect_margin, -rect_margin, -rect_margin);
+
+ // {
+ // // Draw Node Rect
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(r);
+ // }
+
+ float rect_x{(float)r.topLeft().x()};
+ float rect_y{(float)r.topLeft().y()};
+
+ float rect_w{(float)r.width()};
+ float rect_h{(float)r.height()};
+
+ QRectF caption_rect(rect_x, rect_y, rect_w, caption_rect_height);
+
+ {
+ // Draw Caption
+ QString caption{QString::fromStdString(node->get_caption())};
+
+ QFont t_f{painter->font()};
+
+ QFont f("Arial", caption_font_size);
+ f.setBold(true);
+ QFontMetrics fm(f);
+ painter->setFont(f);
+
+ // Calculate the coordinates of the caption
+ float x{(float)(caption_rect.center().x() - (float)fm.horizontalAdvance(caption) * 0.5f)};
+
+ // Instead of subtracting, add the ascent to properly align text within the rect
+ float y{(float)(caption_rect.center().y() + (float)((fm.ascent() + fm.descent()) * 0.5f - fm.descent()))};
+
+ // {
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(QRectF(x,y, (float)fm.horizontalAdvance(caption), (float)(fm.ascent() + fm.descent())));
+ // }
+
+ QPointF coordinate{x, y};
+
+ painter->setPen(this->font_color);
+ painter->drawText(coordinate, caption);
+
+ painter->setFont(t_f); // Reset the font
+ }
+
+ QPointF caption_rect_bl{caption_rect.bottomLeft()};
+ QPointF first_in_port_coordinate{caption_rect_bl.x(), caption_rect_bl.y() + body_rect_header_height};
+
+ // Correct X coordinate: Remove the margin
+ first_in_port_coordinate.setX((float)first_in_port_coordinate.x() - this->rect_margin);
+
+ {
+ // Draw Input Ports
+ int in_port_count{node->get_input_port_count()};
+
+ for (unsigned i{0}; i < in_port_count; ++i) {
+ QPointF port_coordinate{first_in_port_coordinate.x(), first_in_port_coordinate.y() + body_rect_port_step * i};
+
+ QRectF port_rect(port_coordinate.x(), port_coordinate.y(), port_diameter, port_diameter);
+
+ // Adjust the port rect to be centered
+ port_rect.adjust(-port_rect.width() * 0.5f, -port_rect.height() * 0.5f, -port_rect.width() * 0.5f,
+ -port_rect.height() * 0.5f);
+
+ // Draw caption
+ QString p_n{QString::fromStdString(node->get_input_port_name(i))};
+
+ if (!p_n.isEmpty()) {
+ QFont t_f{painter->font()};
+
+ QFont f("Arial", port_caption_font_size);
+ QFontMetrics fm(f);
+ painter->setFont(f);
+
+ float x{rect_x + port_caption_spacing};
+
+ float y{(float)(port_rect.center().y()) + (float)((fm.ascent() + fm.descent()) * 0.5f - fm.descent())};
+
+ // {
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(QRectF(x,y, (float)fm.horizontalAdvance(p_n), (float)(fm.ascent() + fm.descent())));
+ // }
+
+ QPointF coordinate{x, y};
+
+ painter->setPen(this->font_color);
+ painter->drawText(coordinate, p_n);
+
+ painter->setFont(t_f); // Reset the font
+ }
+
+ if (in_port_graphics_objects.find(i) != in_port_graphics_objects.end()) continue;
+
+ // Draw the port
+ VisualShaderInputPortGraphicsObject* p_o{new VisualShaderInputPortGraphicsObject(port_rect, n_id, i, this)};
+ in_port_graphics_objects[i] = p_o;
+
+ // Connect the signals
+ QObject::connect(p_o, &VisualShaderInputPortGraphicsObject::port_pressed, this,
+ &VisualShaderNodeGraphicsObject::on_in_port_pressed);
+ QObject::connect(p_o, &VisualShaderInputPortGraphicsObject::port_dragged, this,
+ &VisualShaderNodeGraphicsObject::on_in_port_dragged);
+ QObject::connect(p_o, &VisualShaderInputPortGraphicsObject::port_dropped, this,
+ &VisualShaderNodeGraphicsObject::on_in_port_dropped);
+ }
+ }
+
+ QPointF caption_rect_br{caption_rect.bottomRight()};
+ QPointF first_out_port_coordinate{caption_rect_br.x(), caption_rect_br.y() + body_rect_header_height};
+
+ // Correct X coordinate: Remove the margin
+ first_out_port_coordinate.setX((float)first_out_port_coordinate.x() + this->rect_margin);
+
+ {
+ // Draw Output Ports
+ int out_port_count{node->get_output_port_count()};
+
+ for (unsigned i{0}; i < out_port_count; ++i) {
+ QPointF port_coordinate{first_out_port_coordinate.x(), first_out_port_coordinate.y() + body_rect_port_step * i};
+
+ QRectF port_rect(port_coordinate.x(), port_coordinate.y(), port_diameter, port_diameter);
+
+ // Adjust the port rect to be centered
+ port_rect.adjust(-port_rect.width() * 0.5f, -port_rect.height() * 0.5f, -port_rect.width() * 0.5f,
+ -port_rect.height() * 0.5f);
+
+ // Draw caption
+ QString p_n{QString::fromStdString(node->get_output_port_name(i))};
+
+ if (!p_n.isEmpty()) {
+ QFont t_f{painter->font()};
+
+ QFont f("Arial", port_caption_font_size);
+ QFontMetrics fm(f);
+ painter->setFont(f);
+
+ float x{rect_x + rect_w - (float)fm.horizontalAdvance(p_n) - port_caption_spacing};
+
+ float y{(float)(port_rect.center().y()) + (float)((fm.ascent() + fm.descent()) * 0.5f - fm.descent())};
+
+ // {
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(QRectF(x,y, (float)fm.horizontalAdvance(p_n), (float)(fm.ascent() + fm.descent())));
+ // }
+
+ QPointF coordinate{x, y};
+
+ painter->setPen(this->font_color);
+ painter->drawText(coordinate, p_n);
+
+ painter->setFont(t_f); // Reset the font
+ }
+
+ if (out_port_graphics_objects.find(i) != out_port_graphics_objects.end()) continue;
+
+ // Draw the port
+ VisualShaderOutputPortGraphicsObject* p_o{new VisualShaderOutputPortGraphicsObject(port_rect, n_id, i, this)};
+ out_port_graphics_objects[i] = p_o;
+
+ // Connect the signals
+ QObject::connect(p_o, &VisualShaderOutputPortGraphicsObject::port_pressed, this,
+ &VisualShaderNodeGraphicsObject::on_out_port_pressed);
+ QObject::connect(p_o, &VisualShaderOutputPortGraphicsObject::port_dragged, this,
+ &VisualShaderNodeGraphicsObject::on_out_port_dragged);
+ QObject::connect(p_o, &VisualShaderOutputPortGraphicsObject::port_dropped, this,
+ &VisualShaderNodeGraphicsObject::on_out_port_dropped);
+ }
+ }
+
+ {
+ // Correct the coordinate of the embed widget
+ if (embed_widget) {
+ float embed_widget_x{rect_x + rect_w * 0.5f - embed_widget->width() * 0.5f};
+ float embed_widget_y{rect_y + caption_rect_height + body_rect_header_height + embed_widget_v_padding};
+
+ embed_widget->setGeometry(embed_widget_x, embed_widget_y, embed_widget->width(), embed_widget->height());
+
+ // {
+ // // Draw Embed Widget
+ // painter->setPen(Qt::red);
+ // painter->setBrush(Qt::NoBrush);
+ // painter->drawRect(embed_widget_x, embed_widget_y, embed_widget->width(), embed_widget->height());
+ // }
+ }
+ }
+}
+
+QVariant VisualShaderNodeGraphicsObject::itemChange(GraphicsItemChange change, const QVariant& value) {
+ if (change == ItemScenePositionHasChanged) {
+ Q_EMIT node_moved(n_id, scenePos());
+ }
+
+ return QGraphicsObject::itemChange(change, value);
+}
+
+void VisualShaderNodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
+ context_menu->exec(event->screenPos());
+}
+
+VisualShaderInputPortGraphicsObject::VisualShaderInputPortGraphicsObject(const QRectF& rect, const int& n_id,
+ const int& p_index, QGraphicsItem* parent)
+ : QGraphicsObject(parent), rect(rect), n_id(n_id), p_index(p_index), connection_graphics_object(nullptr) {
+ setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true);
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setFlag(QGraphicsItem::ItemIsSelectable, true);
+
+ setCursor(Qt::PointingHandCursor);
+
+ setCacheMode(QGraphicsItem::DeviceCoordinateCache);
+
+ setVisible(true);
+ setOpacity(this->opacity);
+
+ setZValue(0);
+}
+
+VisualShaderInputPortGraphicsObject::~VisualShaderInputPortGraphicsObject() {}
+
+QRectF VisualShaderInputPortGraphicsObject::boundingRect() const {
+ QRectF t_rect{rect};
+ t_rect.adjust(-padding, -padding, padding, padding);
+ return t_rect;
+}
+
+void VisualShaderInputPortGraphicsObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
+ QWidget* widget) {
+ painter->setClipRect(option->exposedRect);
+
+ painter->setBrush(this->connection_point_color);
+
+ painter->drawEllipse(rect);
+}
+
+void VisualShaderInputPortGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_pressed(this, event->scenePos());
+ QGraphicsObject::mousePressEvent(event);
+}
+
+void VisualShaderInputPortGraphicsObject::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_dragged(this, event->scenePos());
+ QGraphicsObject::mouseMoveEvent(event);
+}
+
+void VisualShaderInputPortGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_dropped(this, event->scenePos());
+ QGraphicsObject::mouseReleaseEvent(event);
+}
+
+VisualShaderOutputPortGraphicsObject::VisualShaderOutputPortGraphicsObject(const QRectF& rect, const int& n_id,
+ const int& p_index, QGraphicsItem* parent)
+ : QGraphicsObject(parent), rect(rect), n_id(n_id), p_index(p_index) {
+ setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true);
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setFlag(QGraphicsItem::ItemIsSelectable, true);
+
+ setCursor(Qt::PointingHandCursor);
+
+ setCacheMode(QGraphicsItem::DeviceCoordinateCache);
+
+ setVisible(true);
+ setOpacity(this->opacity);
+
+ setZValue(0);
+}
+
+VisualShaderOutputPortGraphicsObject::~VisualShaderOutputPortGraphicsObject() {}
+
+VisualShaderConnectionGraphicsObject* VisualShaderOutputPortGraphicsObject::get_connection_graphics_object(
+ const int& to_node_id, const int& to_port_index) const {
+ for (auto c_g_o : connection_graphics_objects) {
+ if (c_g_o->get_to_node_id() == to_node_id && c_g_o->get_to_port_index() == to_port_index) {
+ return c_g_o;
+ }
+ }
+ return nullptr;
+}
+
+QRectF VisualShaderOutputPortGraphicsObject::boundingRect() const {
+ QRectF t_rect{rect};
+ t_rect.adjust(-padding, -padding, padding, padding);
+ return t_rect;
+}
+
+void VisualShaderOutputPortGraphicsObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
+ QWidget* widget) {
+ painter->setClipRect(option->exposedRect);
+
+ painter->setBrush(this->connection_point_color);
+
+ painter->drawEllipse(rect);
+}
+
+void VisualShaderOutputPortGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_pressed(this, event->scenePos());
+ QGraphicsObject::mousePressEvent(event);
+}
+
+void VisualShaderOutputPortGraphicsObject::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_dragged(this, event->scenePos());
+ QGraphicsObject::mouseMoveEvent(event);
+}
+
+void VisualShaderOutputPortGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
+ Q_EMIT port_dropped(this, event->scenePos());
+ QGraphicsObject::mouseReleaseEvent(event);
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderConnectionGraphicsObject *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+VisualShaderConnectionGraphicsObject::VisualShaderConnectionGraphicsObject(const int& from_n_id,
+ const int& from_p_index,
+ const QPointF& start_coordinate,
+ QGraphicsItem* parent)
+ : QGraphicsObject(parent),
+ from_n_id(from_n_id),
+ from_p_index(from_p_index),
+ to_n_id((int)VisualShader::NODE_ID_INVALID),
+ to_p_index((int)VisualShader::PORT_INDEX_INVALID),
+ start_coordinate(start_coordinate),
+ end_coordinate(start_coordinate),
+ rect_padding(0.0f) {
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setFlag(QGraphicsItem::ItemIsSelectable, true);
+ setAcceptedMouseButtons(Qt::NoButton);
+
+ setZValue(-1.0f);
+}
+
+VisualShaderConnectionGraphicsObject::~VisualShaderConnectionGraphicsObject() {}
+
+QRectF VisualShaderConnectionGraphicsObject::boundingRect() const {
+ QRectF r{calculate_bounding_rect_from_coordinates(start_coordinate, end_coordinate)};
+
+ // Calculate the rect padding
+ // We add a safe area around the rect to prevent the ports from being cut off
+ // Due to inaccuracy in the calculation of the bounding rect we use the point diameter not the radius
+ this->rect_padding = this->point_diameter;
+
+ r.adjust(-rect_padding, -rect_padding, rect_padding, rect_padding);
+
+ return r;
+}
+
+void VisualShaderConnectionGraphicsObject::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
+ QWidget* widget) {
+ painter->setClipRect(option->exposedRect);
+
+ {
+ // Draw the connection
+ QPen p;
+ p.setWidth(this->line_width);
+
+ const bool selected{this->isSelected()};
+
+ std::pair control_points{calculate_control_points(start_coordinate, end_coordinate)};
+
+ QPainterPath cubic(start_coordinate);
+ cubic.cubicTo(control_points.first, control_points.second, end_coordinate);
+
+ p.setColor(this->normal_color);
+
+ if (selected) {
+ p.setColor(this->selected_color);
+ }
+
+ painter->setPen(p);
+ painter->setBrush(Qt::NoBrush);
+
+ painter->drawPath(cubic);
+ }
+
+ painter->setBrush(this->connection_point_color);
+
+ {
+ // Draw start point
+ QRectF start_rect(start_coordinate.x(), start_coordinate.y(), this->point_diameter, this->point_diameter);
+
+ // Adjust the port rect to be centered
+ start_rect.adjust(-start_rect.width() * 0.5f, -start_rect.height() * 0.5f, -start_rect.width() * 0.5f,
+ -start_rect.height() * 0.5f);
+
+ painter->drawEllipse(start_rect);
+ }
+
+ {
+ // Draw end point
+ QRectF end_rect(end_coordinate.x(), end_coordinate.y(), this->point_diameter, this->point_diameter);
+
+ // Adjust the port rect to be centered
+ end_rect.adjust(-end_rect.width() * 0.5f, -end_rect.height() * 0.5f, -end_rect.width() * 0.5f,
+ -end_rect.height() * 0.5f);
+
+ painter->drawEllipse(end_rect);
+ }
+}
+
+int VisualShaderConnectionGraphicsObject::detect_quadrant(const QPointF& reference, const QPointF& target) const {
+ float relative_x{(float)(target.x() - reference.x())};
+ float relative_y{(float)(target.y() - reference.y())};
+
+ // Note that the default coordinate system in Qt is as follows:
+ // - X-axis: Positive to the right, negative to the left
+ // - Y-axis: Positive downwards, negative upwards
+
+ // Check if the point is on an axis or the origin
+ if (relative_x == 0 && relative_y == 0) {
+ return 0; // Stack on the reference
+ } else if (relative_y == 0) {
+ return (relative_x > 0) ? 5 : 6; // On X-axis: 5 is the +ve part while 6 is the -ve one.
+ } else if (relative_x == 0) {
+ return (relative_y < 0) ? 7 : 8; // On Y-axis: 7 is the +ve part while 8 is the -ve one.
+ }
+
+ // Determine the quadrant based on the relative coordinates
+ if (relative_x > 0 && relative_y < 0) {
+ return 1; // Quadrant I
+ } else if (relative_x < 0 && relative_y < 0) {
+ return 2; // Quadrant II
+ } else if (relative_x < 0 && relative_y > 0) {
+ return 3; // Quadrant III
+ } else if (relative_x > 0 && relative_y > 0) {
+ return 4; // Quadrant IV
+ }
+
+ // Default case (should not reach here)
+ return -1;
+}
+
+QRectF VisualShaderConnectionGraphicsObject::calculate_bounding_rect_from_coordinates(
+ const QPointF& start_coordinate, const QPointF& end_coordinate) const {
+ const float x1{(float)start_coordinate.x()};
+ const float y1{(float)start_coordinate.y()};
+ const float x2{(float)end_coordinate.x()};
+ const float y2{(float)end_coordinate.y()};
+
+ // Calculate the expanded rect
+ const float min_x{qMin(x1, x2)};
+ const float min_y{qMin(y1, y2)};
+ const float max_x{qMax(x1, x2)};
+ const float max_y{qMax(y1, y2)};
+
+ QRectF r({min_x, min_y}, QSizeF(max_x - min_x, max_y - min_y));
+
+ const bool in_h_abnormal_region{x2 < (x1 + h_abnormal_offset)};
+ const bool in_v_abnormal_region{std::abs(y2 - y1) < v_abnormal_offset};
+
+ const int quadrant{detect_quadrant({x1, y1}, {x2, y2})};
+
+ // We will expand the bounding rect horizontally so that our connection don't get cut off
+ const float a_width_expansion{((x1 + h_abnormal_offset) - x2) * abnormal_face_to_back_control_width_expansion_factor};
+ const float a_height_expansion{a_width_expansion * abnormal_face_to_back_control_height_expansion_factor};
+
+ if (in_h_abnormal_region) {
+ r.adjust(-a_width_expansion, 0.0f, a_width_expansion, 0.0f);
+ }
+
+ switch (quadrant) {
+ case 2: // Quadrant II: Abnormal face to back
+ case 3: // Quadrant III: Abnormal face to back
+ case 6: // On -ve X-axis: Abnormal face to back
+ // Elipse like curve
+ if (in_v_abnormal_region) {
+ r.adjust(0.0f, -a_height_expansion, 0.0f, a_height_expansion);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return r;
+}
+
+std::pair VisualShaderConnectionGraphicsObject::calculate_control_points(
+ const QPointF& start_coordinate, const QPointF& end_coordinated) const {
+ QPointF cp1;
+ QPointF cp2;
+
+ const float x1{(float)start_coordinate.x()};
+ const float y1{(float)start_coordinate.y()};
+ const float x2{(float)end_coordinate.x()};
+ const float y2{(float)end_coordinate.y()};
+
+ QRectF r{calculate_bounding_rect_from_coordinates(start_coordinate, end_coordinate)};
+
+ const bool in_h_abnormal_region{x2 < (x1 + h_abnormal_offset)};
+ const bool in_v_abnormal_region{std::abs(y2 - y1) < v_abnormal_offset};
+
+ const int quadrant{detect_quadrant({x1, y1}, {x2, y2})};
+
+ // We will expand the bounding rect horizontally so that our connection don't get cut off
+ const float a_width_expansion{((x1 + h_abnormal_offset) - x2) * abnormal_face_to_back_control_width_expansion_factor};
+ const float a_height_expansion{a_width_expansion * abnormal_face_to_back_control_height_expansion_factor};
+
+ const float cp_x_delta_factor{0.8f};
+ const float cp_y_delta_factor{0.25f};
+
+ // Normal region control points deltas
+ const float cp_x_delta{(float)r.width() * cp_x_delta_factor};
+ const float cp_y_delta{(float)r.height() * cp_y_delta_factor};
+
+ // Abnormal region control points deltas
+ const float a_cp_x_delta{((float)r.width() - a_width_expansion) * cp_x_delta_factor};
+ const float a_cp_y_delta{((float)r.height() - a_height_expansion) * cp_y_delta_factor};
+
+ switch (quadrant) {
+ case 1: // Quadrant I: Normal face to back
+ // Find out if the connection is going from left to right normally
+ if (in_h_abnormal_region) {
+ // The connection is not going from left to right normally
+ // Our control points will be outside the end_coordinate and start_coordinate bounding rect
+ // We will expand the bounding rect horizontally to make it easier to get an accurate coordinate of the size
+
+ // Here we cover cases of nodes not facing each other.
+ // This means we can't just send the path straight to the node.
+
+ // Treated as inside Quadrant II
+ cp1 = {x1 + a_cp_x_delta, y1};
+ cp2 = {x2 - a_cp_x_delta, y2};
+ } else {
+ // Treated as inside Quadrant I
+ cp1 = {x1 + cp_x_delta, y1 - cp_y_delta};
+ cp2 = {x2 - cp_x_delta, y2 + cp_y_delta};
+ }
+ break;
+ case 2: // Quadrant II: Abnormal face to back
+ if (in_v_abnormal_region) {
+ cp1 = {x1 + a_cp_x_delta, y1 - a_cp_y_delta};
+ cp2 = {x2 - a_cp_x_delta, y2 - a_cp_y_delta};
+ } else {
+ cp1 = {x1 + a_cp_x_delta, y1};
+ cp2 = {x2 - a_cp_x_delta, y2};
+ }
+ break;
+ case 3: // Quadrant III: Abnormal face to back
+ if (in_v_abnormal_region) {
+ cp1 = {x1 + a_cp_x_delta, y1 - a_cp_y_delta};
+ cp2 = {x2 - a_cp_x_delta, y2 - a_cp_y_delta};
+ } else {
+ cp1 = {x1 + a_width_expansion, y1};
+ cp2 = {x2 - a_width_expansion, y2};
+ }
+ break;
+ case 4: // Quadrant IV: Normal face to back
+ if (in_h_abnormal_region) {
+ // Treated as inside Quadrant III
+ cp1 = {x1 + a_cp_x_delta, y1};
+ cp2 = {x2 - a_cp_x_delta, y2};
+ } else {
+ // Treated as inside Quadrant IV
+ cp1 = {x1 + cp_x_delta, y1 + cp_y_delta};
+ cp2 = {x2 - cp_x_delta, y2 - cp_y_delta};
+ }
+ break;
+ case 5: // On +ve X-axis: Normal face to back
+ // Straight line
+ cp1 = {x1, y1};
+ cp2 = {x2, y2};
+ break;
+ case 6: // On -ve X-axis: Abnormal face to back
+ // Elipse like curve
+ cp1 = {x1 + a_cp_x_delta, y1 - a_cp_y_delta};
+ cp2 = {x2 - a_cp_x_delta, y2 - a_cp_y_delta};
+ break;
+ case 7: // On +ve Y-axis: Abnormal face to back
+ case 8: // On -ve Y-axis: Abnormal face to back
+ cp1 = {x1 + a_cp_x_delta, y1};
+ cp2 = {x2 - a_cp_x_delta, y2};
+ break;
+ default:
+ return std::make_pair(start_coordinate, end_coordinate);
+ }
+
+ return std::make_pair(cp1, cp2);
+}
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** Embed Widgets *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+VisualShaderNodeEmbedWidget::VisualShaderNodeEmbedWidget(const std::shared_ptr& node, QWidget* parent)
+ : QWidget(parent), layout(nullptr), preview_shader_button(nullptr), shader_previewer_widget(nullptr) {
+ layout = new QVBoxLayout(this);
+ layout->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ layout->setSizeConstraint(QLayout::SetNoConstraint);
+ layout->setSpacing(2);
+ layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeInputEmbedWidget* embed_widget = new VisualShaderNodeInputEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeFloatFuncEmbedWidget* embed_widget = new VisualShaderNodeFloatFuncEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeIntFuncEmbedWidget* embed_widget = new VisualShaderNodeIntFuncEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeUIntFuncEmbedWidget* embed_widget = new VisualShaderNodeUIntFuncEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeFloatOpEmbedWidget* embed_widget = new VisualShaderNodeFloatOpEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeIntOpEmbedWidget* embed_widget = new VisualShaderNodeIntOpEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeUIntOpEmbedWidget* embed_widget = new VisualShaderNodeUIntOpEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeValueNoiseEmbedWidget* embed_widget = new VisualShaderNodeValueNoiseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodePerlinNoiseEmbedWidget* embed_widget = new VisualShaderNodePerlinNoiseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget* embed_widget =
+ new VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget(p);
+ VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget* embed_widget2 =
+ new VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ layout->addWidget(embed_widget2);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget2, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeColorConstantEmbedWidget* embed_widget = new VisualShaderNodeColorConstantEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &VisualShaderNodeColorConstantEmbedWidget::color_changed, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeBooleanConstantEmbedWidget* embed_widget = new VisualShaderNodeBooleanConstantEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QCheckBox::stateChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeFloatConstantEmbedWidget* embed_widget = new VisualShaderNodeFloatConstantEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeIntConstantEmbedWidget* embed_widget = new VisualShaderNodeIntConstantEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeUIntConstantEmbedWidget* embed_widget = new VisualShaderNodeUIntConstantEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVec2ConstantEmbedWidget* embed_widget = new VisualShaderNodeVec2ConstantEmbedWidget(p);
+ layout->addLayout(embed_widget);
+ QObject::connect(embed_widget->get_x_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_y_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVec3ConstantEmbedWidget* embed_widget = new VisualShaderNodeVec3ConstantEmbedWidget(p);
+ layout->addLayout(embed_widget);
+ QObject::connect(embed_widget->get_x_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_y_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_z_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVec4ConstantEmbedWidget* embed_widget = new VisualShaderNodeVec4ConstantEmbedWidget(p);
+ layout->addLayout(embed_widget);
+ QObject::connect(embed_widget->get_x_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_y_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_z_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_w_edit_widget(), &QLineEdit::textChanged, this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVectorBaseEmbedWidget* embed_widget = new VisualShaderNodeVectorBaseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ VisualShaderNodeVectorOpEmbedWidget* embed_widget2 = new VisualShaderNodeVectorOpEmbedWidget(p);
+ layout->addWidget(embed_widget2);
+ QObject::connect(embed_widget2, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVectorBaseEmbedWidget* embed_widget = new VisualShaderNodeVectorBaseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ VisualShaderNodeVectorFuncEmbedWidget* embed_widget2 = new VisualShaderNodeVectorFuncEmbedWidget(p);
+ layout->addWidget(embed_widget2);
+ QObject::connect(embed_widget2, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVectorBaseEmbedWidget* embed_widget = new VisualShaderNodeVectorBaseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_node_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeVectorBaseEmbedWidget* embed_widget = new VisualShaderNodeVectorBaseEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_node_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeDerivativeFuncEmbedWidget* embed_widget = new VisualShaderNodeDerivativeFuncEmbedWidget(p);
+ layout->addLayout(embed_widget);
+ QObject::connect(embed_widget->get_op_type_combo_box(), QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+
+ QObject::connect(embed_widget->get_function_combo_box(), QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_precision_combo_box(), QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeCompareEmbedWidget* embed_widget = new VisualShaderNodeCompareEmbedWidget(p);
+ layout->addLayout(embed_widget);
+ QObject::connect(embed_widget->get_comparison_type_combo_box(), QOverload::of(&QComboBox::currentIndexChanged),
+ this, &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_func_combo_box(), QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ QObject::connect(embed_widget->get_condition_combo_box(), QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeSwitchEmbedWidget* embed_widget = new VisualShaderNodeSwitchEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ } else if (auto p{std::dynamic_pointer_cast(node)}) {
+ VisualShaderNodeIsEmbedWidget* embed_widget = new VisualShaderNodeIsEmbedWidget(p);
+ layout->addWidget(embed_widget);
+ QObject::connect(embed_widget, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeEmbedWidget::on_shader_preview_update_requested);
+ }
+
+ // Create the button that will show/hide the shader previewer
+ preview_shader_button = new QPushButton("Show Preview", this);
+ preview_shader_button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ preview_shader_button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ preview_shader_button->setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ preview_shader_button->setToolTip("Create a new node");
+ layout->addWidget(preview_shader_button);
+ QObject::connect(preview_shader_button, &QPushButton::pressed, this,
+ &VisualShaderNodeEmbedWidget::on_preview_shader_button_pressed);
+
+ this->setContentsMargins(10, 10, 10, 10); // Left, top, right, bottom
+ setLayout(layout);
+}
+
+VisualShaderNodeEmbedWidget::~VisualShaderNodeEmbedWidget() {}
+
+/*************************************/
+/* Input Node */
+/*************************************/
+
+VisualShaderNodeInputEmbedWidget::VisualShaderNodeInputEmbedWidget(const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ // Add the default item
+ addItem(QString::fromStdString(node->get_input_name()), "");
+
+ const VisualShaderNodeInput::Port* ps{VisualShaderNodeInput::get_ports()};
+
+ int i{0};
+
+ while (ps[i].type != VisualShaderNode::PortType::PORT_TYPE_ENUM_SIZE) {
+ addItem(QString::fromStdString(ps[i].name));
+ i++;
+ }
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeInputEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeInputEmbedWidget::~VisualShaderNodeInputEmbedWidget() {}
+
+void VisualShaderNodeInputEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_input_name(itemText(index).toStdString());
+}
+
+/*************************************/
+/* Float Op Node */
+/*************************************/
+
+VisualShaderNodeFloatOpEmbedWidget::VisualShaderNodeFloatOpEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Add", (int)VisualShaderNodeFloatOp::OP_ADD);
+ addItem("Subtract", (int)VisualShaderNodeFloatOp::OP_SUB);
+ addItem("Multiply", (int)VisualShaderNodeFloatOp::OP_MUL);
+ addItem("Divide", (int)VisualShaderNodeFloatOp::OP_DIV);
+ addItem("Modulus", (int)VisualShaderNodeFloatOp::OP_MOD);
+ addItem("Power", (int)VisualShaderNodeFloatOp::OP_POW);
+ addItem("Maximum", (int)VisualShaderNodeFloatOp::OP_MAX);
+ addItem("Minimum", (int)VisualShaderNodeFloatOp::OP_MIN);
+ addItem("Arc Tangent 2", (int)VisualShaderNodeFloatOp::OP_ATAN2);
+ addItem("Step", (int)VisualShaderNodeFloatOp::OP_STEP);
+
+ setCurrentIndex((int)node->get_operator());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeFloatOpEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeFloatOpEmbedWidget::~VisualShaderNodeFloatOpEmbedWidget() {}
+
+void VisualShaderNodeFloatOpEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_operator((VisualShaderNodeFloatOp::Operator)itemData(index).toInt());
+}
+
+/*************************************/
+/* Int Op Node */
+/*************************************/
+
+VisualShaderNodeIntOpEmbedWidget::VisualShaderNodeIntOpEmbedWidget(const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Add", (int)VisualShaderNodeIntOp::OP_ADD);
+ addItem("Subtract", (int)VisualShaderNodeIntOp::OP_SUB);
+ addItem("Multiply", (int)VisualShaderNodeIntOp::OP_MUL);
+ addItem("Divide", (int)VisualShaderNodeIntOp::OP_DIV);
+ addItem("Modulus", (int)VisualShaderNodeIntOp::OP_MOD);
+ addItem("Maximum", (int)VisualShaderNodeIntOp::OP_MAX);
+ addItem("Minimum", (int)VisualShaderNodeIntOp::OP_MIN);
+ addItem("Bitwise AND", (int)VisualShaderNodeIntOp::OP_BITWISE_AND);
+ addItem("Bitwise OR", (int)VisualShaderNodeIntOp::OP_BITWISE_OR);
+ addItem("Bitwise XOR", (int)VisualShaderNodeIntOp::OP_BITWISE_XOR);
+ addItem("Bitwise Left Shift", (int)VisualShaderNodeIntOp::OP_BITWISE_LEFT_SHIFT);
+ addItem("Bitwise Right Shift", (int)VisualShaderNodeIntOp::OP_BITWISE_RIGHT_SHIFT);
+
+ setCurrentIndex((int)node->get_operator());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeIntOpEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeIntOpEmbedWidget::~VisualShaderNodeIntOpEmbedWidget() {}
+
+void VisualShaderNodeIntOpEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_operator((VisualShaderNodeIntOp::Operator)itemData(index).toInt());
+}
+
+/*************************************/
+/* UInt Op Node */
+/*************************************/
+
+VisualShaderNodeUIntOpEmbedWidget::VisualShaderNodeUIntOpEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Add", (int)VisualShaderNodeUIntOp::OP_ADD);
+ addItem("Subtract", (int)VisualShaderNodeUIntOp::OP_SUB);
+ addItem("Multiply", (int)VisualShaderNodeUIntOp::OP_MUL);
+ addItem("Divide", (int)VisualShaderNodeUIntOp::OP_DIV);
+ addItem("Modulus", (int)VisualShaderNodeUIntOp::OP_MOD);
+ addItem("Maximum", (int)VisualShaderNodeUIntOp::OP_MAX);
+ addItem("Minimum", (int)VisualShaderNodeUIntOp::OP_MIN);
+ addItem("Bitwise AND", (int)VisualShaderNodeUIntOp::OP_BITWISE_AND);
+ addItem("Bitwise OR", (int)VisualShaderNodeUIntOp::OP_BITWISE_OR);
+ addItem("Bitwise XOR", (int)VisualShaderNodeUIntOp::OP_BITWISE_XOR);
+ addItem("Bitwise Left Shift", (int)VisualShaderNodeUIntOp::OP_BITWISE_LEFT_SHIFT);
+ addItem("Bitwise Right Shift", (int)VisualShaderNodeUIntOp::OP_BITWISE_RIGHT_SHIFT);
+
+ setCurrentIndex((int)node->get_operator());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeUIntOpEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeUIntOpEmbedWidget::~VisualShaderNodeUIntOpEmbedWidget() {}
+
+void VisualShaderNodeUIntOpEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_operator((VisualShaderNodeUIntOp::Operator)itemData(index).toInt());
+}
+
+/*************************************/
+/* Float Funcs Node */
+/*************************************/
+
+VisualShaderNodeFloatFuncEmbedWidget::VisualShaderNodeFloatFuncEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Sin", (int)VisualShaderNodeFloatFunc::FUNC_SIN);
+ addItem("Cos", (int)VisualShaderNodeFloatFunc::FUNC_COS);
+ addItem("Tan", (int)VisualShaderNodeFloatFunc::FUNC_TAN);
+ addItem("Arc Sine", (int)VisualShaderNodeFloatFunc::FUNC_ASIN);
+ addItem("Arc Cosine", (int)VisualShaderNodeFloatFunc::FUNC_ACOS);
+ addItem("Arc Tangent", (int)VisualShaderNodeFloatFunc::FUNC_ATAN);
+ addItem("Sine Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_SINH);
+ addItem("Cosine Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_COSH);
+ addItem("Tangent Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_TANH);
+ addItem("Logarithm", (int)VisualShaderNodeFloatFunc::FUNC_LOG);
+ addItem("Exponential", (int)VisualShaderNodeFloatFunc::FUNC_EXP);
+ addItem("Square Root", (int)VisualShaderNodeFloatFunc::FUNC_SQRT);
+ addItem("Absolute", (int)VisualShaderNodeFloatFunc::FUNC_ABS);
+ addItem("Sign", (int)VisualShaderNodeFloatFunc::FUNC_SIGN);
+ addItem("Floor", (int)VisualShaderNodeFloatFunc::FUNC_FLOOR);
+ addItem("Round", (int)VisualShaderNodeFloatFunc::FUNC_ROUND);
+ addItem("Ceil", (int)VisualShaderNodeFloatFunc::FUNC_CEIL);
+ addItem("Fraction", (int)VisualShaderNodeFloatFunc::FUNC_FRACT);
+ addItem("Saturate", (int)VisualShaderNodeFloatFunc::FUNC_SATURATE);
+ addItem("Negate", (int)VisualShaderNodeFloatFunc::FUNC_NEGATE);
+ addItem("Arc Cosine Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_ACOSH);
+ addItem("Arc Sine Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_ASINH);
+ addItem("Arc Tangent Hyperbolic", (int)VisualShaderNodeFloatFunc::FUNC_ATANH);
+ addItem("Degrees", (int)VisualShaderNodeFloatFunc::FUNC_DEGREES);
+ addItem("Exponential 2", (int)VisualShaderNodeFloatFunc::FUNC_EXP2);
+ addItem("Inverse Square Root", (int)VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT);
+ addItem("Logarithm 2", (int)VisualShaderNodeFloatFunc::FUNC_LOG2);
+ addItem("Radians", (int)VisualShaderNodeFloatFunc::FUNC_RADIANS);
+ addItem("Reciprocal", (int)VisualShaderNodeFloatFunc::FUNC_RECIPROCAL);
+ addItem("Round Even", (int)VisualShaderNodeFloatFunc::FUNC_ROUNDEVEN);
+ addItem("Truncate", (int)VisualShaderNodeFloatFunc::FUNC_TRUNC);
+ addItem("One Minus", (int)VisualShaderNodeFloatFunc::FUNC_ONEMINUS);
+
+ setCurrentIndex((int)node->get_function());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeFloatFuncEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeFloatFuncEmbedWidget::~VisualShaderNodeFloatFuncEmbedWidget() {}
+
+void VisualShaderNodeFloatFuncEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeFloatFunc::Function)itemData(index).toInt());
+}
+
+/*************************************/
+/* Int Funcs Node */
+/*************************************/
+
+VisualShaderNodeIntFuncEmbedWidget::VisualShaderNodeIntFuncEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Abs", (int)VisualShaderNodeIntFunc::FUNC_ABS);
+ addItem("Negate", (int)VisualShaderNodeIntFunc::FUNC_NEGATE);
+ addItem("Sign", (int)VisualShaderNodeIntFunc::FUNC_SIGN);
+ addItem("Bitwise NOT", (int)VisualShaderNodeIntFunc::FUNC_BITWISE_NOT);
+
+ setCurrentIndex((int)node->get_function());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeIntFuncEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeIntFuncEmbedWidget::~VisualShaderNodeIntFuncEmbedWidget() {}
+
+void VisualShaderNodeIntFuncEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeIntFunc::Function)itemData(index).toInt());
+}
+
+/*************************************/
+/* UInt Funcs Node */
+/*************************************/
+
+VisualShaderNodeUIntFuncEmbedWidget::VisualShaderNodeUIntFuncEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Negate", (int)VisualShaderNodeUIntFunc::FUNC_NEGATE);
+ addItem("Bitwise NOT", (int)VisualShaderNodeUIntFunc::FUNC_BITWISE_NOT);
+
+ setCurrentIndex((int)node->get_function());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeUIntFuncEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeUIntFuncEmbedWidget::~VisualShaderNodeUIntFuncEmbedWidget() {}
+
+void VisualShaderNodeUIntFuncEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeUIntFunc::Function)itemData(index).toInt());
+}
+
+/*************************************/
+/* Vector Base */
+/*************************************/
+
+VisualShaderNodeVectorBaseEmbedWidget::VisualShaderNodeVectorBaseEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Vector 2D", (int)VisualShaderNodeVectorBase::OP_TYPE_VECTOR_2D);
+ addItem("Vector 3D", (int)VisualShaderNodeVectorBase::OP_TYPE_VECTOR_3D);
+ addItem("Vector 4D", (int)VisualShaderNodeVectorBase::OP_TYPE_VECTOR_4D);
+
+ // set the current index
+ setCurrentIndex((int)node->get_op_type());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeVectorBaseEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeVectorBaseEmbedWidget::~VisualShaderNodeVectorBaseEmbedWidget() {}
+
+void VisualShaderNodeVectorBaseEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_op_type((VisualShaderNodeVectorBase::OpType)itemData(index).toInt());
+}
+
+/*************************************/
+/* Vector Op Node */
+/*************************************/
+
+VisualShaderNodeVectorOpEmbedWidget::VisualShaderNodeVectorOpEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Add", (int)VisualShaderNodeVectorOp::OP_ADD);
+ addItem("Subtract", (int)VisualShaderNodeVectorOp::OP_SUB);
+ addItem("Multiply", (int)VisualShaderNodeVectorOp::OP_MUL);
+ addItem("Divide", (int)VisualShaderNodeVectorOp::OP_DIV);
+ addItem("Modulus", (int)VisualShaderNodeVectorOp::OP_MOD);
+ addItem("Power", (int)VisualShaderNodeVectorOp::OP_POW);
+ addItem("Maximum", (int)VisualShaderNodeVectorOp::OP_MAX);
+ addItem("Minimum", (int)VisualShaderNodeVectorOp::OP_MIN);
+ addItem("Cross Product", (int)VisualShaderNodeVectorOp::OP_CROSS);
+ addItem("Arc Tangent 2", (int)VisualShaderNodeVectorOp::OP_ATAN2);
+ addItem("Reflect", (int)VisualShaderNodeVectorOp::OP_REFLECT);
+ addItem("Step", (int)VisualShaderNodeVectorOp::OP_STEP);
+
+ setCurrentIndex((int)node->get_operator());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeVectorOpEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeVectorOpEmbedWidget::~VisualShaderNodeVectorOpEmbedWidget() {}
+
+void VisualShaderNodeVectorOpEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_operator((VisualShaderNodeVectorOp::Operator)itemData(index).toInt());
+}
+
+/*************************************/
+/* Vector Funcs Node */
+/*************************************/
+
+VisualShaderNodeVectorFuncEmbedWidget::VisualShaderNodeVectorFuncEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Normalize", (int)VisualShaderNodeVectorFunc::FUNC_NORMALIZE);
+ addItem("Saturate", (int)VisualShaderNodeVectorFunc::FUNC_SATURATE);
+ addItem("Negate", (int)VisualShaderNodeVectorFunc::FUNC_NEGATE);
+ addItem("Reciprocal", (int)VisualShaderNodeVectorFunc::FUNC_RECIPROCAL);
+ addItem("Abs", (int)VisualShaderNodeVectorFunc::FUNC_ABS);
+ addItem("Arc Cosine", (int)VisualShaderNodeVectorFunc::FUNC_ACOS);
+ addItem("Arc Cosine Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_ACOSH);
+ addItem("Arc Sine", (int)VisualShaderNodeVectorFunc::FUNC_ASIN);
+ addItem("Arc Sine Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_ASINH);
+ addItem("Arc Tangent", (int)VisualShaderNodeVectorFunc::FUNC_ATAN);
+ addItem("Arc Tangent Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_ATANH);
+ addItem("Ceil", (int)VisualShaderNodeVectorFunc::FUNC_CEIL);
+ addItem("Cos", (int)VisualShaderNodeVectorFunc::FUNC_COS);
+ addItem("Cosine Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_COSH);
+ addItem("Degrees", (int)VisualShaderNodeVectorFunc::FUNC_DEGREES);
+ addItem("Exp", (int)VisualShaderNodeVectorFunc::FUNC_EXP);
+ addItem("Exp2", (int)VisualShaderNodeVectorFunc::FUNC_EXP2);
+ addItem("Floor", (int)VisualShaderNodeVectorFunc::FUNC_FLOOR);
+ addItem("Fraction", (int)VisualShaderNodeVectorFunc::FUNC_FRACT);
+ addItem("Inverse Square Root", (int)VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT);
+ addItem("Log", (int)VisualShaderNodeVectorFunc::FUNC_LOG);
+ addItem("Log2", (int)VisualShaderNodeVectorFunc::FUNC_LOG2);
+ addItem("Radians", (int)VisualShaderNodeVectorFunc::FUNC_RADIANS);
+ addItem("Round", (int)VisualShaderNodeVectorFunc::FUNC_ROUND);
+ addItem("Round Even", (int)VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN);
+ addItem("Sign", (int)VisualShaderNodeVectorFunc::FUNC_SIGN);
+ addItem("Sin", (int)VisualShaderNodeVectorFunc::FUNC_SIN);
+ addItem("Sine Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_SINH);
+ addItem("Sqrt", (int)VisualShaderNodeVectorFunc::FUNC_SQRT);
+ addItem("Tan", (int)VisualShaderNodeVectorFunc::FUNC_TAN);
+ addItem("Tangent Hyperbolic", (int)VisualShaderNodeVectorFunc::FUNC_TANH);
+ addItem("Truncate", (int)VisualShaderNodeVectorFunc::FUNC_TRUNC);
+ addItem("One Minus", (int)VisualShaderNodeVectorFunc::FUNC_ONEMINUS);
+
+ setCurrentIndex((int)node->get_function());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeVectorFuncEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeVectorFuncEmbedWidget::~VisualShaderNodeVectorFuncEmbedWidget() {}
+
+void VisualShaderNodeVectorFuncEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeVectorFunc::Function)itemData(index).toInt());
+}
+
+/*************************************/
+/* Color Constant Node */
+/*************************************/
+
+VisualShaderNodeColorConstantEmbedWidget::VisualShaderNodeColorConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QPushButton(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ {
+ QPalette palette{this->palette()};
+ TColor c{node->get_constant()};
+ palette.setColor(QPalette::Button, QColor(c.r, c.g, c.b, c.a));
+ this->setPalette(palette);
+ }
+
+ QObject::connect(this, &QPushButton::pressed, this, &VisualShaderNodeColorConstantEmbedWidget::on_pressed);
+}
+
+VisualShaderNodeColorConstantEmbedWidget::~VisualShaderNodeColorConstantEmbedWidget() {}
+
+void VisualShaderNodeColorConstantEmbedWidget::on_pressed() {
+ TColor c{node->get_constant()};
+ QColor color{QColorDialog::getColor(QColor(c.r, c.g, c.b, c.a), this, "Select Color")};
+
+ // If a valid color is picked, update the button and store the color
+ if (color.isValid()) {
+ node->set_constant({(float)color.red(), (float)color.green(), (float)color.blue(), (float)color.alpha()});
+ QPalette palette{this->palette()};
+ palette.setColor(QPalette::Button, color);
+ this->setPalette(palette);
+ this->update();
+ Q_EMIT color_changed();
+ } else {
+ // If the user cancels the color dialog, reset the button to the previous color
+ QColor previous_color{QColor(c.r, c.g, c.b, c.a)};
+ QPalette palette{this->palette()};
+ palette.setColor(QPalette::Button, previous_color);
+ this->setPalette(palette);
+ this->update();
+ }
+}
+
+/*************************************/
+/* Boolean Constant Node */
+/*************************************/
+
+VisualShaderNodeBooleanConstantEmbedWidget::VisualShaderNodeBooleanConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QCheckBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setCheckState(node->get_constant() ? Qt::Checked : Qt::Unchecked);
+
+ QObject::connect(this, &QCheckBox::stateChanged, this, &VisualShaderNodeBooleanConstantEmbedWidget::on_state_changed);
+}
+
+VisualShaderNodeBooleanConstantEmbedWidget::~VisualShaderNodeBooleanConstantEmbedWidget() {}
+
+void VisualShaderNodeBooleanConstantEmbedWidget::on_state_changed(const int& state) {
+ node->set_constant(state == Qt::Checked);
+}
+
+/*************************************/
+/* Float Constant */
+/*************************************/
+
+VisualShaderNodeFloatConstantEmbedWidget::VisualShaderNodeFloatConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Value");
+
+ setText(QString::number(node->get_constant()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this, &VisualShaderNodeFloatConstantEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeFloatConstantEmbedWidget::~VisualShaderNodeFloatConstantEmbedWidget() {}
+
+void VisualShaderNodeFloatConstantEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant(0.0f);
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant(text.toFloat());
+ } else {
+ node->set_constant(0.0f);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Int Constant */
+/*************************************/
+
+VisualShaderNodeIntConstantEmbedWidget::VisualShaderNodeIntConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Value");
+
+ setText(QString::number(node->get_constant()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this, &VisualShaderNodeIntConstantEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeIntConstantEmbedWidget::~VisualShaderNodeIntConstantEmbedWidget() {}
+
+void VisualShaderNodeIntConstantEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant(0);
+ } else {
+ bool ok;
+ text.toInt(&ok);
+ if (ok) {
+ node->set_constant(text.toInt());
+ } else {
+ node->set_constant(0);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* UInt Constant */
+/*************************************/
+
+VisualShaderNodeUIntConstantEmbedWidget::VisualShaderNodeUIntConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Value");
+
+ setText(QString::number(node->get_constant()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this, &VisualShaderNodeUIntConstantEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeUIntConstantEmbedWidget::~VisualShaderNodeUIntConstantEmbedWidget() {}
+
+void VisualShaderNodeUIntConstantEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant(0);
+ } else {
+ bool ok;
+ text.toUInt(&ok);
+ if (ok) {
+ node->set_constant(text.toUInt());
+ } else {
+ node->set_constant(0);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Vec2 Constant Node */
+/*************************************/
+
+VisualShaderNodeVec2ConstantEmbedWidget::VisualShaderNodeVec2ConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QVBoxLayout(), node(node) {
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ setSizeConstraint(QLayout::SetNoConstraint);
+ setSpacing(2);
+ setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ x_edit_widget = new QLineEdit();
+ y_edit_widget = new QLineEdit();
+
+ x_edit_widget->setPlaceholderText("X");
+ y_edit_widget->setPlaceholderText("Y");
+
+ x_edit_widget->setText(QString::number(node->get_constant().x));
+ y_edit_widget->setText(QString::number(node->get_constant().y));
+
+ QObject::connect(x_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec2ConstantEmbedWidget::on_x_text_changed);
+ QObject::connect(y_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec2ConstantEmbedWidget::on_y_text_changed);
+
+ addWidget(x_edit_widget);
+ addWidget(y_edit_widget);
+}
+
+VisualShaderNodeVec2ConstantEmbedWidget::~VisualShaderNodeVec2ConstantEmbedWidget() {}
+
+void VisualShaderNodeVec2ConstantEmbedWidget::on_x_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({0.0f, node->get_constant().y});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({text.toFloat(), node->get_constant().y});
+ } else {
+ node->set_constant({0.0f, node->get_constant().y});
+ x_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec2ConstantEmbedWidget::on_y_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, 0.0f});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, text.toFloat()});
+ } else {
+ node->set_constant({node->get_constant().x, 0.0f});
+ y_edit_widget->setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Vec3 Constant Node */
+/*************************************/
+
+VisualShaderNodeVec3ConstantEmbedWidget::VisualShaderNodeVec3ConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QVBoxLayout(), node(node) {
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ setSizeConstraint(QLayout::SetNoConstraint);
+ setSpacing(2);
+ setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ x_edit_widget = new QLineEdit();
+ y_edit_widget = new QLineEdit();
+ z_edit_widget = new QLineEdit();
+
+ x_edit_widget->setPlaceholderText("X");
+ y_edit_widget->setPlaceholderText("Y");
+ z_edit_widget->setPlaceholderText("Z");
+
+ x_edit_widget->setText(QString::number(node->get_constant().x));
+ y_edit_widget->setText(QString::number(node->get_constant().y));
+ z_edit_widget->setText(QString::number(node->get_constant().z));
+
+ QObject::connect(x_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec3ConstantEmbedWidget::on_x_text_changed);
+ QObject::connect(y_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec3ConstantEmbedWidget::on_y_text_changed);
+ QObject::connect(z_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec3ConstantEmbedWidget::on_z_text_changed);
+
+ addWidget(x_edit_widget);
+ addWidget(y_edit_widget);
+ addWidget(z_edit_widget);
+}
+
+VisualShaderNodeVec3ConstantEmbedWidget::~VisualShaderNodeVec3ConstantEmbedWidget() {}
+
+void VisualShaderNodeVec3ConstantEmbedWidget::on_x_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({0.0f, node->get_constant().y, node->get_constant().z});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({text.toFloat(), node->get_constant().y, node->get_constant().z});
+ } else {
+ node->set_constant({0.0f, node->get_constant().y, node->get_constant().z});
+ x_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec3ConstantEmbedWidget::on_y_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, 0.0f, node->get_constant().z});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, text.toFloat(), node->get_constant().z});
+ } else {
+ node->set_constant({node->get_constant().x, 0.0f, node->get_constant().z});
+ y_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec3ConstantEmbedWidget::on_z_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, 0.0f});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, text.toFloat()});
+ } else {
+ node->set_constant({node->get_constant().x, node->get_constant().y, 0.0f});
+ z_edit_widget->setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Vec4 Constant Node */
+/*************************************/
+
+VisualShaderNodeVec4ConstantEmbedWidget::VisualShaderNodeVec4ConstantEmbedWidget(
+ const std::shared_ptr& node)
+ : QVBoxLayout(), node(node) {
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ setSizeConstraint(QLayout::SetNoConstraint);
+ setSpacing(2);
+ setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ x_edit_widget = new QLineEdit();
+ y_edit_widget = new QLineEdit();
+ z_edit_widget = new QLineEdit();
+ w_edit_widget = new QLineEdit();
+
+ x_edit_widget->setPlaceholderText("X");
+ y_edit_widget->setPlaceholderText("Y");
+ z_edit_widget->setPlaceholderText("Z");
+ w_edit_widget->setPlaceholderText("W");
+
+ x_edit_widget->setText(QString::number(node->get_constant().x));
+ y_edit_widget->setText(QString::number(node->get_constant().y));
+ z_edit_widget->setText(QString::number(node->get_constant().z));
+ w_edit_widget->setText(QString::number(node->get_constant().w));
+
+ QObject::connect(x_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec4ConstantEmbedWidget::on_x_text_changed);
+ QObject::connect(y_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec4ConstantEmbedWidget::on_y_text_changed);
+ QObject::connect(z_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec4ConstantEmbedWidget::on_z_text_changed);
+ QObject::connect(w_edit_widget, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVec4ConstantEmbedWidget::on_w_text_changed);
+
+ addWidget(x_edit_widget);
+ addWidget(y_edit_widget);
+ addWidget(z_edit_widget);
+ addWidget(w_edit_widget);
+}
+
+VisualShaderNodeVec4ConstantEmbedWidget::~VisualShaderNodeVec4ConstantEmbedWidget() {}
+
+void VisualShaderNodeVec4ConstantEmbedWidget::on_x_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({0.0f, node->get_constant().y, node->get_constant().z, node->get_constant().w});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({text.toFloat(), node->get_constant().y, node->get_constant().z, node->get_constant().w});
+ } else {
+ node->set_constant({0.0f, node->get_constant().y, node->get_constant().z, node->get_constant().w});
+ x_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec4ConstantEmbedWidget::on_y_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, 0.0f, node->get_constant().z, node->get_constant().w});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, text.toFloat(), node->get_constant().z, node->get_constant().w});
+ } else {
+ node->set_constant({node->get_constant().x, 0.0f, node->get_constant().z, node->get_constant().w});
+ y_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec4ConstantEmbedWidget::on_z_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, 0.0f, node->get_constant().w});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, text.toFloat(), node->get_constant().w});
+ } else {
+ node->set_constant({node->get_constant().x, node->get_constant().y, 0.0f, node->get_constant().w});
+ z_edit_widget->setText("");
+ }
+ }
+}
+
+void VisualShaderNodeVec4ConstantEmbedWidget::on_w_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, node->get_constant().z, 0.0f});
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_constant({node->get_constant().x, node->get_constant().y, node->get_constant().z, text.toFloat()});
+ } else {
+ node->set_constant({node->get_constant().x, node->get_constant().y, node->get_constant().z, 0.0f});
+ w_edit_widget->setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Derivative Func Node */
+/*************************************/
+
+VisualShaderNodeDerivativeFuncEmbedWidget::VisualShaderNodeDerivativeFuncEmbedWidget(
+ const std::shared_ptr& node)
+ : QVBoxLayout(), node(node) {
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ setSizeConstraint(QLayout::SetNoConstraint);
+ setSpacing(2);
+ setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ op_type_combo_box = new QComboBox();
+ function_combo_box = new QComboBox();
+ precision_combo_box = new QComboBox();
+
+ op_type_combo_box->addItem("Scalar", (int)VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR);
+ op_type_combo_box->addItem("Vector 2D", (int)VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D);
+ op_type_combo_box->addItem("Vector 3D", (int)VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D);
+ op_type_combo_box->addItem("Vector 4D", (int)VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D);
+
+ op_type_combo_box->setCurrentIndex((int)node->get_op_type());
+
+ function_combo_box->addItem("Sum", (int)VisualShaderNodeDerivativeFunc::FUNC_SUM);
+ function_combo_box->addItem("X", (int)VisualShaderNodeDerivativeFunc::FUNC_X);
+ function_combo_box->addItem("Y", (int)VisualShaderNodeDerivativeFunc::FUNC_Y);
+
+ function_combo_box->setCurrentIndex((int)node->get_function());
+
+ precision_combo_box->addItem("None", (int)VisualShaderNodeDerivativeFunc::PRECISION_NONE);
+ precision_combo_box->addItem("Coarse", (int)VisualShaderNodeDerivativeFunc::PRECISION_COARSE);
+ precision_combo_box->addItem("Fine", (int)VisualShaderNodeDerivativeFunc::PRECISION_FINE);
+
+ precision_combo_box->setCurrentIndex((int)node->get_precision());
+
+ QObject::connect(op_type_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeDerivativeFuncEmbedWidget::on_op_type_current_index_changed);
+ QObject::connect(function_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeDerivativeFuncEmbedWidget::on_function_current_index_changed);
+ QObject::connect(precision_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeDerivativeFuncEmbedWidget::on_precision_current_index_changed);
+
+ addWidget(op_type_combo_box);
+ addWidget(function_combo_box);
+ addWidget(precision_combo_box);
+}
+
+VisualShaderNodeDerivativeFuncEmbedWidget::~VisualShaderNodeDerivativeFuncEmbedWidget() {}
+
+void VisualShaderNodeDerivativeFuncEmbedWidget::on_op_type_current_index_changed(const int& index) {
+ node->set_op_type((VisualShaderNodeDerivativeFunc::OpType)op_type_combo_box->itemData(index).toInt());
+}
+
+void VisualShaderNodeDerivativeFuncEmbedWidget::on_function_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeDerivativeFunc::Function)function_combo_box->itemData(index).toInt());
+}
+
+void VisualShaderNodeDerivativeFuncEmbedWidget::on_precision_current_index_changed(const int& index) {
+ node->set_precision((VisualShaderNodeDerivativeFunc::Precision)precision_combo_box->itemData(index).toInt());
+}
+
+/*************************************/
+/* Value Noise Node */
+/*************************************/
+
+VisualShaderNodeValueNoiseEmbedWidget::VisualShaderNodeValueNoiseEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Scale");
+
+ setText(QString::number(node->get_scale()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this, &VisualShaderNodeValueNoiseEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeValueNoiseEmbedWidget::~VisualShaderNodeValueNoiseEmbedWidget() {}
+
+void VisualShaderNodeValueNoiseEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_scale(100.0f);
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_scale(text.toFloat());
+ } else {
+ node->set_scale(0.0f);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Perlin Noise Node */
+/*************************************/
+
+VisualShaderNodePerlinNoiseEmbedWidget::VisualShaderNodePerlinNoiseEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Scale");
+
+ setText(QString::number(node->get_scale()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this, &VisualShaderNodePerlinNoiseEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodePerlinNoiseEmbedWidget::~VisualShaderNodePerlinNoiseEmbedWidget() {}
+
+void VisualShaderNodePerlinNoiseEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_scale(10.0f);
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_scale(text.toFloat());
+ } else {
+ node->set_scale(0.0f);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Voronoi Noise Node */
+/*************************************/
+
+VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget::VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Angle Offset");
+
+ setText(QString::number(node->get_angle_offset()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget::~VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget() {}
+
+void VisualShaderNodeVoronoiNoiseAngleOffsetEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_angle_offset(10.0f);
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_angle_offset(text.toFloat());
+ } else {
+ node->set_angle_offset(0.0f);
+ setText("");
+ }
+ }
+}
+
+VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget::VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget(
+ const std::shared_ptr& node)
+ : QLineEdit(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ setPlaceholderText("Cell Density");
+
+ setText(QString::number(node->get_cell_density()));
+
+ QObject::connect(this, &QLineEdit::textChanged, this,
+ &VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget::on_text_changed);
+}
+
+VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget::~VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget() {}
+
+void VisualShaderNodeVoronoiNoiseCellDensityEmbedWidget::on_text_changed(const QString& text) {
+ if (text.isEmpty()) {
+ node->set_cell_density(10.0f);
+ } else {
+ bool ok;
+ text.toFloat(&ok);
+ if (ok) {
+ node->set_cell_density(text.toFloat());
+ } else {
+ node->set_cell_density(0.0f);
+ setText("");
+ }
+ }
+}
+
+/*************************************/
+/* Logic */
+/*************************************/
+
+/*************************************/
+/* Compare Node */
+/*************************************/
+
+VisualShaderNodeCompareEmbedWidget::VisualShaderNodeCompareEmbedWidget(
+ const std::shared_ptr& node)
+ : QVBoxLayout(), node(node) {
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+ setSizeConstraint(QLayout::SetNoConstraint);
+ setSpacing(2);
+ setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
+
+ comparison_type_combo_box = new QComboBox();
+ func_combo_box = new QComboBox();
+ condition_combo_box = new QComboBox();
+
+ comparison_type_combo_box->addItem("Scalar", (int)VisualShaderNodeCompare::CMP_TYPE_SCALAR);
+ comparison_type_combo_box->addItem("Scalar Int", (int)VisualShaderNodeCompare::CMP_TYPE_SCALAR_INT);
+ comparison_type_combo_box->addItem("Scalar UInt", (int)VisualShaderNodeCompare::CMP_TYPE_SCALAR_UINT);
+ comparison_type_combo_box->addItem("Vector 2D", (int)VisualShaderNodeCompare::CMP_TYPE_VECTOR_2D);
+ comparison_type_combo_box->addItem("Vector 3D", (int)VisualShaderNodeCompare::CMP_TYPE_VECTOR_3D);
+ comparison_type_combo_box->addItem("Vector 4D", (int)VisualShaderNodeCompare::CMP_TYPE_VECTOR_4D);
+ comparison_type_combo_box->addItem("Boolean", (int)VisualShaderNodeCompare::CMP_TYPE_BOOLEAN);
+
+ comparison_type_combo_box->setCurrentIndex((int)node->get_comparison_type());
+
+ func_combo_box->addItem("==", (int)VisualShaderNodeCompare::FUNC_EQUAL);
+ func_combo_box->addItem("!=", (int)VisualShaderNodeCompare::FUNC_NOT_EQUAL);
+ func_combo_box->addItem(">", (int)VisualShaderNodeCompare::FUNC_GREATER_THAN);
+ func_combo_box->addItem(">=", (int)VisualShaderNodeCompare::FUNC_GREATER_THAN_EQUAL);
+ func_combo_box->addItem("<", (int)VisualShaderNodeCompare::FUNC_LESS_THAN);
+ func_combo_box->addItem("<=", (int)VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL);
+
+ func_combo_box->setCurrentIndex((int)node->get_function());
+
+ condition_combo_box->addItem("All", (int)VisualShaderNodeCompare::COND_ALL);
+ condition_combo_box->addItem("Any", (int)VisualShaderNodeCompare::COND_ANY);
+
+ condition_combo_box->setCurrentIndex((int)node->get_condition());
+
+ addWidget(comparison_type_combo_box);
+ addWidget(func_combo_box);
+ addWidget(condition_combo_box);
+}
+
+VisualShaderNodeCompareEmbedWidget::~VisualShaderNodeCompareEmbedWidget() {}
+
+void VisualShaderNodeCompareEmbedWidget::on_comparison_type_current_index_changed(const int& index) {
+ node->set_comparison_type(
+ (VisualShaderNodeCompare::ComparisonType)comparison_type_combo_box->itemData(index).toInt());
+}
+void VisualShaderNodeCompareEmbedWidget::on_func_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeCompare::Function)func_combo_box->itemData(index).toInt());
+}
+void VisualShaderNodeCompareEmbedWidget::on_condition_current_index_changed(const int& index) {
+ node->set_condition((VisualShaderNodeCompare::Condition)condition_combo_box->itemData(index).toInt());
+}
+
+/*************************************/
+/* Switch Node */
+/*************************************/
+
+VisualShaderNodeSwitchEmbedWidget::VisualShaderNodeSwitchEmbedWidget(
+ const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Float", (int)VisualShaderNodeSwitch::OP_TYPE_FLOAT);
+ addItem("Int", (int)VisualShaderNodeSwitch::OP_TYPE_INT);
+ addItem("UInt", (int)VisualShaderNodeSwitch::OP_TYPE_UINT);
+ addItem("Vector 2D", (int)VisualShaderNodeSwitch::OP_TYPE_VECTOR_2D);
+ addItem("Vector 3D", (int)VisualShaderNodeSwitch::OP_TYPE_VECTOR_3D);
+ addItem("Vector 4D", (int)VisualShaderNodeSwitch::OP_TYPE_VECTOR_4D);
+ addItem("Boolean", (int)VisualShaderNodeSwitch::OP_TYPE_BOOLEAN);
+
+ setCurrentIndex((int)node->get_op_type());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeSwitchEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeSwitchEmbedWidget::~VisualShaderNodeSwitchEmbedWidget() {}
+
+void VisualShaderNodeSwitchEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_op_type((VisualShaderNodeSwitch::OpType)itemData(index).toInt());
+}
+
+/*************************************/
+/* Is Node */
+/*************************************/
+
+VisualShaderNodeIsEmbedWidget::VisualShaderNodeIsEmbedWidget(const std::shared_ptr& node)
+ : QComboBox(), node(node) {
+ setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ setContentsMargins(0, 0, 0, 0); // Left, top, right, bottom
+
+ addItem("Is Inf", (int)VisualShaderNodeIs::FUNC_IS_INF);
+ addItem("Is NaN", (int)VisualShaderNodeIs::FUNC_IS_NAN);
+
+ setCurrentIndex((int)node->get_function());
+
+ QObject::connect(this, QOverload::of(&QComboBox::currentIndexChanged), this,
+ &VisualShaderNodeIsEmbedWidget::on_current_index_changed);
+}
+
+VisualShaderNodeIsEmbedWidget::~VisualShaderNodeIsEmbedWidget() {}
+
+void VisualShaderNodeIsEmbedWidget::on_current_index_changed(const int& index) {
+ node->set_function((VisualShaderNodeIs::Function)itemData(index).toInt());
+}
diff --git a/Editors/VisualShaderEditor.h b/Editors/VisualShaderEditor.h
new file mode 100644
index 000000000..71b5d2480
--- /dev/null
+++ b/Editors/VisualShaderEditor.h
@@ -0,0 +1,1516 @@
+/*********************************************************************************/
+/* */
+/* Copyright (C) 2024 Saif Kandil (k0T0z) */
+/* */
+/* This file is a part of the ENIGMA Development Environment. */
+/* */
+/* */
+/* ENIGMA is free software: you can redistribute it and/or modify it under the */
+/* terms of the GNU General Public License as published by the Free Software */
+/* Foundation, version 3 of the license or any later version. */
+/* */
+/* This application and its source code is distributed AS-IS, WITHOUT ANY */
+/* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS */
+/* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more */
+/* details. */
+/* */
+/* You should have recieved a copy of the GNU General Public License along */
+/* with this code. If not, see */
+/* */
+/* ENIGMA is an environment designed to create games and other programs with a */
+/* high-level, fully compilable language. Developers of ENIGMA or anything */
+/* associated with ENIGMA are in no way responsible for its users or */
+/* applications created by its users, or damages caused by the environment */
+/* or programs made in the environment. */
+/* */
+/*********************************************************************************/
+
+#ifndef ENIGMA_VISUAL_SHADER_EDITOR_H
+#define ENIGMA_VISUAL_SHADER_EDITOR_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+// #include
+#include
+#include
+#include
+#include // https://stackoverflow.com/a/64288966/14629018 explains why we need this.
+#include
+
+#include
+#include
+
+#include "Editors/BaseEditor.h"
+#include "ResourceTransformations/VisualShader/visual_shader.h"
+#include "ResourceTransformations/VisualShader/visual_shader_nodes.h"
+#include "ResourceTransformations/VisualShader/vs_noise_nodes.h"
+
+class VisualShaderGraphicsScene;
+class VisualShaderGraphicsView;
+class VisualShaderNodeGraphicsObject;
+class VisualShaderConnectionGraphicsObject;
+class CreateNodeDialog;
+class VisualShaderInputPortGraphicsObject;
+class VisualShaderOutputPortGraphicsObject;
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderEditor *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+// Add const to any function that does not modify the object.
+
+class VisualShaderEditor : public BaseEditor {
+ Q_OBJECT
+
+ public:
+ /**
+ * @brief This constructor is meant to be used for testing purposes. As
+ * it doesn't require a MessageModel object.
+ *
+ * @param parent
+ */
+ VisualShaderEditor(QWidget* parent = nullptr);
+ VisualShaderEditor(MessageModel* model, QWidget* parent = nullptr);
+ ~VisualShaderEditor() override;
+
+ VisualShaderGraphicsScene* get_scene() const { return scene; }
+ VisualShaderGraphicsView* get_view() const { return view; }
+
+ Q_SIGNALS:
+ /**
+ * @brief Request the dialog that has all kinds of nodes we can
+ * create.
+ *
+ * @note This signal is emitted from two sources:
+ * @c VisualShaderEditor::on_create_node_button_pressed and
+ * @c VisualShaderGraphicsView::on_create_node_action_triggered slots
+ * and it is connected to the @c VisualShaderEditor::show_create_node_dialog
+ * function.
+ *
+ * @param coordinate
+ */
+ void create_node_dialog_requested(const QPointF& coordinate);
+
+ private Q_SLOTS:
+ /**
+ * @brief Called when @c VisualShaderEditor::create_node_button is pressed.
+ *
+ * @note Connected in @c VisualShaderEditor::init function
+ * to @c QPushButton::pressed signal.
+ *
+ * @note EMITS @c VisualShaderEditor::create_node_dialog_requested signal.
+ *
+ */
+ void on_create_node_button_pressed();
+ void on_preview_shader_button_pressed();
+
+ void on_menu_button_pressed();
+ void on_load_image_button_pressed();
+ void on_match_image_button_pressed();
+
+ private:
+ VisualShader* visual_shader;
+ MessageModel* visual_shader_model;
+
+ QHBoxLayout* layout;
+
+ QWidget* side_widget;
+ QVBoxLayout* side_outer_layout;
+ QVBoxLayout* side_layout;
+ QLineEdit* name_edit;
+ QPushButton* save_button;
+
+ QHBoxLayout* scene_layer_layout;
+ QWidget* scene_layer; // Layer having the scene.
+ VisualShaderGraphicsScene* scene;
+ VisualShaderGraphicsView* view;
+
+ QWidget* top_layer; // Layer having the menu bar.
+ QHBoxLayout* menu_bar;
+
+ QPushButton* menu_button;
+ QPushButton* create_node_button;
+ QPushButton* preview_shader_button;
+ QPushButton* zoom_in_button;
+ QPushButton* reset_zoom_button;
+ QPushButton* zoom_out_button;
+
+ QPushButton* load_image_button;
+ QPushButton* match_image_button;
+
+ QAction* create_node_action;
+
+ ////////////////////////////////////
+ // Code Previewer
+ ////////////////////////////////////
+
+ QDialog* code_previewer_dialog;
+ QVBoxLayout* code_previewer_layout;
+ QPlainTextEdit* code_previewer;
+
+ ////////////////////////////////////
+ // CreateNodeDialog Nodes Tree
+ ////////////////////////////////////
+
+ struct CreateNodeDialogNodesTreeItem {
+ std::string name;
+ std::string category_path;
+ std::string type;
+ std::string description;
+
+ CreateNodeDialogNodesTreeItem(const std::string& name = std::string(),
+ const std::string& category_path = std::string(),
+ const std::string& type = std::string(),
+ const std::string& description = std::string())
+ : name(name), category_path(category_path), type(type), description(description) {}
+ };
+
+ static const VisualShaderEditor::CreateNodeDialogNodesTreeItem create_node_dialog_nodes_tree_items[];
+
+ CreateNodeDialog* create_node_dialog;
+
+ /**
+ * @brief Initializes the UI
+ *
+ * @note To be called from different constructors. This function shouldn't contain
+ * any code related to MessageModel class as this will break the tests.
+ *
+ */
+ void init();
+
+ /**
+ * @brief The VisualShader class may have some nodes at the beginning. This function
+ * is meant to add those nodes to the scene.
+ *
+ */
+ void init_graph();
+
+ void create_node(const QPointF& coordinate);
+
+ void add_node(QTreeWidgetItem* selected_item, const QPointF& coordinate);
+
+ void show_create_node_dialog(const QPointF& coordinate);
+
+ std::vector parse_node_category_path(const std::string& node_category_path);
+ QTreeWidgetItem* find_or_create_category_item(QTreeWidgetItem* parent, const std::string& category,
+ const std::string& category_path,
+ QTreeWidget* create_node_dialog_nodes_tree,
+ std::unordered_map& category_path_map);
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** CreateNodeDialog *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class CreateNodeDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ CreateNodeDialog(QWidget* parent = nullptr);
+ ~CreateNodeDialog();
+
+ QTreeWidget* get_nodes_tree() const { return create_node_dialog_nodes_tree; }
+
+ QTreeWidgetItem* get_selected_item() const { return selected_item; }
+
+ private Q_SLOTS:
+ void on_create_node_button_pressed();
+ void on_cancel_node_creation_button_pressed();
+
+ void update_selected_item();
+
+ private:
+ QVBoxLayout* layout;
+
+ QVBoxLayout* create_node_dialog_nodes_tree_layout;
+
+ QTreeWidget* create_node_dialog_nodes_tree;
+ QTextEdit* create_node_dialog_nodes_description;
+
+ QHBoxLayout* buttons_layout;
+ QPushButton* create_button;
+ QPushButton* cancel_button;
+
+ QTreeWidgetItem* selected_item;
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** OriginalMatchingImageWidget *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class OriginalMatchingImageWidget : public QWidget {
+ public:
+ OriginalMatchingImageWidget(QWidget* parent = nullptr) : QWidget(parent) {
+ pixmap = QPixmap(size());
+ pixmap.fill(Qt::red); // Fill it with the red color
+ }
+
+ protected:
+ // Override the paintEvent to display the pixmap
+ void paintEvent(QPaintEvent* event) override {
+ QPainter painter(this);
+ painter.drawPixmap(0, 0, pixmap); // Draw the pixmap starting at (0, 0)
+ }
+
+ private:
+ QPixmap pixmap;
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** ShaderPreviewerWidget *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+/**
+ * @brief This class is meant to be a temporary solution to preview the shader
+ * code. We should preview the shader code using ENIGMA's Graphics System.
+ *
+ * @todo Replace this class with ENIGMA's Graphics System.
+ *
+ */
+class ShaderPreviewerWidget : public QOpenGLWidget {
+ Q_OBJECT
+
+ public:
+ ShaderPreviewerWidget(QWidget* parent = nullptr);
+ ~ShaderPreviewerWidget() override;
+
+ void set_code(const std::string& code);
+
+ Q_SIGNALS:
+ void scene_update_requested();
+
+ protected:
+ void initializeGL() override;
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+
+ void showEvent(QShowEvent* event) override;
+ void hideEvent(QHideEvent* event) override;
+
+ private:
+ std::unique_ptr shader_program;
+ GLuint VAO, VBO;
+ QElapsedTimer timer;
+
+ std::string code;
+ bool shader_needs_update{false};
+
+ void init_shaders();
+ void init_buffers();
+ void update_shader_program();
+
+ /**
+ * @brief Cleans up the OpenGL resources.
+ *
+ * @note This function is called automatically when the widget is destroyed.
+ * It is connected @c QOpenGLContext::aboutToBeDestroyed signal.
+ *
+ * @note DON'T call this function in the destructor as it is
+ * called automatically. If you call it from the destructor,
+ * it will crash as @c makeCurrent() won't be able to make the
+ * context current.
+ *
+ */
+ void cleanup();
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderGraphicsScene *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class VisualShaderGraphicsScene : public QGraphicsScene {
+ Q_OBJECT
+
+ public:
+ VisualShaderGraphicsScene(VisualShader* vs, QObject* parent = nullptr);
+
+ ~VisualShaderGraphicsScene();
+
+ bool add_node(const std::string& type, const QPointF& coordinate);
+ bool add_node(const int& n_id, const std::shared_ptr& n, const QPointF& coordinate);
+ bool delete_node(const int& n_id);
+
+ VisualShaderEditor* get_editor() const { return editor; }
+ void set_editor(VisualShaderEditor* editor) const { this->editor = editor; }
+
+ /**
+ * @brief
+ *
+ * @note This function sets the @c temporary_connection_graphics_object if
+ * we have a valid @c from_node_id and @c from_port_index only. Then it
+ * resets it again if we have a valid @c to_node_id and @c to_port_index and
+ * this is important because inside the drag and drop event, we need to know
+ * if we have a valid temporary connection or not.
+ *
+ * @param from_node_id
+ * @param from_port_index
+ * @param to_node_id
+ * @param to_port_index
+ * @return true
+ * @return false
+ */
+ bool add_connection(const int& from_node_id, const int& from_port_index,
+ const int& to_node_id = (int)VisualShader::NODE_ID_INVALID,
+ const int& to_port_index = (int)VisualShader::PORT_INDEX_INVALID);
+
+ bool delete_connection(const int& from_node_id, const int& from_port_index,
+ const int& to_node_id = (int)VisualShader::NODE_ID_INVALID,
+ const int& to_port_index = (int)VisualShader::PORT_INDEX_INVALID);
+
+ VisualShaderNodeGraphicsObject* get_node_graphics_object(const int& n_id) const;
+
+ private Q_SLOTS:
+ /**
+ * @brief Called when an interaction with a port is made.
+ *
+ * @note Connected in @c VisualShaderNodeGraphicsObject::paint function
+ * to @c VisualShaderInputPortGraphicsObject::port_* signals.
+ *
+ * @param port
+ * @param coordinate
+ */
+ void on_port_pressed(QGraphicsObject* port, const QPointF& coordinate);
+ void on_port_dragged(QGraphicsObject* port, const QPointF& coordinate);
+ void on_port_dropped(QGraphicsObject* port, const QPointF& coordinate);
+
+ /**
+ * @brief Called when a node is moved.
+ *
+ * @note Connected in @c VisualShaderGraphicsScene::VisualShaderGraphicsScene constructor
+ * to @c VisualShaderGraphicsScene::node_moved signal.
+ *
+ * @param n_id
+ * @param new_coordinate
+ */
+ void on_node_moved(const int& n_id, const QPointF& new_coordinate);
+
+ /**
+ * @brief Called when a delete node action is triggered.
+ *
+ * @note Connected in @c VisualShaderGraphicsScene::add_node function
+ * to @c VisualShaderNodeGraphicsObject::node_deleted signal.
+ *
+ * @param n_id
+ */
+ void on_node_deleted(const int& n_id);
+
+ /**
+ * @brief Updates the code inside all the nodes except the output node.
+ *
+ */
+ void on_update_shader_previewer_widgets_requested();
+
+ void on_scene_update_requested();
+
+ void on_in_port_remove_requested(VisualShaderInputPortGraphicsObject* in_port);
+ void on_out_port_remove_requested(VisualShaderOutputPortGraphicsObject* out_port);
+
+ private:
+ VisualShader* vs;
+ mutable VisualShaderEditor* editor;
+
+ std::unordered_map node_graphics_objects;
+
+ VisualShaderConnectionGraphicsObject* temporary_connection_graphics_object;
+
+ void remove_item(QGraphicsItem* item);
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderGraphicsView *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class VisualShaderGraphicsView : public QGraphicsView {
+ Q_OBJECT
+
+ public:
+ VisualShaderGraphicsView(VisualShaderGraphicsScene* scene, QWidget* parent = nullptr);
+
+ ~VisualShaderGraphicsView();
+
+ float get_x() const { return rect_x; }
+ float get_y() const { return rect_y; }
+ float get_width() const { return rect_width; }
+ float get_height() const { return rect_height; }
+
+ public Q_SLOTS:
+ /**
+ * @todo If the button is pressed then zoom in from the center of the view.
+ */
+ void zoom_in();
+ void reset_zoom();
+ void zoom_out();
+
+ private Q_SLOTS:
+ /**
+ * @brief Called when @c VisualShaderGraphicsView::create_node_action is triggered.
+ *
+ * @note Connected in @c VisualShaderGraphicsView::VisualShaderGraphicsView constructor
+ * to @c QAction::triggered signal.
+ *
+ * @note EMITS @c VisualShaderEditor::create_node_dialog_requested signal.
+ *
+ */
+ void on_create_node_action_triggered();
+
+ Q_SIGNALS:
+ void zoom_changed(const float& zoom);
+
+ private:
+ VisualShaderGraphicsScene* scene;
+
+ // Style
+ QColor background_color = QColor(40, 40, 40); // Dark Charcoal
+ QColor fine_grid_color = QColor(50, 50, 50); // Soft Dark Gray
+ QColor coarse_grid_color = QColor(30, 30, 30); // Muted Deep Gray
+
+ // Scene Rect
+ float t_size = std::numeric_limits::max(); // 32767
+ float rect_x = -1.0f * t_size * 0.5f;
+ float rect_y = -1.0f * t_size * 0.5f;
+ float rect_width = t_size;
+ float rect_height = t_size;
+
+ float fit_in_view_margin = 50.0f;
+
+ // Zoom
+ float zoom = 1.0f;
+ float zoom_step = 1.2f;
+ float zoom_min;
+ float zoom_max;
+
+ QMenu* context_menu;
+ QPointF last_context_menu_coordinate = {0, 0};
+ QAction* create_node_action;
+
+ QAction* zoom_in_action;
+ QAction* reset_zoom_action;
+ QAction* zoom_out_action;
+
+ QPointF last_click_coordinate;
+
+ void drawBackground(QPainter* painter, const QRectF& r) override;
+ void contextMenuEvent(QContextMenuEvent* event) override;
+ void wheelEvent(QWheelEvent* event) override;
+ void mousePressEvent(QMouseEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mouseReleaseEvent(QMouseEvent* event) override;
+ void showEvent(QShowEvent* event) override;
+
+ void move_view_to_fit_items();
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderNodeGraphicsObject *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class VisualShaderNodeGraphicsObject : public QGraphicsObject {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeGraphicsObject(const int& n_id, const QPointF& coordinate,
+ const std::shared_ptr& node, QGraphicsItem* parent = nullptr);
+ ~VisualShaderNodeGraphicsObject();
+
+ VisualShaderInputPortGraphicsObject* get_input_port_graphics_object(const int& p_index) const;
+ VisualShaderOutputPortGraphicsObject* get_output_port_graphics_object(const int& p_index) const;
+
+ QWidget* get_embed_widget() const { return embed_widget; }
+ void set_embed_widget(QWidget* embed_widget) { this->embed_widget = embed_widget; }
+
+ ShaderPreviewerWidget* get_shader_previewer_widget() const { return shader_previewer_widget; }
+
+ public Q_SLOTS:
+ void on_node_update_requested();
+
+ Q_SIGNALS:
+ /**
+ * @brief Send a request to delete a node.
+ *
+ * @note EMITTED from @c VisualShaderNodeGraphicsObject::on_delete_node_action_triggered slot.
+ *
+ * @note Connected in @c VisualShaderGraphicsScene::add_node function to
+ * @c VisualShaderGraphicsScene::on_node_deleted slot.
+ *
+ * @param n_id
+ */
+ void node_deleted(const int& n_id);
+
+ /**
+ * @brief Notify the scene that a node has been moved.
+ *
+ * @note EMITTED from @c VisualShaderNodeGraphicsObject::itemChange function.
+ *
+ * @note Connected to @c VisualShaderGraphicsScene::on_node_moved slot in
+ * @c VisualShaderGraphicsScene::VisualShaderGraphicsScene constructor.
+ *
+ * @param n_id
+ * @param new_coordinate
+ */
+ void node_moved(const int& n_id, const QPointF& new_coordinate);
+
+ void in_port_pressed(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+ void in_port_dragged(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+ void in_port_dropped(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+
+ void out_port_pressed(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+ void out_port_dragged(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+ void out_port_dropped(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+
+ void scene_update_requested();
+
+ void in_port_remove_requested(VisualShaderInputPortGraphicsObject* in_port);
+ void out_port_remove_requested(VisualShaderOutputPortGraphicsObject* out_port);
+
+ private Q_SLOTS:
+ /**
+ * @brief Called when @c VisualShaderNodeGraphicsObject::delete_node_action is triggered.
+ *
+ * @note Connected in @c VisualShaderNodeGraphicsObject::VisualShaderNodeGraphicsObject constructor
+ * to @c QAction::triggered signal.
+ *
+ * @note EMITS @c VisualShaderNodeGraphicsObject::node_deleted signal.
+ *
+ */
+ void on_delete_node_action_triggered();
+
+ void on_in_port_pressed(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT in_port_pressed(port, coordinate);
+ }
+ void on_in_port_dragged(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT in_port_dragged(port, coordinate);
+ }
+ void on_in_port_dropped(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT in_port_dropped(port, coordinate);
+ }
+
+ void on_out_port_pressed(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT out_port_pressed(port, coordinate);
+ }
+ void on_out_port_dragged(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT out_port_dragged(port, coordinate);
+ }
+ void on_out_port_dropped(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate) {
+ Q_EMIT out_port_dropped(port, coordinate);
+ }
+
+ private:
+ int n_id;
+ QPointF coordinate;
+ std::shared_ptr node;
+
+ QMenu* context_menu;
+ QAction* delete_node_action;
+
+ std::unordered_map in_port_graphics_objects;
+ std::unordered_map out_port_graphics_objects;
+
+ // Style
+ QColor normal_boundary_color = QColor(220, 20, 60); // Crimson Red
+ QColor selected_boundary_color = QColor(255, 69, 0); // Red-Orange
+ QColor font_color = QColor(255, 255, 255); // Pure White
+ QColor fill_color = QColor(40, 40, 40, 200); // Semi-transparent Dark Gray
+
+ float pen_width = 1.0f;
+
+ float opacity = 0.8f;
+ float corner_radius = 3.0f;
+ float port_diameter = 8.0f;
+
+ float caption_h_padding = 10.0f;
+ float caption_v_padding = 8.0f;
+
+ mutable float rect_width; // Calculated in boundingRect()
+ mutable float caption_rect_height; // Calculated in boundingRect()
+
+ mutable float rect_height; // Calculated in boundingRect()
+ float body_rect_header_height = 30.0f;
+ float body_rect_port_step = 40.0f;
+ float body_rect_footer_height = 30.0f;
+
+ mutable float rect_padding; // Calculated in boundingRect()
+ mutable float rect_margin; // Calculated in boundingRect()
+
+ float port_caption_spacing = 4.0f; // Distance between the port and its caption
+
+ // Ports Style
+ float connected_port_diameter = 8.0f;
+ float unconnected_port_diameter = 6.0f;
+
+ // Caption
+ float caption_font_size = 18.0f;
+ float port_caption_font_size = 8.0f;
+
+ QWidget* embed_widget;
+ float embed_widget_h_padding = 15.0f;
+ float embed_widget_v_padding = 5.0f;
+
+ OriginalMatchingImageWidget* matching_image_widget;
+ float spacing_between_output_node_and_matching_image = 10.0f;
+
+ ShaderPreviewerWidget* shader_previewer_widget;
+ float spacing_between_current_node_and_shader_previewer = 10.0f;
+
+ QRectF boundingRect() const override;
+
+ /**
+ * @brief
+ *
+ * @note This function contains some commented code that is meant to be used
+ * for debugging purposes.
+ *
+ * @param painter
+ * @param option
+ * @param widget
+ */
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
+ QVariant itemChange(GraphicsItemChange change, const QVariant& value) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
+};
+
+class VisualShaderInputPortGraphicsObject : public QGraphicsObject {
+ Q_OBJECT
+
+ public:
+ VisualShaderInputPortGraphicsObject(const QRectF& rect, const int& n_id, const int& p_index,
+ QGraphicsItem* parent = nullptr);
+ ~VisualShaderInputPortGraphicsObject();
+
+ QPointF get_global_coordinate() const { return mapToScene(rect.center()); }
+
+ int get_node_id() const { return n_id; }
+ int get_port_index() const { return p_index; }
+
+ VisualShaderConnectionGraphicsObject* get_connection_graphics_object() const { return connection_graphics_object; }
+ void connect(VisualShaderConnectionGraphicsObject* c_g_o) const { this->connection_graphics_object = c_g_o; }
+ void detach_connection() const { this->connection_graphics_object = nullptr; }
+ bool is_connected() const { return connection_graphics_object != nullptr; }
+
+ Q_SIGNALS:
+ /**
+ * @brief Called when the an interaction with the port is made.
+ *
+ * @note Connected in @c VisualShaderNodeGraphicsObject::paint function to
+ * @c VisualShaderGraphicsScene::on_port_* slots.
+ *
+ * @param port
+ * @param coordinate
+ */
+ void port_pressed(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+ void port_dragged(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+ void port_dropped(VisualShaderInputPortGraphicsObject* port, const QPointF& coordinate);
+
+ private:
+ int n_id;
+ int p_index;
+ QRectF rect;
+
+ mutable VisualShaderConnectionGraphicsObject* connection_graphics_object;
+
+ float padding = 0.5f;
+
+ // Style
+ QColor font_color = QColor(255, 255, 255);
+ QColor connection_point_color = QColor(220, 20, 60); // Crimson
+
+ float opacity = 1.0f;
+
+ QRectF boundingRect() const override;
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+};
+
+class VisualShaderOutputPortGraphicsObject : public QGraphicsObject {
+ Q_OBJECT
+
+ public:
+ VisualShaderOutputPortGraphicsObject(const QRectF& rect, const int& n_id, const int& p_index,
+ QGraphicsItem* parent = nullptr);
+ ~VisualShaderOutputPortGraphicsObject();
+
+ QPointF get_global_coordinate() const { return mapToScene(rect.center()); }
+
+ int get_node_id() const { return n_id; }
+ int get_port_index() const { return p_index; }
+
+ std::vector get_connection_graphics_objects() const {
+ return connection_graphics_objects;
+ }
+ VisualShaderConnectionGraphicsObject* get_connection_graphics_object(const int& to_node_id,
+ const int& to_port_index) const;
+ void connect(VisualShaderConnectionGraphicsObject* c_o) { this->connection_graphics_objects.emplace_back(c_o); }
+ void detach_connection(VisualShaderConnectionGraphicsObject* c_o) {
+ connection_graphics_objects.erase(
+ std::remove(connection_graphics_objects.begin(), connection_graphics_objects.end(), c_o),
+ connection_graphics_objects.end());
+ }
+ bool is_connected() const { return connection_graphics_objects.size() > 0; }
+
+ Q_SIGNALS:
+ /**
+ * @brief Called when the an interaction with the port is made.
+ *
+ * @note Connected in @c VisualShaderNodeGraphicsObject::paint function to
+ * @c VisualShaderGraphicsScene::on_port_* slots.
+ *
+ * @param port
+ * @param coordinate
+ */
+ void port_pressed(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+ void port_dragged(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+ void port_dropped(VisualShaderOutputPortGraphicsObject* port, const QPointF& coordinate);
+
+ private:
+ int n_id;
+ int p_index;
+ QRectF rect;
+
+ // An output port can have multiple connections.
+ std::vector connection_graphics_objects;
+
+ float padding = 0.5f;
+
+ // Style
+ QColor font_color = QColor(255, 255, 255);
+ QColor connection_point_color = QColor(220, 20, 60); // Crimson
+
+ float opacity = 1.0f;
+
+ QRectF boundingRect() const override;
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** VisualShaderConnectionGraphicsObject *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class VisualShaderConnectionGraphicsObject : public QGraphicsObject {
+ Q_OBJECT
+
+ public:
+ VisualShaderConnectionGraphicsObject(const int& from_n_id, const int& from_p_index, const QPointF& start_coordinate,
+ QGraphicsItem* parent = nullptr);
+ ~VisualShaderConnectionGraphicsObject();
+
+ int get_from_node_id() const { return from_n_id; }
+ int get_from_port_index() const { return from_p_index; }
+
+ int get_to_node_id() const { return to_n_id; }
+ int get_to_port_index() const { return to_p_index; }
+
+ void detach_end() const {
+ this->set_to_node_id((int)VisualShader::NODE_ID_INVALID);
+ this->set_to_port_index((int)VisualShader::PORT_INDEX_INVALID);
+ }
+
+ void set_to_node_id(const int& to_n_id) const { this->to_n_id = to_n_id; }
+ void set_to_port_index(const int& to_p_index) const { this->to_p_index = to_p_index; }
+
+ void set_start_coordinate(const QPointF& start_coordinate) {
+ this->start_coordinate = start_coordinate;
+ update();
+ }
+ void set_end_coordinate(const QPointF& end_coordinate) {
+ this->end_coordinate = end_coordinate;
+ update();
+ }
+
+ private:
+ int from_n_id;
+ mutable int to_n_id;
+ int from_p_index;
+ mutable int to_p_index;
+
+ QPointF start_coordinate;
+ QPointF end_coordinate;
+
+ // Style
+ QColor construction_color = QColor(139, 0, 0); // Dark Red
+ QColor normal_color = QColor(178, 34, 34); // Firebrick Red
+ QColor selected_color = QColor(55, 55, 55); // Dark Gray
+ QColor connection_point_color = QColor(211, 211, 211);
+
+ float line_width = 3.0f;
+ float construction_line_width = 2.0f;
+ float point_diameter = 10.0f;
+
+ mutable float rect_padding; // Calculated in boundingRect()
+
+ float h_abnormal_offset = 50.0f;
+ float v_abnormal_offset = 40.0f;
+ float abnormal_face_to_back_control_width_expansion_factor = 0.5f;
+ float abnormal_face_to_back_control_height_expansion_factor = 1.0f;
+
+ QRectF boundingRect() const override;
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
+
+ int detect_quadrant(const QPointF& reference, const QPointF& target) const;
+ QRectF calculate_bounding_rect_from_coordinates(const QPointF& start_coordinate, const QPointF& end_coordinate) const;
+ std::pair calculate_control_points(const QPointF& start_coordinate,
+ const QPointF& end_coordinate) const;
+};
+
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+/***** *****/
+/***** Embed Widgets *****/
+/***** *****/
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+class VisualShaderNodeEmbedWidget : public QWidget {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeEmbedWidget(const std::shared_ptr& node, QWidget* parent = nullptr);
+ ~VisualShaderNodeEmbedWidget();
+
+ void set_shader_previewer_widget(QWidget* shader_previewer_widget) {
+ this->shader_previewer_widget = shader_previewer_widget;
+ }
+
+ Q_SIGNALS:
+ void shader_preview_update_requested();
+ void node_update_requested();
+
+ private Q_SLOTS:
+ void on_preview_shader_button_pressed() {
+ bool is_visible{shader_previewer_widget->isVisible()};
+ shader_previewer_widget->setVisible(!is_visible);
+ preview_shader_button->setText(!is_visible ? "Hide Preview" : "Show Preview");
+ }
+
+ void on_shader_preview_update_requested() { Q_EMIT shader_preview_update_requested(); }
+
+ void on_node_update_requested() { Q_EMIT node_update_requested(); }
+
+ private:
+ QVBoxLayout* layout;
+
+ QPushButton* preview_shader_button;
+
+ QWidget* shader_previewer_widget;
+};
+
+/*************************************/
+/* Input Node */
+/*************************************/
+
+class VisualShaderNodeInputEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeInputEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeInputEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Float Op Node */
+/*************************************/
+
+class VisualShaderNodeFloatOpEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeFloatOpEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeFloatOpEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Int Op Node */
+/*************************************/
+
+class VisualShaderNodeIntOpEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeIntOpEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeIntOpEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* UInt Op Node */
+/*************************************/
+
+class VisualShaderNodeUIntOpEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeUIntOpEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeUIntOpEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Float Funcs Node */
+/*************************************/
+
+class VisualShaderNodeFloatFuncEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeFloatFuncEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeFloatFuncEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Int Funcs Node */
+/*************************************/
+
+class VisualShaderNodeIntFuncEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeIntFuncEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeIntFuncEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* UInt Funcs Node */
+/*************************************/
+
+class VisualShaderNodeUIntFuncEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeUIntFuncEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeUIntFuncEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Vector Base */
+/*************************************/
+
+class VisualShaderNodeVectorBaseEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeVectorBaseEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeVectorBaseEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Vector Op Node */
+/*************************************/
+
+class VisualShaderNodeVectorOpEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeVectorOpEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeVectorOpEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Vector Funcs Node */
+/*************************************/
+
+class VisualShaderNodeVectorFuncEmbedWidget : public QComboBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeVectorFuncEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeVectorFuncEmbedWidget();
+
+ void set_current_index(const int& index) { this->setCurrentIndex(index); }
+
+ private Q_SLOTS:
+ void on_current_index_changed(const int& index);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Color Constant Node */
+/*************************************/
+
+class VisualShaderNodeColorConstantEmbedWidget : public QPushButton {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeColorConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeColorConstantEmbedWidget();
+
+ Q_SIGNALS:
+ void color_changed();
+
+ private Q_SLOTS:
+ void on_pressed();
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Boolean Constant Node */
+/*************************************/
+
+class VisualShaderNodeBooleanConstantEmbedWidget : public QCheckBox {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeBooleanConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeBooleanConstantEmbedWidget();
+
+ private Q_SLOTS:
+ void on_state_changed(const int& state);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Float Constant */
+/*************************************/
+
+class VisualShaderNodeFloatConstantEmbedWidget : public QLineEdit {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeFloatConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeFloatConstantEmbedWidget();
+
+ private Q_SLOTS:
+ void on_text_changed(const QString& text);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Int Constant */
+/*************************************/
+
+class VisualShaderNodeIntConstantEmbedWidget : public QLineEdit {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeIntConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeIntConstantEmbedWidget();
+
+ private Q_SLOTS:
+ void on_text_changed(const QString& text);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* UInt Constant */
+/*************************************/
+
+class VisualShaderNodeUIntConstantEmbedWidget : public QLineEdit {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeUIntConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeUIntConstantEmbedWidget();
+
+ private Q_SLOTS:
+ void on_text_changed(const QString& text);
+
+ private:
+ std::shared_ptr node;
+};
+
+/*************************************/
+/* Vec2 Constant Node */
+/*************************************/
+
+class VisualShaderNodeVec2ConstantEmbedWidget : public QVBoxLayout {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeVec2ConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeVec2ConstantEmbedWidget();
+
+ QLineEdit* get_x_edit_widget() const { return x_edit_widget; }
+ QLineEdit* get_y_edit_widget() const { return y_edit_widget; }
+
+ private Q_SLOTS:
+
+ void on_x_text_changed(const QString& text);
+ void on_y_text_changed(const QString& text);
+
+ private:
+ std::shared_ptr node;
+
+ QLineEdit* x_edit_widget;
+ QLineEdit* y_edit_widget;
+};
+
+/*************************************/
+/* Vec3 Constant Node */
+/*************************************/
+
+class VisualShaderNodeVec3ConstantEmbedWidget : public QVBoxLayout {
+ Q_OBJECT
+
+ public:
+ VisualShaderNodeVec3ConstantEmbedWidget(const std::shared_ptr& node);
+ ~VisualShaderNodeVec3ConstantEmbedWidget();
+
+ QLineEdit* get_x_edit_widget() const { return x_edit_widget; }
+ QLineEdit* get_y_edit_widget() const { return y_edit_widget; }
+ QLineEdit* get_z_edit_widget() const { return z_edit_widget; }
+
+ private Q_SLOTS:
+
+ void on_x_text_changed(const QString& text);
+ void on_y_text_changed(const QString& text);
+ void on_z_text_changed(const QString& text);
+
+ private:
+ std::shared_ptr