diff --git a/.clang-format b/.clang-format index dd0e5447d5f..6a547cf6f11 100644 --- a/.clang-format +++ b/.clang-format @@ -124,7 +124,7 @@ IndentCaseLabels: true # IndentRequiresClause: true IndentWidth: 4 # IndentWrappedFunctionNames: false -# InsertBraces: false +InsertBraces: true # InsertNewlineAtEOF: false # InsertTrailingCommas: None # IntegerLiteralSeparator: diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 27580f7c7f1..65344a32c4a 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -13,9 +13,8 @@ jobs: fetch-depth: 2 - name: Install APT dependencies - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libxml2-utils + run: | + sudo apt install -y libxml2-utils - name: Install Python dependencies and general setup run: | diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 5f892f6e300..96a074fac89 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -187,9 +187,14 @@ License: Apache-2.0 Files: ./thirdparty/basis_universal/ Comment: Basis Universal -Copyright: 2022, Binomial LLC. +Copyright: 2019-2024, Binomial LLC. License: Apache-2.0 +Files: ./thirdparty/betsy/ +Comment: Betsy +Copyright: 2020-2022, Matias N. Goldberg +License: Expat + Files: ./thirdparty/brotli/ Comment: Brotli Copyright: 2009, 2010, 2013-2016 by the Brotli Authors. @@ -338,8 +343,8 @@ License: BSD-3-clause Files: ./thirdparty/libpng/ Comment: libpng -Copyright: 1995-2024, The PNG Reference Library Authors. - 2018-2024, Cosmin Truta. +Copyright: 1995-2025, The PNG Reference Library Authors. + 2018-2025, Cosmin Truta. 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. 1996-1997, Andreas Dilger. 1995-1996, Guy Eric Schalnat, Group 42, Inc. @@ -397,6 +402,11 @@ Copyright: 1998-2010, Gilles Vollant 2009-2010, Mathias Svensson License: Zlib +Files: ./thirdparty/misc/bcdec.h +Comment: bcdec +Copyright: 2022, Sergii Kudlai +License: Expat + Files: ./thirdparty/misc/cubemap_coeffs.h Comment: Fast Filtering of Reflection Probes Copyright: 2016, Activision Publishing, Inc. @@ -478,7 +488,7 @@ License: BSD-2-clause Files: ./thirdparty/msdfgen/ Comment: Multi-channel signed distance field generator -Copyright: 2016-2022, Viktor Chlumsky +Copyright: 2014-2024, Viktor Chlumsky License: Expat Files: ./thirdparty/nvapi/nvapi_minimal.h @@ -517,11 +527,6 @@ Comment: SPIRV-Reflect Copyright: 2017-2022, Google Inc. License: Apache-2.0 -Files: ./thirdparty/squish/ -Comment: libSquish -Copyright: 2006, Simon Brown -License: Expat - Files: ./thirdparty/thorvg/ Comment: ThorVG Copyright: 2020-2024, The ThorVG Project diff --git a/SConstruct b/SConstruct index 4f9afa537f1..99fb5c54c0f 100644 --- a/SConstruct +++ b/SConstruct @@ -5,12 +5,10 @@ EnsureSConsVersion(4, 0) EnsurePythonVersion(3, 8) # System -import atexit import glob import os import pickle import sys -import time from collections import OrderedDict from importlib.util import module_from_spec, spec_from_file_location from types import ModuleType @@ -52,13 +50,14 @@ _helper_module("platform_methods", "platform_methods.py") _helper_module("version", "version.py") _helper_module("core.core_builders", "core/core_builders.py") _helper_module("main.main_builders", "main/main_builders.py") +_helper_module("misc.utility.color", "misc/utility/color.py") # Local import gles3_builders import glsl_builders import methods import scu_builders -from methods import Ansi, print_error, print_info, print_warning +from misc.utility.color import STDERR_COLOR, print_error, print_info, print_warning from platform_methods import architecture_aliases, architectures, compatibility_platform_aliases if ARGUMENTS.get("target", "editor") == "editor": @@ -74,8 +73,6 @@ platform_doc_class_path = {} platform_exporters = [] platform_apis = [] -time_at_start = time.time() - for x in sorted(glob.glob("platform/*")): if not os.path.isdir(x) or not os.path.exists(x + "/detect.py"): continue @@ -198,7 +195,7 @@ opts.Add(BoolVariable("threads", "Enable threading support", True)) opts.Add(BoolVariable("deprecated", "Enable compatibility code for deprecated and removed features", True)) opts.Add(EnumVariable("precision", "Set the floating-point precision level", "single", ("single", "double"))) opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", True)) -opts.Add(BoolVariable("brotli", "Enable Brotli for decompresson and WOFF2 fonts support", True)) +opts.Add(BoolVariable("brotli", "Enable Brotli for decompression and WOFF2 fonts support", True)) opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver on supported platforms", False)) opts.Add(BoolVariable("vulkan", "Enable the vulkan rendering driver", True)) opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 rendering driver", True)) @@ -702,6 +699,14 @@ if env["arch"] == "x86_32": else: env.Append(CCFLAGS=["-msse2"]) +# Explicitly specify colored output. +if methods.using_gcc(env): + env.AppendUnique(CCFLAGS=["-fdiagnostics-color" if STDERR_COLOR else "-fno-diagnostics-color"]) +elif methods.using_clang(env) or methods.using_emcc(env): + env.AppendUnique(CCFLAGS=["-fcolor-diagnostics" if STDERR_COLOR else "-fno-color-diagnostics"]) + if sys.platform == "win32": + env.AppendUnique(CCFLAGS=["-fansi-escape-codes"]) + # Set optimize and debug_symbols flags. # "custom" means do nothing and let users set their own optimization flags. # Needs to happen after configure to have `env.msvc` defined. @@ -1086,30 +1091,5 @@ methods.show_progress(env) # TODO: replace this with `env.Dump(format="json")` # once we start requiring SCons 4.0 as min version. methods.dump(env) - - -def print_elapsed_time(): - elapsed_time_sec = round(time.time() - time_at_start, 2) - time_centiseconds = round((elapsed_time_sec % 1) * 100) - print( - "{}[Time elapsed: {}.{:02}]{}".format( - Ansi.GRAY, - time.strftime("%H:%M:%S", time.gmtime(elapsed_time_sec)), - time_centiseconds, - Ansi.RESET, - ) - ) - - -atexit.register(print_elapsed_time) - - -def purge_flaky_files(): - paths_to_keep = [env["ninja_file"]] - for build_failure in GetBuildFailures(): - path = build_failure.node.path - if os.path.isfile(path) and path not in paths_to_keep: - os.remove(path) - - -atexit.register(purge_flaky_files) +methods.prepare_purge(env) +methods.prepare_timer() diff --git a/core/SCsub b/core/SCsub index 1c01bbbef6d..62db0cdeade 100644 --- a/core/SCsub +++ b/core/SCsub @@ -99,6 +99,8 @@ if env["builtin_zlib"]: env.Prepend(CPPPATH=[thirdparty_zlib_dir]) if env.dev_build: env_thirdparty.Append(CPPDEFINES=["ZLIB_DEBUG"]) + # Affects headers so it should also be defined for Godot code + env.Append(CPPDEFINES=["ZLIB_DEBUG"]) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_zlib_sources) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 2c9c11f5e93..fd3919bb816 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -650,6 +650,28 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b return err; } +#ifdef MACOS_ENABLED + // Attempt to load project file from macOS .app bundle resources. + resource_path = OS::get_singleton()->get_bundle_resource_dir(); + if (!resource_path.is_empty()) { + if (resource_path[resource_path.length() - 1] == '/') { + resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end. + } + Ref d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", resource_path)); + d->change_dir(resource_path); + + Error err; + + err = _load_settings_text_or_binary(resource_path.path_join("project.godot"), resource_path.path_join("project.binary")); + if (err == OK && !p_ignore_override) { + // Optional, we don't mind if it fails. + _load_settings_text(resource_path.path_join("override.cfg")); + return err; + } + } +#endif + // Nothing was found, try to find a project file in provided path (`p_path`) // or, if requested (`p_upwards`) in parent directories. diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 408ee3a0e96..3e72563e19c 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -1023,7 +1023,19 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { d2["is_required"] = (F.flags & METHOD_FLAG_VIRTUAL_REQUIRED) ? true : false; d2["is_vararg"] = false; d2["is_virtual"] = true; - // virtual functions have no hash since no MethodBind is involved + d2["hash"] = mi.get_compatibility_hash(); + + Vector compat_hashes = ClassDB::get_virtual_method_compatibility_hashes(class_name, method_name); + Array compatibility; + if (compat_hashes.size()) { + for (int i = 0; i < compat_hashes.size(); i++) { + compatibility.push_back(compat_hashes[i]); + } + } + if (compatibility.size() > 0) { + d2["hash_compatibility"] = compatibility; + } + bool has_return = mi.return_val.type != Variant::NIL || (mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); if (has_return) { PropertyInfo pinfo = mi.return_val; @@ -1476,8 +1488,8 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_ if (p_compare_hashes) { if (!old_elem.has("hash")) { - if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !new_elem.has("hash")) { - continue; // No hash for virtual methods, go on. + if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !old_elem.has("hash")) { + continue; // Virtual methods didn't use to have hashes, so skip check if it's missing in the old file. } failed = true; diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 7a8e346de5b..6de1b26e247 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -262,7 +262,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ nullptr, // GDExtensionClassRecreateInstance recreate_instance_func; - p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + nullptr, // GDExtensionClassGetVirtual get_virtual_func; nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func; p_extension_funcs->class_userdata, // void *class_userdata; @@ -273,6 +273,8 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; + p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + nullptr, }; _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } @@ -298,8 +300,8 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; - p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; - p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + nullptr, // GDExtensionClassGetVirtual get_virtual_func; + nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; p_extension_funcs->class_userdata, // void *class_userdata; }; @@ -309,6 +311,8 @@ void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_librar p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; + p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func; }; _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } @@ -334,8 +338,8 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar nullptr, // GDExtensionClassCreateInstance2 create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ p_extension_funcs->recreate_instance_func, // GDExtensionClassRecreateInstance recreate_instance_func; - p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; - p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + nullptr, // GDExtensionClassGetVirtual get_virtual_func; + nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; p_extension_funcs->call_virtual_with_data_func, // GDExtensionClassCallVirtualWithData call_virtual_func; p_extension_funcs->class_userdata, // void *class_userdata; }; @@ -345,6 +349,8 @@ void GDExtension::_register_extension_class3(GDExtensionClassLibraryPtr p_librar nullptr, // GDExtensionClassFreePropertyList free_property_list_func; p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance2 create_instance_func; p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; + p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; + p_extension_funcs->get_virtual_call_data_func, // GDExtensionClassGetVirtual get_virtual_func; }; _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info4, &legacy); } @@ -433,6 +439,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr extension->gdextension.free_property_list = p_deprecated_funcs->free_property_list_func; extension->gdextension.create_instance = p_deprecated_funcs->create_instance_func; extension->gdextension.get_rid = p_deprecated_funcs->get_rid_func; + extension->gdextension.get_virtual = p_deprecated_funcs->get_virtual_func; + extension->gdextension.get_virtual_call_data = p_deprecated_funcs->get_virtual_call_data_func; } #endif // DISABLE_DEPRECATED extension->gdextension.notification2 = p_extension_funcs->notification_func; @@ -443,8 +451,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr extension->gdextension.create_instance2 = p_extension_funcs->create_instance_func; extension->gdextension.free_instance = p_extension_funcs->free_instance_func; extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func; - extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func; - extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func; + extension->gdextension.get_virtual2 = p_extension_funcs->get_virtual_func; + extension->gdextension.get_virtual_call_data2 = p_extension_funcs->get_virtual_call_data_func; extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func; extension->gdextension.reloadable = self->reloadable; diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 735750c0601..9d4ea3c36fa 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -74,6 +74,8 @@ class GDExtension : public Resource { GDExtensionClassFreePropertyList free_property_list_func = nullptr; GDExtensionClassCreateInstance create_instance_func = nullptr; GDExtensionClassGetRID get_rid_func = nullptr; + GDExtensionClassGetVirtual get_virtual_func = nullptr; + GDExtensionClassGetVirtualCallData get_virtual_call_data_func = nullptr; #endif // DISABLE_DEPRECATED }; diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index af34edea4be..71de9283b64 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -275,7 +275,9 @@ typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_us typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance); typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object); typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); +typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash); typedef void *(*GDExtensionClassGetVirtualCallData)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); +typedef void *(*GDExtensionClassGetVirtualCallData2)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash); typedef void (*GDExtensionClassCallVirtualWithData)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); typedef struct { @@ -386,14 +388,14 @@ typedef struct { GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory. GDExtensionClassRecreateInstance recreate_instance_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function. - GDExtensionClassGetVirtual get_virtual_func; + GDExtensionClassGetVirtual2 get_virtual_func; // Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that // need or benefit from extra data when calling virtual functions. // Returns user data that will be passed to `call_virtual_with_data_func`. // Returning `NULL` from this function signals to Godot that the virtual function is not overridden. // Data returned from this function should be managed by the extension and must be valid until the extension is deinitialized. // You should supply either `get_virtual_func`, or `get_virtual_call_data_func` with `call_virtual_with_data_func`. - GDExtensionClassGetVirtualCallData get_virtual_call_data_func; + GDExtensionClassGetVirtualCallData2 get_virtual_call_data_func; // Used to call virtual functions when `get_virtual_call_data_func` is not null. GDExtensionClassCallVirtualWithData call_virtual_with_data_func; void *class_userdata; // Per-class user data, later accessible in instance bindings. diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 43fafc4306c..c589de07b47 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -1228,6 +1228,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem } else { // Try file remap. // Usually, there's no remap file and FileAccess::exists() is faster than FileAccess::open(). + new_path = ResourceUID::ensure_path(new_path); if (FileAccess::exists(new_path + ".remap")) { Error err; Ref f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index f571f23b28e..7d4c64e5340 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -153,11 +153,14 @@ void ResourceUID::set_id(ID p_id, const String &p_path) { } String ResourceUID::get_id_path(ID p_id) const { + ERR_FAIL_COND_V_MSG(p_id == INVALID_ID, String(), "Invalid UID."); MutexLock l(mutex); - ERR_FAIL_COND_V(!unique_ids.has(p_id), String()); - const CharString &cs = unique_ids[p_id].cs; + const ResourceUID::Cache *cache = unique_ids.getptr(p_id); + ERR_FAIL_COND_V_MSG(!cache, String(), vformat("Unrecognized UID: \"%s\".", id_to_text(p_id))); + const CharString &cs = cache->cs; return String::utf8(cs.ptr()); } + void ResourceUID::remove_id(ID p_id) { MutexLock l(mutex); ERR_FAIL_COND(!unique_ids.has(p_id)); diff --git a/core/math/delaunay_3d.h b/core/math/delaunay_3d.h index 5c7feae8731..8bddc2d41cf 100644 --- a/core/math/delaunay_3d.h +++ b/core/math/delaunay_3d.h @@ -137,9 +137,9 @@ class Delaunay3D { R128 row3_y = v3_y - v0_y; R128 row3_z = v3_z - v0_z; - R128 sq_lenght1 = row1_x * row1_x + row1_y * row1_y + row1_z * row1_z; - R128 sq_lenght2 = row2_x * row2_x + row2_y * row2_y + row2_z * row2_z; - R128 sq_lenght3 = row3_x * row3_x + row3_y * row3_y + row3_z * row3_z; + R128 sq_length1 = row1_x * row1_x + row1_y * row1_y + row1_z * row1_z; + R128 sq_length2 = row2_x * row2_x + row2_y * row2_y + row2_z * row2_z; + R128 sq_length3 = row3_x * row3_x + row3_y * row3_y + row3_z * row3_z; // Compute the determinant of said matrix. R128 determinant = row1_x * (row2_y * row3_z - row3_y * row2_z) - row2_x * (row1_y * row3_z - row3_y * row1_z) + row3_x * (row1_y * row2_z - row2_y * row1_z); @@ -148,9 +148,9 @@ class Delaunay3D { R128 volume = determinant / R128(6.f); R128 i12volume = R128(1.f) / (volume * R128(12.f)); - R128 center_x = v0_x + i12volume * ((row2_y * row3_z - row3_y * row2_z) * sq_lenght1 - (row1_y * row3_z - row3_y * row1_z) * sq_lenght2 + (row1_y * row2_z - row2_y * row1_z) * sq_lenght3); - R128 center_y = v0_y + i12volume * (-(row2_x * row3_z - row3_x * row2_z) * sq_lenght1 + (row1_x * row3_z - row3_x * row1_z) * sq_lenght2 - (row1_x * row2_z - row2_x * row1_z) * sq_lenght3); - R128 center_z = v0_z + i12volume * ((row2_x * row3_y - row3_x * row2_y) * sq_lenght1 - (row1_x * row3_y - row3_x * row1_y) * sq_lenght2 + (row1_x * row2_y - row2_x * row1_y) * sq_lenght3); + R128 center_x = v0_x + i12volume * ((row2_y * row3_z - row3_y * row2_z) * sq_length1 - (row1_y * row3_z - row3_y * row1_z) * sq_length2 + (row1_y * row2_z - row2_y * row1_z) * sq_length3); + R128 center_y = v0_y + i12volume * (-(row2_x * row3_z - row3_x * row2_z) * sq_length1 + (row1_x * row3_z - row3_x * row1_z) * sq_length2 - (row1_x * row2_z - row2_x * row1_z) * sq_length3); + R128 center_z = v0_z + i12volume * ((row2_x * row3_y - row3_x * row2_y) * sq_length1 - (row1_x * row3_y - row3_x * row1_y) * sq_length2 + (row1_x * row2_y - row2_x * row1_y) * sq_length3); // Once we know the center, the radius is clearly the distance to any vertex. R128 rel1_x = center_x - v0_x; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 12a7c519d4b..1059ac38657 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -221,7 +221,7 @@ class PlaceholderExtensionInstance { memdelete(instance); } - static GDExtensionClassCallVirtual placeholder_class_get_virtual(void *p_class_userdata, GDExtensionConstStringNamePtr p_name) { + static GDExtensionClassCallVirtual placeholder_class_get_virtual(void *p_class_userdata, GDExtensionConstStringNamePtr p_name, uint32_t p_hash) { return nullptr; } }; @@ -715,8 +715,12 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) #endif // DISABLE_DEPRECATED placeholder_extension->create_instance2 = &PlaceholderExtensionInstance::placeholder_class_create_instance; placeholder_extension->free_instance = &PlaceholderExtensionInstance::placeholder_class_free_instance; - placeholder_extension->get_virtual = &PlaceholderExtensionInstance::placeholder_class_get_virtual; +#ifndef DISABLE_DEPRECATED + placeholder_extension->get_virtual = nullptr; placeholder_extension->get_virtual_call_data = nullptr; +#endif // DISABLE_DEPRECATED + placeholder_extension->get_virtual2 = &PlaceholderExtensionInstance::placeholder_class_get_virtual; + placeholder_extension->get_virtual_call_data2 = nullptr; placeholder_extension->call_virtual_with_data = nullptr; placeholder_extension->recreate_instance = &PlaceholderExtensionInstance::placeholder_class_recreate_instance; @@ -940,7 +944,7 @@ void ClassDB::get_method_list_with_compatibility(const StringName &p_class, List #ifdef DEBUG_METHODS_ENABLED for (const MethodInfo &E : type->virtual_methods) { - Pair pair(E, 0); + Pair pair(E, E.get_compatibility_hash()); p_methods->push_back(pair); } @@ -2017,6 +2021,22 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_ #endif } +void ClassDB::add_virtual_compatibility_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector &p_arg_names, bool p_object_core) { + ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class)); + + OBJTYPE_WLOCK; + + HashMap> &virtual_methods_compat = classes[p_class].virtual_methods_compat; + + Vector *compat_hashes = virtual_methods_compat.getptr(p_method.name); + if (!compat_hashes) { + virtual_methods_compat[p_method.name] = Vector(); + compat_hashes = &virtual_methods_compat[p_method.name]; + } + + compat_hashes->push_back(p_method.get_compatibility_hash()); +} + void ClassDB::get_virtual_methods(const StringName &p_class, List *p_methods, bool p_no_inheritance) { ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class)); @@ -2038,6 +2058,25 @@ void ClassDB::get_virtual_methods(const StringName &p_class, List *p #endif } +Vector ClassDB::get_virtual_method_compatibility_hashes(const StringName &p_class, const StringName &p_name) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->virtual_methods_compat.has(p_name)) { + Vector *compat_hashes = type->virtual_methods_compat.getptr(p_name); + if (compat_hashes) { + return *compat_hashes; + } + break; + } + type = type->inherits_ptr; + } + + return Vector(); +} + void ClassDB::add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info) { ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class)); diff --git a/core/object/class_db.h b/core/object/class_db.h index 101f8fd3018..35d191f8977 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -129,6 +129,7 @@ class ClassDB { HashMap> linked_properties; #endif HashMap property_setget; + HashMap> virtual_methods_compat; StringName inherits; StringName name; @@ -454,8 +455,10 @@ class ClassDB { static Vector get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name); static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector &p_arg_names = Vector(), bool p_object_core = false); + static void add_virtual_compatibility_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector &p_arg_names = Vector(), bool p_object_core = false); static void get_virtual_methods(const StringName &p_class, List *p_methods, bool p_no_inheritance = false); static void add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info); + static Vector get_virtual_method_compatibility_hashes(const StringName &p_class, const StringName &p_name); static void bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int64_t p_constant, bool p_is_bitfield = false); static void get_integer_constant_list(const StringName &p_class, List *p_constants, bool p_no_inheritance = false); diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 2974346a46f..325477692a6 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -1,36 +1,46 @@ -proto = """#define GDVIRTUAL$VER($RET m_name $ARG)\\ - StringName _gdvirtual_##m_name##_sn = #m_name;\\ - mutable bool _gdvirtual_##m_name##_initialized = false;\\ - mutable void *_gdvirtual_##m_name = nullptr;\\ - _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\ - ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ +script_call = """ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ if (_script_instance) {\\ Callable::CallError ce;\\ $CALLSIARGS\\ - $CALLSIBEGIN_script_instance->callp(_gdvirtual_##m_name##_sn, $CALLSIARGPASS, ce);\\ + $CALLSIBEGIN_script_instance->callp(_gdvirtual_##$VARNAME##_sn, $CALLSIARGPASS, ce);\\ if (ce.error == Callable::CallError::CALL_OK) {\\ $CALLSIRET\\ return true;\\ }\\ - }\\ - if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - _gdvirtual_##m_name = nullptr;\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - } else if (_get_extension()->get_virtual) {\\ - _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + }""" + +script_has_method = """ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ + if (_script_instance && _script_instance->has_method(_gdvirtual_##$VARNAME##_sn)) {\\ + return true;\\ + }""" + +proto = """#define GDVIRTUAL$VER($ALIAS $RET m_name $ARG)\\ + StringName _gdvirtual_##$VARNAME##_sn = #m_name;\\ + mutable bool _gdvirtual_##$VARNAME##_initialized = false;\\ + mutable void *_gdvirtual_##$VARNAME = nullptr;\\ + _FORCE_INLINE_ bool _gdvirtual_##$VARNAME##_call($CALLARGS) $CONST {\\ + $SCRIPTCALL\\ + if (unlikely(_get_extension() && !_gdvirtual_##$VARNAME##_initialized)) {\\ + MethodInfo mi = _gdvirtual_##$VARNAME##_get_method_info();\\ + uint32_t hash = mi.get_compatibility_hash();\\ + _gdvirtual_##$VARNAME = nullptr;\\ + if (_get_extension()->get_virtual_call_data2 && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##$VARNAME = _get_extension()->get_virtual_call_data2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\ + } else if (_get_extension()->get_virtual2) {\\ + _gdvirtual_##$VARNAME = (void *)_get_extension()->get_virtual2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\ }\\ - GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized);\\ - _gdvirtual_##m_name##_initialized = true;\\ + _GDVIRTUAL_GET_DEPRECATED(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_sn, $COMPAT)\\ + _GDVIRTUAL_TRACK(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_initialized);\\ + _gdvirtual_##$VARNAME##_initialized = true;\\ }\\ - if (_gdvirtual_##m_name) {\\ + if (_gdvirtual_##$VARNAME) {\\ $CALLPTRARGS\\ $CALLPTRRETDEF\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##m_name##_sn, _gdvirtual_##m_name, $CALLPTRARGPASS, $CALLPTRRETPASS);\\ + if (_get_extension()->call_virtual_with_data) {\\ + _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##$VARNAME##_sn, _gdvirtual_##$VARNAME, $CALLPTRARGPASS, $CALLPTRRETPASS);\\ $CALLPTRRET\\ } else {\\ - ((GDExtensionClassCallVirtual)_gdvirtual_##m_name)(_get_extension_instance(), $CALLPTRARGPASS, $CALLPTRRETPASS);\\ + ((GDExtensionClassCallVirtual)_gdvirtual_##$VARNAME)(_get_extension_instance(), $CALLPTRARGPASS, $CALLPTRRETPASS);\\ $CALLPTRRET\\ }\\ return true;\\ @@ -39,27 +49,27 @@ $RVOID\\ return false;\\ }\\ - _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\ - ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ - if (_script_instance && _script_instance->has_method(_gdvirtual_##m_name##_sn)) {\\ - return true;\\ - }\\ - if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - _gdvirtual_##m_name = nullptr;\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - } else if (_get_extension()->get_virtual) {\\ - _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + _FORCE_INLINE_ bool _gdvirtual_##$VARNAME##_overridden() const {\\ + $SCRIPTHASMETHOD\\ + if (unlikely(_get_extension() && !_gdvirtual_##$VARNAME##_initialized)) {\\ + MethodInfo mi = _gdvirtual_##$VARNAME##_get_method_info();\\ + uint32_t hash = mi.get_compatibility_hash();\\ + _gdvirtual_##$VARNAME = nullptr;\\ + if (_get_extension()->get_virtual_call_data2 && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##$VARNAME = _get_extension()->get_virtual_call_data2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\ + } else if (_get_extension()->get_virtual2) {\\ + _gdvirtual_##$VARNAME = (void *)_get_extension()->get_virtual2(_get_extension()->class_userdata, &_gdvirtual_##$VARNAME##_sn, hash);\\ }\\ - GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized);\\ - _gdvirtual_##m_name##_initialized = true;\\ + _GDVIRTUAL_GET_DEPRECATED(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_sn, $COMPAT)\\ + _GDVIRTUAL_TRACK(_gdvirtual_##$VARNAME, _gdvirtual_##$VARNAME##_initialized);\\ + _gdvirtual_##$VARNAME##_initialized = true;\\ }\\ - if (_gdvirtual_##m_name) {\\ + if (_gdvirtual_##$VARNAME) {\\ return true;\\ }\\ return false;\\ }\\ - _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\ + _FORCE_INLINE_ static MethodInfo _gdvirtual_##$VARNAME##_get_method_info() {\\ MethodInfo method_info;\\ method_info.name = #m_name;\\ method_info.flags = $METHOD_FLAGS;\\ @@ -70,8 +80,15 @@ """ -def generate_version(argcount, const=False, returns=False, required=False): +def generate_version(argcount, const=False, returns=False, required=False, compat=False): s = proto + if compat: + s = s.replace("$SCRIPTCALL", "") + s = s.replace("$SCRIPTHASMETHOD", "") + else: + s = s.replace("$SCRIPTCALL", script_call) + s = s.replace("$SCRIPTHASMETHOD", script_has_method) + sproto = str(argcount) method_info = "" method_flags = "METHOD_FLAG_VIRTUAL" @@ -104,6 +121,16 @@ def generate_version(argcount, const=False, returns=False, required=False): else: s = s.replace("\t\t$REQCHECK\\\n", "") + if compat: + sproto += "_COMPAT" + s = s.replace("$COMPAT", "true") + s = s.replace("$ALIAS", "m_alias,") + s = s.replace("$VARNAME", "m_alias") + else: + s = s.replace("$COMPAT", "false") + s = s.replace("$ALIAS ", "") + s = s.replace("$VARNAME", "m_name") + s = s.replace("$METHOD_FLAGS", method_flags) s = s.replace("$VER", sproto) argtext = "" @@ -188,7 +215,7 @@ def run(target, source, env): #include #ifdef TOOLS_ENABLED -#define GDVIRTUAL_TRACK(m_virtual, m_initialized)\\ +#define _GDVIRTUAL_TRACK(m_virtual, m_initialized)\\ if (_get_extension()->reloadable) {\\ VirtualMethodTracker *tracker = memnew(VirtualMethodTracker);\\ tracker->method = (void **)&m_virtual;\\ @@ -197,7 +224,20 @@ def run(target, source, env): virtual_method_list = tracker;\\ } #else -#define GDVIRTUAL_TRACK(m_virtual, m_initialized) +#define _GDVIRTUAL_TRACK(m_virtual, m_initialized) +#endif + +#ifndef DISABLE_DEPRECATED +#define _GDVIRTUAL_GET_DEPRECATED(m_virtual, m_name_sn, m_compat)\\ + else if (m_compat || ClassDB::get_virtual_method_compatibility_hashes(get_class_static(), m_name_sn).size() == 0) {\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + m_virtual = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &m_name_sn);\\ + } else if (_get_extension()->get_virtual) {\\ + m_virtual = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &m_name_sn);\\ + }\\ + } +#else +#define _GDVIRTUAL_GET_DEPRECATED(m_name, m_name_sn, m_compat) #endif // MSVC WORKAROUND START @@ -243,6 +283,10 @@ def run(target, source, env): txt += generate_version(i, False, True, True) txt += generate_version(i, True, False, True) txt += generate_version(i, True, True, True) + txt += generate_version(i, False, False, False, True) + txt += generate_version(i, False, True, False, True) + txt += generate_version(i, True, False, False, True) + txt += generate_version(i, True, True, False, True) txt += "#endif // GDVIRTUAL_GEN_H\n" diff --git a/core/object/object.cpp b/core/object/object.cpp index 1e25af7e3da..4a1b562d5c0 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -167,6 +167,38 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { return mi; } +// This was copied from MethodBind::get_hash() so that the compatibility hashes for virtual and non-virtual methods would be the same. +uint32_t MethodInfo::get_compatibility_hash() const { + bool has_return = (return_val.type != Variant::NIL) || (return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); + + uint32_t hash = hash_murmur3_one_32(has_return); + hash = hash_murmur3_one_32(arguments.size(), hash); + + if (has_return) { + hash = hash_murmur3_one_32(return_val.type, hash); + if (return_val.class_name != StringName()) { + hash = hash_murmur3_one_32(return_val.class_name.hash(), hash); + } + } + + for (const PropertyInfo &arg : arguments) { + hash = hash_murmur3_one_32(arg.type, hash); + if (arg.class_name != StringName()) { + hash = hash_murmur3_one_32(arg.class_name.hash(), hash); + } + } + + hash = hash_murmur3_one_32(default_arguments.size(), hash); + for (const Variant &v : default_arguments) { + hash = hash_murmur3_one_32(v.hash(), hash); + } + + hash = hash_murmur3_one_32(flags & METHOD_FLAG_CONST ? 1 : 0, hash); + hash = hash_murmur3_one_32(flags & METHOD_FLAG_VARARG ? 1 : 0, hash); + + return hash_fmix32(hash); +} + Object::Connection::operator Variant() const { Dictionary d; d["signal"] = signal; diff --git a/core/object/object.h b/core/object/object.h index 239c44f5168..6c99086467b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -249,6 +249,8 @@ struct MethodInfo { static MethodInfo from_dict(const Dictionary &p_dict); + uint32_t get_compatibility_hash() const; + MethodInfo() {} explicit MethodInfo(const GDExtensionMethodInfo &pinfo) : @@ -362,8 +364,12 @@ struct ObjectGDExtension { #endif // DISABLE_DEPRECATED GDExtensionClassCreateInstance2 create_instance2; GDExtensionClassFreeInstance free_instance; +#ifndef DISABLE_DEPRECATED GDExtensionClassGetVirtual get_virtual; GDExtensionClassGetVirtualCallData get_virtual_call_data; +#endif // DISABLE_DEPRECATED + GDExtensionClassGetVirtual2 get_virtual2; + GDExtensionClassGetVirtualCallData2 get_virtual_call_data2; GDExtensionClassCallVirtualWithData call_virtual_with_data; GDExtensionClassRecreateInstance recreate_instance; @@ -382,6 +388,7 @@ struct ObjectGDExtension { #else #define GDVIRTUAL_BIND(m_name, ...) #endif +#define GDVIRTUAL_BIND_COMPAT(m_alias, ...) ::ClassDB::add_virtual_compatibility_method(get_class_static(), _gdvirtual_##m_alias##_get_method_info(), true, sarray(__VA_ARGS__)); #define GDVIRTUAL_IS_OVERRIDDEN(m_name) _gdvirtual_##m_name##_overridden() #define GDVIRTUAL_IS_OVERRIDDEN_PTR(m_obj, m_name) m_obj->_gdvirtual_##m_name##_overridden() diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp index 61dcf02ba7d..b6cad1486a7 100644 --- a/core/string/node_path.cpp +++ b/core/string/node_path.cpp @@ -257,7 +257,7 @@ NodePath NodePath::slice(int p_begin, int p_end) const { if (end < 0) { end += total_count; } - const int sub_begin = MAX(begin - name_count - 1, 0); + const int sub_begin = MAX(begin - name_count, 0); const int sub_end = MAX(end - name_count, 0); const Vector names = get_names().slice(begin, end); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 54c512c3f6d..3c8d3dbfe30 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -3709,8 +3709,7 @@ int String::_count(const String &p_string, int p_from, int p_to, bool p_case_ins return 0; } if (p_from == 0 && p_to == len) { - str = String(); - str.copy_from_unchecked(&get_data()[0], len); + str = *this; } else { str = substr(p_from, p_to - p_from); } @@ -3746,8 +3745,7 @@ int String::_count(const char *p_string, int p_from, int p_to, bool p_case_insen return 0; } if (p_from == 0 && search_limit == source_length) { - str = String(); - str.copy_from_unchecked(&get_data()[0], source_length); + str = *this; } else { str = substr(p_from, search_limit - p_from); } diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index 94491697f5b..f2781a78e9a 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -59,6 +59,7 @@ class LocalVector { return data; } + // Must take a copy instead of a reference (see GH-31736). _FORCE_INLINE_ void push_back(T p_elem) { if (unlikely(count == capacity)) { capacity = tight ? (capacity + 1) : MAX((U)1, capacity << 1); diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h index edc4cad60b1..1c35e1bdbd6 100644 --- a/core/templates/rid_owner.h +++ b/core/templates/rid_owner.h @@ -419,8 +419,8 @@ class RID_Alloc : public RID_AllocBase { } } - void set_description(const char *p_descrption) { - description = p_descrption; + void set_description(const char *p_description) { + description = p_description; } RID_Alloc(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) { @@ -517,8 +517,8 @@ class RID_PtrOwner { alloc.fill_owned_buffer(p_rid_buffer); } - void set_description(const char *p_descrption) { - alloc.set_description(p_descrption); + void set_description(const char *p_description) { + alloc.set_description(p_description); } RID_PtrOwner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) : @@ -572,8 +572,8 @@ class RID_Owner { alloc.fill_owned_buffer(p_rid_buffer); } - void set_description(const char *p_descrption) { - alloc.set_description(p_descrption); + void set_description(const char *p_description) { + alloc.set_description(p_description); } RID_Owner(uint32_t p_target_chunk_byte_size = 65536, uint32_t p_maximum_number_of_elements = 262144) : alloc(p_target_chunk_byte_size, p_maximum_number_of_elements) {} diff --git a/core/templates/vector.h b/core/templates/vector.h index c29011bb45e..05c338d93b4 100644 --- a/core/templates/vector.h +++ b/core/templates/vector.h @@ -73,6 +73,7 @@ class Vector { CowData _cowdata; public: + // Must take a copy instead of a reference (see GH-31736). bool push_back(T p_elem); _FORCE_INLINE_ bool append(const T &p_elem) { return push_back(p_elem); } //alias void fill(T p_elem); @@ -101,12 +102,14 @@ class Vector { Error resize(Size p_size) { return _cowdata.resize(p_size); } Error resize_zeroed(Size p_size) { return _cowdata.template resize(p_size); } _FORCE_INLINE_ const T &operator[](Size p_index) const { return _cowdata.get(p_index); } + // Must take a copy instead of a reference (see GH-31736). Error insert(Size p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); } Size find(const T &p_val, Size p_from = 0) const { return _cowdata.find(p_val, p_from); } Size rfind(const T &p_val, Size p_from = -1) const { return _cowdata.rfind(p_val, p_from); } Size count(const T &p_val) const { return _cowdata.count(p_val); } - void append_array(const Vector &p_other); + // Must take a copy instead of a reference (see GH-31736). + void append_array(Vector p_other); _FORCE_INLINE_ bool has(const T &p_val) const { return find(p_val) != -1; } @@ -303,7 +306,7 @@ void Vector::reverse() { } template -void Vector::append_array(const Vector &p_other) { +void Vector::append_array(Vector p_other) { const Size ds = p_other.size(); if (ds == 0) { return; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 9d1878d5b55..c2a8433974d 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -2380,66 +2380,79 @@ Variant::Variant(const ObjectID &p_id) : Variant::Variant(const StringName &p_string) : type(STRING_NAME) { memnew_placement(_data._mem, StringName(p_string)); + static_assert(sizeof(StringName) <= sizeof(_data._mem)); } Variant::Variant(const String &p_string) : type(STRING) { memnew_placement(_data._mem, String(p_string)); + static_assert(sizeof(String) <= sizeof(_data._mem)); } Variant::Variant(const char *const p_cstring) : type(STRING) { memnew_placement(_data._mem, String((const char *)p_cstring)); + static_assert(sizeof(String) <= sizeof(_data._mem)); } Variant::Variant(const char32_t *p_wstring) : type(STRING) { memnew_placement(_data._mem, String(p_wstring)); + static_assert(sizeof(String) <= sizeof(_data._mem)); } Variant::Variant(const Vector3 &p_vector3) : type(VECTOR3) { memnew_placement(_data._mem, Vector3(p_vector3)); + static_assert(sizeof(Vector3) <= sizeof(_data._mem)); } Variant::Variant(const Vector3i &p_vector3i) : type(VECTOR3I) { memnew_placement(_data._mem, Vector3i(p_vector3i)); + static_assert(sizeof(Vector3i) <= sizeof(_data._mem)); } Variant::Variant(const Vector4 &p_vector4) : type(VECTOR4) { memnew_placement(_data._mem, Vector4(p_vector4)); + static_assert(sizeof(Vector4) <= sizeof(_data._mem)); } Variant::Variant(const Vector4i &p_vector4i) : type(VECTOR4I) { memnew_placement(_data._mem, Vector4i(p_vector4i)); + static_assert(sizeof(Vector4i) <= sizeof(_data._mem)); } Variant::Variant(const Vector2 &p_vector2) : type(VECTOR2) { memnew_placement(_data._mem, Vector2(p_vector2)); + static_assert(sizeof(Vector2) <= sizeof(_data._mem)); } Variant::Variant(const Vector2i &p_vector2i) : type(VECTOR2I) { memnew_placement(_data._mem, Vector2i(p_vector2i)); + static_assert(sizeof(Vector2i) <= sizeof(_data._mem)); } Variant::Variant(const Rect2 &p_rect2) : type(RECT2) { memnew_placement(_data._mem, Rect2(p_rect2)); + static_assert(sizeof(Rect2) <= sizeof(_data._mem)); } Variant::Variant(const Rect2i &p_rect2i) : type(RECT2I) { memnew_placement(_data._mem, Rect2i(p_rect2i)); + static_assert(sizeof(Rect2i) <= sizeof(_data._mem)); } Variant::Variant(const Plane &p_plane) : type(PLANE) { memnew_placement(_data._mem, Plane(p_plane)); + static_assert(sizeof(Plane) <= sizeof(_data._mem)); } Variant::Variant(const ::AABB &p_aabb) : @@ -2457,6 +2470,7 @@ Variant::Variant(const Basis &p_matrix) : Variant::Variant(const Quaternion &p_quaternion) : type(QUATERNION) { memnew_placement(_data._mem, Quaternion(p_quaternion)); + static_assert(sizeof(Quaternion) <= sizeof(_data._mem)); } Variant::Variant(const Transform3D &p_transform) : @@ -2480,16 +2494,19 @@ Variant::Variant(const Transform2D &p_transform) : Variant::Variant(const Color &p_color) : type(COLOR) { memnew_placement(_data._mem, Color(p_color)); + static_assert(sizeof(Color) <= sizeof(_data._mem)); } Variant::Variant(const NodePath &p_node_path) : type(NODE_PATH) { memnew_placement(_data._mem, NodePath(p_node_path)); + static_assert(sizeof(NodePath) <= sizeof(_data._mem)); } Variant::Variant(const ::RID &p_rid) : type(RID) { memnew_placement(_data._mem, ::RID(p_rid)); + static_assert(sizeof(::RID) <= sizeof(_data._mem)); } Variant::Variant(const Object *p_object) : @@ -2501,21 +2518,25 @@ Variant::Variant(const Object *p_object) : Variant::Variant(const Callable &p_callable) : type(CALLABLE) { memnew_placement(_data._mem, Callable(p_callable)); + static_assert(sizeof(Callable) <= sizeof(_data._mem)); } Variant::Variant(const Signal &p_callable) : type(SIGNAL) { memnew_placement(_data._mem, Signal(p_callable)); + static_assert(sizeof(Signal) <= sizeof(_data._mem)); } Variant::Variant(const Dictionary &p_dictionary) : type(DICTIONARY) { memnew_placement(_data._mem, Dictionary(p_dictionary)); + static_assert(sizeof(Dictionary) <= sizeof(_data._mem)); } Variant::Variant(const Array &p_array) : type(ARRAY) { memnew_placement(_data._mem, Array(p_array)); + static_assert(sizeof(Array) <= sizeof(_data._mem)); } Variant::Variant(const PackedByteArray &p_byte_array) : diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 91cfe6dfdcb..a445e2d0835 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -481,7 +481,7 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { } \ }; -#define INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(m_base_type, m_elem_type, m_assign_type, m_max) \ +#define INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(m_base_type, m_elem_type, m_assign_type, m_max) \ struct VariantIndexedSetGet_##m_base_type { \ static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \ if (index < 0 || index >= m_max) { \ @@ -543,7 +543,7 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { } \ }; -#define INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(m_base_type, m_elem_type, m_accessor, m_max) \ +#define INDEXED_SETGET_STRUCT_BUILTIN_ACCESSOR(m_base_type, m_elem_type, m_accessor, m_max) \ struct VariantIndexedSetGet_##m_base_type { \ static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \ if (index < 0 || index >= m_max) { \ @@ -599,7 +599,7 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { } \ }; -#define INDEXED_SETGET_STRUCT_BULTIN_FUNC(m_base_type, m_elem_type, m_set, m_get, m_max) \ +#define INDEXED_SETGET_STRUCT_BUILTIN_FUNC(m_base_type, m_elem_type, m_set, m_get, m_max) \ struct VariantIndexedSetGet_##m_base_type { \ static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \ if (index < 0 || index >= m_max) { \ @@ -846,18 +846,18 @@ struct VariantIndexedSetGet_String { static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); } }; -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3i, int64_t, int32_t, 3) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector4, double, real_t, 4) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector4i, int64_t, int32_t, 4) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Quaternion, double, real_t, 4) -INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Color, double, float, 4) - -INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(Transform2D, Vector2, .columns, 3) -INDEXED_SETGET_STRUCT_BULTIN_FUNC(Basis, Vector3, set_column, get_column, 3) -INDEXED_SETGET_STRUCT_BULTIN_ACCESSOR(Projection, Vector4, .columns, 4) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector2, double, real_t, 2) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector2i, int64_t, int32_t, 2) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector3, double, real_t, 3) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector3i, int64_t, int32_t, 3) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector4, double, real_t, 4) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Vector4i, int64_t, int32_t, 4) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Quaternion, double, real_t, 4) +INDEXED_SETGET_STRUCT_BUILTIN_NUMERIC(Color, double, float, 4) + +INDEXED_SETGET_STRUCT_BUILTIN_ACCESSOR(Transform2D, Vector2, .columns, 3) +INDEXED_SETGET_STRUCT_BUILTIN_FUNC(Basis, Vector3, set_column, get_column, 3) +INDEXED_SETGET_STRUCT_BUILTIN_ACCESSOR(Projection, Vector4, .columns, 4) INDEXED_SETGET_STRUCT_TYPED_NUMERIC(PackedByteArray, int64_t, uint8_t) INDEXED_SETGET_STRUCT_TYPED_NUMERIC(PackedInt32Array, int64_t, int32_t) diff --git a/doc/Doxyfile b/doc/Doxyfile index 6d965478fd7..ae5edcb5a18 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1475,7 +1475,7 @@ EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # diff --git a/doc/classes/AudioStreamGenerator.xml b/doc/classes/AudioStreamGenerator.xml index f618e69631e..6fce5a8af15 100644 --- a/doc/classes/AudioStreamGenerator.xml +++ b/doc/classes/AudioStreamGenerator.xml @@ -11,6 +11,7 @@ var playback # Will hold the AudioStreamGeneratorPlayback. @onready var sample_hz = $AudioStreamPlayer.stream.mix_rate var pulse_hz = 440.0 # The frequency of the sound wave. + var phase = 0.0 func _ready(): $AudioStreamPlayer.play() @@ -18,7 +19,6 @@ fill_buffer() func fill_buffer(): - var phase = 0.0 var increment = pulse_hz / sample_hz var frames_available = playback.get_frames_available() @@ -32,6 +32,7 @@ private AudioStreamGeneratorPlayback _playback; // Will hold the AudioStreamGeneratorPlayback. private float _sampleHz; private float _pulseHz = 440.0f; // The frequency of the sound wave. + private double phase = 0.0; public override void _Ready() { @@ -46,7 +47,6 @@ public void FillBuffer() { - double phase = 0.0; float increment = _pulseHz / _sampleHz; int framesAvailable = _playback.GetFramesAvailable(); diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml index 566109c0437..7440b558c1c 100644 --- a/doc/classes/AudioStreamWAV.xml +++ b/doc/classes/AudioStreamWAV.xml @@ -13,11 +13,11 @@ - + - Creates a new [AudioStreamWAV] instance from the given buffer. The keys and values of [param options] match the properties of [ResourceImporterWAV]. - The usage of [param options] is identical to [method AudioStreamWAV.load_from_file]. + Creates a new [AudioStreamWAV] instance from the given buffer. The buffer must contain WAV data. + The keys and values of [param options] match the properties of [ResourceImporterWAV]. The usage of [param options] is identical to [method AudioStreamWAV.load_from_file]. @@ -25,7 +25,8 @@ - Creates a new [AudioStreamWAV] instance from the given file path. The keys and values of [param options] match the properties of [ResourceImporterWAV]. + Creates a new [AudioStreamWAV] instance from the given file path. The file must be in WAV format. + The keys and values of [param options] match the properties of [ResourceImporterWAV]. [b]Example:[/b] Load the first file dropped as a WAV and play it: [codeblock] @onready var audio_player = $AudioStreamPlayer diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 150c9d6be6b..333ec75de86 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -145,7 +145,7 @@ Returns this basis's rotation as a [Quaternion]. - [b]Note:[/b] Quatenions are much more suitable for 3D math but are less intuitive. For user interfaces, consider using the [method get_euler] method, which returns Euler angles. + [b]Note:[/b] Quaternions are much more suitable for 3D math but are less intuitive. For user interfaces, consider using the [method get_euler] method, which returns Euler angles. diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index 49115e1cbc8..12b5e75e979 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -189,6 +189,9 @@ The image displayed over the color box/circle (depending on the [member picker_shape]), marking the currently selected color. + + The fill image displayed behind the picker cursor. + Background panel for the color preview box (visible when the color is translucent). diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index cc6b27f0a29..2971a600570 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1215,6 +1215,13 @@ [b]Note:[/b] This method is implemented on Android, iOS, macOS, Windows, and Linux (X11/Wayland). + + + + Opens system emoji and symbol picker. + [b]Note:[/b] This method is implemented on macOS and Windows. + + @@ -1941,6 +1948,9 @@ Native file selection dialog supports MIME types as filters. + + Display server supports system emoji and symbol picker. [b]Windows, macOS[/b] + Makes the mouse cursor visible if it is hidden. diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml index d513a4fce06..fdf65a75f26 100644 --- a/doc/classes/EditorDebuggerPlugin.xml +++ b/doc/classes/EditorDebuggerPlugin.xml @@ -103,7 +103,7 @@ - Override this method to enable receiving messages from the debugger. If [param capture] is "my_message" then messages starting with "my_message:" will be passes to the [method _capture] method. + Override this method to enable receiving messages from the debugger. If [param capture] is "my_message" then messages starting with "my_message:" will be passed to the [method _capture] method. diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml index c60b6cfa6fc..aa9e9d0a821 100644 --- a/doc/classes/Environment.xml +++ b/doc/classes/Environment.xml @@ -320,7 +320,8 @@ The tonemapping mode to use. Tonemapping is the process that "converts" HDR values to be suitable for rendering on an LDR display. (Redot doesn't support rendering on HDR displays yet.) - The white reference value for tonemapping (also called "whitepoint"). Higher values can make highlights look less blown out, and will also slightly darken the whole scene as a result. Only effective if the [member tonemap_mode] isn't set to [constant TONE_MAPPER_LINEAR]. See also [member tonemap_exposure]. + The white reference value for tonemapping (also called "whitepoint"). Higher values can make highlights look less blown out, and will also slightly darken the whole scene as a result. See also [member tonemap_exposure]. + [b]Note:[/b] [member tonemap_white] is ignored when using [constant TONE_MAPPER_LINEAR] or [constant TONE_MAPPER_AGX]. The [Color] of the volumetric fog when interacting with lights. Mist and fog have an albedo close to [code]Color(1, 1, 1, 1)[/code] while smoke has a darker albedo. @@ -425,6 +426,9 @@ Use the Academy Color Encoding System tonemapper. ACES is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. ACES typically has a more contrasted output compared to [constant TONE_MAPPER_REINHARDT] and [constant TONE_MAPPER_FILMIC]. [b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Godot 3.x. + + Use the AgX tonemapper. AgX is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. AgX is less likely to darken parts of the scene compared to [constant TONE_MAPPER_ACES] and can match the overall scene brightness of [constant TONE_MAPPER_FILMIC] more closely. + Additive glow blending mode. Mostly used for particles, glows (bloom), lens flare, bright sources. diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml index b91b380fa41..aa9f05d9423 100644 --- a/doc/classes/ItemList.xml +++ b/doc/classes/ItemList.xml @@ -63,6 +63,13 @@ Forces an update to the list size based on its items. This happens automatically whenever size of the items, or other relevant settings like [member auto_height], change. The method can be used to trigger the update ahead of next drawing pass. + + + + Returns the horizontal scrollbar. + [b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property. + + @@ -420,6 +427,10 @@ Sets the clipping behavior when the text exceeds an item's bounding rectangle. See [enum TextServer.OverrunBehavior] for a description of all modes. + + If [code]true[/code], the control will automatically move items into a new row to fit its content. See also [HFlowContainer] for this behavior. + If [code]false[/code], the control will add a horizontal scrollbar to make all items visible. + diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 6948d7d454d..dd0d6ed4117 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -276,6 +276,9 @@ If [code]false[/code], existing text cannot be modified and new text cannot be added. + + If [code]false[/code], "Emoji and Symbols" menu is enabled. + If [code]true[/code], the [LineEdit] width will increase to stay longer than the [member text]. It will [b]not[/b] compress if the [member text] is shortened. @@ -478,7 +481,10 @@ Inserts soft hyphen (SHY) character. - + + Opens system emoji and symbol picker. + + Represents the size of the [enum MenuItems] enum. diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index a883a8bf5ad..28febfb995b 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -809,7 +809,8 @@ Requests the OS to open a resource identified by [param uri] with the most appropriate program. For example: - - [code]OS.shell_open("C:\\Users\name\Downloads")[/code] on Windows opens the file explorer at the user's Downloads folder. + - [code]OS.shell_open("C:\\Users\\name\\Downloads")[/code] on Windows opens the file explorer at the user's Downloads folder. + - [code]OS.shell_open("C:/Users/name/Downloads")[/code] also works on Windows and opens the file explorer at the user's Downloads folder. - [code]OS.shell_open("https://www.redotengine.org")[/code] opens the default web browser on the official Redot website. - [code]OS.shell_open("mailto:example@example.com")[/code] opens the default email client with the "To" field set to [code]example@example.com[/code]. See [url=https://datatracker.ietf.org/doc/html/rfc2368]RFC 2368 - The [code]mailto[/code] URL scheme[/url] for a list of fields that can be added. Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] project path into a system path for use with this method. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ab4fb912d40..4817fe0a7a4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -551,7 +551,7 @@ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the [code]@static_unload[/code] annotation is used in a script without any static variables. - + When enabled, using a property, enum, or function that was renamed since Godot 3 will produce a hint if an error occurs. diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml index 18a544d1145..8ede8bb4c3c 100644 --- a/doc/classes/RayCast3D.xml +++ b/doc/classes/RayCast3D.xml @@ -1,10 +1,10 @@ - A ray in 3D space, used to find the first [CollisionObject3D] it intersects. + A ray in 3D space, used to find the first object it intersects. - A raycast represents a ray from its origin to its [member target_position] that finds the closest [CollisionObject3D] along its path, if it intersects any. + A raycast represents a ray from its origin to its [member target_position] that finds the closest object along its path, if it intersects any. [RayCast3D] can ignore some objects by adding them to an exception list, by making its detection reporting ignore [Area3D]s ([member collide_with_areas]) or [PhysicsBody3D]s ([member collide_with_bodies]), or by configuring physics layers. [RayCast3D] calculates intersection every physics frame, and it holds the result until the next physics frame. For an immediate raycast, or if you want to configure a [RayCast3D] multiple times within the same physics frame, use [method force_raycast_update]. To sweep over a region of 3D space, you can approximate the region with multiple [RayCast3D]s or use [ShapeCast3D]. @@ -45,6 +45,7 @@ Returns the first object that the ray intersects, or [code]null[/code] if no object is intersecting the ray (i.e. [method is_colliding] returns [code]false[/code]). + [b]Note:[/b] This object is not guaranteed to be a [CollisionObject3D]. For example, if the ray intersects a [CSGShape3D] or a [GridMap], the method will return a [CSGShape3D] or [GridMap] instance. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 2265b8f1748..ad3746b0507 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1280,7 +1280,7 @@ - + Sets the values to be used for ambient light rendering. See [Environment] for more details. @@ -5365,6 +5365,9 @@ Use the Academy Color Encoding System tonemapper. ACES is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. ACES typically has a more contrasted output compared to [constant ENV_TONE_MAPPER_REINHARD] and [constant ENV_TONE_MAPPER_FILMIC]. [b]Note:[/b] This tonemapping operator is called "ACES Fitted" in Redot 3.x. + + Use the AgX tonemapper. AgX is slightly more expensive than other options, but it handles bright lighting in a more realistic fashion by desaturating it as it becomes brighter. AgX is less likely to darken parts of the scene compared to [constant ENV_TONE_MAPPER_ACES], and can match [constant ENV_TONE_MAPPER_FILMIC] more closely. + Lowest quality of roughness filter for screen-space reflections. Rough materials will not have blurrier screen-space reflections compared to smooth (non-rough) materials. This is the fastest option. diff --git a/doc/classes/SkeletonProfileHumanoid.xml b/doc/classes/SkeletonProfileHumanoid.xml index 9c9cc2d8c71..bbf5ba0a18e 100644 --- a/doc/classes/SkeletonProfileHumanoid.xml +++ b/doc/classes/SkeletonProfileHumanoid.xml @@ -31,6 +31,7 @@ │ └─ LeftHand │ ├─ LeftThumbMetacarpal │ │ └─ LeftThumbProximal + │ │ └─ LeftThumbDistal │ ├─ LeftIndexProximal │ │ └─ LeftIndexIntermediate │ │ └─ LeftIndexDistal @@ -49,6 +50,7 @@ └─ RightHand ├─ RightThumbMetacarpal │ └─ RightThumbProximal + │ └─ RightThumbDistal ├─ RightIndexProximal │ └─ RightIndexIntermediate │ └─ RightIndexDistal diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index ff9259b610c..b2691a0cbd0 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1299,6 +1299,9 @@ If [code]false[/code], existing text cannot be modified and new text cannot be added. + + If [code]false[/code], "Emoji and Symbols" menu is enabled. + If [code]true[/code], copying or cutting without a selection is performed on all lines with a caret. Otherwise, copy and cut require a selection. @@ -1519,7 +1522,10 @@ Inserts soft hyphen (SHY) character. - + + Opens system emoji and symbol picker. + + Represents the size of the [enum MenuItems] enum. diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 4683f063f13..f347c9a08ff 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -16,6 +16,7 @@ https://godotengine.org/asset-library/asset/2723 https://godotengine.org/asset-library/asset/2729 https://godotengine.org/asset-library/asset/2719 + https://godotengine.org/asset-library/asset/2713 diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml index 43708ca142e..b17d3899ef3 100644 --- a/doc/classes/TileMapLayer.xml +++ b/doc/classes/TileMapLayer.xml @@ -9,6 +9,14 @@ To force an update earlier on, call [method update_internals]. + $DOCS_URL/tutorials/2d/using_tilemaps.html + https://godotengine.org/asset-library/asset/2727 + https://godotengine.org/asset-library/asset/2718 + https://godotengine.org/asset-library/asset/2717 + https://godotengine.org/asset-library/asset/2723 + https://godotengine.org/asset-library/asset/2729 + https://godotengine.org/asset-library/asset/2719 + https://godotengine.org/asset-library/asset/2713 diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 193dbe6a107..90eba0bdea8 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -557,6 +557,19 @@ Makes the [Window] appear. This enables interactions with the [Window] and doesn't change any of its property other than visibility (unlike e.g. [method popup]). + + + + Starts an interactive drag operation on the window, using the current mouse position. Call this method when handling a mouse button being pressed to simulate a pressed event on the window's title bar. Using this method allows the window to participate in space switching, tiling, and other system features. + + + + + + + Starts an interactive resize operation on the window, using the current mouse position. Call this method when handling a mouse button being pressed to simulate a pressed event on the window's edge. + + diff --git a/doc/classes/XRVRS.xml b/doc/classes/XRVRS.xml index b19b461fab6..ec92d4a667a 100644 --- a/doc/classes/XRVRS.xml +++ b/doc/classes/XRVRS.xml @@ -23,6 +23,9 @@ The minimum radius around the focal point where full quality is guaranteed if VRS is used as a percentage of screen size. + + The render region that the VRS texture will be scaled to when generated. + The strength used to calculate the VRS density map. The greater this value, the more noticeable VRS is. diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py index dc52a38bddd..db2b19447b5 100755 --- a/doc/tools/doc_status.py +++ b/doc/tools/doc_status.py @@ -10,14 +10,14 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) -from methods import COLOR_SUPPORTED, Ansi, toggle_color +from misc.utility.color import STDOUT_COLOR, Ansi, toggle_color ################################################################################ # Config # ################################################################################ flags = { - "c": COLOR_SUPPORTED, + "c": STDOUT_COLOR, "b": False, "g": False, "s": False, @@ -330,7 +330,8 @@ def generate_for_class(c: ET.Element): table_column_names.append("Docs URL") table_columns.append("url") -toggle_color(flags["c"]) +if flags["c"]: + toggle_color(True) ################################################################################ # Help # diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index dca96d77055..8933d6b96e7 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -13,7 +13,7 @@ sys.path.insert(0, root_directory := os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) import version -from methods import Ansi, toggle_color +from misc.utility.color import Ansi, toggle_color # $DOCS_URL/path/to/page.html(#fragment-tag) GODOT_DOCS_PATTERN = re.compile(r"^\$DOCS_URL/(.*)\.html(#.*)?$") @@ -697,7 +697,8 @@ def main() -> None: ) args = parser.parse_args() - toggle_color(args.color) + if args.color: + toggle_color(True) # Retrieve heading translations for the given language. if not args.dry_run and args.lang != "en": diff --git a/drivers/SCsub b/drivers/SCsub index 03ad70649b2..153298c99e9 100644 --- a/drivers/SCsub +++ b/drivers/SCsub @@ -26,7 +26,9 @@ if env["xaudio2"]: print_error("Target platform '{}' does not support the XAudio2 audio driver".format(env["platform"])) Exit(255) SConscript("xaudio2/SCsub") - +# Shared Apple platform drivers +if env["platform"] in ["macos", "ios"]: + SConscript("apple/SCsub") # Midi drivers SConscript("alsamidi/SCsub") SConscript("coremidi/SCsub") diff --git a/drivers/apple/SCsub b/drivers/apple/SCsub new file mode 100644 index 00000000000..83ac27f4b61 --- /dev/null +++ b/drivers/apple/SCsub @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +Import("env") + +# Driver source files +env.add_source_files(env.drivers_sources, "*.mm") diff --git a/platform/macos/joypad_macos.h b/drivers/apple/joypad_apple.h similarity index 61% rename from platform/macos/joypad_macos.h rename to drivers/apple/joypad_apple.h index 4a2542c1be5..53c9cc9b5bd 100644 --- a/platform/macos/joypad_macos.h +++ b/drivers/apple/joypad_apple.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* joypad_macos.h */ +/* joypad_apple.h */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ @@ -33,59 +33,41 @@ #include "core/input/input.h" #define Key _QKey -#import #import #undef Key -@interface JoypadMacOSObserver : NSObject +@class GCController; +class RumbleContext; -- (void)startObserving; -- (void)startProcessing; -- (void)finishObserving; +struct GameController { + int joy_id; + GCController *controller; + RumbleContext *rumble_context API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) = nil; + NSInteger ff_effect_timestamp = 0; + bool force_feedback = false; -@end - -API_AVAILABLE(macosx(11)) -@interface RumbleMotor : NSObject -@property(strong, nonatomic) CHHapticEngine *engine; -@property(strong, nonatomic) id player; -@end - -API_AVAILABLE(macosx(11)) -@interface RumbleContext : NSObject -// High frequency motor, it's usually the right engine. -@property(strong, nonatomic) RumbleMotor *weak_motor; -// Low frequency motor, it's usually the left engine. -@property(strong, nonatomic) RumbleMotor *strong_motor; -@end - -// Controller support for macOS begins with macOS 10.9+, -// however haptics (vibrations) are only supported in macOS 11+. -@interface Joypad : NSObject - -@property(assign, nonatomic) BOOL force_feedback; -@property(assign, nonatomic) NSInteger ff_effect_timestamp; -@property(strong, nonatomic) GCController *controller; -@property(strong, nonatomic) RumbleContext *rumble_context API_AVAILABLE(macosx(11)); + GameController(int p_joy_id, GCController *p_controller); + ~GameController(); +}; -- (instancetype)init; -- (instancetype)init:(GCController *)controller; +class JoypadApple { +private: + id connect_observer = nil; + id disconnect_observer = nil; + HashMap joypads; + HashMap controller_to_joy_id; -@end + GCControllerPlayerIndex get_free_player_index(); -class JoypadMacOS { -private: - JoypadMacOSObserver *observer; + void add_joypad(GCController *p_controller); + void remove_joypad(GCController *p_controller); public: - JoypadMacOS(); - ~JoypadMacOS(); + JoypadApple(); + ~JoypadApple(); - API_AVAILABLE(macosx(11)) - void joypad_vibration_start(Joypad *p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp); - API_AVAILABLE(macosx(11)) - void joypad_vibration_stop(Joypad *p_joypad, uint64_t p_timestamp); + void joypad_vibration_start(GameController &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)); + void joypad_vibration_stop(GameController &p_joypad, uint64_t p_timestamp) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)); - void start_processing(); void process_joypads(); }; diff --git a/drivers/apple/joypad_apple.mm b/drivers/apple/joypad_apple.mm new file mode 100644 index 00000000000..47edb1ed6ff --- /dev/null +++ b/drivers/apple/joypad_apple.mm @@ -0,0 +1,429 @@ +/**************************************************************************/ +/* joypad_apple.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#import "joypad_apple.h" + +#include +#import + +#include "core/config/project_settings.h" +#include "main/main.h" + +class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleMotor { + CHHapticEngine *engine; + id player; + bool is_started; + + RumbleMotor(GCController *p_controller, GCHapticsLocality p_locality) { + engine = [p_controller.haptics createEngineWithLocality:p_locality]; + engine.autoShutdownEnabled = YES; + } + +public: + static RumbleMotor *create(GCController *p_controller, GCHapticsLocality p_locality) { + if ([p_controller.haptics.supportedLocalities containsObject:p_locality]) { + return memnew(RumbleMotor(p_controller, p_locality)); + } + return nullptr; + } + + _ALWAYS_INLINE_ bool has_active_player() { + return player != nil; + } + + void execute_pattern(CHHapticPattern *p_pattern) { + NSError *error; + if (!is_started) { + ERR_FAIL_COND_MSG(![engine startAndReturnError:&error], "Couldn't start controller haptic engine: " + String::utf8(error.localizedDescription.UTF8String)); + is_started = YES; + } + + player = [engine createPlayerWithPattern:p_pattern error:&error]; + ERR_FAIL_COND_MSG(error, "Couldn't create controller haptic pattern player: " + String::utf8(error.localizedDescription.UTF8String)); + ERR_FAIL_COND_MSG(![player startAtTime:CHHapticTimeImmediate error:&error], "Couldn't execute controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String)); + } + + void stop() { + id old_player = player; + player = nil; + + NSError *error; + ERR_FAIL_COND_MSG(![old_player stopAtTime:CHHapticTimeImmediate error:&error], "Couldn't stop controller haptic pattern: " + String::utf8(error.localizedDescription.UTF8String)); + } +}; + +class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleContext { + RumbleMotor *weak_motor; + RumbleMotor *strong_motor; + +public: + RumbleContext(GCController *p_controller) { + weak_motor = RumbleMotor::create(p_controller, GCHapticsLocalityRightHandle); + strong_motor = RumbleMotor::create(p_controller, GCHapticsLocalityLeftHandle); + } + + ~RumbleContext() { + if (weak_motor) { + memdelete(weak_motor); + } + if (strong_motor) { + memdelete(strong_motor); + } + } + + _ALWAYS_INLINE_ bool has_motors() { + return weak_motor != nullptr && strong_motor != nullptr; + } + + _ALWAYS_INLINE_ bool has_active_players() { + if (!has_motors()) { + return false; + } + return (weak_motor && weak_motor->has_active_player()) || (strong_motor && strong_motor->has_active_player()); + } + + void stop() { + if (weak_motor) { + weak_motor->stop(); + } + if (strong_motor) { + strong_motor->stop(); + } + } + + void play_weak_pattern(CHHapticPattern *p_pattern) { + if (weak_motor) { + weak_motor->execute_pattern(p_pattern); + } + } + + void play_strong_pattern(CHHapticPattern *p_pattern) { + if (strong_motor) { + strong_motor->execute_pattern(p_pattern); + } + } +}; + +GameController::GameController(int p_joy_id, GCController *p_controller) : + joy_id(p_joy_id), controller(p_controller) { + force_feedback = NO; + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + if (controller.haptics != nil) { + // Create a rumble context for the controller. + rumble_context = memnew(RumbleContext(p_controller)); + + // If the rumble motors aren't available, disable force feedback. + force_feedback = rumble_context->has_motors(); + } + } + + int l_joy_id = joy_id; + + auto BUTTON = [l_joy_id](JoyButton p_button) { + return ^(GCControllerButtonInput *button, float value, BOOL pressed) { + Input::get_singleton()->joy_button(l_joy_id, p_button, pressed); + }; + }; + + if (controller.extendedGamepad != nil) { + GCExtendedGamepad *gamepad = controller.extendedGamepad; + + gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A); + gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::B); + gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X); + gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y); + gamepad.leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER); + gamepad.rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER); + gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP); + gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN); + gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT); + gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT); + + gamepad.leftThumbstick.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) { + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::LEFT_X, xValue); + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::LEFT_Y, -yValue); + }; + + gamepad.rightThumbstick.valueChangedHandler = ^(GCControllerDirectionPad *dpad, float xValue, float yValue) { + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::RIGHT_X, xValue); + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::RIGHT_Y, -yValue); + }; + gamepad.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::TRIGGER_LEFT, value); + }; + gamepad.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { + Input::get_singleton()->joy_axis(l_joy_id, JoyAxis::TRIGGER_RIGHT, value); + }; + + if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { + gamepad.leftThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::LEFT_STICK); + gamepad.rightThumbstickButton.pressedChangedHandler = BUTTON(JoyButton::RIGHT_STICK); + } + + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + gamepad.buttonOptions.pressedChangedHandler = BUTTON(JoyButton::BACK); + gamepad.buttonMenu.pressedChangedHandler = BUTTON(JoyButton::START); + } + + if (@available(macOS 11, iOS 14.0, tvOS 14.0, *)) { + gamepad.buttonHome.pressedChangedHandler = BUTTON(JoyButton::GUIDE); + if ([gamepad isKindOfClass:[GCXboxGamepad class]]) { + GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad; + xboxGamepad.paddleButton1.pressedChangedHandler = BUTTON(JoyButton::PADDLE1); + xboxGamepad.paddleButton2.pressedChangedHandler = BUTTON(JoyButton::PADDLE2); + xboxGamepad.paddleButton3.pressedChangedHandler = BUTTON(JoyButton::PADDLE3); + xboxGamepad.paddleButton4.pressedChangedHandler = BUTTON(JoyButton::PADDLE4); + } + } + + if (@available(macOS 12, iOS 15.0, tvOS 15.0, *)) { + if ([gamepad isKindOfClass:[GCXboxGamepad class]]) { + GCXboxGamepad *xboxGamepad = (GCXboxGamepad *)gamepad; + xboxGamepad.buttonShare.pressedChangedHandler = BUTTON(JoyButton::MISC1); + } + } + } else if (controller.microGamepad != nil) { + GCMicroGamepad *gamepad = controller.microGamepad; + + gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A); + gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X); + gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP); + gamepad.dpad.down.pressedChangedHandler = BUTTON(JoyButton::DPAD_DOWN); + gamepad.dpad.left.pressedChangedHandler = BUTTON(JoyButton::DPAD_LEFT); + gamepad.dpad.right.pressedChangedHandler = BUTTON(JoyButton::DPAD_RIGHT); + } + + // TODO: Need to add support for controller.motion which gives us access to + // the orientation of the device (if supported). +} + +GameController::~GameController() { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + if (rumble_context) { + memdelete(rumble_context); + } + } +} + +JoypadApple::JoypadApple() { + connect_observer = [NSNotificationCenter.defaultCenter + addObserverForName:GCControllerDidConnectNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + GCController *controller = notification.object; + if (!controller) { + return; + } + add_joypad(controller); + }]; + + disconnect_observer = [NSNotificationCenter.defaultCenter + addObserverForName:GCControllerDidDisconnectNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *notification) { + GCController *controller = notification.object; + if (!controller) { + return; + } + remove_joypad(controller); + }]; + + if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) { + GCController.shouldMonitorBackgroundEvents = YES; + } +} + +JoypadApple::~JoypadApple() { + for (KeyValue &E : joypads) { + memdelete(E.value); + E.value = nullptr; + } + + [NSNotificationCenter.defaultCenter removeObserver:connect_observer]; + [NSNotificationCenter.defaultCenter removeObserver:disconnect_observer]; +} + +// Finds the rightmost set bit in a number, n. +// variation of https://www.geeksforgeeks.org/position-of-rightmost-set-bit/ +int rightmost_one(int n) { + return __builtin_ctz(n & -n) + 1; +} + +GCControllerPlayerIndex JoypadApple::get_free_player_index() { + // player_set will be a bitfield where each bit represents a player index. + __block uint32_t player_set = 0; + for (const KeyValue &E : controller_to_joy_id) { + player_set |= 1U << E.key.playerIndex; + } + + // invert, as we want to find the first unset player index. + int n = rightmost_one((int)(~player_set)); + if (n >= 5) { + return GCControllerPlayerIndexUnset; + } + + return (GCControllerPlayerIndex)(n - 1); +} + +void JoypadApple::add_joypad(GCController *p_controller) { + if (controller_to_joy_id.has(p_controller)) { + return; + } + + // Get a new id for our controller. + int joy_id = Input::get_singleton()->get_unused_joy_id(); + + if (joy_id == -1) { + print_verbose("Couldn't retrieve new joy ID."); + return; + } + + // Assign our player index. + if (p_controller.playerIndex == GCControllerPlayerIndexUnset) { + p_controller.playerIndex = get_free_player_index(); + } + + // Tell Godot about our new controller. + Input::get_singleton()->joy_connection_changed(joy_id, true, String::utf8(p_controller.vendorName.UTF8String)); + + // Assign our player index. + joypads.insert(joy_id, memnew(GameController(joy_id, p_controller))); + controller_to_joy_id.insert(p_controller, joy_id); +} + +void JoypadApple::remove_joypad(GCController *p_controller) { + if (!controller_to_joy_id.has(p_controller)) { + return; + } + + int joy_id = controller_to_joy_id[p_controller]; + controller_to_joy_id.erase(p_controller); + + // Tell Godot this joystick is no longer there. + Input::get_singleton()->joy_connection_changed(joy_id, false, ""); + + // And remove it from our dictionary. + GameController **old = joypads.getptr(joy_id); + memdelete(*old); + *old = nullptr; + joypads.erase(joy_id); +} + +API_AVAILABLE(macos(10.15), ios(13.0), tvos(14.0)) +CHHapticPattern *get_vibration_pattern(float p_magnitude, float p_duration) { + // Creates a vibration pattern with an intensity and duration. + NSDictionary *hapticDict = @{ + CHHapticPatternKeyPattern : @[ + @{ + CHHapticPatternKeyEvent : @{ + CHHapticPatternKeyEventType : CHHapticEventTypeHapticContinuous, + CHHapticPatternKeyTime : @(CHHapticTimeImmediate), + CHHapticPatternKeyEventDuration : [NSNumber numberWithFloat:p_duration], + + CHHapticPatternKeyEventParameters : @[ + @{ + CHHapticPatternKeyParameterID : CHHapticEventParameterIDHapticIntensity, + CHHapticPatternKeyParameterValue : [NSNumber numberWithFloat:p_magnitude] + }, + ], + }, + }, + ], + }; + NSError *error; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error]; + return pattern; +} + +void JoypadApple::joypad_vibration_start(GameController &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) { + if (!p_joypad.force_feedback || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) { + return; + } + + // If there is active vibration players, stop them. + if (p_joypad.rumble_context->has_active_players()) { + joypad_vibration_stop(p_joypad, p_timestamp); + } + + // Gets the default vibration pattern and creates a player for each motor. + CHHapticPattern *weak_pattern = get_vibration_pattern(p_weak_magnitude, p_duration); + CHHapticPattern *strong_pattern = get_vibration_pattern(p_strong_magnitude, p_duration); + + p_joypad.rumble_context->play_weak_pattern(weak_pattern); + p_joypad.rumble_context->play_strong_pattern(strong_pattern); + + p_joypad.ff_effect_timestamp = p_timestamp; +} + +void JoypadApple::joypad_vibration_stop(GameController &p_joypad, uint64_t p_timestamp) { + if (!p_joypad.force_feedback) { + return; + } + // If there is no active vibration players, exit. + if (!p_joypad.rumble_context->has_active_players()) { + return; + } + + p_joypad.rumble_context->stop(); + + p_joypad.ff_effect_timestamp = p_timestamp; +} + +void JoypadApple::process_joypads() { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { + for (KeyValue &E : joypads) { + int id = E.key; + GameController &joypad = *E.value; + + if (joypad.force_feedback) { + Input *input = Input::get_singleton(); + uint64_t timestamp = input->get_joy_vibration_timestamp(id); + + if (timestamp > (unsigned)joypad.ff_effect_timestamp) { + Vector2 strength = input->get_joy_vibration_strength(id); + float duration = input->get_joy_vibration_duration(id); + if (duration == 0) { + duration = GCHapticDurationInfinite; + } + + if (strength.x == 0 && strength.y == 0) { + joypad_vibration_stop(joypad, timestamp); + } else { + joypad_vibration_start(joypad, strength.x, strength.y, duration, timestamp); + } + } + } + } + } +} diff --git a/drivers/d3d12/rendering_device_driver_d3d12.cpp b/drivers/d3d12/rendering_device_driver_d3d12.cpp index a6cb1683eba..0f1693279c1 100644 --- a/drivers/d3d12/rendering_device_driver_d3d12.cpp +++ b/drivers/d3d12/rendering_device_driver_d3d12.cpp @@ -1662,7 +1662,7 @@ bool RenderingDeviceDriverD3D12::texture_can_make_shared_with_format(TextureID p /**** SAMPLER ****/ /*****************/ -static const D3D12_TEXTURE_ADDRESS_MODE RD_REPEAT_MODE_TO_D3D12_ADDRES_MODE[RDD::SAMPLER_REPEAT_MODE_MAX] = { +static const D3D12_TEXTURE_ADDRESS_MODE RD_REPEAT_MODE_TO_D3D12_ADDRESS_MODE[RDD::SAMPLER_REPEAT_MODE_MAX] = { D3D12_TEXTURE_ADDRESS_MODE_WRAP, D3D12_TEXTURE_ADDRESS_MODE_MIRROR, D3D12_TEXTURE_ADDRESS_MODE_CLAMP, @@ -1717,9 +1717,9 @@ RDD::SamplerID RenderingDeviceDriverD3D12::sampler_create(const SamplerState &p_ p_state.enable_compare ? D3D12_FILTER_REDUCTION_TYPE_COMPARISON : D3D12_FILTER_REDUCTION_TYPE_STANDARD); } - sampler_desc.AddressU = RD_REPEAT_MODE_TO_D3D12_ADDRES_MODE[p_state.repeat_u]; - sampler_desc.AddressV = RD_REPEAT_MODE_TO_D3D12_ADDRES_MODE[p_state.repeat_v]; - sampler_desc.AddressW = RD_REPEAT_MODE_TO_D3D12_ADDRES_MODE[p_state.repeat_w]; + sampler_desc.AddressU = RD_REPEAT_MODE_TO_D3D12_ADDRESS_MODE[p_state.repeat_u]; + sampler_desc.AddressV = RD_REPEAT_MODE_TO_D3D12_ADDRESS_MODE[p_state.repeat_v]; + sampler_desc.AddressW = RD_REPEAT_MODE_TO_D3D12_ADDRESS_MODE[p_state.repeat_w]; for (int i = 0; i < 4; i++) { sampler_desc.BorderColor[i] = RD_TO_D3D12_SAMPLER_BORDER_COLOR[p_state.border_color][i]; @@ -3813,7 +3813,7 @@ void RenderingDeviceDriverD3D12::shader_destroy_modules(ShaderID p_shader) { /**** UNIFORM SET ****/ /*********************/ -static void _add_descriptor_count_for_uniform(RenderingDevice::UniformType p_type, uint32_t p_binding_length, bool p_dobule_srv_uav_ambiguous, uint32_t &r_num_resources, uint32_t &r_num_samplers, bool &r_srv_uav_ambiguity) { +static void _add_descriptor_count_for_uniform(RenderingDevice::UniformType p_type, uint32_t p_binding_length, bool p_double_srv_uav_ambiguous, uint32_t &r_num_resources, uint32_t &r_num_samplers, bool &r_srv_uav_ambiguity) { r_srv_uav_ambiguity = false; // Some resource types can be SRV or UAV, depending on what NIR-DXIL decided for a specific shader variant. @@ -3834,11 +3834,11 @@ static void _add_descriptor_count_for_uniform(RenderingDevice::UniformType p_typ r_num_resources += 1; } break; case RenderingDevice::UNIFORM_TYPE_STORAGE_BUFFER: { - r_num_resources += p_dobule_srv_uav_ambiguous ? 2 : 1; + r_num_resources += p_double_srv_uav_ambiguous ? 2 : 1; r_srv_uav_ambiguity = true; } break; case RenderingDevice::UNIFORM_TYPE_IMAGE: { - r_num_resources += p_binding_length * (p_dobule_srv_uav_ambiguous ? 2 : 1); + r_num_resources += p_binding_length * (p_double_srv_uav_ambiguous ? 2 : 1); r_srv_uav_ambiguity = true; } break; default: { diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 0a4ebea3ad4..5f236c1ef77 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2256,7 +2256,12 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ RenderDataGLES3 render_data; { render_data.render_buffers = rb; - render_data.transparent_bg = rt ? rt->is_transparent : false; + + if (rt) { + render_data.transparent_bg = rt->is_transparent; + render_data.render_region = rt->render_region; + } + // Our first camera is used by default render_data.cam_transform = p_camera_data->main_transform; render_data.inv_cam_transform = render_data.cam_transform.affine_inverse(); @@ -2495,6 +2500,10 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ RENDER_TIMESTAMP("Depth Prepass"); //pre z pass + if (render_data.render_region != Rect2i()) { + glViewport(render_data.render_region.position.x, render_data.render_region.position.y, render_data.render_region.size.width, render_data.render_region.size.height); + } + scene_state.enable_gl_depth_test(true); scene_state.enable_gl_depth_draw(true); scene_state.enable_gl_blend(false); @@ -2574,6 +2583,10 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ RENDER_TIMESTAMP("Render Opaque Pass"); uint64_t spec_constant_base_flags = 0; + if (render_data.render_region != Rect2i()) { + glViewport(render_data.render_region.position.x, render_data.render_region.position.y, render_data.render_region.size.width, render_data.render_region.size.height); + } + { // Specialization Constants that apply for entire rendering pass. if (render_data.directional_light_count == 0) { diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index d576b023939..43c006f64cf 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -100,6 +100,7 @@ enum SkyUniformLocation { struct RenderDataGLES3 { Ref render_buffers; bool transparent_bg = false; + Rect2i render_region; Transform3D cam_transform; Transform3D inv_cam_transform; diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 139e42a2511..310db788c7a 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -208,18 +208,19 @@ void main() { // no crash or freeze on all Adreno 3xx with 'if / else if' and slightly faster! int vertex_id = gl_VertexID % 6; vec2 vertex_base; - if (vertex_id == 0) + if (vertex_id == 0) { vertex_base = vec2(0.0, 0.0); - else if (vertex_id == 1) + } else if (vertex_id == 1) { vertex_base = vec2(0.0, 1.0); - else if (vertex_id == 2) + } else if (vertex_id == 2) { vertex_base = vec2(1.0, 1.0); - else if (vertex_id == 3) + } else if (vertex_id == 3) { vertex_base = vec2(1.0, 0.0); - else if (vertex_id == 4) + } else if (vertex_id == 4) { vertex_base = vec2(0.0, 0.0); - else if (vertex_id == 5) + } else if (vertex_id == 5) { vertex_base = vec2(1.0, 1.0); + } vec2 uv = read_draw_data_src_rect.xy + abs(read_draw_data_src_rect.zw) * ((read_draw_data_flags & INSTANCE_FLAGS_TRANSPOSE_RECT) != uint(0) ? vertex_base.yx : vertex_base.xy); vec4 color = read_draw_data_modulation; diff --git a/drivers/gles3/shaders/tonemap_inc.glsl b/drivers/gles3/shaders/tonemap_inc.glsl index 6738bdf748c..ce98a62c23a 100644 --- a/drivers/gles3/shaders/tonemap_inc.glsl +++ b/drivers/gles3/shaders/tonemap_inc.glsl @@ -27,6 +27,14 @@ vec3 srgb_to_linear(vec3 color) { #ifdef APPLY_TONEMAPPING +// Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt +vec3 tonemap_reinhard(vec3 color, float p_white) { + float white_squared = p_white * p_white; + vec3 white_squared_color = white_squared * color; + // Equivalent to color * (1 + color / white_squared) / (1 + color) + return (white_squared_color + color * color) / (white_squared_color + white_squared); +} + vec3 tonemap_filmic(vec3 color, float p_white) { // exposure bias: input scale (color *= bias, white *= bias) to make the brightness consistent with other tonemappers // also useful to scale the input to the range that the tonemapper is designed for (some require very high input values) @@ -76,18 +84,82 @@ vec3 tonemap_aces(vec3 color, float p_white) { return color_tonemapped / p_white_tonemapped; } -// Based on Reinhard's extended formula, see equation 4 in https://doi.org/cjbgrt -vec3 tonemap_reinhard(vec3 color, float p_white) { - float white_squared = p_white * p_white; - vec3 white_squared_color = white_squared * color; - // Equivalent to color * (1 + color / white_squared) / (1 + color) - return (white_squared_color + color * color) / (white_squared_color + white_squared); +// Polynomial approximation of EaryChow's AgX sigmoid curve. +// x must be within the range [0.0, 1.0] +vec3 agx_default_contrast_approx(vec3 x) { + // Generated with Excel trendline + // Input data: Generated using python sigmoid with EaryChow's configuration and 57 steps + // Additional padding values were added to give correct intersections at 0.0 and 1.0 + // 6th order, intercept of 0.0 to remove an operation and ensure intersection at 0.0 + vec3 x2 = x * x; + vec3 x4 = x2 * x2; + return 0.021 * x + 4.0111 * x2 - 25.682 * x2 * x + 70.359 * x4 - 74.778 * x4 * x + 27.069 * x4 * x2; +} + +const mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3( + vec3(0.6274, 0.0691, 0.0164), + vec3(0.3293, 0.9195, 0.0880), + vec3(0.0433, 0.0113, 0.8956)); + +// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender. +// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses. +// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py +vec3 tonemap_agx(vec3 color) { + const mat3 agx_inset_matrix = mat3( + 0.856627153315983, 0.137318972929847, 0.11189821299995, + 0.0951212405381588, 0.761241990602591, 0.0767994186031903, + 0.0482516061458583, 0.101439036467562, 0.811302368396859); + + // Combined inverse AgX outset matrix and linear Rec 2020 to linear sRGB matrices. + const mat3 agx_outset_rec2020_to_srgb_matrix = mat3( + 1.9648846919172409596, -0.29937618452442253746, -0.16440106280678278299, + -0.85594737466675834968, 1.3263980951083531115, -0.23819967517076844919, + -0.10883731725048386702, -0.02702191058393112346, 1.4025007379775505276); + + // LOG2_MIN = -10.0 + // LOG2_MAX = +6.5 + // MIDDLE_GRAY = 0.18 + const float min_ev = -12.4739311883324; // log2(pow(2, LOG2_MIN) * MIDDLE_GRAY) + const float max_ev = 4.02606881166759; // log2(pow(2, LOG2_MAX) * MIDDLE_GRAY) + + // Do AGX in rec2020 to match Blender. + color = LINEAR_SRGB_TO_LINEAR_REC2020 * color; + + // Preventing negative values is required for the AgX inset matrix to behave correctly. + // This could also be done before the Rec. 2020 transform, allowing the transform to + // be combined with the AgX inset matrix, but doing this causes a loss of color information + // that could be correctly interpreted within the Rec. 2020 color space. + color = max(color, vec3(0.0)); + + color = agx_inset_matrix * color; + + // Log2 space encoding. + color = max(color, 1e-10); // Prevent log2(0.0). Possibly unnecessary. + // Must be clamped because agx_blender_default_contrast_approx may not work + // well with values outside of the range [0.0, 1.0] + color = clamp(log2(color), min_ev, max_ev); + color = (color - min_ev) / (max_ev - min_ev); + + // Apply sigmoid function approximation. + color = agx_default_contrast_approx(color); + + // Convert back to linear before applying outset matrix. + color = pow(color, vec3(2.4)); + + // Apply outset to make the result more chroma-laden and then go back to linear sRGB. + color = agx_outset_rec2020_to_srgb_matrix * color; + + // Simply hard clip instead of Blender's complex lusRGB.compensate_low_side. + color = max(color, vec3(0.0)); + + return color; } #define TONEMAPPER_LINEAR 0 #define TONEMAPPER_REINHARD 1 #define TONEMAPPER_FILMIC 2 #define TONEMAPPER_ACES 3 +#define TONEMAPPER_AGX 4 vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR // Ensure color values passed to tonemappers are positive. @@ -98,8 +170,10 @@ vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR return tonemap_reinhard(max(vec3(0.0f), color), p_white); } else if (tonemapper == TONEMAPPER_FILMIC) { return tonemap_filmic(max(vec3(0.0f), color), p_white); - } else { // TONEMAPPER_ACES + } else if (tonemapper == TONEMAPPER_ACES) { return tonemap_aces(max(vec3(0.0f), color), p_white); + } else { // TONEMAPPER_AGX + return tonemap_agx(color); } } diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index e9d748b71ed..54570db4818 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -759,7 +759,7 @@ class LightStorage : public RendererLightStorage { virtual void shadow_atlas_free(RID p_atlas) override; virtual void shadow_atlas_set_size(RID p_atlas, int p_size, bool p_16_bits = true) override; virtual void shadow_atlas_set_quadrant_subdivision(RID p_atlas, int p_quadrant, int p_subdivision) override; - virtual bool shadow_atlas_update_light(RID p_atlas, RID p_light_intance, float p_coverage, uint64_t p_light_version) override; + virtual bool shadow_atlas_update_light(RID p_atlas, RID p_light_instance, float p_coverage, uint64_t p_light_version) override; _FORCE_INLINE_ bool shadow_atlas_owns_light_instance(RID p_atlas, RID p_light_instance) { ShadowAtlas *atlas = shadow_atlas_owner.get_or_null(p_atlas); @@ -885,7 +885,7 @@ class LightStorage : public RendererLightStorage { virtual void shadow_atlas_update(RID p_atlas) override; virtual void directional_shadow_atlas_set_size(int p_size, bool p_16_bits = true) override; - virtual int get_directional_light_shadow_size(RID p_light_intance) override; + virtual int get_directional_light_shadow_size(RID p_light_instance) override; virtual void set_directional_shadow_count(int p_count) override; Rect2i get_directional_shadow_rect(); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index e96f0d057d1..8428fef7884 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1074,7 +1074,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { // It also allows for reading compressed textures, mipmaps, and more formats. Vector data; - int data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, texture->real_format, texture->mipmaps > 1); + int64_t data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, texture->real_format, texture->mipmaps > 1); data.resize(data_size * 2); // Add some memory at the end, just in case for buggy drivers. uint8_t *w = data.ptrw(); @@ -1086,7 +1086,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); for (int i = 0; i < texture->mipmaps; i++) { - int ofs = Image::get_image_mipmap_offset(texture->alloc_width, texture->alloc_height, texture->real_format, i); + int64_t ofs = Image::get_image_mipmap_offset(texture->alloc_width, texture->alloc_height, texture->real_format, i); if (texture->compressed) { glPixelStorei(GL_PACK_ALIGNMENT, 4); @@ -1106,7 +1106,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { ERR_FAIL_V_MSG(Ref(), vformat("Texture %s has no data.", path_str)); } - if (texture->format != texture->real_format) { + if (texture->format != texture->real_format && !Image::is_format_compressed(texture->real_format)) { image->convert(texture->format); } } @@ -1116,7 +1116,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { Vector data; // On web and mobile we always read an RGBA8 image with no mipmaps. - int data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, Image::FORMAT_RGBA8, false); + int64_t data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, Image::FORMAT_RGBA8, false); data.resize(data_size * 2); // Add some memory at the end, just in case for buggy drivers. uint8_t *w = data.ptrw(); @@ -1166,7 +1166,7 @@ Ref TextureStorage::texture_2d_get(RID p_texture) const { ERR_FAIL_V_MSG(Ref(), vformat("Texture %s has no data.", path_str)); } - if (texture->format != Image::FORMAT_RGBA8) { + if (texture->format != Image::FORMAT_RGBA8 && !Image::is_format_compressed(texture->format)) { image->convert(texture->format); } @@ -1191,7 +1191,7 @@ Ref TextureStorage::texture_2d_layer_get(RID p_texture, int p_layer) cons Vector data; - int data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, Image::FORMAT_RGBA8, false); + int64_t data_size = Image::get_image_data_size(texture->alloc_width, texture->alloc_height, Image::FORMAT_RGBA8, false); data.resize(data_size * 2); //add some memory at the end, just in case for buggy drivers uint8_t *w = data.ptrw(); @@ -1241,7 +1241,7 @@ Ref TextureStorage::texture_2d_layer_get(RID p_texture, int p_layer) cons ERR_FAIL_V_MSG(Ref(), vformat("Texture %s has no data.", path_str)); } - if (texture->format != Image::FORMAT_RGBA8) { + if (texture->format != Image::FORMAT_RGBA8 && !Image::is_format_compressed(texture->format)) { image->convert(texture->format); } @@ -1263,7 +1263,7 @@ Vector> TextureStorage::_texture_3d_read_framebuffer(GLES3::Texture * int depth = p_texture->depth; for (int mipmap_level = 0; mipmap_level < p_texture->mipmaps; mipmap_level++) { - int data_size = Image::get_image_data_size(width, height, Image::FORMAT_RGBA8, false); + int64_t data_size = Image::get_image_data_size(width, height, Image::FORMAT_RGBA8, false); glViewport(0, 0, width, height); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); @@ -1282,7 +1282,7 @@ Vector> TextureStorage::_texture_3d_read_framebuffer(GLES3::Texture * Ref img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, data); ERR_FAIL_COND_V(img->is_empty(), Vector>()); - if (p_texture->format != Image::FORMAT_RGBA8) { + if (p_texture->format != Image::FORMAT_RGBA8 && !Image::is_format_compressed(p_texture->format)) { img->convert(p_texture->format); } @@ -2570,6 +2570,20 @@ RID TextureStorage::render_target_get_override_velocity(RID p_render_target) con return rt->overridden.velocity; } +void TextureStorage::render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL(rt); + + rt->render_region = p_render_region; +} + +Rect2i TextureStorage::render_target_get_render_region(RID p_render_target) const { + RenderTarget *rt = render_target_owner.get_or_null(p_render_target); + ERR_FAIL_NULL_V(rt, Rect2i()); + + return rt->render_region; +} + RID TextureStorage::render_target_get_texture(RID p_render_target) { RenderTarget *rt = render_target_owner.get_or_null(p_render_target); ERR_FAIL_NULL_V(rt, RID()); diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h index 5e7ff8bcaa1..925b4415036 100644 --- a/drivers/gles3/storage/texture_storage.h +++ b/drivers/gles3/storage/texture_storage.h @@ -374,6 +374,8 @@ struct RenderTarget { RS::ViewportMSAA msaa = RS::VIEWPORT_MSAA_DISABLED; bool reattach_textures = false; + Rect2i render_region; + struct RTOverridden { bool is_overridden = false; RID color; @@ -693,6 +695,9 @@ class TextureStorage : public RendererTextureStorage { virtual RID render_target_get_override_velocity(RID p_render_target) const override; virtual RID render_target_get_override_velocity_depth(RID p_render_target) const override { return RID(); } + virtual void render_target_set_render_region(RID p_render_target, const Rect2i &p_render_region) override; + virtual Rect2i render_target_get_render_region(RID p_render_target) const override; + virtual RID render_target_get_texture(RID p_render_target) override; virtual void render_target_set_velocity_target_size(RID p_render_target, const Size2i &p_target_size) override {} diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h index 0e4fb1fcc9c..74d8ce714aa 100644 --- a/drivers/metal/metal_objects.h +++ b/drivers/metal/metal_objects.h @@ -852,8 +852,9 @@ class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPipeline final : public MDPi uint32_t front_reference = 0; uint32_t back_reference = 0; _FORCE_INLINE_ void apply(id __unsafe_unretained p_enc) const { - if (!enabled) + if (!enabled) { return; + } [p_enc setStencilFrontReferenceValue:front_reference backReferenceValue:back_reference]; } } stencil; diff --git a/drivers/metal/pixel_formats.h b/drivers/metal/pixel_formats.h index ee063f3f08b..ac6e14e8dbd 100644 --- a/drivers/metal/pixel_formats.h +++ b/drivers/metal/pixel_formats.h @@ -276,7 +276,7 @@ class API_AVAILABLE(macos(11.0), ios(14.0)) PixelFormats { MTLFormatType getFormatType(DataFormat p_format); /** Returns the format type corresponding to the specified Metal MTLPixelFormat, */ - MTLFormatType getFormatType(MTLPixelFormat p_formt); + MTLFormatType getFormatType(MTLPixelFormat p_format); /** * Returns the Metal MTLPixelFormat corresponding to the specified Redot pixel diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm index ce1e30e38f5..473d6b75ad0 100644 --- a/drivers/metal/pixel_formats.mm +++ b/drivers/metal/pixel_formats.mm @@ -146,8 +146,8 @@ void clear(T *p_val, size_t p_count = 1) { return getDataFormatDesc(p_format).formatType; } -MTLFormatType PixelFormats::getFormatType(MTLPixelFormat p_formt) { - return getDataFormatDesc(p_formt).formatType; +MTLFormatType PixelFormats::getFormatType(MTLPixelFormat p_format) { + return getDataFormatDesc(p_format).formatType; } MTLPixelFormat PixelFormats::getMTLPixelFormat(DataFormat p_format) { diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 626cdc3d7fe..b1f8ab60b79 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -1200,8 +1200,9 @@ void write_buffer(uint8_t const *p_buffer, uint32_t p_length) { uint64_t pos = 0; bool check_length(size_t p_size) { - if (status != Status::OK) + if (status != Status::OK) { return false; + } if (pos + p_size > length) { status = Status::SHORT_BUFFER; @@ -2520,8 +2521,9 @@ void deserialize(BufReader &p_reader) { for (UniformInfo const &uniform : set.uniforms) { BindingInfo const *binding_info = uniform.bindings.getptr(stage); - if (binding_info == nullptr) + if (binding_info == nullptr) { continue; + } [descriptors addObject:binding_info->new_argument_descriptor()]; BindingInfo const *secondary_binding_info = uniform.bindings_secondary.getptr(stage); diff --git a/drivers/png/SCsub b/drivers/png/SCsub index 84819b4858d..a48f92c9cc6 100644 --- a/drivers/png/SCsub +++ b/drivers/png/SCsub @@ -54,6 +54,9 @@ if env["builtin_libpng"]: env_thirdparty.Append(CPPDEFINES=["PNG_INTEL_SSE"]) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "intel/intel_init.c") env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "intel/filter_sse2_intrinsics.c") + elif env["arch"] == "loongarch64": + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "loongarch/loongarch_lsx_init.c") + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "loongarch/filter_lsx_intrinsics.c") elif env["arch"] == "ppc64": env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "powerpc/powerpc_init.c") env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "powerpc/filter_vsx_intrinsics.c") diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index 1cb9a4e14fa..d301de1e917 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -313,18 +313,16 @@ bool FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) { } bool FileAccessUnix::file_exists(const String &p_path) { - int err; struct stat st = {}; - String filename = fix_path(p_path); + const CharString filename_utf8 = fix_path(p_path).utf8(); // Does the name exist at all? - err = stat(filename.utf8().get_data(), &st); - if (err) { + if (stat(filename_utf8.get_data(), &st)) { return false; } // See if we have access to the file - if (access(filename.utf8().get_data(), F_OK)) { + if (access(filename_utf8.get_data(), F_OK)) { return false; } diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp index 0308a1537ae..eb7f12285a0 100644 --- a/drivers/unix/file_access_unix_pipe.cpp +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -69,10 +69,12 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); path = String("/tmp/") + p_path.replace("pipe://", "").replace("/", "_"); + const CharString path_utf8 = path.utf8(); + struct stat st = {}; - int err = stat(path.utf8().get_data(), &st); + int err = stat(path_utf8.get_data(), &st); if (err) { - if (mkfifo(path.utf8().get_data(), 0600) != 0) { + if (mkfifo(path_utf8.get_data(), 0600) != 0) { last_error = ERR_FILE_CANT_OPEN; return last_error; } @@ -81,7 +83,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file."); } - int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK); + int f = ::open(path_utf8.get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK); if (f < 0) { switch (errno) { case ENOENT: { diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp index 44fcdd9e5cc..1e8e7377a9e 100644 --- a/editor/animation_track_editor_plugins.cpp +++ b/editor/animation_track_editor_plugins.cpp @@ -419,8 +419,8 @@ Rect2 AnimationTrackEditSpriteFrame::get_key_rect(int p_index, float p_pixels_se animation_path = animation_path.replace(":frame", ":animation"); int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track())); float track_time = get_animation()->track_get_key_time(get_track(), p_index); - int animaiton_index = get_animation()->track_find_key(animation_track, track_time); - animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index); + int animation_index = get_animation()->track_find_key(animation_track, track_time); + animation_name = get_animation()->track_get_key_value(animation_track, animation_index); } Ref texture = sf->get_frame_texture(animation_name, frame); @@ -511,8 +511,8 @@ void AnimationTrackEditSpriteFrame::draw_key(int p_index, float p_pixels_sec, in animation_path = animation_path.replace(":frame", ":animation"); int animation_track = get_animation()->find_track(animation_path, get_animation()->track_get_type(get_track())); float track_time = get_animation()->track_get_key_time(get_track(), p_index); - int animaiton_index = get_animation()->track_find_key(animation_track, track_time); - animation_name = get_animation()->track_get_key_value(animation_track, animaiton_index); + int animation_index = get_animation()->track_find_key(animation_track, track_time); + animation_name = get_animation()->track_get_key_value(animation_track, animation_index); } texture = sf->get_frame_texture(animation_name, frame); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index bb92e22aafe..34471d552cc 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -77,7 +77,7 @@ static String _translate_doc_string(const String &p_text) { return translated.indent(indent); } -// Comparator for constructors, based on `MetodDoc` operator. +// Comparator for constructors, based on `MethodDoc` operator. struct ConstructorCompare { _FORCE_INLINE_ bool operator()(const DocData::MethodDoc &p_lhs, const DocData::MethodDoc &p_rhs) const { // Must be a constructor (i.e. assume named for the class) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0f13e747501..448b45871ea 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -105,6 +105,7 @@ #include "editor/export/editor_export.h" #include "editor/export/export_template_manager.h" #include "editor/export/project_export.h" +#include "editor/export/project_zip_packer.h" #include "editor/fbx_importer_manager.h" #include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" @@ -315,10 +316,10 @@ void EditorNode::disambiguate_filenames(const Vector p_full_paths, Vecto void EditorNode::_version_control_menu_option(int p_idx) { switch (vcs_actions_menu->get_item_id(p_idx)) { - case RUN_VCS_METADATA: { + case VCS_METADATA: { VersionControlEditorPlugin::get_singleton()->popup_vcs_metadata_dialog(); } break; - case RUN_VCS_SETTINGS: { + case VCS_SETTINGS: { VersionControlEditorPlugin::get_singleton()->popup_vcs_set_up_dialog(gui_base); } break; } @@ -753,7 +754,7 @@ void EditorNode::_notification(int p_what) { case NOTIFICATION_APPLICATION_FOCUS_OUT: { // Save on focus loss before applying the FPS limit to avoid slowing down the saving process. if (EDITOR_GET("interface/editor/save_on_focus_loss")) { - _menu_option_confirm(FILE_SAVE_SCENE_SILENTLY, false); + _save_scene_silently(); } // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused. @@ -846,8 +847,8 @@ void EditorNode::_update_update_spinner() { const bool update_continuously = EDITOR_GET("interface/editor/update_continuously"); PopupMenu *update_popup = update_spinner->get_popup(); - update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_CONTINUOUSLY), update_continuously); - update_popup->set_item_checked(update_popup->get_item_index(SETTINGS_UPDATE_WHEN_CHANGED), !update_continuously); + update_popup->set_item_checked(update_popup->get_item_index(SPINNER_UPDATE_CONTINUOUSLY), update_continuously); + update_popup->set_item_checked(update_popup->get_item_index(SPINNER_UPDATE_WHEN_CHANGED), !update_continuously); if (update_continuously) { update_spinner->set_tooltip_text(TTR("Spins when the editor window redraws.\nUpdate Continuously is enabled, which can increase power usage. Click to disable it.")); @@ -1279,8 +1280,8 @@ void EditorNode::_titlebar_resized() { void EditorNode::_update_undo_redo_allowed() { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - file_menu->set_item_disabled(file_menu->get_item_index(EDIT_UNDO), !undo_redo->has_undo()); - file_menu->set_item_disabled(file_menu->get_item_index(EDIT_REDO), !undo_redo->has_redo()); + file_menu->set_item_disabled(file_menu->get_item_index(FILE_UNDO), !undo_redo->has_undo()); + file_menu->set_item_disabled(file_menu->get_item_index(FILE_REDO), !undo_redo->has_redo()); } void EditorNode::_node_renamed() { @@ -1862,6 +1863,17 @@ int EditorNode::_save_external_resources(bool p_also_save_external_data) { return saved; } +void EditorNode::_save_scene_silently() { + // Save scene without displaying progress dialog. Used to work around + // errors about parent node being busy setting up children + // when Save on Focus Loss kicks in. + Node *scene = editor_data.get_edited_scene_root(); + if (scene && !scene->get_scene_file_path().is_empty() && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { + _save_scene(scene->get_scene_file_path()); + save_editor_layout_delayed(); + } +} + static void _reset_animation_mixers(Node *p_node, List>> *r_anim_backups) { for (int i = 0; i < p_node->get_child_count(); i++) { AnimationMixer *mixer = Object::cast_to(p_node->get_child(i)); @@ -2195,6 +2207,15 @@ void EditorNode::_dialog_action(String p_file) { } break; + case PROJECT_PACK_AS_ZIP: { + ProjectZIPPacker::pack_project_zip(p_file); + { + Ref f = FileAccess::open(p_file, FileAccess::READ); + ERR_FAIL_COND_MSG(f.is_null(), vformat("Unable to create ZIP file at: %s. Check for write permissions and whether you have enough disk space left.", p_file)); + } + + } break; + case RESOURCE_SAVE: case RESOURCE_SAVE_AS: { ERR_FAIL_COND(saving_resource.is_null()); @@ -2211,7 +2232,7 @@ void EditorNode::_dialog_action(String p_file) { ERR_FAIL_NULL(current_obj); current_obj->notify_property_list_changed(); } break; - case SETTINGS_LAYOUT_SAVE: { + case LAYOUT_SAVE: { if (p_file.is_empty()) { return; } @@ -2239,7 +2260,7 @@ void EditorNode::_dialog_action(String p_file) { } } break; - case SETTINGS_LAYOUT_DELETE: { + case LAYOUT_DELETE: { Ref config; config.instantiate(); Error err = config->load(EditorSettings::get_singleton()->get_editor_layouts_config()); @@ -2689,6 +2710,15 @@ void EditorNode::_android_export_preset_selected(int p_index) { install_android_build_template_message->set_text(vformat(TTR(INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); } +void EditorNode::_android_install_build_template() { + gradle_build_manage_templates->hide(); + file_android_build_source->popup_centered_ratio(); +} + +void EditorNode::_android_explore_build_templates() { + OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(export_template_manager->get_android_build_directory(android_export_preset).get_base_dir()), true); +} + void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (!p_confirmed) { // FIXME: this may be a hack. current_menu_option = (MenuOptions)p_option; @@ -2735,7 +2765,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { previous_scenes.pop_back(); } break; - case FILE_CLOSE_OTHERS: { + case EditorSceneTabs::SCENE_CLOSE_OTHERS: { tab_closing_menu_option = -1; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { if (i == editor_data.get_edited_scene()) { @@ -2745,14 +2775,14 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } _proceed_closing_scene_tabs(); } break; - case FILE_CLOSE_RIGHT: { + case EditorSceneTabs::SCENE_CLOSE_RIGHT: { tab_closing_menu_option = -1; for (int i = editor_data.get_edited_scene() + 1; i < editor_data.get_edited_scene_count(); i++) { tabs_to_close.push_back(editor_data.get_scene_path(i)); } _proceed_closing_scene_tabs(); } break; - case FILE_CLOSE_ALL: { + case EditorSceneTabs::SCENE_CLOSE_ALL: { tab_closing_menu_option = -1; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { tabs_to_close.push_back(editor_data.get_scene_path(i)); @@ -2762,16 +2792,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case FILE_CLOSE: { _scene_tab_closed(editor_data.get_edited_scene()); } break; - case FILE_SAVE_SCENE_SILENTLY: { - // Save scene without displaying progress dialog. Used to work around - // errors about parent node being busy setting up children - // when Save on Focus Loss kicks in. - Node *scene = editor_data.get_edited_scene_root(); - if (scene && !scene->get_scene_file_path().is_empty() && DirAccess::exists(scene->get_scene_file_path().get_base_dir())) { - _save_scene(scene->get_scene_file_path()); - save_editor_layout_delayed(); - } - } break; case SCENE_TAB_CLOSE: case FILE_SAVE_SCENE: { int scene_idx = (p_option == FILE_SAVE_SCENE) ? -1 : tab_closing_idx; @@ -2853,32 +2873,29 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { _save_all_scenes(); } break; - case FILE_RUN_SCENE: { + case EditorSceneTabs::SCENE_RUN: { project_run_bar->play_current_scene(); } break; - case FILE_EXPORT_PROJECT: { + case PROJECT_EXPORT: { project_export->popup_export(); } break; - case FILE_EXTERNAL_OPEN_SCENE: { - if (unsaved_cache && !p_confirmed) { - confirmation->set_ok_button_text(TTR("Open")); - confirmation->set_text(TTR("Current scene not saved. Open anyway?")); - confirmation->popup_centered(); - break; - } - - bool oprev = opening_prev; - Error err = load_scene(external_file); - if (err == OK && oprev) { - previous_scenes.pop_back(); - opening_prev = false; - } + case PROJECT_PACK_AS_ZIP: { + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + const String base_path = resource_path.substr(0, resource_path.rfind_char('/')) + "/"; + file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file->clear_filters(); + file->set_current_path(base_path); + file->set_current_file(ProjectZIPPacker::get_project_zip_safe_name()); + file->add_filter("*.zip", "ZIP Archive"); + file->set_title(TTR("Pack Project as ZIP...")); + file->popup_file_dialog(); } break; - case EDIT_UNDO: { + case FILE_UNDO: { if ((int)Input::get_singleton()->get_mouse_button_mask() & 0x7) { log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR); } else { @@ -2901,7 +2918,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } } break; - case EDIT_REDO: { + case FILE_REDO: { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); if ((int)Input::get_singleton()->get_mouse_button_mask() & 0x7) { log->add_message(TTR("Can't redo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR); @@ -2928,7 +2945,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } break; - case EDIT_RELOAD_SAVED_SCENE: { + case FILE_RELOAD_SAVED_SCENE: { Node *scene = get_edited_scene(); if (!scene) { @@ -2962,17 +2979,18 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; - case FILE_SHOW_IN_FILESYSTEM: { + case EditorSceneTabs::SCENE_SHOW_IN_FILESYSTEM: { String path = editor_data.get_scene_path(editor_data.get_edited_scene()); if (!path.is_empty()) { FileSystemDock::get_singleton()->navigate_to_path(path); } } break; - case RUN_SETTINGS: { + case PROJECT_OPEN_SETTINGS: { project_settings_editor->popup_project_settings(); } break; - case FILE_INSTALL_ANDROID_SOURCE: { + + case PROJECT_INSTALL_ANDROID_SOURCE: { if (p_confirmed) { if (export_template_manager->is_android_template_installed(android_export_preset)) { remove_android_build_template->set_text(vformat(TTR(REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE), export_template_manager->get_android_build_directory(android_export_preset))); @@ -3016,17 +3034,14 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } } } break; - case RUN_USER_DATA_FOLDER: { + case PROJECT_OPEN_USER_DATA_FOLDER: { // Ensure_user_data_dir() to prevent the edge case: "Open User Data Folder" won't work after the project was renamed in ProjectSettingsEditor unless the project is saved. OS::get_singleton()->ensure_user_data_dir(); OS::get_singleton()->shell_show_in_file_manager(OS::get_singleton()->get_user_data_dir(), true); } break; - case FILE_EXPLORE_ANDROID_BUILD_TEMPLATES: { - OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(export_template_manager->get_android_build_directory(android_export_preset).get_base_dir()), true); - } break; case FILE_QUIT: - case RUN_PROJECT_MANAGER: - case RELOAD_CURRENT_PROJECT: { + case PROJECT_QUIT_TO_PROJECT_MANAGER: + case PROJECT_RELOAD_CURRENT_PROJECT: { if (p_confirmed && plugin_to_save) { plugin_to_save->save_external_data(); p_confirmed = false; @@ -3036,7 +3051,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { bool save_each = EDITOR_GET("interface/editor/save_each_scene_on_quit"); if (_next_unsaved_scene(!save_each) == -1) { if (EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorUndoRedoManager::GLOBAL_HISTORY)) { - if (p_option == RELOAD_CURRENT_PROJECT) { + if (p_option == PROJECT_RELOAD_CURRENT_PROJECT) { save_confirmation->set_ok_button_text(TTR("Save & Reload")); save_confirmation->set_text(TTR("Save modified resources before reloading?")); } else { @@ -3052,7 +3067,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) { const String unsaved_status = editor_data.get_editor_plugin(i)->get_unsaved_status(); if (!unsaved_status.is_empty()) { - if (p_option == RELOAD_CURRENT_PROJECT) { + if (p_option == PROJECT_RELOAD_CURRENT_PROJECT) { save_confirmation->set_ok_button_text(TTR("Save & Reload")); save_confirmation->set_text(unsaved_status); } else { @@ -3087,7 +3102,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { unsaved_scenes += "\n " + editor_data.get_edited_scene_root(i)->get_scene_file_path(); i = _next_unsaved_scene(true, ++i); } - if (p_option == RELOAD_CURRENT_PROJECT) { + if (p_option == PROJECT_RELOAD_CURRENT_PROJECT) { save_confirmation->set_ok_button_text(TTR("Save & Reload")); save_confirmation->set_text(TTR("Save changes to the following scene(s) before reloading?") + unsaved_scenes); } else { @@ -3104,44 +3119,40 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { _save_external_resources(); _discard_changes(); } break; - case SETTINGS_UPDATE_CONTINUOUSLY: { + case SPINNER_UPDATE_CONTINUOUSLY: { EditorSettings::get_singleton()->set("interface/editor/update_continuously", true); _update_update_spinner(); show_accept(TTR("This option is deprecated. Situations where refresh must be forced are now considered a bug. Please report."), TTR("OK")); } break; - case SETTINGS_UPDATE_WHEN_CHANGED: { + case SPINNER_UPDATE_WHEN_CHANGED: { EditorSettings::get_singleton()->set("interface/editor/update_continuously", false); _update_update_spinner(); } break; - case SETTINGS_UPDATE_SPINNER_HIDE: { + case SPINNER_UPDATE_SPINNER_HIDE: { EditorSettings::get_singleton()->set("interface/editor/show_update_spinner", 2); // Disabled _update_update_spinner(); } break; - case SETTINGS_PREFERENCES: { + case EDITOR_OPEN_SETTINGS: { editor_settings_dialog->popup_edit_settings(); } break; - case SETTINGS_EDITOR_DATA_FOLDER: { + case EDITOR_OPEN_DATA_FOLDER: { OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_data_dir(), true); } break; - case SETTINGS_EDITOR_CONFIG_FOLDER: { + case EDITOR_OPEN_CONFIG_FOLDER: { OS::get_singleton()->shell_show_in_file_manager(EditorPaths::get_singleton()->get_config_dir(), true); } break; - case SETTINGS_MANAGE_EXPORT_TEMPLATES: { + case EDITOR_MANAGE_EXPORT_TEMPLATES: { export_template_manager->popup_manager(); } break; - case SETTINGS_MANAGE_FBX_IMPORTER: { + case EDITOR_CONFIGURE_FBX_IMPORTER: { #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) fbx_importer_manager->show_dialog(); #endif } break; - case SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE: { - gradle_build_manage_templates->hide(); - file_android_build_source->popup_centered_ratio(); - } break; - case SETTINGS_MANAGE_FEATURE_PROFILES: { + case EDITOR_MANAGE_FEATURE_PROFILES: { feature_profile_manager->popup_centered_clamped(Size2(900, 800) * EDSCALE, 0.8); } break; - case SETTINGS_TOGGLE_FULLSCREEN: { + case EDITOR_TOGGLE_FULLSCREEN: { DisplayServer::WindowMode mode = DisplayServer::get_singleton()->window_get_mode(); if (mode == DisplayServer::WINDOW_MODE_FULLSCREEN || mode == DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { DisplayServer::get_singleton()->window_set_mode(prev_mode); @@ -3150,7 +3161,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_FULLSCREEN); } } break; - case EDITOR_SCREENSHOT: { + case EDITOR_TAKE_SCREENSHOT: { screenshot_timer->start(); } break; case SETTINGS_PICK_MAIN_SCENE: { @@ -3173,7 +3184,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case HELP_SEARCH: { emit_signal(SNAME("request_help_search"), ""); } break; - case HELP_COMMAND_PALETTE: { + case EDITOR_COMMAND_PALETTE: { command_palette->open_popup(); } break; case HELP_DOCS: { @@ -3204,23 +3215,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case HELP_SUPPORT_GODOT_DEVELOPMENT: { OS::get_singleton()->shell_open("https://redotengine.org/donate"); } break; - case SET_RENDERER_NAME_SAVE_AND_RESTART: { - ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method", renderer_request); - if (renderer_request == "mobile" || renderer_request == "gl_compatibility") { - // Also change the mobile override if changing to a compatible rendering method. - // This prevents visual discrepancies between desktop and mobile platforms. - ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", renderer_request); - } else if (renderer_request == "forward_plus") { - // Use the equivalent mobile rendering method. This prevents the rendering method from staying - // on its old choice if moving from `gl_compatibility` to `forward_plus`. - ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", "mobile"); - } - - ProjectSettings::get_singleton()->save(); - - save_all_scenes(); - restart_editor(); - } break; } } @@ -3449,10 +3443,10 @@ void EditorNode::_discard_changes(const String &p_str) { _exit_editor(EXIT_SUCCESS); } break; - case RUN_PROJECT_MANAGER: { + case PROJECT_QUIT_TO_PROJECT_MANAGER: { restart_editor(true); } break; - case RELOAD_CURRENT_PROJECT: { + case PROJECT_RELOAD_CURRENT_PROJECT: { restart_editor(); } break; } @@ -5476,11 +5470,11 @@ bool EditorNode::has_scenes_in_session() { } void EditorNode::undo() { - trigger_menu_option(EDIT_UNDO, true); + trigger_menu_option(FILE_UNDO, true); } void EditorNode::redo() { - trigger_menu_option(EDIT_REDO, true); + trigger_menu_option(FILE_REDO, true); } bool EditorNode::ensure_main_scene(bool p_from_native) { @@ -5558,10 +5552,10 @@ void EditorNode::_update_layouts_menu() { overridden_default_layout = -1; editor_layouts->reset_size(); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTRC("Save Layout...")), SETTINGS_LAYOUT_SAVE); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTRC("Delete Layout...")), SETTINGS_LAYOUT_DELETE); + editor_layouts->add_shortcut(ED_SHORTCUT("layout/save", TTRC("Save Layout...")), LAYOUT_SAVE); + editor_layouts->add_shortcut(ED_SHORTCUT("layout/delete", TTRC("Delete Layout...")), LAYOUT_DELETE); editor_layouts->add_separator(); - editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTRC("Default")), SETTINGS_LAYOUT_DEFAULT); + editor_layouts->add_shortcut(ED_SHORTCUT("layout/default", TTRC("Default")), LAYOUT_DEFAULT); Ref config; config.instantiate(); @@ -5575,7 +5569,7 @@ void EditorNode::_update_layouts_menu() { for (const String &layout : layouts) { if (layout == TTR("Default")) { - editor_layouts->remove_item(editor_layouts->get_item_index(SETTINGS_LAYOUT_DEFAULT)); + editor_layouts->remove_item(editor_layouts->get_item_index(LAYOUT_DEFAULT)); overridden_default_layout = editor_layouts->get_item_count(); } @@ -5585,21 +5579,21 @@ void EditorNode::_update_layouts_menu() { void EditorNode::_layout_menu_option(int p_id) { switch (p_id) { - case SETTINGS_LAYOUT_SAVE: { + case LAYOUT_SAVE: { current_menu_option = p_id; layout_dialog->set_title(TTR("Save Layout")); layout_dialog->set_ok_button_text(TTR("Save")); layout_dialog->set_name_line_enabled(true); layout_dialog->popup_centered(); } break; - case SETTINGS_LAYOUT_DELETE: { + case LAYOUT_DELETE: { current_menu_option = p_id; layout_dialog->set_title(TTR("Delete Layout")); layout_dialog->set_ok_button_text(TTR("Delete")); layout_dialog->set_name_line_enabled(false); layout_dialog->popup_centered(); } break; - case SETTINGS_LAYOUT_DEFAULT: { + case LAYOUT_DEFAULT: { editor_dock_manager->load_docks_from_config(default_layout, "docks"); _save_editor_layout(); } break; @@ -5645,7 +5639,7 @@ void EditorNode::_proceed_closing_scene_tabs() { } bool EditorNode::_is_closing_editor() const { - return tab_closing_menu_option == FILE_QUIT || tab_closing_menu_option == RUN_PROJECT_MANAGER || tab_closing_menu_option == RELOAD_CURRENT_PROJECT; + return tab_closing_menu_option == FILE_QUIT || tab_closing_menu_option == PROJECT_QUIT_TO_PROJECT_MANAGER || tab_closing_menu_option == PROJECT_RELOAD_CURRENT_PROJECT; } void EditorNode::_scene_tab_closed(int p_tab) { @@ -6627,6 +6621,24 @@ void EditorNode::_add_renderer_entry(const String &p_renderer_name, bool p_mark_ renderer->add_item(item_text); } +void EditorNode::_set_renderer_name_save_and_restart() { + ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method", renderer_request); + if (renderer_request == "mobile" || renderer_request == "gl_compatibility") { + // Also change the mobile override if changing to a compatible rendering method. + // This prevents visual discrepancies between desktop and mobile platforms. + ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", renderer_request); + } else if (renderer_request == "forward_plus") { + // Use the equivalent mobile rendering method. This prevents the rendering method from staying + // on its old choice if moving from `gl_compatibility` to `forward_plus`. + ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", "mobile"); + } + + ProjectSettings::get_singleton()->save(); + + save_all_scenes(); + restart_editor(); +} + void EditorNode::_resource_saved(Ref p_resource, const String &p_path) { if (singleton->saving_resources_in_path.has(p_resource)) { // This is going to be handled by save_resource_in_path when the time is right. @@ -7341,11 +7353,11 @@ EditorNode::EditorNode() { export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option)); file_menu->add_separator(); - file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, false, true); - file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, false, true); + file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), FILE_UNDO, false, true); + file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), FILE_REDO, false, true); file_menu->add_separator(); - file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTRC("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTRC("Reload Saved Scene")), FILE_RELOAD_SAVED_SCENE); file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTRC("Close Scene"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE); ED_SHORTCUT_OVERRIDE("editor/close_scene", "macos", KeyModifierMask::CMD_OR_CTRL + Key::W); @@ -7363,7 +7375,7 @@ EditorNode::EditorNode() { apple_menu->set_system_menu(NativeMenu::APPLICATION_MENU_ID); main_menu->add_child(apple_menu); - apple_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + apple_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), EDITOR_OPEN_SETTINGS); apple_menu->add_separator(); apple_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_menu_option)); } @@ -7373,17 +7385,18 @@ EditorNode::EditorNode() { project_menu->set_name(TTR("Project")); main_menu->add_child(project_menu); - project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTRC("Project Settings..."), Key::NONE, TTRC("Project Settings")), RUN_SETTINGS); + project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTRC("Project Settings..."), Key::NONE, TTRC("Project Settings")), PROJECT_OPEN_SETTINGS); project_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_menu_option)); project_menu->add_separator(); - project_menu->add_item(TTR("Version Control"), VCS_MENU); + project_menu->add_item(TTR("Version Control"), PROJECT_VERSION_CONTROL); project_menu->add_separator(); - project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTRC("Export..."), Key::NONE, TTRC("Export")), FILE_EXPORT_PROJECT); + project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTRC("Export..."), Key::NONE, TTRC("Export")), PROJECT_EXPORT); + project_menu->add_item(TTR("Pack Project as ZIP..."), PROJECT_PACK_AS_ZIP); #ifndef ANDROID_ENABLED - project_menu->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE); - project_menu->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER); + project_menu->add_item(TTR("Install Android Build Template..."), PROJECT_INSTALL_ANDROID_SOURCE); + project_menu->add_item(TTR("Open User Data Folder"), PROJECT_OPEN_USER_DATA_FOLDER); #endif project_menu->add_separator(); @@ -7396,10 +7409,10 @@ EditorNode::EditorNode() { tool_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/upgrade_mesh_surfaces", TTRC("Upgrade Mesh Surfaces...")), TOOLS_SURFACE_UPGRADE); project_menu->add_separator(); - project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTRC("Reload Current Project")), RELOAD_CURRENT_PROJECT); + project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTRC("Reload Current Project")), PROJECT_RELOAD_CURRENT_PROJECT); ED_SHORTCUT_AND_COMMAND("editor/quit_to_project_list", TTRC("Quit to Project List"), KeyModifierMask::CTRL + KeyModifierMask::SHIFT + Key::Q); ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + KeyModifierMask::ALT + Key::Q); - project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true); + project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), PROJECT_QUIT_TO_PROJECT_MANAGER, true); // Spacer to center 2D / 3D / Script buttons. HBoxContainer *left_spacer = memnew(HBoxContainer); @@ -7435,12 +7448,12 @@ EditorNode::EditorNode() { #ifdef MACOS_ENABLED if (!global_menu) { - settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), EDITOR_OPEN_SETTINGS); } #else - settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), EDITOR_OPEN_SETTINGS); #endif - settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTRC("Command Palette..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE); + settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTRC("Command Palette..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::P), EDITOR_COMMAND_PALETTE); settings_menu->add_separator(); settings_menu->add_submenu_node_item(TTR("Editor Docks"), editor_dock_manager->get_docks_menu()); @@ -7453,31 +7466,31 @@ EditorNode::EditorNode() { ED_SHORTCUT_AND_COMMAND("editor/take_screenshot", TTRC("Take Screenshot"), KeyModifierMask::CTRL | Key::F12); ED_SHORTCUT_OVERRIDE("editor/take_screenshot", "macos", KeyModifierMask::META | Key::F12); - settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_TAKE_SCREENSHOT); settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\").")); ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTRC("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11); ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F); - settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), EDITOR_TOGGLE_FULLSCREEN); settings_menu->add_separator(); #ifndef ANDROID_ENABLED if (OS::get_singleton()->get_data_path() == OS::get_singleton()->get_config_path()) { // Configuration and data folders are located in the same place (Windows/macOS). - settings_menu->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER); + settings_menu->add_item(TTR("Open Editor Data/Settings Folder"), EDITOR_OPEN_DATA_FOLDER); } else { // Separate configuration and data folders (Linux). - settings_menu->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER); - settings_menu->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER); + settings_menu->add_item(TTR("Open Editor Data Folder"), EDITOR_OPEN_DATA_FOLDER); + settings_menu->add_item(TTR("Open Editor Settings Folder"), EDITOR_OPEN_CONFIG_FOLDER); } settings_menu->add_separator(); #endif - settings_menu->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES); - settings_menu->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES); + settings_menu->add_item(TTR("Manage Editor Features..."), EDITOR_MANAGE_FEATURE_PROFILES); + settings_menu->add_item(TTR("Manage Export Templates..."), EDITOR_MANAGE_EXPORT_TEMPLATES); #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) - settings_menu->add_item(TTR("Configure FBX Importer..."), SETTINGS_MANAGE_FBX_IMPORTER); + settings_menu->add_item(TTR("Configure FBX Importer..."), EDITOR_CONFIGURE_FBX_IMPORTER); #endif help_menu = memnew(PopupMenu); @@ -7575,7 +7588,7 @@ EditorNode::EditorNode() { video_restart_dialog = memnew(ConfirmationDialog); video_restart_dialog->set_ok_button_text(TTR("Save & Restart")); - video_restart_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_menu_option).bind(SET_RENDERER_NAME_SAVE_AND_RESTART)); + video_restart_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_set_renderer_name_save_and_restart)); gui_base->add_child(video_restart_dialog); progress_hb = memnew(BackgroundProgress); @@ -7591,10 +7604,10 @@ EditorNode::EditorNode() { update_spinner->set_button_icon(theme->get_icon(SNAME("Progress1"), EditorStringName(EditorIcons))); update_spinner->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_menu_option)); PopupMenu *p = update_spinner->get_popup(); - p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY); - p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED); + p->add_radio_check_item(TTR("Update Continuously"), SPINNER_UPDATE_CONTINUOUSLY); + p->add_radio_check_item(TTR("Update When Changed"), SPINNER_UPDATE_WHEN_CHANGED); p->add_separator(); - p->add_item(TTR("Hide Update Spinner"), SETTINGS_UPDATE_SPINNER_HIDE); + p->add_item(TTR("Hide Update Spinner"), SPINNER_UPDATE_SPINNER_HIDE); _update_update_spinner(); // Instantiate and place editor docks. @@ -7690,8 +7703,8 @@ EditorNode::EditorNode() { gradle_build_manage_templates = memnew(ConfirmationDialog); gradle_build_manage_templates->set_text(TTR("Android build template is missing, please install relevant templates.")); gradle_build_manage_templates->set_ok_button_text(TTR("Manage Templates")); - gradle_build_manage_templates->add_button(TTR("Install from file"))->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE)); - gradle_build_manage_templates->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_menu_option).bind(SETTINGS_MANAGE_EXPORT_TEMPLATES)); + gradle_build_manage_templates->add_button(TTR("Install from file"))->connect(SceneStringName(pressed), callable_mp(this, &EditorNode::_android_install_build_template)); + gradle_build_manage_templates->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_menu_option).bind(EDITOR_MANAGE_EXPORT_TEMPLATES)); gui_base->add_child(gradle_build_manage_templates); file_android_build_source = memnew(EditorFileDialog); @@ -7723,7 +7736,7 @@ EditorNode::EditorNode() { remove_android_build_template = memnew(ConfirmationDialog); remove_android_build_template->set_ok_button_text(TTR("Show in File Manager")); - remove_android_build_template->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_menu_option).bind(FILE_EXPLORE_ANDROID_BUILD_TEMPLATES)); + remove_android_build_template->connect(SceneStringName(confirmed), callable_mp(this, &EditorNode::_android_explore_build_templates)); gui_base->add_child(remove_android_build_template); file_templates = memnew(EditorFileDialog); @@ -7832,9 +7845,9 @@ EditorNode::EditorNode() { vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel(); vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option)); - vcs_actions_menu->add_item(TTR("Create/Override Version Control Metadata..."), RUN_VCS_METADATA); - vcs_actions_menu->add_item(TTR("Version Control Settings..."), RUN_VCS_SETTINGS); - project_menu->set_item_submenu_node(project_menu->get_item_index(VCS_MENU), vcs_actions_menu); + vcs_actions_menu->add_item(TTR("Create/Override Version Control Metadata..."), VCS_METADATA); + vcs_actions_menu->add_item(TTR("Version Control Settings..."), VCS_SETTINGS); + project_menu->set_item_submenu_node(project_menu->get_item_index(PROJECT_VERSION_CONTROL), vcs_actions_menu); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); diff --git a/editor/editor_node.h b/editor/editor_node.h index b62bf0b6a44..d7831f37a59 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -135,92 +135,83 @@ class EditorNode : public Node { }; enum MenuOptions { + // Scene menu. FILE_NEW_SCENE, FILE_NEW_INHERITED_SCENE, FILE_OPEN_SCENE, + FILE_OPEN_PREV, + FILE_OPEN_RECENT, FILE_SAVE_SCENE, - FILE_SAVE_SCENE_SILENTLY, FILE_SAVE_AS_SCENE, FILE_SAVE_ALL_SCENES, - FILE_SAVE_AND_RUN, - FILE_SAVE_AND_RUN_MAIN_SCENE, - FILE_RUN_SCENE, - FILE_SHOW_IN_FILESYSTEM, - FILE_EXPORT_PROJECT, - FILE_EXPORT_MESH_LIBRARY, - FILE_INSTALL_ANDROID_SOURCE, - FILE_EXPLORE_ANDROID_BUILD_TEMPLATES, - FILE_SAVE_OPTIMIZED, - FILE_OPEN_RECENT, - FILE_OPEN_OLD_SCENE, FILE_QUICK_OPEN, FILE_QUICK_OPEN_SCENE, FILE_QUICK_OPEN_SCRIPT, - FILE_OPEN_PREV, + FILE_UNDO, + FILE_REDO, + FILE_RELOAD_SAVED_SCENE, FILE_CLOSE, - FILE_CLOSE_OTHERS, - FILE_CLOSE_RIGHT, - FILE_CLOSE_ALL, FILE_QUIT, - FILE_EXTERNAL_OPEN_SCENE, - EDIT_UNDO, - EDIT_REDO, - EDIT_RELOAD_SAVED_SCENE, + + FILE_EXPORT_MESH_LIBRARY, + + // Project menu. + PROJECT_OPEN_SETTINGS, + PROJECT_VERSION_CONTROL, + PROJECT_EXPORT, + PROJECT_PACK_AS_ZIP, + PROJECT_INSTALL_ANDROID_SOURCE, + PROJECT_OPEN_USER_DATA_FOLDER, + PROJECT_RELOAD_CURRENT_PROJECT, + PROJECT_QUIT_TO_PROJECT_MANAGER, + TOOLS_ORPHAN_RESOURCES, TOOLS_BUILD_PROFILE_MANAGER, TOOLS_SURFACE_UPGRADE, TOOLS_CUSTOM, - RESOURCE_SAVE, - RESOURCE_SAVE_AS, - - RUN_SETTINGS, - RUN_USER_DATA_FOLDER, - RELOAD_CURRENT_PROJECT, - RUN_PROJECT_MANAGER, - VCS_MENU, - RUN_VCS_METADATA, - RUN_VCS_SETTINGS, - SETTINGS_UPDATE_CONTINUOUSLY, - SETTINGS_UPDATE_WHEN_CHANGED, - SETTINGS_UPDATE_ALWAYS, - SETTINGS_UPDATE_CHANGES, - SETTINGS_UPDATE_SPINNER_HIDE, - SETTINGS_PREFERENCES, - SETTINGS_LAYOUT_SAVE, - SETTINGS_LAYOUT_DELETE, - SETTINGS_LAYOUT_DEFAULT, - SETTINGS_EDITOR_DATA_FOLDER, - SETTINGS_EDITOR_CONFIG_FOLDER, - SETTINGS_MANAGE_EXPORT_TEMPLATES, - SETTINGS_MANAGE_FBX_IMPORTER, - SETTINGS_MANAGE_FEATURE_PROFILES, - SETTINGS_INSTALL_ANDROID_BUILD_TEMPLATE, - SETTINGS_PICK_MAIN_SCENE, - SETTINGS_TOGGLE_FULLSCREEN, - SETTINGS_HELP, - - SCENE_TAB_CLOSE, - - EDITOR_SCREENSHOT, - EDITOR_OPEN_SCREENSHOT, + VCS_METADATA, + VCS_SETTINGS, + + // Editor menu. + EDITOR_OPEN_SETTINGS, + EDITOR_COMMAND_PALETTE, + EDITOR_TAKE_SCREENSHOT, + EDITOR_TOGGLE_FULLSCREEN, + EDITOR_OPEN_DATA_FOLDER, + EDITOR_OPEN_CONFIG_FOLDER, + EDITOR_MANAGE_FEATURE_PROFILES, + EDITOR_MANAGE_EXPORT_TEMPLATES, + EDITOR_CONFIGURE_FBX_IMPORTER, + + LAYOUT_SAVE, + LAYOUT_DELETE, + LAYOUT_DEFAULT, + + // Help menu. HELP_SEARCH, - HELP_COMMAND_PALETTE, HELP_DOCS, HELP_FORUM, - HELP_REPORT_A_BUG, + HELP_COMMUNITY, HELP_COPY_SYSTEM_INFO, + HELP_REPORT_A_BUG, HELP_SUGGEST_A_FEATURE, HELP_SEND_DOCS_FEEDBACK, - HELP_COMMUNITY, HELP_ABOUT, HELP_SUPPORT_GODOT_DEVELOPMENT, - SET_RENDERER_NAME_SAVE_AND_RESTART, + // Update spinner menu. + SPINNER_UPDATE_CONTINUOUSLY, + SPINNER_UPDATE_WHEN_CHANGED, + SPINNER_UPDATE_SPINNER_HIDE, - IMPORT_PLUGIN_BASE = 100, - - TOOL_MENU_BASE = 1000 + // Non-menu options. + SCENE_TAB_CLOSE, + FILE_SAVE_AND_RUN, + FILE_SAVE_AND_RUN_MAIN_SCENE, + RESOURCE_SAVE, + RESOURCE_SAVE_AS, + SETTINGS_PICK_MAIN_SCENE, }; struct ExecuteThreadArgs { @@ -239,7 +230,7 @@ class EditorNode : public Node { enum { MAX_INIT_CALLBACKS = 128, - MAX_BUILD_CALLBACKS = 128 + MAX_BUILD_CALLBACKS = 128, }; struct ExportDefer { @@ -450,9 +441,6 @@ class EditorNode : public Node { uint64_t update_spinner_step_frame = 0; int update_spinner_step = 0; - String _tmp_import_path; - String external_file; - String open_navigate; String saving_scene; EditorProgress *save_scene_progress = nullptr; @@ -529,6 +517,8 @@ class EditorNode : public Node { void _android_build_source_selected(const String &p_file); void _android_export_preset_selected(int p_index); + void _android_install_build_template(); + void _android_explore_build_templates(); void _request_screenshot(); void _screenshot(bool p_use_utc = false); @@ -566,6 +556,7 @@ class EditorNode : public Node { void _update_undo_redo_allowed(); int _save_external_resources(bool p_also_save_external_data = false); + void _save_scene_silently(); void _set_current_scene(int p_idx); void _set_current_scene_nocheck(int p_idx); @@ -599,6 +590,7 @@ class EditorNode : public Node { void _renderer_selected(int); void _update_renderer_color(); void _add_renderer_entry(const String &p_renderer_name, bool p_mark_overridden); + void _set_renderer_name_save_and_restart(); void _exit_editor(int p_exit_code); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 6af67322b22..6634f46d720 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -349,10 +349,15 @@ void EditorPropertyArray::_create_new_property_slot() { slots.push_back(slot); } +void EditorPropertyArray::set_preview_value(bool p_preview_value) { + preview_value = p_preview_value; +} + void EditorPropertyArray::update_property() { Variant array = get_edited_property_value(); String array_type_name = Variant::get_type_name(array_type); + String array_sub_type_name; if (array_type == Variant::ARRAY && subtype != Variant::NIL) { String type_name; if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) { @@ -361,11 +366,23 @@ void EditorPropertyArray::update_property() { type_name = Variant::get_type_name(subtype); } - array_type_name = vformat("%s[%s]", array_type_name, type_name); + if (preview_value) { + array_sub_type_name = vformat("[%s] ", type_name); + } else { + array_type_name = vformat("%s[%s]", array_type_name, type_name); + } } if (!array.is_array()) { - edit->set_text(vformat(TTR("(Nil) %s"), array_type_name)); + if (preview_value) { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + edit->set_button_icon(get_editor_theme_icon(SNAME("Nil"))); + edit->set_text(array_type_name); + } else { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit->set_button_icon(Ref()); + edit->set_text(vformat(TTR("(Nil) %s"), array_type_name)); + } edit->set_pressed(false); if (container) { set_bottom_editor(nullptr); @@ -385,7 +402,25 @@ void EditorPropertyArray::update_property() { _page_changed(max_page); } - edit->set_text(vformat(TTR("%s (size %s)"), array_type_name, itos(size))); + if (preview_value) { + String ctr_str = array.get_construct_string().trim_prefix(array_type_name + "(").trim_suffix(")").replace("\n", ""); + if (array_type == Variant::ARRAY && subtype != Variant::NIL) { + int type_end = ctr_str.find("]("); + if (type_end > 0) { + ctr_str = ctr_str.substr(type_end + 2); + } + } + + edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + edit->set_button_icon(get_editor_theme_icon(array_type_name)); + edit->set_text(vformat("%s%s", array_sub_type_name, ctr_str)); + edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), array_type_name, array_sub_type_name, size)); + } else { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit->set_button_icon(Ref()); + edit->set_text(vformat(TTR("%s (size %s)"), array_type_name, itos(size))); + } bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); if (edit->is_pressed() != unfolded) { @@ -963,20 +998,7 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { EditorProperty *prop_key = nullptr; if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { - if (key_subtype == Variant::OBJECT) { - EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); - editor->setup("Object"); - prop_key = editor; - } else { - prop_key = EditorInspector::instantiate_property_editor(this, key_subtype, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE); - } - prop_key->set_read_only(true); - prop_key->set_selectable(false); - prop_key->set_focus_mode(Control::FOCUS_NONE); - prop_key->set_draw_background(false); - prop_key->set_use_folding(is_using_folding()); - prop_key->set_h_size_flags(SIZE_EXPAND_FILL); - prop_key->set_draw_label(false); + prop_key = memnew(EditorPropertyNil); hbox->add_child(prop_key); } @@ -1098,10 +1120,15 @@ void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_s } } +void EditorPropertyDictionary::set_preview_value(bool p_preview_value) { + preview_value = p_preview_value; +} + void EditorPropertyDictionary::update_property() { Variant updated_val = get_edited_property_value(); String dict_type_name = "Dictionary"; + String dict_sub_type_name; if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) { String key_subtype_name = "Variant"; if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) { @@ -1115,11 +1142,23 @@ void EditorPropertyDictionary::update_property() { } else if (value_subtype != Variant::NIL) { value_subtype_name = Variant::get_type_name(value_subtype); } - dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name); + if (preview_value) { + dict_sub_type_name = vformat("[%s, %s] ", key_subtype_name, value_subtype_name); + } else { + dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name); + } } if (updated_val.get_type() != Variant::DICTIONARY) { - edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property. + if (preview_value) { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + edit->set_button_icon(get_editor_theme_icon(SNAME("Nil"))); + edit->set_text(dict_type_name); + } else { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit->set_button_icon(Ref()); + edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); + } edit->set_pressed(false); if (container) { set_bottom_editor(nullptr); @@ -1135,7 +1174,25 @@ void EditorPropertyDictionary::update_property() { Dictionary dict = updated_val; object->set_dict(updated_val); - edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size())); + if (preview_value) { + String ctr_str = updated_val.get_construct_string().replace("\n", ""); + if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) { + int type_end = ctr_str.find("]("); + if (type_end > 0) { + ctr_str = ctr_str.substr(type_end + 2).trim_suffix(")"); + } + } + + edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + edit->set_button_icon(get_editor_theme_icon(dict_type_name)); + edit->set_text(vformat("%s%s", dict_sub_type_name, ctr_str)); + edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), dict_type_name, dict_sub_type_name, dict.size())); + } else { + edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit->set_button_icon(Ref()); + edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size())); + } bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); if (edit->is_pressed() != unfolded) { @@ -1201,6 +1258,44 @@ void EditorPropertyDictionary::update_property() { if (!slot_visible) { continue; } + + // Check if the editor property key needs to be updated. + if (slot.prop_key) { + Variant key; + object->get_by_property_name(slot.key_name, key); + Variant::Type key_type = key.get_type(); + + bool key_as_id = Object::cast_to(key); + if (key_type != slot.key_type || (key_type == Variant::OBJECT && key_as_id != slot.key_as_id)) { + slot.key_as_id = key_as_id; + slot.key_type = key_type; + EditorProperty *new_prop = nullptr; + if (key_type == Variant::OBJECT && key_as_id) { + EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID); + editor->setup("Object"); + new_prop = editor; + } else { + new_prop = EditorInspector::instantiate_property_editor(this, key_type, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE); + } + new_prop->set_read_only(true); + new_prop->set_selectable(false); + new_prop->set_focus_mode(Control::FOCUS_NONE); + new_prop->set_draw_background(false); + new_prop->set_use_folding(is_using_folding()); + new_prop->set_h_size_flags(SIZE_EXPAND_FILL); + new_prop->set_draw_label(false); + EditorPropertyArray *arr_prop = Object::cast_to(new_prop); + if (arr_prop) { + arr_prop->set_preview_value(true); + } + EditorPropertyDictionary *dict_prop = Object::cast_to(new_prop); + if (dict_prop) { + dict_prop->set_preview_value(true); + } + slot.set_key_prop(new_prop); + } + } + Variant value; object->get_by_property_name(slot.prop_name, value); Variant::Type value_type = value.get_type(); @@ -1233,7 +1328,6 @@ void EditorPropertyDictionary::update_property() { } else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) { Variant key = dict.get_key_at_index(slot.index); String cs = key.get_construct_string(); - slot.prop->set_label(cs); slot.prop->set_tooltip_text(cs); } diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index fd1aa973fdd..051dce2020e 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -117,6 +117,7 @@ class EditorPropertyArray : public EditorProperty { PopupMenu *change_type = nullptr; + bool preview_value = false; int page_length = 20; int page_index = 0; int changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE; @@ -171,6 +172,7 @@ class EditorPropertyArray : public EditorProperty { public: void setup(Variant::Type p_array_type, const String &p_hint_string = ""); + void set_preview_value(bool p_preview_value); virtual void update_property() override; virtual bool is_colored(ColorationMode p_mode) override; EditorPropertyArray(); @@ -184,7 +186,9 @@ class EditorPropertyDictionary : public EditorProperty { HBoxContainer *container = nullptr; int index = -1; Variant::Type type = Variant::VARIANT_MAX; + Variant::Type key_type = Variant::VARIANT_MAX; bool as_id = false; + bool key_as_id = false; EditorProperty *prop = nullptr; EditorProperty *prop_key = nullptr; String prop_name; @@ -204,6 +208,15 @@ class EditorPropertyDictionary : public EditorProperty { update_prop_or_index(); } + void set_key_prop(EditorProperty *p_prop) { + if (prop_key) { + prop_key->add_sibling(p_prop); + prop_key->queue_free(); + prop_key = p_prop; + update_prop_or_index(); + } + } + void update_prop_or_index() { prop->set_object_and_property(object.ptr(), prop_name); if (prop_key) { @@ -217,6 +230,7 @@ class EditorPropertyDictionary : public EditorProperty { PopupMenu *change_type = nullptr; bool updating = false; + bool preview_value = false; Ref object; int page_length = 20; int page_index = 0; @@ -254,6 +268,7 @@ class EditorPropertyDictionary : public EditorProperty { public: void setup(PropertyHint p_hint, const String &p_hint_string = ""); + void set_preview_value(bool p_preview_value); virtual void update_property() override; virtual bool is_colored(ColorationMode p_mode) override; EditorPropertyDictionary(); diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index 1c2341337f2..b96085e73bb 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -334,6 +334,10 @@ void EditorSettingsDialog::_event_config_confirmed() { void EditorSettingsDialog::_update_builtin_action(const String &p_name, const Array &p_events) { Array old_input_array = EditorSettings::get_singleton()->get_builtin_action_overrides(p_name); + if (old_input_array.is_empty()) { + List> defaults = InputMap::get_singleton()->get_builtins_with_feature_overrides_applied()[current_edited_identifier]; + old_input_array = _event_list_to_array_helper(defaults); + } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(vformat(TTR("Edit Built-in Action: %s"), p_name)); @@ -341,11 +345,11 @@ void EditorSettingsDialog::_update_builtin_action(const String &p_name, const Ar undo_redo->add_undo_method(EditorSettings::get_singleton(), "mark_setting_changed", "builtin_action_overrides"); undo_redo->add_do_method(EditorSettings::get_singleton(), "set_builtin_action_override", p_name, p_events); undo_redo->add_undo_method(EditorSettings::get_singleton(), "set_builtin_action_override", p_name, old_input_array); + undo_redo->add_do_method(this, "_update_shortcuts"); + undo_redo->add_undo_method(this, "_update_shortcuts"); undo_redo->add_do_method(this, "_settings_changed"); undo_redo->add_undo_method(this, "_settings_changed"); undo_redo->commit_action(); - - _update_shortcuts(); } void EditorSettingsDialog::_update_shortcut_events(const String &p_path, const Array &p_events) { diff --git a/editor/export/codesign.h b/editor/export/codesign.h index 88659dbf51d..7b2305929ed 100644 --- a/editor/export/codesign.h +++ b/editor/export/codesign.h @@ -277,7 +277,7 @@ class CodeSignCodeDirectory : public CodeSignBlob { uint32_t spare3; // Not used. uint64_t code_limit_64; // Set to 0 and ignore. // Version 0x20400 - uint64_t exec_seg_base; // Start of the signed code segmet. + uint64_t exec_seg_base; // Start of the signed code segment. uint64_t exec_seg_limit; // Code segment (__TEXT) vmsize. uint64_t exec_seg_flags; // Executable segment flags. // Version 0x20500 diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index b71d658b8e2..b73860217af 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -1687,8 +1687,8 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_ 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions 1 << 11); // Bit 11 is the language encoding flag. When set, filename and comment fields must be encoded using UTF-8. - String target = da->read_link(f); - zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); + const CharString target_utf8 = da->read_link(f).utf8(); + zipWriteInFileInZip(p_zip, target_utf8.get_data(), target_utf8.size()); zipCloseFileInZip(p_zip); } else if (da->current_is_dir()) { zip_folder_recursive(p_zip, p_root_path, p_folder.path_join(f), p_pkg_name); diff --git a/editor/export/project_zip_packer.cpp b/editor/export/project_zip_packer.cpp new file mode 100644 index 00000000000..8aa30aeeb13 --- /dev/null +++ b/editor/export/project_zip_packer.cpp @@ -0,0 +1,124 @@ +/**************************************************************************/ +/* project_zip_packer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "project_zip_packer.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" +#include "core/os/time.h" + +String ProjectZIPPacker::get_project_zip_safe_name() { + // Name the downloaded ZIP file to contain the project name and download date for easier organization. + // Replace characters not allowed (or risky) in Windows file names with safe characters. + // In the project name, all invalid characters become an empty string so that a name + // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". + const String project_name = GLOBAL_GET("application/config/name"); + const String project_name_safe = project_name.to_lower().replace(" ", "_"); + const String datetime_safe = + Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe)); + return output_name; +} + +void ProjectZIPPacker::pack_project_zip(const String &p_path) { + Ref io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + const String base_path = resource_path.substr(0, resource_path.rfind_char('/')) + "/"; + + zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); + _zip_recursive(resource_path, base_path, zip); + zipClose(zip, nullptr); +} + +void ProjectZIPPacker::_zip_file(const String &p_path, const String &p_base_path, zipFile p_zip) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + if (f.is_null()) { + WARN_PRINT("Unable to open file for zipping: " + p_path); + return; + } + Vector data; + uint64_t len = f->get_length(); + data.resize(len); + f->get_buffer(data.ptrw(), len); + + String path = p_path.replace_first(p_base_path, ""); + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + nullptr, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(p_zip, data.ptr(), data.size()); + zipCloseFileInZip(p_zip); +} + +void ProjectZIPPacker::_zip_recursive(const String &p_path, const String &p_base_path, zipFile p_zip) { + Ref dir = DirAccess::open(p_path); + if (dir.is_null()) { + WARN_PRINT("Unable to open directory for zipping: " + p_path); + return; + } + dir->list_dir_begin(); + String cur = dir->get_next(); + String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); + while (!cur.is_empty()) { + String cs = p_path.path_join(cur); + if (cur == "." || cur == ".." || cur == project_data_dir_name) { + // Skip + } else if (dir->current_is_dir()) { + String path = cs.replace_first(p_base_path, "") + "/"; + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + nullptr, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipCloseFileInZip(p_zip); + _zip_recursive(cs, p_base_path, p_zip); + } else { + _zip_file(cs, p_base_path, p_zip); + } + cur = dir->get_next(); + } +} diff --git a/platform/ios/joypad_ios.h b/editor/export/project_zip_packer.h similarity index 81% rename from platform/ios/joypad_ios.h rename to editor/export/project_zip_packer.h index 8bd4bd78f47..ed8fd1c5f2d 100644 --- a/platform/ios/joypad_ios.h +++ b/editor/export/project_zip_packer.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* joypad_ios.h */ +/* project_zip_packer.h */ /**************************************************************************/ /* This file is part of: */ /* REDOT ENGINE */ @@ -30,23 +30,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import +#ifndef PROJECT_ZIP_PACKER_H +#define PROJECT_ZIP_PACKER_H -@interface JoypadIOSObserver : NSObject +#include "core/io/zip_io.h" +#include "core/variant/variant.h" -- (void)startObserving; -- (void)startProcessing; -- (void)finishObserving; - -@end - -class JoypadIOS { -private: - JoypadIOSObserver *observer; +class ProjectZIPPacker { + static void _zip_file(const String &p_path, const String &p_base_path, zipFile p_zip); + static void _zip_recursive(const String &p_path, const String &p_base_path, zipFile p_zip); public: - JoypadIOS(); - ~JoypadIOS(); - - void start_processing(); + static String get_project_zip_safe_name(); + static void pack_project_zip(const String &p_path); }; + +#endif // PROJECT_ZIP_PACKER_H diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index bcabc209e34..c28c911cb65 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -84,7 +84,7 @@ Control *FileSystemList::make_custom_tooltip(const String &p_text) const { } void FileSystemList::_line_editor_submit(const String &p_text) { - if (popup_edit_commited) { + if (popup_edit_committed) { return; // Already processed by _text_editor_popup_modal_close } @@ -92,7 +92,7 @@ void FileSystemList::_line_editor_submit(const String &p_text) { return; // ESC pressed, app focus lost, or forced close from code. } - popup_edit_commited = true; // End edit popup processing. + popup_edit_committed = true; // End edit popup processing. popup_editor->hide(); emit_signal(SNAME("item_edited")); @@ -141,7 +141,7 @@ bool FileSystemList::edit_selected() { line_editor->set_text(name); line_editor->select(0, name.rfind_char('.')); - popup_edit_commited = false; // Start edit popup processing. + popup_edit_committed = false; // Start edit popup processing. popup_editor->popup(); popup_editor->child_controls_changed(); line_editor->grab_focus(); @@ -153,7 +153,7 @@ String FileSystemList::get_edit_text() { } void FileSystemList::_text_editor_popup_modal_close() { - if (popup_edit_commited) { + if (popup_edit_committed) { return; // Already processed by _text_editor_popup_modal_close } @@ -1186,7 +1186,7 @@ HashSet FileSystemDock::_get_valid_conversions_for_file_paths(const Vect return HashSet(); } - // Get a list of all potentional conversion-to targets. + // Get a list of all potential conversion-to targets. HashSet current_valid_conversion_to_targets; for (const Ref &E : conversions) { const String what = E->converts_to(); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 917e66f6ac0..8e0d9e3702f 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -62,7 +62,7 @@ class FileSystemTree : public Tree { class FileSystemList : public ItemList { GDCLASS(FileSystemList, ItemList); - bool popup_edit_commited = true; + bool popup_edit_committed = true; VBoxContainer *popup_editor_vb = nullptr; Popup *popup_editor = nullptr; LineEdit *line_editor = nullptr; diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index c5661d4ddd2..76212d95e72 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -323,7 +323,7 @@ void EditorRunBar::recovery_mode_show_dialog() { } void EditorRunBar::recovery_mode_reload_project() { - EditorNode::get_singleton()->trigger_menu_option(EditorNode::RELOAD_CURRENT_PROJECT, false); + EditorNode::get_singleton()->trigger_menu_option(EditorNode::PROJECT_RELOAD_CURRENT_PROJECT, false); } void EditorRunBar::play_main_scene(bool p_from_native) { diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index 624510a5c3c..d0224ac737f 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -50,8 +50,6 @@ #include "scene/gui/tab_bar.h" #include "scene/gui/texture_rect.h" -EditorSceneTabs *EditorSceneTabs::singleton = nullptr; - void EditorSceneTabs::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { @@ -166,6 +164,11 @@ void EditorSceneTabs::_reposition_active_tab(int p_to_index) { } void EditorSceneTabs::_update_context_menu() { +#define DISABLE_LAST_OPTION_IF(m_condition) \ + if (m_condition) { \ + scene_tabs_context_menu->set_item_disabled(-1, true); \ + } + scene_tabs_context_menu->clear(); scene_tabs_context_menu->reset_size(); @@ -175,12 +178,11 @@ void EditorSceneTabs::_update_context_menu() { scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), EditorNode::FILE_NEW_SCENE); if (tab_id >= 0) { scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), EditorNode::FILE_SAVE_SCENE); - _disable_menu_option_if(EditorNode::FILE_SAVE_SCENE, no_root_node); + DISABLE_LAST_OPTION_IF(no_root_node); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), EditorNode::FILE_SAVE_AS_SCENE); - _disable_menu_option_if(EditorNode::FILE_SAVE_AS_SCENE, no_root_node); + DISABLE_LAST_OPTION_IF(no_root_node); } - scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::FILE_SAVE_ALL_SCENES); bool can_save_all_scenes = false; for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) { if (!EditorNode::get_editor_data().get_scene_path(i).is_empty() && EditorNode::get_editor_data().get_edited_scene_root(i)) { @@ -188,39 +190,36 @@ void EditorSceneTabs::_update_context_menu() { break; } } - _disable_menu_option_if(EditorNode::FILE_SAVE_ALL_SCENES, !can_save_all_scenes); + scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::FILE_SAVE_ALL_SCENES); + DISABLE_LAST_OPTION_IF(!can_save_all_scenes); if (tab_id >= 0) { scene_tabs_context_menu->add_separator(); - scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), EditorNode::FILE_SHOW_IN_FILESYSTEM); - _disable_menu_option_if(EditorNode::FILE_SHOW_IN_FILESYSTEM, !ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id))); - scene_tabs_context_menu->add_item(TTR("Play This Scene"), EditorNode::FILE_RUN_SCENE); - _disable_menu_option_if(EditorNode::FILE_RUN_SCENE, no_root_node); + scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), SCENE_SHOW_IN_FILESYSTEM); + DISABLE_LAST_OPTION_IF(!ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id))); + scene_tabs_context_menu->add_item(TTR("Play This Scene"), SCENE_RUN); + DISABLE_LAST_OPTION_IF(no_root_node); scene_tabs_context_menu->add_separator(); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene"), EditorNode::FILE_CLOSE); - scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE), TTR("Close Tab")); + scene_tabs_context_menu->set_item_text(-1, TTR("Close Tab")); scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/reopen_closed_scene"), EditorNode::FILE_OPEN_PREV); - scene_tabs_context_menu->set_item_text(scene_tabs_context_menu->get_item_index(EditorNode::FILE_OPEN_PREV), TTR("Undo Close Tab")); - _disable_menu_option_if(EditorNode::FILE_OPEN_PREV, !EditorNode::get_singleton()->has_previous_scenes()); - scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), EditorNode::FILE_CLOSE_OTHERS); - _disable_menu_option_if(EditorNode::FILE_CLOSE_OTHERS, EditorNode::get_editor_data().get_edited_scene_count() <= 1); - scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), EditorNode::FILE_CLOSE_RIGHT); - _disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1); - scene_tabs_context_menu->add_item(TTR("Close All Tabs"), EditorNode::FILE_CLOSE_ALL); + scene_tabs_context_menu->set_item_text(-1, TTR("Undo Close Tab")); + DISABLE_LAST_OPTION_IF(!EditorNode::get_singleton()->has_previous_scenes()); + scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), SCENE_CLOSE_OTHERS); + DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() <= 1); + scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), SCENE_CLOSE_RIGHT); + DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1); + scene_tabs_context_menu->add_item(TTR("Close All Tabs"), SCENE_CLOSE_ALL); const PackedStringArray paths = { EditorNode::get_editor_data().get_scene_path(tab_id) }; EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, paths); } else { EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, {}); } - last_hovered_tab = tab_id; -} +#undef DISABLE_LAST_OPTION_IF -void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) { - if (p_condition) { - scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(p_option), true); - } + last_hovered_tab = tab_id; } void EditorSceneTabs::_custom_menu_option(int p_option) { diff --git a/editor/gui/editor_scene_tabs.h b/editor/gui/editor_scene_tabs.h index a42bb145f30..bc3db27a363 100644 --- a/editor/gui/editor_scene_tabs.h +++ b/editor/gui/editor_scene_tabs.h @@ -46,8 +46,18 @@ class TextureRect; class EditorSceneTabs : public MarginContainer { GDCLASS(EditorSceneTabs, MarginContainer); - static EditorSceneTabs *singleton; + inline static EditorSceneTabs *singleton = nullptr; +public: + enum { + SCENE_SHOW_IN_FILESYSTEM = 3000, // Prevents conflicts with EditorNode options. + SCENE_RUN, + SCENE_CLOSE_OTHERS, + SCENE_CLOSE_RIGHT, + SCENE_CLOSE_ALL, + }; + +private: PanelContainer *tabbar_panel = nullptr; HBoxContainer *tabbar_container = nullptr; @@ -72,7 +82,6 @@ class EditorSceneTabs : public MarginContainer { void _update_tab_titles(); void _reposition_active_tab(int p_to_index); void _update_context_menu(); - void _disable_menu_option_if(int p_option, bool p_condition); void _custom_menu_option(int p_option); void _tab_preview_done(const String &p_path, const Ref &p_preview, const Ref &p_small_preview, const Variant &p_udata); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 78f359494f9..e62608eba82 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -717,6 +717,7 @@ void EditorSpinSlider::_ensure_input_popup() { add_child(value_input_popup); value_input = memnew(LineEdit); + value_input->set_emoji_menu_enabled(false); value_input->set_focus_mode(FOCUS_CLICK); value_input_popup->add_child(value_input); value_input->set_anchors_and_offsets_preset(PRESET_FULL_RECT); diff --git a/editor/icons/PickerCursor.svg b/editor/icons/PickerCursor.svg index 2eaad3c1285..d59b2d53a29 100644 --- a/editor/icons/PickerCursor.svg +++ b/editor/icons/PickerCursor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/editor/icons/PickerCursorBg.svg b/editor/icons/PickerCursorBg.svg new file mode 100644 index 00000000000..fa51b709ad7 --- /dev/null +++ b/editor/icons/PickerCursorBg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index b81e033abb0..51dd9c4f5b1 100644 --- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -647,13 +647,13 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory // Scan descendants for mapped bones. bool found_mapped = false; - Vector decendants_to_process = src_skeleton->get_bone_children(src_idx); - while (decendants_to_process.size() > 0) { - int desc_idx = decendants_to_process[0]; - decendants_to_process.erase(desc_idx); + Vector descendants_to_process = src_skeleton->get_bone_children(src_idx); + while (descendants_to_process.size() > 0) { + int desc_idx = descendants_to_process[0]; + descendants_to_process.erase(desc_idx); Vector desc_children = src_skeleton->get_bone_children(desc_idx); for (const int &desc_child : desc_children) { - decendants_to_process.push_back(desc_child); + descendants_to_process.push_back(desc_child); } StringName desc_bone_name = is_renamed ? StringName(src_skeleton->get_bone_name(desc_idx)) : bone_map->find_profile_bone_name(src_skeleton->get_bone_name(desc_idx)); diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index f8ee1de745a..ca79fc41fdb 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -2365,12 +2365,12 @@ void ResourceImporterScene::get_import_options(const String &p_path, Listpush_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_type", PROPERTY_HINT_TYPE_STRING, "Node"), "")); r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "nodes/root_name"), "")); - List script_extentions; - ResourceLoader::get_recognized_extensions_for_type("Script", &script_extentions); + List script_extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &script_extensions); String script_ext_hint; - for (const String &E : script_extentions) { + for (const String &E : script_extensions) { if (!script_ext_hint.is_empty()) { script_ext_hint += ","; } diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index cc0d66b2c8b..84c5d5d5018 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -491,7 +491,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source _save_tex(*r_texture_import->slices, r_texture_import->save_path + "." + extension, r_texture_import->compress_mode, r_texture_import->lossy, Image::COMPRESS_S3TC /* IGNORED */, *r_texture_import->csource, r_texture_import->used_channels, r_texture_import->mipmaps, false); return; } - // Must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc). + // Must import in all formats, in order of priority (so platform chooses the best supported one. IE, etc2 over etc). // Android, GLES 2.x const bool can_s3tc_bptc = ResourceImporterTextureSettings::should_import_s3tc_bptc(); diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index 96303fea1b4..918a32a97d6 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -36,6 +36,7 @@ #include "core/debugger/debugger_marshalls.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_command_palette.h" +#include "editor/editor_feature_profile.h" #include "editor/editor_interface.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" @@ -51,6 +52,10 @@ #include "scene/gui/separator.h" void GameViewDebugger::_session_started(Ref p_session) { + if (!is_feature_enabled) { + return; + } + Array setup_data; Dictionary settings; settings["editors/panning/2d_editor_panning_scheme"] = EDITOR_GET("editors/panning/2d_editor_panning_scheme"); @@ -75,9 +80,17 @@ void GameViewDebugger::_session_started(Ref p_session) { } void GameViewDebugger::_session_stopped() { + if (!is_feature_enabled) { + return; + } + emit_signal(SNAME("session_stopped")); } +void GameViewDebugger::set_is_feature_enabled(bool p_enabled) { + is_feature_enabled = p_enabled; +} + void GameViewDebugger::set_suspend(bool p_enabled) { Array message; message.append(p_enabled); @@ -200,6 +213,9 @@ void GameView::_instance_starting_static(int p_idx, List &r_arguments) { } void GameView::_instance_starting(int p_idx, List &r_arguments) { + if (!is_feature_enabled) { + return; + } if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) { window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect); } @@ -208,6 +224,10 @@ void GameView::_instance_starting(int p_idx, List &r_arguments) { } void GameView::_play_pressed() { + if (!is_feature_enabled) { + return; + } + OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process(); if (current_process_id == 0) { return; @@ -233,6 +253,10 @@ void GameView::_play_pressed() { } void GameView::_stop_pressed() { + if (!is_feature_enabled) { + return; + } + EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true); embedded_process->reset(); _update_ui(); @@ -478,6 +502,10 @@ void GameView::_notification(int p_what) { } } +void GameView::set_is_feature_enabled(bool p_enabled) { + is_feature_enabled = p_enabled; +} + void GameView::set_state(const Dictionary &p_state) { if (p_state.has("hide_selection")) { hide_selection->set_pressed(p_state["hide_selection"]); @@ -803,6 +831,22 @@ void GameViewPlugin::_notification(int p_what) { } } +void GameViewPlugin::_feature_profile_changed() { + bool is_feature_enabled = true; + Ref profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); + if (profile.is_valid()) { + is_feature_enabled = !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME); + } + + if (debugger.is_valid()) { + debugger->set_is_feature_enabled(is_feature_enabled); + } + + if (game_view) { + game_view->set_is_feature_enabled(is_feature_enabled); + } +} + void GameViewPlugin::_window_visibility_changed(bool p_visible) { _focus_another_editor(); } @@ -836,6 +880,8 @@ GameViewPlugin::GameViewPlugin() { window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL); window_wrapper->hide(); window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_window_visibility_changed)); + + EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewPlugin::_feature_profile_changed)); } GameViewPlugin::~GameViewPlugin() { diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index 2ac83b7bf12..350068c5498 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -49,6 +49,7 @@ class GameViewDebugger : public EditorDebuggerPlugin { private: Vector> sessions; + bool is_feature_enabled = true; int node_type = RuntimeNodeSelect::NODE_TYPE_NONE; bool selection_visible = true; int select_mode = RuntimeNodeSelect::SELECT_MODE_SINGLE; @@ -61,6 +62,8 @@ class GameViewDebugger : public EditorDebuggerPlugin { static void _bind_methods(); public: + void set_is_feature_enabled(bool p_enabled); + void set_suspend(bool p_enabled); void next_frame(); @@ -97,6 +100,7 @@ class GameView : public VBoxContainer { Ref debugger; WindowWrapper *window_wrapper = nullptr; + bool is_feature_enabled = true; int active_sessions = 0; int screen_index_before_start = -1; @@ -165,6 +169,8 @@ class GameView : public VBoxContainer { void _notification(int p_what); public: + void set_is_feature_enabled(bool p_enabled); + void set_state(const Dictionary &p_state); Dictionary get_state() const; @@ -184,6 +190,7 @@ class GameViewPlugin : public EditorPlugin { String last_editor; + void _feature_profile_changed(); void _window_visibility_changed(bool p_visible); void _save_last_editor(const String &p_editor); void _focus_another_editor(); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index a8e400a8bd7..1fad74d2025 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -36,6 +36,8 @@ #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" +#include "modules/modules_enabled.gen.h" // For lightmapper_rd. + void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { LightmapGI::BakeError err = LightmapGI::BAKE_ERROR_OK; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index f0588ed33f1..7b52992d4e5 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -130,21 +130,22 @@ constexpr real_t MAX_FOV = 179; void ViewportNavigationControl::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (!is_connected(SceneStringName(mouse_exited), callable_mp(this, &ViewportNavigationControl::_on_mouse_exited))) { - connect(SceneStringName(mouse_exited), callable_mp(this, &ViewportNavigationControl::_on_mouse_exited)); - } - if (!is_connected(SceneStringName(mouse_entered), callable_mp(this, &ViewportNavigationControl::_on_mouse_entered))) { - connect(SceneStringName(mouse_entered), callable_mp(this, &ViewportNavigationControl::_on_mouse_entered)); - } - } break; - case NOTIFICATION_DRAW: { if (viewport != nullptr) { _draw(); _update_navigation(); } } break; + + case NOTIFICATION_MOUSE_ENTER: { + hovered = true; + queue_redraw(); + } break; + + case NOTIFICATION_MOUSE_EXIT: { + hovered = false; + queue_redraw(); + } break; } } @@ -281,16 +282,6 @@ void ViewportNavigationControl::_update_navigation() { } } -void ViewportNavigationControl::_on_mouse_entered() { - hovered = true; - queue_redraw(); -} - -void ViewportNavigationControl::_on_mouse_exited() { - hovered = false; - queue_redraw(); -} - void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) { nav_mode = p_nav_mode; } @@ -315,10 +306,6 @@ void ViewportRotationControl::_notification(int p_what) { axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor))); axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor))); queue_redraw(); - - if (!is_connected(SceneStringName(mouse_exited), callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { - connect(SceneStringName(mouse_exited), callable_mp(this, &ViewportRotationControl::_on_mouse_exited)); - } } break; case NOTIFICATION_DRAW: { @@ -326,6 +313,11 @@ void ViewportRotationControl::_notification(int p_what) { _draw(); } } break; + + case NOTIFICATION_MOUSE_EXIT: { + focused_axis = -2; + queue_redraw(); + } break; } } @@ -421,7 +413,7 @@ void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bo orbiting_index = p_index; } } else { - if (focused_axis > -1) { + if (focused_axis > -1 && gizmo_activated) { viewport->_menu_option(axis_menu_options[focused_axis]); _update_focus(); } @@ -434,10 +426,11 @@ void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bo } void ViewportRotationControl::_process_drag(Ref p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) { - if (orbiting_index == p_index) { + if (orbiting_index == p_index && gizmo_activated) { if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); orbiting_mouse_start = p_position; + viewport->previous_cursor = viewport->cursor; } viewport->_nav_orbit(p_event, p_relative_position); focused_axis = -1; @@ -449,10 +442,35 @@ void ViewportRotationControl::_process_drag(Ref p_event void ViewportRotationControl::gui_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); + // Key events + const Ref k = p_event; + + if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) { + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse(orbiting_mouse_start); + viewport->cursor = viewport->previous_cursor; + gizmo_activated = false; + } + } + // Mouse events const Ref mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { - _process_click(100, mb->get_position(), mb->is_pressed()); + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::LEFT) { + _process_click(100, mb->get_position(), mb->is_pressed()); + if (mb->is_pressed()) { + gizmo_activated = true; + grab_focus(); + } + } else if (mb->get_button_index() == MouseButton::RIGHT) { + if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->warp_mouse(orbiting_mouse_start); + viewport->cursor = viewport->previous_cursor; + gizmo_activated = false; + } + } } const Ref mm = p_event; @@ -496,11 +514,6 @@ void ViewportRotationControl::_update_focus() { } } -void ViewportRotationControl::_on_mouse_exited() { - focused_axis = -2; - queue_redraw(); -} - void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) { viewport = p_viewport; } @@ -2074,8 +2087,8 @@ void Node3DEditorViewport::_sinput(const Ref &p_event) { } bool current_hover_handle_secondary = false; - int curreny_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary); - if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != curreny_hover_handle || found_handle_secondary != current_hover_handle_secondary) { + int current_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary); + if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != current_hover_handle || found_handle_secondary != current_hover_handle_secondary) { spatial_editor->set_current_hover_gizmo(found_gizmo); spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary); spatial_editor->get_single_selected_node()->update_gizmos(); @@ -3327,7 +3340,7 @@ void Node3DEditorViewport::_draw() { force_over_plugin_list->forward_3d_force_draw_over_viewport(surface); } - if (surface->has_focus()) { + if (surface->has_focus() || rotation_control->has_focus()) { Size2 size = surface->get_size(); Rect2 r = Rect2(Point2(), size); get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(surface->get_canvas_item(), r); @@ -5780,6 +5793,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE); rotation_control->set_h_size_flags(SIZE_SHRINK_END); rotation_control->set_viewport(this); + rotation_control->set_focus_mode(FOCUS_CLICK); top_right_vbox->add_child(rotation_control); frame_time_panel = memnew(PanelContainer); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 45f31b327fc..a7bb3d5fc69 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -87,6 +87,7 @@ class ViewportRotationControl : public Control { Vector2i orbiting_mouse_start; int orbiting_index = -1; int focused_axis = -2; + bool gizmo_activated = false; const float AXIS_CIRCLE_RADIUS = 8.0f * EDSCALE; @@ -97,7 +98,6 @@ class ViewportRotationControl : public Control { void _draw_axis(const Axis2D &p_axis); void _get_sorted_axis(Vector &r_axis); void _update_focus(); - void _on_mouse_exited(); void _process_click(int p_index, Vector2 p_position, bool p_pressed); void _process_drag(Ref p_event, int p_index, Vector2 p_position, Vector2 p_relative_position); @@ -415,6 +415,7 @@ class Node3DEditorViewport : public Control { // so one cursor is the real cursor, while the other can be an interpolated version. Cursor cursor; // Immediate cursor Cursor camera_cursor; // That one may be interpolated (don't modify this one except for smoothing purposes) + Cursor previous_cursor; // Storing previous cursor state for canceling purposes void scale_fov(real_t p_fov_offset); void reset_fov(); @@ -1048,8 +1049,6 @@ class ViewportNavigationControl : public Control { void _notification(int p_what); virtual void gui_input(const Ref &p_event) override; void _draw(); - void _on_mouse_entered(); - void _on_mouse_exited(); void _process_click(int p_index, Vector2 p_position, bool p_pressed); void _process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position); void _update_navigation(); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 94225e1d23b..449638fb7f6 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -342,16 +342,19 @@ void Path3DGizmo::redraw() { // Path3D as a ribbon. ribbon_ptr[i] = p1; - // Fish Bone. - const Vector3 p_left = p1 + (side + forward - up * 0.3) * 0.06; - const Vector3 p_right = p1 + (-side + forward - up * 0.3) * 0.06; - - const int bone_idx = i * 4; - - bones_ptr[bone_idx] = p1; - bones_ptr[bone_idx + 1] = p_left; - bones_ptr[bone_idx + 2] = p1; - bones_ptr[bone_idx + 3] = p_right; + if (i % 4 == 0) { + // Draw fish bone every 4 points to reduce visual noise and performance impact + // (compared to drawing it for every point). + const Vector3 p_left = p1 + (side + forward - up * 0.3) * 0.06; + const Vector3 p_right = p1 + (-side + forward - up * 0.3) * 0.06; + + const int bone_idx = i * 4; + + bones_ptr[bone_idx] = p1; + bones_ptr[bone_idx + 1] = p_left; + bones_ptr[bone_idx + 2] = p1; + bones_ptr[bone_idx + 3] = p_right; + } } add_collision_segments(_collision_segments); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 4c5e4e7b1e9..8bde091ed24 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1279,8 +1279,8 @@ Ref