diff --git a/CMakeLists.txt b/CMakeLists.txt index c49da96b..58171773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,46 +14,48 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## -CMAKE_MINIMUM_REQUIRED(VERSION 3.20) +CMAKE_MINIMUM_REQUIRED(VERSION 3.20 FATAL_ERROR) SET(GDEXTENSION_LIB_NAME orchestrator) SET(GDEXTENSION_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/project/addons/orchestrator") -# This doesn't work for editor plugins -# ADD_COMPILE_DEFINITIONS(HOT_RELOAD_ENABLED) -add_compile_definitions(TOOLS_ENABLED) - -OPTION( - AUTOFORMAT_SRC_ON_CONFIGURE - "If enabled, clang-format will be used to format all sources in src/ during configuration" - OFF -) +# Configurable options +OPTION(AUTOFORMAT_SRC_ON_CONFIGURE "If enabled, clang-format will be used to format all sources in /src during configuration" OFF) +# Set basic CMAKE properties SET(CMAKE_CXX_STANDARD 20) SET(CMAKE_CXX_EXTENSIONS ON) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_COLOR_DIAGNOSTICS ON) SET(CMAKE_MESSAGE_LOG_LEVEL STATUS) +# Get the current repository Git commit hash +# This is used when creating the data from VERSION to show the commit hash in the about box FIND_PACKAGE(Git QUIET) -IF(GIT_FOUND) +IF (GIT_FOUND) EXECUTE_PROCESS( COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -ELSE() - SET(GIT_COMMIT_HASH "") -ENDIF() + OUTPUT_STRIP_TRAILING_WHITESPACE) -INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/_generated) + EXECUTE_PROCESS( + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/extern/godot-cpp" + OUTPUT_VARIABLE GODOT_CPP_GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE) +ELSE () + # In this case the about box will simply show an unknown git commit + SET(GIT_COMMIT_HASH "") + SET(GODOT_CPP_GIT_COMMIT_HASH "") +ENDIF () +# Setup the CMAKE_MODULE_PATH LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" - "${CMAKE_CURRENT_SOURCE_DIR}/extern/godot-cpp/cmake/" -) + "${CMAKE_CURRENT_SOURCE_DIR}/extern/godot-cpp/cmake/") +# Include and execute various CMAKE modules INCLUDE(generate-authors) INCLUDE(generate-license) INCLUDE(generate-version) @@ -62,20 +64,33 @@ INCLUDE(godot-extension-db-generator) INCLUDE(godot-docs-generator) # Generation steps +GENERATE_AUTHORS() GENERATE_LICENSE() GENERATE_VERSION() -GENERATE_AUTHORS() GENERATE_DONORS() GENERATE_GODOT_EXTENSION_DB() GENERATE_GODOT_DOCUMENTATION() +# Configure project +PROJECT("${GDEXTENSION_LIB_NAME}" LANGUAGES C CXX VERSION ${RESOLVED_VERSION}) + +# Generate library resource +IF (WIN32) + SET(win32_product_name "${RESOLVED_VERSION_NAME}") + SET(win32_file_version "${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0") + SET(win32_product_version "${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0") + SET(win32_file_description "Godot ${win32_product_name} Plug-in") + CONFIGURE_FILE(cmake/templates/windows.rc.in ${CMAKE_CURRENT_BINARY_DIR}/_generated/version.rc @ONLY) +ENDIF () + +# Orchestrator is an editor plug-in, force TOOLS_ENABLED +ADD_COMPILE_DEFINITIONS(TOOLS_ENABLED) + # MacOS universal binary support IF (APPLE) SET(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "Build architectures for OSX" FORCE) ENDIF () -PROJECT("${GDEXTENSION_LIB_NAME}" LANGUAGES C CXX VERSION ${RESOLVED_VERSION}) - # Compiler Identification SET(compiler_is_clang "$,$>") SET(compiler_is_gnu "$") @@ -91,7 +106,8 @@ FILE(GLOB_RECURSE gdext_sources "${CMAKE_CURRENT_SOURCE_DIR}/src/*.[hc]pp" # Includes the generated doc data from /doc_classes "${CMAKE_BINARY_DIR}/_generated/*.cpp" -) + # Include windows version resource, if exists + "${CMAKE_CURRENT_BINARY_DIR}/_generated/version.rc") # GDExtension library ADD_LIBRARY(${PROJECT_NAME} SHARED ${gdext_sources}) @@ -132,17 +148,16 @@ TARGET_COMPILE_OPTIONS(${PROJECT_NAME} PUBLIC $<$: -O3 > - > -) + >) find_program(ccache_exe ccache) -if (ccache_exe) - set(CMAKE_VS_GLOBALS - "TrackFileAccess=false" - "UseMultiToolTask=true" - "DebugInformationFormat=OldStyle" - ) -endif() +IF (ccache_exe) + SET(CMAKE_VS_GLOBALS "TrackFileAccess=false" "UseMultiToolTask=true" "DebugInformationFormat=OldStyle") +ENDIF () + +# Add the special "_generated" directory to the include list +# This is where the generator CMAKE module steps creates automated files +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}/_generated) # Include directories for GDExtension library TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") @@ -157,20 +172,18 @@ IF (NOT APPLE) $<$: $<$:-s> > - > - ) + >) ENDIF () -if (AUTOFORMAT_SRC_ON_CONFIGURE MATCHES ON) +IF (AUTOFORMAT_SRC_ON_CONFIGURE MATCHES ON) include(clang-format) ENDIF () # Dependency linking -TARGET_LINK_LIBRARIES(${PROJECT_NAME} - PUBLIC godot::cpp -) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} PUBLIC godot::cpp) SET(GDEXTENSION_LIB_PREFIX "") + IF (NOT ANDROID) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) SET(system_bits 64) @@ -186,20 +199,7 @@ ELSE () ENDIF () ENDIF () -STRING(TOLOWER "${PROJECT_NAME}.${CMAKE_SYSTEM_NAME}.${system_bits}.${CMAKE_BUILD_TYPE}" gde_lib_name) - -# Generate library resource -IF(WIN32) - SET(win32_product_name "${RESOLVED_VERSION_NAME}") - SET(win32_file_version "${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0") - SET(win32_product_version "${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0") - SET(win32_file_description "Godot ${win32_product_name}") - CONFIGURE_FILE(cmake/windows.rc.in ${CMAKE_CURRENT_BINARY_DIR}/_generated/version.rc @ONLY) -ENDIF() - -IF (WIN32) - LIST(APPEND gdext_sources "${CMAKE_CURRENT_BINARY_DIR}/_generated/version.rc") -ENDIF() +STRING(TOLOWER "${PROJECT_NAME}.${CMAKE_SYSTEM_NAME}.${system_bits}.${CMAKE_BUILD_TYPE}" GDEXTENSION_LIB_NAME) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES @@ -212,8 +212,8 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY "${GDEXTENSION_LIB_PATH}" CMAKE_PDB_OUTPUT_DIRECTORY "${GDEXTENSION_LIB_PATH}" CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY "${GDEXTENSION_LIB_PATH}" - OUTPUT_NAME "${gde_lib_name}" -) + OUTPUT_NAME "${GDEXTENSION_LIB_NAME}") INCLUDE(cmake-utils) + PRINT_PROJECT_VARIABLES() diff --git a/cmake/clang-format.cmake b/cmake/clang-format.cmake index 1086ecb1..0501f55b 100644 --- a/cmake/clang-format.cmake +++ b/cmake/clang-format.cmake @@ -14,33 +14,31 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() find_program(CLANG_FORMAT_PROGRAM NAMES clang-format) +IF (CLANG_FORMAT_PROGRAM) + EXECUTE_PROCESS( + COMMAND "${CLANG_FORMAT_PROGRAM}" --version + OUTPUT_VARIABLE CLANG_FORMAT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) -#if (CLANG_FORMAT_PROGRAM) -# execute_process( -# COMMAND "${CLANG_FORMAT_PROGRAM}" --version -# OUTPUT_VARIABLE CLANG_FORMAT_VERSION -# OUTPUT_STRIP_TRAILING_WHITESPACE -# ) -# -# message("Using clang-format: ${CLANG_FORMAT_PROGRAM} (${CLANG_FORMAT_VERSION})") -# -# file(GLOB_RECURSE -# format_src_list -# RELATIVE -# "${CMAKE_CURRENT_SOURCE_DIR}" -# "src/*.[hc]" -# "src/*.[hc]pp" -# ) -# -# foreach (_src_file ${format_src_list}) -# message(" formatting => ${_src_file}") -# execute_process( -# COMMAND "${CLANG_FORMAT_PROGRAM}" --style=file -i "${_src_file}" -# WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" -# ) -# endforeach () -# -# unset(CLANG_FORMAT_VERSION) -#endif () \ No newline at end of file + MESSAGE("Using clang-format: ${CLANG_FORMAT_PROGRAM} (${CLANG_FORMAT_VERSION})") + + FILE(GLOB_RECURSE + format_src_list + RELATIVE + "${CMAKE_CURRENT_SOURCE_DIR}" + "src/*.[hc]" + "src/*.[hc]pp" + ) + + FOREACH (_src_file ${format_src_list}) + MESSAGE(" formatting => ${_src_file}") + EXECUTE_PROCESS( + COMMAND "${CLANG_FORMAT_PROGRAM}" --style=file -i "${_src_file}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + ENDFOREACH () + + UNSET(CLANG_FORMAT_VERSION) +ENDIF () \ No newline at end of file diff --git a/cmake/cmake-utils.cmake b/cmake/cmake-utils.cmake index 627c1f48..774a13cb 100644 --- a/cmake/cmake-utils.cmake +++ b/cmake/cmake-utils.cmake @@ -108,7 +108,11 @@ FUNCTION( PRINT_PROJECT_VARIABLES ) MESSAGE( NOTICE " CMAKE_MINIMUM_REQUIRED_VERSION:..........: " ${CMAKE_MINIMUM_REQUIRED_VERSION} ) MESSAGE( NOTICE " VCPKG_TARGET_TRIPLET.....................: " ${VCPKG_TARGET_TRIPLET} ) MESSAGE( NOTICE " CMAKE_DEBUG_POSTFIX......................: " ${CMAKE_DEBUG_POSTFIX} ) + MESSAGE( NOTICE " system_bits..............................: " ${system_bits} ) + MESSAGE( NOTICE " GDEXTENSION_LIB_NAME.....................: " ${GDEXTENSION_LIB_NAME} ) + MESSAGE( NOTICE " RESOLVED_VERSION.........................: " ${RESOLVED_VERSION} ) MESSAGE( NOTICE " GIT_COMMIT_HASH..........................: " ${GIT_COMMIT_HASH} ) + MESSAGE( NOTICE " GODOT_CPP_GIT_COMMIT_HASH................: " ${GODOT_CPP_GIT_COMMIT_HASH} ) MESSAGE( NOTICE "" ) MESSAGE( NOTICE "CMake Paths" ) MESSAGE( NOTICE " CMAKE_CURRENT_SOURCE_DIR.................: " ${CMAKE_CURRENT_SOURCE_DIR} ) @@ -118,7 +122,9 @@ FUNCTION( PRINT_PROJECT_VARIABLES ) MESSAGE( NOTICE " CLANG_FORMAT_PROGRAM:....................: " ${CLANG_FORMAT_PROGRAM} ) MESSAGE( NOTICE " SCONS_PROGRAM:...........................: " ${SCONS_PROGRAM} ) MESSAGE( NOTICE " CMAKE_CXX_COMPILER:......................: " ${CMAKE_CXX_COMPILER} ) + MESSAGE( NOTICE " CMAKE_CXX_FLAGS..........................: " ${CMAKE_CXX_FLAGS} ) MESSAGE( NOTICE " CMAKE_LINKER:............................: " ${CMAKE_LINKER} ) + MESSAGE( NOTICE " CMAKE_EXE_LINKER_FLAGS...................: " ${CMAKE_EXE_LINKER_FLAGS} ) MESSAGE( NOTICE " CMAKE_BUILD_TOOL:........................: " ${CMAKE_BUILD_TOOL} ) MESSAGE( NOTICE " vcpkg_executable:........................: " ${vcpkg_executable} ) MESSAGE( NOTICE " godot_debug_editor_executable:...........: " ${godot_debug_editor_executable} ) diff --git a/cmake/generate-authors.cmake b/cmake/generate-authors.cmake index a5ea559f..db0eff97 100644 --- a/cmake/generate-authors.cmake +++ b/cmake/generate-authors.cmake @@ -14,6 +14,7 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() INCLUDE(markdown-utils) @@ -35,5 +36,5 @@ FUNCTION( GENERATE_AUTHORS ) ENDIF() MATH(EXPR index "${index} + 1") ENDWHILE() - CONFIGURE_FILE(cmake/authors.h.in _generated/authors.gen.h @ONLY) + CONFIGURE_FILE(cmake/templates/authors.h.in _generated/authors.gen.h @ONLY) ENDFUNCTION() \ No newline at end of file diff --git a/cmake/generate-donors.cmake b/cmake/generate-donors.cmake index 39abf18f..c1e45be2 100644 --- a/cmake/generate-donors.cmake +++ b/cmake/generate-donors.cmake @@ -14,6 +14,7 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() INCLUDE(markdown-utils) @@ -37,5 +38,5 @@ FUNCTION( GENERATE_DONORS ) ENDIF() MATH(EXPR index "${index} + 1") ENDWHILE() - CONFIGURE_FILE(cmake/donors.h.in _generated/donors.gen.h @ONLY) + CONFIGURE_FILE(cmake/templates/donors.h.in _generated/donors.gen.h @ONLY) ENDFUNCTION() \ No newline at end of file diff --git a/cmake/generate-license.cmake b/cmake/generate-license.cmake index f471ce4b..bed1c4fd 100644 --- a/cmake/generate-license.cmake +++ b/cmake/generate-license.cmake @@ -14,11 +14,12 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() FUNCTION( GENERATE_LICENSE ) FILE(READ LICENSE license_text) STRING(REPLACE "\"" "\\042" license_text_formatted "${license_text}") STRING(REPLACE "\n" "\\n\"\n \"" license_text_formatted "${license_text_formatted}") - CONFIGURE_FILE(cmake/license.h.in _generated/license.gen.h @ONLY) + CONFIGURE_FILE(cmake/templates/license.h.in _generated/license.gen.h @ONLY) ENDFUNCTION() diff --git a/cmake/generate-version.cmake b/cmake/generate-version.cmake index 3c850059..81108b33 100644 --- a/cmake/generate-version.cmake +++ b/cmake/generate-version.cmake @@ -14,6 +14,7 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() FUNCTION( GENERATE_VERSION ) FILE(READ VERSION version_text) @@ -54,7 +55,7 @@ FUNCTION( GENERATE_VERSION ) ENDFOREACH() PAD_STRING("VERSION_HASH" ${version_max_length} version_hash) SET(version_formatted "${version_formatted}#define ${version_hash}\t\"${GIT_COMMIT_HASH}\"") - CONFIGURE_FILE(cmake/version.h.in _generated/version.gen.h @ONLY) + CONFIGURE_FILE(cmake/templates/version.h.in _generated/version.gen.h @ONLY) # Pass certain values up to the parent scope # These are used to create windows DLL resource file details diff --git a/cmake/godot-docs-generator.cmake b/cmake/godot-docs-generator.cmake index 7b08896b..43f65dc5 100644 --- a/cmake/godot-docs-generator.cmake +++ b/cmake/godot-docs-generator.cmake @@ -24,7 +24,7 @@ FUNCTION( GENERATE_GODOT_DOCUMENTATION ) STRING(JOIN "," DOC_DATA_CPP_STR ${DOC_DATA_CPP_FILE}) # Run python to generate the doc_data.cpp file EXECUTE_PROCESS( - COMMAND cmd /c py ${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_godot_docs.py ${DOC_DATA_CPP_STR} ${XML_FILES_STR} + COMMAND cmd /c py ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/generate_godot_docs.py ${DOC_DATA_CPP_STR} ${XML_FILES_STR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) ENDFUNCTION() \ No newline at end of file diff --git a/cmake/godot-extension-db-generator.cmake b/cmake/godot-extension-db-generator.cmake index bb59084b..43886f21 100644 --- a/cmake/godot-extension-db-generator.cmake +++ b/cmake/godot-extension-db-generator.cmake @@ -14,10 +14,11 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() FUNCTION( GENERATE_GODOT_EXTENSION_DB ) EXECUTE_PROCESS( - COMMAND cmd /c py ${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_godot_extension_db.py ${CMAKE_CURRENT_SOURCE_DIR}/extern/godot-cpp/gdextension/extension_api.json + COMMAND cmd /c py ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/generate_godot_extension_db.py ${CMAKE_CURRENT_SOURCE_DIR}/extern/godot-cpp/gdextension/extension_api.json WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE godot_extension_db ) @@ -26,7 +27,7 @@ FUNCTION( GENERATE_GODOT_EXTENSION_DB ) STRING(SUBSTRING "${godot_extension_db}" 0 ${pos} godot_extension_db_hpp) MATH(EXPR pos "${pos} + 5") STRING(SUBSTRING "${godot_extension_db}" ${pos} -1 godot_extension_db_cpp) - CONFIGURE_FILE(cmake/extension_db.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/api/extension_db.h @ONLY NEWLINE_STYLE LF) - CONFIGURE_FILE(cmake/extension_db.cpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/api/extension_db.cpp @ONLY NEWLINE_STYLE LF) + CONFIGURE_FILE(cmake/templates/extension_db.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/api/extension_db.h @ONLY NEWLINE_STYLE LF) + CONFIGURE_FILE(cmake/templates/extension_db.cpp.in ${CMAKE_CURRENT_SOURCE_DIR}/src/api/extension_db.cpp @ONLY NEWLINE_STYLE LF) ENDIF() ENDFUNCTION() \ No newline at end of file diff --git a/cmake/markdown-utils.cmake b/cmake/markdown-utils.cmake index 9d279092..83c850ee 100644 --- a/cmake/markdown-utils.cmake +++ b/cmake/markdown-utils.cmake @@ -14,6 +14,7 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## +INCLUDE_GUARD() FUNCTION( TRIM text out ) STRING(REGEX REPLACE "^[ \t\n\r]+" "" text "${text}") diff --git a/cmake/generate_godot_docs.py b/cmake/scripts/generate_godot_docs.py similarity index 100% rename from cmake/generate_godot_docs.py rename to cmake/scripts/generate_godot_docs.py diff --git a/cmake/generate_godot_extension_db.py b/cmake/scripts/generate_godot_extension_db.py similarity index 100% rename from cmake/generate_godot_extension_db.py rename to cmake/scripts/generate_godot_extension_db.py diff --git a/cmake/authors.h.in b/cmake/templates/authors.h.in similarity index 100% rename from cmake/authors.h.in rename to cmake/templates/authors.h.in diff --git a/cmake/donors.h.in b/cmake/templates/donors.h.in similarity index 100% rename from cmake/donors.h.in rename to cmake/templates/donors.h.in diff --git a/cmake/extension_db.cpp.in b/cmake/templates/extension_db.cpp.in similarity index 100% rename from cmake/extension_db.cpp.in rename to cmake/templates/extension_db.cpp.in diff --git a/cmake/extension_db.h.in b/cmake/templates/extension_db.h.in similarity index 100% rename from cmake/extension_db.h.in rename to cmake/templates/extension_db.h.in diff --git a/cmake/license.h.in b/cmake/templates/license.h.in similarity index 100% rename from cmake/license.h.in rename to cmake/templates/license.h.in diff --git a/cmake/version.h.in b/cmake/templates/version.h.in similarity index 100% rename from cmake/version.h.in rename to cmake/templates/version.h.in diff --git a/cmake/windows.rc.in b/cmake/templates/windows.rc.in similarity index 97% rename from cmake/windows.rc.in rename to cmake/templates/windows.rc.in index 6bb19fa4..6a759f75 100644 --- a/cmake/windows.rc.in +++ b/cmake/templates/windows.rc.in @@ -1,4 +1,4 @@ -#include +#include VS_VERSION_INFO VERSIONINFO FILEVERSION @win32_file_version@ diff --git a/src/common/method_utils.cpp b/src/common/method_utils.cpp index 11076770..6f29d7f7 100644 --- a/src/common/method_utils.cpp +++ b/src/common/method_utils.cpp @@ -19,6 +19,8 @@ #include "dictionary_utils.h" #include "property_utils.h" #include "script/script_server.h" +#include "variant_operators.h" +#include "variant_utils.h" #include @@ -139,4 +141,46 @@ namespace MethodUtils { return p_method.arguments.size() - p_method.default_arguments.size(); } + + bool has_same_signature(const MethodInfo& p_method_a, const MethodInfo& p_method_b) + { + if (p_method_a.arguments.size() != p_method_b.arguments.size()) + return false; + + if (p_method_a.default_arguments.size() != p_method_b.default_arguments.size()) + return false; + + if (p_method_a.flags != p_method_b.flags) + return false; + + if (p_method_a.name != p_method_b.name) + return false; + + if (!PropertyUtils::are_equal(p_method_a.return_val, p_method_b.return_val)) + return false; + + for (int i = 0; i < p_method_a.arguments.size(); i++) + { + const PropertyInfo& a = p_method_a.arguments[i]; + const PropertyInfo& b = p_method_b.arguments[i]; + + // PropertyUtils::are_equal does not compare names + // But here we want to compare names + if (a.name != b.name) + return false; + + if (!PropertyUtils::are_equal(a, b)) + return false; + } + + for (int i = 0; i < p_method_b.default_arguments.size(); i++) + { + const Variant& a = p_method_a.default_arguments[i]; + const Variant& b = p_method_b.default_arguments[i]; + if (!VariantUtils::evaluate(Variant::OP_EQUAL, a, b)) + return false; + } + + return true; + } } \ No newline at end of file diff --git a/src/common/method_utils.h b/src/common/method_utils.h index 6524b327..b81ef8a4 100644 --- a/src/common/method_utils.h +++ b/src/common/method_utils.h @@ -55,6 +55,12 @@ namespace MethodUtils /// @param p_method the method /// @return the number of arguments that have no default values size_t get_argument_count_without_defaults(const MethodInfo& p_method); + + //// Checks whether two MethodInfo structures have the same structures + /// @param p_method_a the left method structure + /// @param p_method_b the right method structure + /// @return true if the method structures are the same, false otherwise + bool has_same_signature(const MethodInfo& p_method_a, const MethodInfo& p_method_b); } #endif // ORCHESTRATOR_METHOD_UTILS_H \ No newline at end of file diff --git a/src/common/variant_utils.cpp b/src/common/variant_utils.cpp index b844280d..cca12f52 100644 --- a/src/common/variant_utils.cpp +++ b/src/common/variant_utils.cpp @@ -301,4 +301,11 @@ namespace VariantUtils Variant::evaluate(p_operator, p_left, p_right, r_value, valid); return valid; } + + Variant evaluate(Variant::Operator p_operator, const Variant& p_left, const Variant& p_right) + { + Variant result; + evaluate(p_operator, p_left, p_right, result); + return result; + } } \ No newline at end of file diff --git a/src/common/variant_utils.h b/src/common/variant_utils.h index d5e51a45..4ea10bd1 100644 --- a/src/common/variant_utils.h +++ b/src/common/variant_utils.h @@ -76,6 +76,13 @@ namespace VariantUtils /// @param r_value the return value /// @return true if the evaluation was successful, false otherwise bool evaluate(Variant::Operator p_operator, const Variant& p_left, const Variant& p_right, Variant& r_value); + + /// Evaluates two variants, returning the result. + /// @param p_operator the operation to be performed + /// @param p_left the left operand + /// @param p_right the right operand + /// @return the evaluated result value between the two operands + Variant evaluate(Variant::Operator p_operator, const Variant& p_left, const Variant& p_right); } #endif // ORCHESTRATOR_VARIANT_UTILS_H diff --git a/src/editor/build_output_panel.cpp b/src/editor/build_output_panel.cpp index 059f8a5d..16b2ca46 100644 --- a/src/editor/build_output_panel.cpp +++ b/src/editor/build_output_panel.cpp @@ -58,6 +58,9 @@ void OrchestratorBuildOutputPanel::add_message(const String& p_text) void OrchestratorBuildOutputPanel::set_tool_button(Button* p_button) { _button = p_button; + + if (_button) + _button->set_tooltip_text("Toggle Orchestrator Build Bottom Panel"); } void OrchestratorBuildOutputPanel::_notification(int p_what) diff --git a/src/editor/component_panels/component_panel.cpp b/src/editor/component_panels/component_panel.cpp index 738e0ee5..365a101e 100644 --- a/src/editor/component_panels/component_panel.cpp +++ b/src/editor/component_panels/component_panel.cpp @@ -58,7 +58,8 @@ void OrchestratorScriptComponentPanel::_tree_add_item() void OrchestratorScriptComponentPanel::_tree_item_activated() { TreeItem* item = _tree->get_selected(); - ERR_FAIL_COND_MSG(!item, "Cannot activate when no item selected"); + if (!item) + return; _handle_item_activated(item); } @@ -361,6 +362,15 @@ bool OrchestratorScriptComponentPanel::_find_child_and_activate(const String& p_ return false; } +void OrchestratorScriptComponentPanel::_tree_gui_input(const Ref& p_event) +{ + TreeItem* selected = _tree->get_selected(); + if (!selected) + return; + + _handle_tree_gui_input(p_event, selected); +} + void OrchestratorScriptComponentPanel::_gui_input(const Ref& p_event) { const Ref mb = p_event; @@ -432,7 +442,6 @@ void OrchestratorScriptComponentPanel::_notification(int p_what) _tree->set_h_size_flags(SIZE_EXPAND_FILL); _tree->set_v_size_flags(SIZE_FILL); _tree->set_hide_root(true); - _tree->set_focus_mode(FOCUS_NONE); _tree->create_item()->set_text(0, "Root"); // creates the root item add_child(_tree); @@ -461,6 +470,7 @@ void OrchestratorScriptComponentPanel::_notification(int p_what) _tree->connect("item_mouse_selected", callable_mp(this, &OrchestratorScriptComponentPanel::_tree_item_mouse_selected)); _tree->connect("item_collapsed", callable_mp_lambda(this, [&]([[maybe_unused]] TreeItem* i) { _tree->update_minimum_size(); })); _tree->connect("button_clicked", callable_mp(this, &OrchestratorScriptComponentPanel::_tree_item_button_clicked)); + _tree->connect("gui_input", callable_mp(this, &OrchestratorScriptComponentPanel::_tree_gui_input)); _context_menu->connect("id_pressed", callable_mp_lambda(this, [&](int id) { _handle_context_menu(id); })); _confirm->connect("confirmed", callable_mp(this, &OrchestratorScriptComponentPanel::_remove_confirmed)); diff --git a/src/editor/component_panels/component_panel.h b/src/editor/component_panels/component_panel.h index dd4b3156..c0e6a4df 100644 --- a/src/editor/component_panels/component_panel.h +++ b/src/editor/component_panels/component_panel.h @@ -196,6 +196,15 @@ class OrchestratorScriptComponentPanel : public VBoxContainer /// @return data to be used for the draggable item, returning an empty Dictionary cancels the drag virtual Dictionary _handle_drag_data(const Vector2& p_position) { return {}; } + /// Called when the tree receives a gui_input event + /// @param p_event the event received + void _tree_gui_input(const Ref& p_event); + + /// Handles gui_input events in the tree widget + /// @param p_event the event received + /// @param p_item the current selected item + virtual void _handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) { } + /// Default constructor OrchestratorScriptComponentPanel() = default; diff --git a/src/editor/component_panels/functions_panel.cpp b/src/editor/component_panels/functions_panel.cpp index 64ae65c2..970adc6d 100644 --- a/src/editor/component_panels/functions_panel.cpp +++ b/src/editor/component_panels/functions_panel.cpp @@ -26,7 +26,9 @@ #include #include #include +#include #include +#include #include void OrchestratorScriptFunctionsComponentPanel::_show_function_graph(TreeItem* p_item) @@ -95,9 +97,9 @@ String OrchestratorScriptFunctionsComponentPanel::_get_remove_confirm_text(TreeI bool OrchestratorScriptFunctionsComponentPanel::_populate_context_menu(TreeItem* p_item) { - _context_menu->add_item("Open in Graph", CM_OPEN_FUNCTION_GRAPH); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_FUNCTION); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_FUNCTION); + _context_menu->add_item("Open in Graph", CM_OPEN_FUNCTION_GRAPH, KEY_ENTER); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_FUNCTION, KEY_F2); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_FUNCTION, KEY_DELETE); if (p_item->has_meta("__slot") && p_item->get_meta("__slot")) { @@ -218,6 +220,29 @@ Dictionary OrchestratorScriptFunctionsComponentPanel::_handle_drag_data(const Ve return data; } +void OrchestratorScriptFunctionsComponentPanel::_handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) +{ + const Ref key = p_event; + if (key.is_valid() && key->is_pressed() && !key->is_echo()) + { + if (key->get_keycode() == KEY_ENTER) + { + _handle_context_menu(CM_OPEN_FUNCTION_GRAPH); + accept_event(); + } + else if (key->get_keycode() == KEY_F2) + { + _handle_context_menu(CM_RENAME_FUNCTION); + accept_event(); + } + else if (key->get_keycode() == KEY_DELETE) + { + _handle_context_menu(CM_REMOVE_FUNCTION); + accept_event(); + } + } +} + void OrchestratorScriptFunctionsComponentPanel::update() { _clear_tree(); diff --git a/src/editor/component_panels/functions_panel.h b/src/editor/component_panels/functions_panel.h index 9ec50301..8ffff5c5 100644 --- a/src/editor/component_panels/functions_panel.h +++ b/src/editor/component_panels/functions_panel.h @@ -55,6 +55,7 @@ class OrchestratorScriptFunctionsComponentPanel : public OrchestratorScriptCompo void _handle_remove(TreeItem* p_item) override; void _handle_button_clicked(TreeItem* p_item, int p_column, int p_id, int p_mouse_button) override; Dictionary _handle_drag_data(const Vector2& p_position) override; + void _handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) override; //~ End OrchestratorScriptComponentPanel Interface //~ Begin Signal handlers diff --git a/src/editor/component_panels/graphs_panel.cpp b/src/editor/component_panels/graphs_panel.cpp index 36dedf72..ee44efbb 100644 --- a/src/editor/component_panels/graphs_panel.cpp +++ b/src/editor/component_panels/graphs_panel.cpp @@ -21,6 +21,7 @@ #include "common/settings.h" #include "editor/script_connections.h" +#include #include #include @@ -88,17 +89,17 @@ bool OrchestratorScriptGraphsComponentPanel::_populate_context_menu(TreeItem* p_ Ref graph = _orchestration->get_graph(_get_tree_item_name(p_item)); bool rename_disabled = !graph->get_flags().has_flag(OScriptGraph::GraphFlags::GF_RENAMABLE); bool delete_disabled = !graph->get_flags().has_flag(OScriptGraph::GraphFlags::GF_DELETABLE); - _context_menu->add_item("Open Graph", CM_OPEN_GRAPH); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_GRAPH); + _context_menu->add_item("Open Graph", CM_OPEN_GRAPH, KEY_ENTER); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_GRAPH, KEY_F2); _context_menu->set_item_disabled(_context_menu->get_item_count() - 1, rename_disabled); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_GRAPH); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_GRAPH, KEY_DELETE); _context_menu->set_item_disabled(_context_menu->get_item_count() - 1, delete_disabled); } else { // Graph Functions - _context_menu->add_item("Focus", CM_FOCUS_FUNCTION); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_FUNCTION); + _context_menu->add_item("Focus", CM_FOCUS_FUNCTION, KEY_ENTER); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_FUNCTION, KEY_DELETE); if (p_item->has_meta("__slot") && p_item->get_meta("__slot")) { @@ -188,6 +189,42 @@ void OrchestratorScriptGraphsComponentPanel::_handle_button_clicked(TreeItem* p_ dialog->popup_connections(_get_tree_item_name(p_item), nodes); } +void OrchestratorScriptGraphsComponentPanel::_handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) +{ + Ref key = p_event; + if (!key.is_valid() || !key->is_pressed() || key->is_echo()) + return; + + if (key->get_keycode() == KEY_ENTER) + { + _handle_context_menu(p_item->get_parent() == _tree->get_root() ? CM_OPEN_GRAPH : CM_FOCUS_FUNCTION); + accept_event(); + } + else if (key->get_keycode() == KEY_F2) + { + if (_tree->get_root() == p_item->get_parent()) + { + Ref graph = _orchestration->get_graph(_get_tree_item_name(p_item)); + if (graph->get_flags().has_flag(OScriptGraph::GraphFlags::GF_RENAMABLE)) + _handle_context_menu(CM_RENAME_GRAPH); + } + accept_event(); + } + else if (key->get_keycode() == KEY_DELETE) + { + if (_tree->get_root() == p_item->get_parent()) + { + Ref graph = _orchestration->get_graph(_get_tree_item_name(p_item)); + if(graph->get_flags().has_flag(OScriptGraph::GraphFlags::GF_DELETABLE)) + _handle_context_menu(CM_REMOVE_GRAPH); + } + else + _handle_context_menu(CM_REMOVE_FUNCTION); + + accept_event(); + } +} + void OrchestratorScriptGraphsComponentPanel::_update_slots() { if (_orchestration->get_type() != OrchestrationType::OT_Script) diff --git a/src/editor/component_panels/graphs_panel.h b/src/editor/component_panels/graphs_panel.h index 1bbc9117..fd0556e2 100644 --- a/src/editor/component_panels/graphs_panel.h +++ b/src/editor/component_panels/graphs_panel.h @@ -52,6 +52,7 @@ class OrchestratorScriptGraphsComponentPanel : public OrchestratorScriptComponen bool _handle_item_renamed(const String& p_old_name, const String& p_new_name) override; void _handle_remove(TreeItem* p_item) override; void _handle_button_clicked(TreeItem* p_item, int p_column, int p_id, int p_mouse_button) override; + void _handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) override; //~ End OrchestratorScriptComponentPanel Interface /// Updates the slot icons on tree items diff --git a/src/editor/component_panels/signals_panel.cpp b/src/editor/component_panels/signals_panel.cpp index 834cb592..5ee66291 100644 --- a/src/editor/component_panels/signals_panel.cpp +++ b/src/editor/component_panels/signals_panel.cpp @@ -20,6 +20,7 @@ #include "common/scene_utils.h" #include +#include #include #include @@ -43,8 +44,8 @@ String OrchestratorScriptSignalsComponentPanel::_get_remove_confirm_text(TreeIte bool OrchestratorScriptSignalsComponentPanel::_populate_context_menu(TreeItem* p_item) { - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_SIGNAL); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_SIGNAL); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_SIGNAL, KEY_F2); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_SIGNAL, KEY_DELETE); return true; } @@ -120,6 +121,24 @@ Dictionary OrchestratorScriptSignalsComponentPanel::_handle_drag_data(const Vect return data; } +void OrchestratorScriptSignalsComponentPanel::_handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) +{ + const Ref key = p_event; + if (key.is_valid() && key->is_pressed() && !key->is_echo()) + { + if (key->get_keycode() == KEY_F2) + { + _handle_context_menu(CM_RENAME_SIGNAL); + accept_event(); + } + else if (key->get_keycode() == KEY_DELETE) + { + _handle_context_menu(CM_REMOVE_SIGNAL); + accept_event(); + } + } +} + void OrchestratorScriptSignalsComponentPanel::update() { _clear_tree(); diff --git a/src/editor/component_panels/signals_panel.h b/src/editor/component_panels/signals_panel.h index e9f8fe1f..d53c12b2 100644 --- a/src/editor/component_panels/signals_panel.h +++ b/src/editor/component_panels/signals_panel.h @@ -45,6 +45,7 @@ class OrchestratorScriptSignalsComponentPanel : public OrchestratorScriptCompone bool _handle_item_renamed(const String& p_old_name, const String& p_new_name) override; void _handle_remove(TreeItem* p_item) override; Dictionary _handle_drag_data(const Vector2& p_position) override; + void _handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) override; //~ End OrchestratorScriptViewSection Interface /// Default constructor diff --git a/src/editor/component_panels/variables_panel.cpp b/src/editor/component_panels/variables_panel.cpp index 64723817..cae6e726 100644 --- a/src/editor/component_panels/variables_panel.cpp +++ b/src/editor/component_panels/variables_panel.cpp @@ -23,6 +23,7 @@ #include "editor/plugins/orchestrator_editor_plugin.h" #include +#include #include #include @@ -117,8 +118,8 @@ String OrchestratorScriptVariablesComponentPanel::_get_remove_confirm_text(TreeI bool OrchestratorScriptVariablesComponentPanel::_populate_context_menu(TreeItem* p_item) { - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_VARIABLE); - _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_VARIABLE); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Rename"), "Rename", CM_RENAME_VARIABLE, KEY_F2); + _context_menu->add_icon_item(SceneUtils::get_editor_icon("Remove"), "Remove", CM_REMOVE_VARIABLE, KEY_DELETE); return true; } @@ -217,6 +218,24 @@ Dictionary OrchestratorScriptVariablesComponentPanel::_handle_drag_data(const Ve return data; } +void OrchestratorScriptVariablesComponentPanel::_handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) +{ + const Ref key = p_event; + if (key.is_valid() && key->is_pressed() && !key->is_echo()) + { + if (key->get_keycode() == KEY_F2) + { + _handle_context_menu(CM_RENAME_VARIABLE); + accept_event(); + } + else if (key->get_keycode() == KEY_DELETE) + { + _handle_context_menu(CM_REMOVE_VARIABLE); + accept_event(); + } + } +} + void OrchestratorScriptVariablesComponentPanel::update() { Callable callback = callable_mp(this, &OrchestratorScriptVariablesComponentPanel::_update_variables); diff --git a/src/editor/component_panels/variables_panel.h b/src/editor/component_panels/variables_panel.h index 64ea9ef7..1caf2110 100644 --- a/src/editor/component_panels/variables_panel.h +++ b/src/editor/component_panels/variables_panel.h @@ -48,6 +48,7 @@ class OrchestratorScriptVariablesComponentPanel : public OrchestratorScriptCompo void _handle_remove(TreeItem* p_item) override; void _handle_button_clicked(TreeItem* p_item, int p_column, int p_id, int p_mouse_button) override; Dictionary _handle_drag_data(const Vector2& p_position) override; + void _handle_tree_gui_input(const Ref& p_event, TreeItem* p_item) override; //~ End OrchestratorScriptViewSection Interface void _create_variable_item(TreeItem* p_parent, const Ref& p_variable); diff --git a/src/editor/graph/graph_edit.cpp b/src/editor/graph/graph_edit.cpp index a4140e95..22073495 100644 --- a/src/editor/graph/graph_edit.cpp +++ b/src/editor/graph/graph_edit.cpp @@ -20,7 +20,9 @@ #include "common/callable_lambda.h" #include "common/dictionary_utils.h" #include "common/logger.h" +#include "common/method_utils.h" #include "common/name_utils.h" +#include "common/property_utils.h" #include "common/scene_utils.h" #include "common/settings.h" #include "common/string_utils.h" @@ -684,6 +686,14 @@ void OrchestratorGraphEdit::_gui_input(const Ref& p_event) const Ref key = p_event; if (key.is_valid() && key->is_pressed()) { + // todo: Submitted https://github.com/godotengine/godot/pull/95614 + // Can eventually rely on the "cut_nodes_request" signal rather than this approach + if (key->is_action("ui_cut", true)) + { + _on_cut_nodes_request(); + accept_event(); + } + if (key->is_action("ui_left", true)) { _move_selected(Vector2(is_snapping_enabled() ? -get_snapping_distance() : -1, 0)); @@ -1798,7 +1808,7 @@ void OrchestratorGraphEdit::_on_delete_nodes_requested(const PackedStringArray& return; OrchestratorSettings* settings = OrchestratorSettings::get_singleton(); - if (settings->get_setting("ui/graph/confirm_on_delete", true)) + if (!_disable_delete_confirmation && settings->get_setting("ui/graph/confirm_on_delete", true)) { const String message = vformat("Do you wish to delete %d node(s)?", p_node_names.size()); _confirm_yes_no(message, "Confirm deletion", callable_mp(this, &OrchestratorGraphEdit::_delete_nodes).bind(p_node_names)); @@ -2008,26 +2018,95 @@ void OrchestratorGraphEdit::_on_copy_nodes_request() { _clipboard->reset(); - for_each_graph_node([&](OrchestratorGraphNode* node) { - if (node->is_selected()) + const Vector> selected = get_selected_script_nodes(); + if (selected.is_empty()) + { + _notify("No nodes selected, nothing copied to clipboard.", "Clipboard error"); + return; + } + + // Check if any selected nodes cannot be copied, showing message if not. + for (const Ref& node : selected) + { + if (!node->can_duplicate()) { - Ref script_node = node->get_script_node(); + _notify(vformat("Cannot duplicate node '%s' (%d).", node->get_node_title(), node->get_id()), "Clipboard error"); + return; + } + } - if (!script_node->can_duplicate()) + // Local cache of copied objects + // Prevents creating multiple instances on paste of the same function, variable, or signal + RBSet> functions_cache; + RBSet> variables_cache; + RBSet> signals_cache; + + // Perform copy to clipboard + for (const Ref& node : selected) + { + const Ref call_function_node = node; + if (call_function_node.is_valid()) + { + const Ref function = call_function_node->get_function(); + if (!functions_cache.has(function)) { - WARN_PRINT_ONCE_ED("There are some nodes that cannot be copied, they were not placed on the clipboard."); - return; + functions_cache.insert(function); + _clipboard->functions.insert(function->duplicate()); + } + } + + const Ref variable_node = node; + if (variable_node.is_valid()) + { + const Ref variable = variable_node->get_variable(); + if (!variables_cache.has(variable)) + { + variables_cache.insert(variable); + _clipboard->variables.insert(variable->duplicate()); } + } - const int id = script_node->get_id(); - _clipboard->positions[id] = script_node->get_position(); - _clipboard->nodes[id] = _script_graph->copy_node(id, true); + const Ref emit_signal_node = node; + if (emit_signal_node.is_valid()) + { + const Ref signal = emit_signal_node->get_signal(); + if (!signals_cache.has(signal)) + { + signals_cache.insert(signal); + _clipboard->signals.insert(signal->duplicate()); + } } - }); + const int node_id = node->get_id(); + _clipboard->positions[node_id] = node->get_position(); + _clipboard->nodes[node_id] = _script_graph->copy_node(node_id, true); + } + + // Connections between pasted nodes, copy connections for (const OScriptConnection& E : get_orchestration()->get_connections()) + { if (_clipboard->nodes.has(E.from_node) && _clipboard->nodes.has(E.to_node)) _clipboard->connections.insert(E); + } +} + +void OrchestratorGraphEdit::_on_cut_nodes_request() +{ + _clipboard->reset(); + + _on_copy_nodes_request(); + + PackedStringArray selected_names; + for (int index = 0; index < get_child_count(); index++) + { + GraphElement* element = Object::cast_to(get_child(index)); + if (element && element->is_selected()) + selected_names.push_back(element->get_name()); + } + + _disable_delete_confirmation = true; + _on_delete_nodes_requested(selected_names); + _disable_delete_confirmation = false; } void OrchestratorGraphEdit::_on_duplicate_nodes_request() @@ -2078,15 +2157,97 @@ void OrchestratorGraphEdit::_on_paste_nodes_request() if (_clipboard->is_empty()) return; - Vector duplications; - RBSet existing_positions; - for (const KeyValue& E : _clipboard->positions) + Orchestration* orchestration = _script_graph->get_orchestration(); + + // Iterate copied function declarations and assert if paste is invalid + // Functions are unique in that we do not clone their nodes or structure, so the function must exist + // in the target orchestration with the same signature for the paste to be valid. + for (const Ref& E : _clipboard->functions) + { + if (!orchestration->has_function(E->get_function_name())) + { + _notify(vformat("Function '%s' does not exist in this orchestration.", E->get_function_name()), "Clipboard error"); + return; + } + + // Exists, verify if its identical + const Ref other = orchestration->find_function(E->get_function_name()); + if (!MethodUtils::has_same_signature(E->get_method_info(), other->get_method_info())) + { + _notify(vformat("Function '%s' exists with a different definition.", E->get_function_name()), "Clipboard error"); + return; + } + } + + // Iterate copied variable declarations and assert if paste is invalid + Vector> variables_to_create; + for (const Ref& E : _clipboard->variables) { - existing_positions.insert(E.value.snapped(Vector2(2, 2))); - duplications.push_back(E.key); + if (!orchestration->has_variable(E->get_variable_name())) + { + variables_to_create.push_back(E); + continue; + } + + // Exists, verify if its identical + const Ref other = orchestration->get_variable(E->get_variable_name()); + if (!PropertyUtils::are_equal(E->get_info(), other->get_info())) + { + _notify(vformat("Variable '%s' exists with a different definition.", E->get_variable_name()), "Clipboard error"); + return; + } } - Vector2 mouse_up_position = get_screen_position() + get_local_mouse_position(); + // Iterate copied signal declarations and assert if paste is invalid + Vector> signals_to_create; + for (const Ref& E : _clipboard->signals) + { + if (!orchestration->has_custom_signal(E->get_signal_name())) + { + signals_to_create.push_back(E); + continue; + } + + // When signal exists, verify whether the signal has the same signature and fail if it doesn't. + const Ref other = orchestration->get_custom_signal(E->get_signal_name()); + if (!MethodUtils::has_same_signature(E->get_method_info(), other->get_method_info())) + { + _notify(vformat("Signal '%s' exists with a different definition.", E->get_signal_name()), "Clipboard error"); + return; + } + } + + for (const KeyValue>& E : _clipboard->nodes) + { + const Ref call_script_function_node = E.value; + if (call_script_function_node.is_valid()) + { + const StringName function_name = call_script_function_node->get_function()->get_function_name(); + const Ref this_function = get_orchestration()->find_function(function_name); + if (this_function.is_valid()) + { + // Since source OScriptFunction matches this OScriptFunction declaration, copy the + // GUID from this orchestrations script function and set it on the node + call_script_function_node->set("guid", this_function->get_guid().to_string()); + } + } + } + + // Iterate variables to be created + for (const Ref& E : variables_to_create) + { + Ref new_variable = orchestration->create_variable(E->get_variable_name()); + new_variable->copy_persistent_state(E); + } + + // Iterate signals to be created + for (const Ref& E : signals_to_create) + { + Ref new_signal = orchestration->create_custom_signal(E->get_signal_name()); + new_signal->copy_persistent_state(E); + } + + const Vector2 mouse_up_position = get_screen_position() + get_local_mouse_position(); Vector2 position_offset = (get_scroll_offset() + (mouse_up_position - get_screen_position())) / get_zoom(); if (is_snapping_enabled()) { @@ -2119,6 +2280,8 @@ void OrchestratorGraphEdit::_on_paste_nodes_request() if (OrchestratorGraphNode* node = _get_node_by_id(selected_id)) node->set_selected(true); } + + emit_signal("nodes_changed"); } void OrchestratorGraphEdit::_on_script_changed() diff --git a/src/editor/graph/graph_edit.h b/src/editor/graph/graph_edit.h index 71ff34a6..8475b921 100644 --- a/src/editor/graph/graph_edit.h +++ b/src/editor/graph/graph_edit.h @@ -19,7 +19,10 @@ #include "actions/action_menu.h" #include "common/version.h" -#include "graph_node.h" +#include "editor/graph/graph_node.h" +#include "script/function.h" +#include "script/signals.h" +#include "script/variable.h" #include @@ -98,6 +101,9 @@ class OrchestratorGraphEdit : public GraphEdit HashMap> nodes; HashMap positions; RBSet connections; + RBSet> functions; + RBSet> variables; + RBSet> signals; /// Returns whether the clipboard is empty bool is_empty() const @@ -111,6 +117,9 @@ class OrchestratorGraphEdit : public GraphEdit nodes.clear(); positions.clear(); connections.clear(); + signals.clear(); + variables.clear(); + functions.clear(); } }; @@ -135,6 +144,7 @@ class OrchestratorGraphEdit : public GraphEdit GDExtensionGodotVersion _version; //! Godot version bool _is_43p{ false }; //! Is Godot 4.3+ bool _box_selection{ false }; //! Is graph doing box selection? + bool _disable_delete_confirmation{ false }; //! Allows temporarily disabling delete confirmation Vector2 _box_selection_from; //! Mouse position box selection started from OrchestratorScriptAutowireSelections* _autowire{ nullptr }; @@ -487,6 +497,9 @@ class OrchestratorGraphEdit : public GraphEdit /// Dispatched when the user presses {@code Ctrl+C} to copy selected nodes to the clipboard. void _on_copy_nodes_request(); + /// Dispatched when the user pressed {@code Ctrl+X} to cut selected nodes to the clipboard. + void _on_cut_nodes_request(); + /// Dispatched when the user wants to duplicate a graph node. void _on_duplicate_nodes_request(); diff --git a/src/editor/graph/graph_node.cpp b/src/editor/graph/graph_node.cpp index b1763dd1..66c50c1f 100644 --- a/src/editor/graph/graph_node.cpp +++ b/src/editor/graph/graph_node.cpp @@ -79,7 +79,15 @@ void OrchestratorGraphNode::_notification(int p_what) // Used to replicate size/position state to underlying node resource connect("dragged", callable_mp(this, &OrchestratorGraphNode::_on_node_moved)); + + // Godot 4.3 introduced a new resize_end callback that we will use now to handle triggering the + // final size of a node. This helps avoid issues with editor scale changes being problematic by + // leaving nodes too large after scale up. + #if GODOT_VERSION < 0x040300 connect("resized", callable_mp(this, &OrchestratorGraphNode::_on_node_resized)); + #else + connect("resize_end", callable_mp(this, &OrchestratorGraphNode::_on_resize_end)); + #endif // Used to replicate state changes from node resource to the UI _node->connect("pins_changed", callable_mp(this, &OrchestratorGraphNode::_on_pins_changed)); @@ -671,10 +679,17 @@ void OrchestratorGraphNode::_on_node_moved([[maybe_unused]] Vector2 p_old_pos, V _node->set_position(p_new_pos); } +#if GODOT_VERSION < 0x040300 void OrchestratorGraphNode::_on_node_resized() { _node->set_size(get_size()); } +#else +void OrchestratorGraphNode::_on_resize_end(const Vector2& p_size) +{ + _node->set_size(p_size); +} +#endif void OrchestratorGraphNode::_on_pins_changed() { diff --git a/src/editor/graph/graph_node.h b/src/editor/graph/graph_node.h index 66069044..3273a43a 100644 --- a/src/editor/graph/graph_node.h +++ b/src/editor/graph/graph_node.h @@ -233,8 +233,14 @@ class OrchestratorGraphNode : public GraphNode /// @param p_new_pos new position void _on_node_moved(Vector2 p_old_pos, Vector2 p_new_pos); + #if GODOT_VERSION < 0x040300 /// Called when the graph node is resized void _on_node_resized(); + #else + /// Called when the graph node manual resize finishes + /// @param p_size the new size + void _on_resize_end(const Vector2& p_size); + #endif /// Called when any pin detail has changed for this node void _on_pins_changed(); diff --git a/src/script/instances/script_instance.cpp b/src/script/instances/script_instance.cpp index 84ff8d6e..e2878b63 100644 --- a/src/script/instances/script_instance.cpp +++ b/src/script/instances/script_instance.cpp @@ -122,23 +122,33 @@ bool OScriptInstance::set(const StringName& p_name, const Variant& p_value, Prop bool OScriptInstance::get(const StringName& p_name, Variant& p_value, PropertyError* r_err) { + // First check if we have a member variable const String variable_name = _get_variable_name_from_path(p_name); - - OScriptVirtualMachine::Variable* variable = _vm.get_variable(variable_name); - if (!variable || !variable->exported) + if (_vm.has_variable(variable_name)) { + OScriptVirtualMachine::Variable* variable = _vm.get_variable(variable_name); + if (!variable || !variable->exported) + { + if (r_err) + *r_err = PROP_NOT_FOUND; + return false; + } + if (r_err) - *r_err = PROP_NOT_FOUND; + *r_err = PROP_OK; - return false; + p_value = variable->value; + return true; } - if (r_err) - *r_err = PROP_OK; - - p_value = variable->value; + // Next check signals - for named access, i.e. "await obj.signal" + if (_vm.has_signal(p_name)) + { + p_value = _vm.get_signal(p_name); + return true; + } - return true; + return false; } GDExtensionPropertyInfo* OScriptInstance::get_property_list(uint32_t* r_count) diff --git a/src/script/instances/script_instance_placeholder.cpp b/src/script/instances/script_instance_placeholder.cpp index b2a441cb..85eb6f15 100644 --- a/src/script/instances/script_instance_placeholder.cpp +++ b/src/script/instances/script_instance_placeholder.cpp @@ -110,10 +110,8 @@ bool OScriptPlaceHolderInstance::set(const StringName& p_name, const Variant& p_ { if (_script->_has_property_default_value(p_name)) { - Variant def_value = _script->get_property_default_value(p_name); - - Variant result; - if (VariantUtils::evaluate(Variant::OP_EQUAL, def_value, p_value, result)) + const Variant def_value = _script->get_property_default_value(p_name); + if (VariantUtils::evaluate(Variant::OP_EQUAL, def_value, p_value)) { _values.erase(p_name); return true; @@ -126,9 +124,8 @@ bool OScriptPlaceHolderInstance::set(const StringName& p_name, const Variant& p_ { if (_script->_has_property_default_value(p_name)) { - Variant def_value = _script->get_property_default_value(p_name); - Variant result; - if (VariantUtils::evaluate(Variant::OP_NOT_EQUAL, def_value, p_value, result)) + const Variant def_value = _script->get_property_default_value(p_name); + if (VariantUtils::evaluate(Variant::OP_NOT_EQUAL, def_value, p_value)) _values[p_name] = p_value; return true; } diff --git a/src/script/node.cpp b/src/script/node.cpp index 39ed25ce..e2e6c1f9 100644 --- a/src/script/node.cpp +++ b/src/script/node.cpp @@ -372,6 +372,9 @@ Vector> OScriptNode::get_eligible_autowire_pins(const Ref& p_pin) { + if (p_pin.is_valid()) + p_pin->reset_default_value(); + emit_signal("pin_connected", p_pin->get_direction(), p_pin->get_pin_index()); } diff --git a/src/script/node_pin.cpp b/src/script/node_pin.cpp index e4782b53..abf5c47a 100644 --- a/src/script/node_pin.cpp +++ b/src/script/node_pin.cpp @@ -294,7 +294,10 @@ void OScriptNodePin::set_type(Variant::Type p_type) _property.type = p_type; if (_set_type_resets_default) - reset_default_value(); + { + _default_value = Variant(); + _generated_default_value = _target_class.is_empty() ? VariantUtils::make_default(_property.type) : Variant(); + } emit_changed(); } @@ -320,7 +323,10 @@ void OScriptNodePin::set_target_class(const StringName& p_target_class) _property.type = Variant::OBJECT; if (_set_type_resets_default) - reset_default_value(); + { + _default_value = Variant(); + _generated_default_value = _target_class.is_empty() ? VariantUtils::make_default(_property.type) : Variant(); + } emit_changed(); } @@ -351,8 +357,7 @@ void OScriptNodePin::set_default_value(const Variant& p_default_value) void OScriptNodePin::reset_default_value() { - _default_value = Variant(); - _generated_default_value = _target_class.is_empty() ? VariantUtils::make_default(_property.type) : Variant(); + set_default_value(_generated_default_value); } Variant OScriptNodePin::get_generated_default_value() const diff --git a/src/script/nodes/dialogue/dialogue_message.cpp b/src/script/nodes/dialogue/dialogue_message.cpp index f612a79c..2a0e9e9b 100644 --- a/src/script/nodes/dialogue/dialogue_message.cpp +++ b/src/script/nodes/dialogue/dialogue_message.cpp @@ -25,6 +25,7 @@ #include #include #include +#include class OScriptNodeDialogueMessageInstance : public OScriptNodeInstance { @@ -105,6 +106,15 @@ class OScriptNodeDialogueMessageInstance : public OScriptNodeInstance state->connect_to_signal(_ui, "show_message_finished", Array()); Node* root = node->get_tree()->get_current_scene(); + if (!root) + root = node->get_tree()->get_root()->get_child(0); + + if (!root) + { + p_context.set_error("Cannot find root node"); + return -1 | STEP_FLAG_END; + } + if (!root->is_node_ready()) root->call_deferred("add_child", _ui); else diff --git a/src/script/nodes/functions/call_function.cpp b/src/script/nodes/functions/call_function.cpp index 18b6dabd..332552eb 100644 --- a/src/script/nodes/functions/call_function.cpp +++ b/src/script/nodes/functions/call_function.cpp @@ -332,7 +332,10 @@ void OScriptNodeCallFunction::_create_pins_for_method(const MethodInfo& p_method } if (argument_index >= default_start_index) + { + pin->set_generated_default_value(p_method.default_arguments[default_index]); pin->set_default_value(p_method.default_arguments[default_index++]); + } } argument_index++; } diff --git a/src/script/nodes/properties/property.cpp b/src/script/nodes/properties/property.cpp index ae6a3182..a55234e8 100644 --- a/src/script/nodes/properties/property.cpp +++ b/src/script/nodes/properties/property.cpp @@ -308,25 +308,26 @@ void OScriptNodeProperty::validate_node_during_build(BuildLog& p_log) const { p_log.error(this, "Requires a connection."); } - else - { - const Ref source = target->get_connections()[0]; - const Ref target_object = source->resolve_target(); - if (!target_object.is_valid() || !target_object->has_target()) - { - if (!_property_exists(ClassDB::class_get_property_list(target->get_property_info().class_name))) - p_log.error(this, vformat("No property name '%s' found in class '%s'", _property.name, _property.class_name)); - } - else - { - const Ref