diff options
43 files changed, 687 insertions, 483 deletions
diff --git a/SConstruct b/SConstruct index 81ce4bca52..cfedcc84ed 100644 --- a/SConstruct +++ b/SConstruct @@ -61,7 +61,6 @@ _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("modules.modules_builders", "modules/modules_builders.py") # Local import methods @@ -69,7 +68,7 @@ import glsl_builders import gles3_builders import scu_builders from methods import print_warning, print_error -from platform_methods import architectures, architecture_aliases, generate_export_icons +from platform_methods import architectures, architecture_aliases if ARGUMENTS.get("target", "editor") == "editor": _helper_module("editor.editor_builders", "editor/editor_builders.py") @@ -107,7 +106,6 @@ for x in sorted(glob.glob("platform/*")): if os.path.exists(x + "/export/export.cpp"): platform_exporters.append(platform_name) - generate_export_icons(x, platform_name) if os.path.exists(x + "/api/api.cpp"): platform_apis.append(platform_name) if detect.can_build(): @@ -428,7 +426,7 @@ for name, path in modules_detected.items(): sys.path.remove(path) sys.modules.pop("config") -methods.write_modules(modules_detected) +env.modules_detected = modules_detected # Update the environment again after all the module options are added. opts.Update(env) @@ -544,7 +542,7 @@ env.Append(CFLAGS=env.get("cflags", "").split()) env.Append(LINKFLAGS=env.get("linkflags", "").split()) # Feature build profile -disabled_classes = [] +env.disabled_classes = [] if env["build_profile"] != "": print('Using feature build profile: "{}"'.format(env["build_profile"])) import json @@ -552,7 +550,7 @@ if env["build_profile"] != "": try: ft = json.load(open(env["build_profile"])) if "disabled_classes" in ft: - disabled_classes = ft["disabled_classes"] + env.disabled_classes = ft["disabled_classes"] if "disabled_build_options" in ft: dbo = ft["disabled_build_options"] for c in dbo: @@ -560,7 +558,6 @@ if env["build_profile"] != "": except: print_error('Failed to open feature build profile: "{}"'.format(env["build_profile"])) Exit(255) -methods.write_disabled_classes(disabled_classes) # Platform specific flags. # These can sometimes override default options. @@ -926,7 +923,7 @@ if env.editor_build: print_error("Not all modules required by editor builds are enabled.") Exit(255) -methods.generate_version_header(env.module_version_string) +env.version_info = methods.get_version_info(env.module_version_string) env["PROGSUFFIX_WRAP"] = suffix + env.module_version_string + ".console" + env["PROGSUFFIX"] env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"] diff --git a/core/SCsub b/core/SCsub index 91620cb075..640c6de6a1 100644 --- a/core/SCsub +++ b/core/SCsub @@ -4,44 +4,9 @@ Import("env") import core_builders import methods - -env.core_sources = [] - - -# Generate AES256 script encryption key import os -txt = "0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0" -if "SCRIPT_AES256_ENCRYPTION_KEY" in os.environ: - key = os.environ["SCRIPT_AES256_ENCRYPTION_KEY"] - ec_valid = True - if len(key) != 64: - ec_valid = False - else: - txt = "" - for i in range(len(key) >> 1): - if i > 0: - txt += "," - txts = "0x" + key[i * 2 : i * 2 + 2] - try: - int(txts, 16) - except Exception: - ec_valid = False - txt += txts - if not ec_valid: - methods.print_error( - f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{key}".\n' - "Unset 'SCRIPT_AES256_ENCRYPTION_KEY' in your environment " - "or make sure that it contains exactly 64 hexadecimal characters." - ) - Exit(255) - - -script_encryption_key_contents = ( - '#include "core/config/project_settings.h"\nuint8_t script_encryption_key[32]={' + txt + "};\n" -) - -methods.write_file_if_needed("script_encryption_key.gen.cpp", script_encryption_key_contents) +env.core_sources = [] # Add required thirdparty code. @@ -193,8 +158,96 @@ env.core_sources += thirdparty_obj # Godot source files env.add_source_files(env.core_sources, "*.cpp") -env.add_source_files(env.core_sources, "script_encryption_key.gen.cpp") -env.add_source_files(env.core_sources, "version_hash.gen.cpp") + + +# Generate disabled classes +def disabled_class_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for c in source[0].read(): + cs = c.strip() + if cs != "": + file.write(f"#define ClassDB_Disable_{cs} 1") + + +env.CommandNoCache("disabled_classes.gen.h", env.Value(env.disabled_classes), env.Run(disabled_class_builder)) + + +# Generate version info +def version_info_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + """\ +#define VERSION_SHORT_NAME "{short_name}" +#define VERSION_NAME "{name}" +#define VERSION_MAJOR {major} +#define VERSION_MINOR {minor} +#define VERSION_PATCH {patch} +#define VERSION_STATUS "{status}" +#define VERSION_BUILD "{build}" +#define VERSION_MODULE_CONFIG "{module_config}" +#define VERSION_WEBSITE "{website}" +#define VERSION_DOCS_BRANCH "{docs_branch}" +#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH +""".format( + **env.version_info + ) + ) + + +env.CommandNoCache("version_generated.gen.h", "#version.py", env.Run(version_info_builder)) + + +# Generate version hash +def version_hash_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + """\ +#include "core/version.h" + +const char *const VERSION_HASH = "{git_hash}"; +const uint64_t VERSION_TIMESTAMP = {git_timestamp}; +""".format( + **env.version_info + ) + ) + + +gen_hash = env.CommandNoCache( + "version_hash.gen.cpp", env.Value(env.version_info["git_hash"]), env.Run(version_hash_builder) +) +env.add_source_files(env.core_sources, gen_hash) + + +# Generate AES256 script encryption key +def encryption_key_builder(target, source, env): + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "core/config/project_settings.h" + +uint8_t script_encryption_key[32] = {{ + {source[0]} +}};""" + ) + + +gdkey = os.environ.get("SCRIPT_AES256_ENCRYPTION_KEY", "0" * 64) +ec_valid = len(gdkey) == 64 +if ec_valid: + try: + gdkey = ", ".join([str(int(f"{a}{b}", 16)) for a, b in zip(gdkey[0::2], gdkey[1::2])]) + except Exception: + ec_valid = False +if not ec_valid: + methods.print_error( + f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{gdkey}".\n' + "Unset `SCRIPT_AES256_ENCRYPTION_KEY` in your environment " + "or make sure that it contains exactly 64 hexadecimal characters." + ) + Exit(255) +gen_encrypt = env.CommandNoCache("script_encryption_key.gen.cpp", env.Value(gdkey), env.Run(encryption_key_builder)) +env.add_source_files(env.core_sources, gen_encrypt) + # Certificates env.Depends( diff --git a/core/templates/command_queue_mt.cpp b/core/templates/command_queue_mt.cpp index d9e5e0b217..ef75a70868 100644 --- a/core/templates/command_queue_mt.cpp +++ b/core/templates/command_queue_mt.cpp @@ -42,6 +42,7 @@ void CommandQueueMT::unlock() { } CommandQueueMT::CommandQueueMT() { + command_mem.reserve(DEFAULT_COMMAND_MEM_SIZE_KB * 1024); } CommandQueueMT::~CommandQueueMT() { diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index dbf938a117..349404d75b 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -325,9 +325,7 @@ class CommandQueueMT { /***** BASE *******/ - enum { - DEFAULT_COMMAND_MEM_SIZE_KB = 256, - }; + static const uint32_t DEFAULT_COMMAND_MEM_SIZE_KB = 64; BinaryMutex mutex; LocalVector<uint8_t> command_mem; diff --git a/doc/classes/Crypto.xml b/doc/classes/Crypto.xml index 65abc4c641..0effd54fb9 100644 --- a/doc/classes/Crypto.xml +++ b/doc/classes/Crypto.xml @@ -8,66 +8,67 @@ Currently, this includes asymmetric key encryption/decryption, signing/verification, and generating cryptographically secure random bytes, RSA keys, HMAC digests, and self-signed [X509Certificate]s. [codeblocks] [gdscript] - extends Node - var crypto = Crypto.new() - var key = CryptoKey.new() - var cert = X509Certificate.new() - - func _ready(): - # Generate new RSA key. - key = crypto.generate_rsa(4096) - # Generate new self-signed certificate with the given key. - cert = crypto.generate_self_signed_certificate(key, "CN=mydomain.com,O=My Game Company,C=IT") - # Save key and certificate in the user folder. - key.save("user://generated.key") - cert.save("user://generated.crt") - # Encryption - var data = "Some data" - var encrypted = crypto.encrypt(key, data.to_utf8_buffer()) - # Decryption - var decrypted = crypto.decrypt(key, encrypted) - # Signing - var signature = crypto.sign(HashingContext.HASH_SHA256, data.sha256_buffer(), key) - # Verifying - var verified = crypto.verify(HashingContext.HASH_SHA256, data.sha256_buffer(), signature, key) - # Checks - assert(verified) - assert(data.to_utf8_buffer() == decrypted) + + # Generate new RSA key. + var key = crypto.generate_rsa(4096) + + # Generate new self-signed certificate with the given key. + var cert = crypto.generate_self_signed_certificate(key, "CN=mydomain.com,O=My Game Company,C=IT") + + # Save key and certificate in the user folder. + key.save("user://generated.key") + cert.save("user://generated.crt") + + # Encryption + var data = "Some data" + var encrypted = crypto.encrypt(key, data.to_utf8_buffer()) + + # Decryption + var decrypted = crypto.decrypt(key, encrypted) + + # Signing + var signature = crypto.sign(HashingContext.HASH_SHA256, data.sha256_buffer(), key) + + # Verifying + var verified = crypto.verify(HashingContext.HASH_SHA256, data.sha256_buffer(), signature, key) + + # Checks + assert(verified) + assert(data.to_utf8_buffer() == decrypted) [/gdscript] [csharp] using Godot; using System.Diagnostics; - public partial class MyNode : Node - { - private Crypto _crypto = new Crypto(); - private CryptoKey _key = new CryptoKey(); - private X509Certificate _cert = new X509Certificate(); - - public override void _Ready() - { - // Generate new RSA key. - _key = _crypto.GenerateRsa(4096); - // Generate new self-signed certificate with the given key. - _cert = _crypto.GenerateSelfSignedCertificate(_key, "CN=mydomain.com,O=My Game Company,C=IT"); - // Save key and certificate in the user folder. - _key.Save("user://generated.key"); - _cert.Save("user://generated.crt"); - // Encryption - string data = "Some data"; - byte[] encrypted = _crypto.Encrypt(_key, data.ToUtf8Buffer()); - // Decryption - byte[] decrypted = _crypto.Decrypt(_key, encrypted); - // Signing - byte[] signature = _crypto.Sign(HashingContext.HashType.Sha256, Data.Sha256Buffer(), _key); - // Verifying - bool verified = _crypto.Verify(HashingContext.HashType.Sha256, Data.Sha256Buffer(), signature, _key); - // Checks - Debug.Assert(verified); - Debug.Assert(data.ToUtf8Buffer() == decrypted); - } - } + Crypto crypto = new Crypto(); + + // Generate new RSA key. + CryptoKey key = crypto.GenerateRsa(4096); + + // Generate new self-signed certificate with the given key. + X509Certificate cert = crypto.GenerateSelfSignedCertificate(key, "CN=mydomain.com,O=My Game Company,C=IT"); + + // Save key and certificate in the user folder. + key.Save("user://generated.key"); + cert.Save("user://generated.crt"); + + // Encryption + string data = "Some data"; + byte[] encrypted = crypto.Encrypt(key, data.ToUtf8Buffer()); + + // Decryption + byte[] decrypted = crypto.Decrypt(key, encrypted); + + // Signing + byte[] signature = crypto.Sign(HashingContext.HashType.Sha256, Data.Sha256Buffer(), key); + + // Verifying + bool verified = crypto.Verify(HashingContext.HashType.Sha256, Data.Sha256Buffer(), signature, key); + + // Checks + Debug.Assert(verified); + Debug.Assert(data.ToUtf8Buffer() == decrypted); [/csharp] [/codeblocks] </description> diff --git a/doc/classes/CryptoKey.xml b/doc/classes/CryptoKey.xml index ff826a3ae5..dd128b6806 100644 --- a/doc/classes/CryptoKey.xml +++ b/doc/classes/CryptoKey.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CryptoKey" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A cryptographic key (RSA). + A cryptographic key (RSA or elliptic-curve). </brief_description> <description> The CryptoKey class represents a cryptographic key. Keys can be loaded and saved like any other [Resource]. diff --git a/doc/classes/SkeletonModifier3D.xml b/doc/classes/SkeletonModifier3D.xml index fab33750ea..c0b1b6fd53 100644 --- a/doc/classes/SkeletonModifier3D.xml +++ b/doc/classes/SkeletonModifier3D.xml @@ -6,9 +6,19 @@ <description> [SkeletonModifier3D] retrieves a target [Skeleton3D] by having a [Skeleton3D] parent. If there is [AnimationMixer], modification always performs after playback process of the [AnimationMixer]. + This node should be used to implement custom IK solvers, constraints, or skeleton physics </description> <tutorials> </tutorials> + <methods> + <method name="_process_modification" qualifiers="virtual"> + <return type="void" /> + <description> + Override this virtual method to implement a custom skeleton modifier. You should do things like get the [Skeleton3D]'s current pose and apply the pose here. + [method _process_modification] must not apply [member influence] to bone poses because the [Skeleton3D] automatically applies influence to all bone poses set by the modifier. + </description> + </method> + </methods> <members> <member name="active" type="bool" setter="set_active" getter="is_active" default="true"> If [code]true[/code], the [SkeletonModifier3D] will be processing. diff --git a/editor/SCsub b/editor/SCsub index e3b17b83f8..e613a71238 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -10,40 +10,59 @@ import editor_builders import methods -def _make_doc_data_class_path(to_path): - file_path = os.path.join(to_path, "doc_data_class_path.gen.h") - - class_path_data = "" - class_path_data += "static const int _doc_data_class_path_count = " + str(len(env.doc_class_path)) + ";\n" - class_path_data += "struct _DocDataClassPath { const char* name; const char* path; };\n" - class_path_data += ( - "static const _DocDataClassPath _doc_data_class_paths[" + str(len(env.doc_class_path) + 1) + "] = {\n" - ) - for c in sorted(env.doc_class_path): - class_path_data += '\t{"' + c + '", "' + env.doc_class_path[c] + '"},\n' - class_path_data += "\t{nullptr, nullptr}\n" - class_path_data += "};\n" - - methods.write_file_if_needed(file_path, class_path_data) - - if env.editor_build: + # Generate doc data paths + def doc_data_class_path_builder(target, source, env): + paths = dict(sorted(source[0].read().items())) + data = "\n".join([f'\t{{"{key}", "{value}"}},' for key, value in paths.items()]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +static const int _doc_data_class_path_count = {len(paths)}; + +struct _DocDataClassPath {{ + const char *name; + const char *path; +}}; + +static const _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) + 1}] = {{ +{data} + {{nullptr, nullptr}}, +}}; +""" + ) + + env.CommandNoCache("doc_data_class_path.gen.h", env.Value(env.doc_class_path), env.Run(doc_data_class_path_builder)) + # Register exporters - reg_exporters_inc = '#include "register_exporters.h"\n\n' - reg_exporters = "void register_exporters() {\n" + def register_exporters_builder(target, source, env): + platforms = source[0].read() + exp_inc = "\n".join([f'#include "platform/{p}/export/export.h"' for p in platforms]) + exp_reg = "\n".join([f"\tregister_{p}_exporter();" for p in platforms]) + exp_type = "\n".join([f"\tregister_{p}_exporter_types();" for p in platforms]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_exporters.h" + +{exp_inc} + +void register_exporters() {{ +{exp_reg} +}} + +void register_exporter_types() {{ +{exp_type} +}} +""" + ) + + gen_exporters = env.CommandNoCache( + "register_exporters.gen.cpp", env.Value(env.platform_exporters), env.Run(register_exporters_builder) + ) for e in env.platform_exporters: # Add all .cpp files in export folder - env.add_source_files(env.editor_sources, "../platform/" + e + "/export/" + "*.cpp") - - reg_exporters += "\tregister_" + e + "_exporter();\n" - reg_exporters_inc += '#include "platform/' + e + '/export/export.h"\n' - reg_exporters += "}\n\n" - reg_exporters += "void register_exporter_types() {\n" - for e in env.platform_exporters: - reg_exporters += "\tregister_" + e + "_exporter_types();\n" - reg_exporters += "}\n" - - methods.write_file_if_needed("register_exporters.gen.cpp", reg_exporters_inc + reg_exporters) + env.add_source_files(env.editor_sources, f"../platform/{e}/export/*.cpp") # Core API documentation. docs = [] @@ -61,8 +80,6 @@ if env.editor_build: else: docs += Glob(d + "/*.xml") # Custom. - _make_doc_data_class_path(env.Dir("#editor").abspath) - docs = sorted(docs) env.Depends("#editor/doc_data_compressed.gen.h", docs) env.CommandNoCache( @@ -115,7 +132,7 @@ if env.editor_build: ) env.add_source_files(env.editor_sources, "*.cpp") - env.add_source_files(env.editor_sources, "register_exporters.gen.cpp") + env.add_source_files(env.editor_sources, gen_exporters) SConscript("debugger/SCsub") SConscript("export/SCsub") diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 7d26cb21fb..08e75ee812 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1964,6 +1964,7 @@ void EditorFileDialog::_bind_methods() { Option defaults; base_property_helper.set_prefix("option_"); + base_property_helper.set_array_length_getter(&EditorFileDialog::get_option_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &EditorFileDialog::set_option_name, &EditorFileDialog::get_option_name); base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &EditorFileDialog::set_option_values, &EditorFileDialog::get_option_values); base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &EditorFileDialog::set_option_default, &EditorFileDialog::get_option_default); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index e2ef7c08ac..8f58a6a6f9 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4073,6 +4073,13 @@ void CanvasItemEditor::_notification(int p_what) { override_camera_button->set_pressed(false); } } break; + + case NOTIFICATION_APPLICATION_FOCUS_OUT: { + if (drag_type != DRAG_NONE) { + _reset_drag(); + viewport->queue_redraw(); + } + } break; } } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 3a1de937e2..6f0a8bc909 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2865,21 +2865,24 @@ void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node) { void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties, bool p_remove_old) { ERR_FAIL_COND_MSG(!p_node->is_inside_tree(), "_replace_node() can't be called on a node outside of tree. You might have called it twice."); - Node *n = p_node; + Node *oldnode = p_node; Node *newnode = p_by_node; if (p_keep_properties) { - Node *default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(n->get_class())); + Node *default_oldnode = Object::cast_to<Node>(ClassDB::instantiate(oldnode->get_class())); + List<PropertyInfo> pinfo; - n->get_property_list(&pinfo); + oldnode->get_property_list(&pinfo); for (const PropertyInfo &E : pinfo) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - if (default_oldnode->get(E.name) != n->get(E.name)) { - newnode->set(E.name, n->get(E.name)); + bool valid; + const Variant &default_val = default_oldnode->get(E.name, &valid); + if (!valid || default_val != oldnode->get(E.name)) { + newnode->set(E.name, oldnode->get(E.name)); } } @@ -2891,10 +2894,10 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro //reconnect signals List<MethodInfo> sl; - n->get_signal_list(&sl); + oldnode->get_signal_list(&sl); for (const MethodInfo &E : sl) { List<Object::Connection> cl; - n->get_signal_connection_list(E.name, &cl); + oldnode->get_signal_connection_list(E.name, &cl); for (const Object::Connection &c : cl) { if (!(c.flags & Object::CONNECT_PERSIST)) { @@ -2904,15 +2907,15 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro } } - String newname = n->get_name(); + String newname = oldnode->get_name(); List<Node *> to_erase; - for (int i = 0; i < n->get_child_count(); i++) { - if (n->get_child(i)->get_owner() == nullptr && n->is_owned_by_parent()) { - to_erase.push_back(n->get_child(i)); + for (int i = 0; i < oldnode->get_child_count(); i++) { + if (oldnode->get_child(i)->get_owner() == nullptr && oldnode->is_owned_by_parent()) { + to_erase.push_back(oldnode->get_child(i)); } } - n->replace_by(newnode, true); + oldnode->replace_by(newnode, true); //small hack to make collisionshapes and other kind of nodes to work for (int i = 0; i < newnode->get_child_count(); i++) { @@ -2928,7 +2931,7 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro _push_item(newnode); if (p_remove_old) { - memdelete(n); + memdelete(oldnode); while (to_erase.front()) { memdelete(to_erase.front()->get()); diff --git a/editor/themes/editor_color_map.cpp b/editor/themes/editor_color_map.cpp index 99bcf109d0..9046a8b688 100644 --- a/editor/themes/editor_color_map.cpp +++ b/editor/themes/editor_color_map.cpp @@ -169,9 +169,7 @@ void EditorColorMap::create() { add_conversion_exception("ZoomReset"); add_conversion_exception("LockViewport"); add_conversion_exception("GroupViewport"); - add_conversion_exception("StatusError"); add_conversion_exception("StatusSuccess"); - add_conversion_exception("StatusWarning"); add_conversion_exception("OverbrightIndicator"); add_conversion_exception("MaterialPreviewCube"); add_conversion_exception("MaterialPreviewSphere"); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index ee008e5636..2ef62c60a2 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -436,8 +436,8 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme, if (!p_config.dark_theme) { // Darken some colors to be readable on a light background. p_config.success_color = p_config.success_color.lerp(p_config.mono_color, 0.35); - p_config.warning_color = p_config.warning_color.lerp(p_config.mono_color, 0.35); - p_config.error_color = p_config.error_color.lerp(p_config.mono_color, 0.25); + p_config.warning_color = Color(0.82, 0.56, 0.1); + p_config.error_color = Color(0.8, 0.22, 0.22); } p_theme->set_color("mono_color", EditorStringName(Editor), p_config.mono_color); @@ -1901,14 +1901,20 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme // When pressed, don't tint the icons with the accent color, just leave them normal. p_theme->set_color("icon_pressed_color", "EditorLogFilterButton", p_config.icon_normal_color); // When unpressed, dim the icons. - p_theme->set_color("icon_normal_color", "EditorLogFilterButton", p_config.icon_disabled_color); + Color icon_normal_color = Color(p_config.icon_normal_color, (p_config.dark_theme ? 0.4 : 0.8)); + p_theme->set_color("icon_normal_color", "EditorLogFilterButton", icon_normal_color); + Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_theme ? 1.15 : 1.0); + icon_hover_color.a = 1.0; + p_theme->set_color("icon_hover_color", "EditorLogFilterButton", icon_hover_color); // When pressed, add a small bottom border to the buttons to better show their active state, // similar to active tabs. Ref<StyleBoxFlat> editor_log_button_pressed = style_flat_button_pressed->duplicate(); editor_log_button_pressed->set_border_width(SIDE_BOTTOM, 2 * EDSCALE); editor_log_button_pressed->set_border_color(p_config.accent_color); - + if (!p_config.dark_theme) { + editor_log_button_pressed->set_bg_color(flat_pressed_color.lightened(0.5)); + } p_theme->set_stylebox("normal", "EditorLogFilterButton", style_flat_button); p_theme->set_stylebox("hover", "EditorLogFilterButton", style_flat_button_hover); p_theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed); diff --git a/methods.py b/methods.py index 0c29632f10..da221cc0ea 100644 --- a/methods.py +++ b/methods.py @@ -3,13 +3,16 @@ import sys import re import glob import subprocess +import contextlib from collections import OrderedDict from collections.abc import Mapping from enum import Enum -from typing import Iterator +from typing import Generator, Optional +from io import TextIOWrapper, StringIO from pathlib import Path from os.path import normpath, basename + # Get the "Godot" folder name ahead of time base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" base_folder_only = os.path.basename(os.path.normpath(base_folder_path)) @@ -277,79 +280,6 @@ def get_version_info(module_version_string="", silent=False): return version_info -_cleanup_env = None -_cleanup_bool = False - - -def write_file_if_needed(path, string): - """Generates a file only if it doesn't already exist or the content has changed. - - Utilizes a dedicated SCons environment to ensure the files are properly removed - during cleanup; will not attempt to create files during cleanup. - - - `path` - Path to the file in question; used to create cleanup logic. - - `string` - Content to compare against an existing file. - """ - global _cleanup_env - global _cleanup_bool - - if _cleanup_env is None: - from SCons.Environment import Environment - - _cleanup_env = Environment() - _cleanup_bool = _cleanup_env.GetOption("clean") - - _cleanup_env.Clean("#", path) - if _cleanup_bool: - return - - try: - with open(path, "r", encoding="utf-8", newline="\n") as f: - if f.read() == string: - return - except FileNotFoundError: - pass - - with open(path, "w", encoding="utf-8", newline="\n") as f: - f.write(string) - - -def generate_version_header(module_version_string=""): - version_info = get_version_info(module_version_string) - - version_info_header = """\ -/* THIS FILE IS GENERATED DO NOT EDIT */ -#ifndef VERSION_GENERATED_GEN_H -#define VERSION_GENERATED_GEN_H -#define VERSION_SHORT_NAME "{short_name}" -#define VERSION_NAME "{name}" -#define VERSION_MAJOR {major} -#define VERSION_MINOR {minor} -#define VERSION_PATCH {patch} -#define VERSION_STATUS "{status}" -#define VERSION_BUILD "{build}" -#define VERSION_MODULE_CONFIG "{module_config}" -#define VERSION_WEBSITE "{website}" -#define VERSION_DOCS_BRANCH "{docs_branch}" -#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH -#endif // VERSION_GENERATED_GEN_H -""".format( - **version_info - ) - - version_hash_data = """\ -/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "core/version.h" -const char *const VERSION_HASH = "{git_hash}"; -const uint64_t VERSION_TIMESTAMP = {git_timestamp}; -""".format( - **version_info - ) - - write_file_if_needed("core/version_generated.gen.h", version_info_header) - write_file_if_needed("core/version_hash.gen.cpp", version_hash_data) - - def parse_cg_file(fname, uniforms, sizes, conditionals): with open(fname, "r", encoding="utf-8") as fs: line = fs.readline() @@ -465,63 +395,6 @@ def is_module(path): return True -def write_disabled_classes(class_list): - file_contents = "" - - file_contents += "/* THIS FILE IS GENERATED DO NOT EDIT */\n" - file_contents += "#ifndef DISABLED_CLASSES_GEN_H\n" - file_contents += "#define DISABLED_CLASSES_GEN_H\n\n" - for c in class_list: - cs = c.strip() - if cs != "": - file_contents += "#define ClassDB_Disable_" + cs + " 1\n" - file_contents += "\n#endif\n" - - write_file_if_needed("core/disabled_classes.gen.h", file_contents) - - -def write_modules(modules): - includes_cpp = "" - initialize_cpp = "" - uninitialize_cpp = "" - - for name, path in modules.items(): - try: - with open(os.path.join(path, "register_types.h")): - includes_cpp += '#include "' + path + '/register_types.h"\n' - initialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" - initialize_cpp += "\tinitialize_" + name + "_module(p_level);\n" - initialize_cpp += "#endif\n" - uninitialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" - uninitialize_cpp += "\tuninitialize_" + name + "_module(p_level);\n" - uninitialize_cpp += "#endif\n" - except OSError: - pass - - modules_cpp = """// register_module_types.gen.cpp -/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "register_module_types.h" - -#include "modules/modules_enabled.gen.h" - -%s - -void initialize_modules(ModuleInitializationLevel p_level) { -%s -} - -void uninitialize_modules(ModuleInitializationLevel p_level) { -%s -} -""" % ( - includes_cpp, - initialize_cpp, - uninitialize_cpp, - ) - - write_file_if_needed("modules/register_module_types.gen.cpp", modules_cpp) - - def convert_custom_modules_path(path): if not path: return path @@ -1649,3 +1522,112 @@ def generate_vs_project(env, original_args, project_name="godot"): if get_bool(original_args, "vsproj_gen_only", True): sys.exit() + + +def generate_copyright_header(filename: str) -> str: + MARGIN = 70 + TEMPLATE = """\ +/**************************************************************************/ +/* %s*/ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ +""" + filename = filename.split("/")[-1].ljust(MARGIN) + if len(filename) > MARGIN: + print(f'WARNING: Filename "{filename}" too large for copyright header.') + return TEMPLATE % filename + + +@contextlib.contextmanager +def generated_wrapper( + path, # FIXME: type with `Union[str, Node, List[Node]]` when pytest conflicts are resolved + guard: Optional[bool] = None, + prefix: str = "", + suffix: str = "", +) -> Generator[TextIOWrapper, None, None]: + """ + Wrapper class to automatically handle copyright headers and header guards + for generated scripts. Meant to be invoked via `with` statement similar to + creating a file. + + - `path`: The path of the file to be created. Can be passed a raw string, an + isolated SCons target, or a full SCons target list. If a target list contains + multiple entries, produces a warning & only creates the first entry. + - `guard`: Optional bool to determine if a header guard should be added. If + unassigned, header guards are determined by the file extension. + - `prefix`: Custom prefix to prepend to a header guard. Produces a warning if + provided a value when `guard` evaluates to `False`. + - `suffix`: Custom suffix to append to a header guard. Produces a warning if + provided a value when `guard` evaluates to `False`. + """ + + # Handle unfiltered SCons target[s] passed as path. + if not isinstance(path, str): + if isinstance(path, list): + if len(path) > 1: + print_warning( + "Attempting to use generated wrapper with multiple targets; " + f"will only use first entry: {path[0]}" + ) + path = path[0] + if not hasattr(path, "get_abspath"): + raise TypeError(f'Expected type "str", "Node" or "List[Node]"; was passed {type(path)}.') + path = path.get_abspath() + + path = str(path).replace("\\", "/") + if guard is None: + guard = path.endswith((".h", ".hh", ".hpp", ".inc")) + if not guard and (prefix or suffix): + print_warning(f'Trying to assign header guard prefix/suffix while `guard` is disabled: "{path}".') + + header_guard = "" + if guard: + if prefix: + prefix += "_" + if suffix: + suffix = f"_{suffix}" + split = path.split("/")[-1].split(".") + header_guard = (f"{prefix}{split[0]}{suffix}.{'.'.join(split[1:])}".upper() + .replace(".", "_").replace("-", "_").replace(" ", "_").replace("__", "_")) # fmt: skip + + with open(path, "wt", encoding="utf-8", newline="\n") as file: + file.write(generate_copyright_header(path)) + file.write("\n/* THIS FILE IS GENERATED. EDITS WILL BE LOST. */\n\n") + + if guard: + file.write(f"#ifndef {header_guard}\n") + file.write(f"#define {header_guard}\n\n") + + with StringIO(newline="\n") as str_io: + yield str_io + file.write(str_io.getvalue().strip() or "/* NO CONTENT */") + + if guard: + file.write(f"\n\n#endif // {header_guard}") + + file.write("\n") diff --git a/modules/SCsub b/modules/SCsub index 6fb2aa67f4..739c5de0b5 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -1,6 +1,6 @@ #!/usr/bin/env python -import modules_builders +import methods import os Import("env") @@ -12,15 +12,51 @@ env_modules.Append(CPPDEFINES=["GODOT_MODULE"]) Export("env_modules") -# Header with MODULE_*_ENABLED defines. -env.Depends("modules_enabled.gen.h", Value(env.module_list)) -env.CommandNoCache( - "modules_enabled.gen.h", - Value(env.module_list), - env.Run(modules_builders.generate_modules_enabled), + +def register_module_types_builder(target, source, env): + modules = source[0].read() + mod_inc = "\n".join([f'#include "{p}/register_types.h"' for p in modules.values()]) + mod_init = "\n".join( + [f"#ifdef MODULE_{n.upper()}_ENABLED\n\tinitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] + ) + mod_uninit = "\n".join( + [f"#ifdef MODULE_{n.upper()}_ENABLED\n\tuninitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] + ) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_module_types.h" + +#include "modules/modules_enabled.gen.h" + +{mod_inc} + +void initialize_modules(ModuleInitializationLevel p_level) {{ +{mod_init} +}} + +void uninitialize_modules(ModuleInitializationLevel p_level) {{ +{mod_uninit} +}} +""" + ) + + +register_module_types = env.CommandNoCache( + "register_module_types.gen.cpp", env.Value(env.modules_detected), env.Run(register_module_types_builder) ) +# Header with MODULE_*_ENABLED defines. +def modules_enabled_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for module in source[0].read(): + file.write(f"#define MODULE_{module.upper()}_ENABLED\n") + + +env.CommandNoCache("modules_enabled.gen.h", env.Value(env.module_list), env.Run(modules_enabled_builder)) + + vs_sources = [] test_headers = [] # libmodule_<name>.a for each active module. @@ -47,18 +83,19 @@ for name, path in env.module_list.items(): # Generate header to be included in `tests/test_main.cpp` to run module-specific tests. if env["tests"]: - env.Depends("modules_tests.gen.h", test_headers) - env.CommandNoCache( - "modules_tests.gen.h", - test_headers, - env.Run(modules_builders.generate_modules_tests), - ) + + def modules_tests_builder(target, source, env): + with methods.generated_wrapper(target) as file: + for header in source: + file.write('#include "{}"\n'.format(os.path.normpath(header.path).replace("\\", "/"))) + + env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_tests_builder)) # libmodules.a with only register_module_types. # Must be last so that all libmodule_<name>.a libraries are on the right side # in the linker command. env.modules_sources = [] -env_modules.add_source_files(env.modules_sources, "register_module_types.gen.cpp") +env_modules.add_source_files(env.modules_sources, register_module_types) lib = env_modules.add_library("modules", env.modules_sources) env.Prepend(LIBS=[lib]) if env["vsproj"]: diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index 941c1f44be..499cf739c4 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -132,7 +132,10 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { int dst_mip_w, dst_mip_h; int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). - ERR_FAIL_COND(dst_ofs % 8 != 0); + if (unlikely(dst_ofs % 8 != 0)) { + astcenc_context_free(context); + ERR_FAIL_MSG("astcenc: Mip offset is not a multiple of 8."); + } uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs]; // Compress image. diff --git a/modules/modules_builders.py b/modules/modules_builders.py deleted file mode 100644 index 5db7c88a90..0000000000 --- a/modules/modules_builders.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Functions used to generate source files during build time""" - - -def generate_modules_enabled(target, source, env): - with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: - for module in env.module_list: - f.write("#define %s\n" % ("MODULE_" + module.upper() + "_ENABLED")) - - -def generate_modules_tests(target, source, env): - import os - - with open(target[0].path, "w", encoding="utf-8", newline="\n") as f: - for header in source: - f.write('#include "%s"\n' % (os.path.normpath(header.path))) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index ff2ca9f0ce..36c8a40ed9 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1596,14 +1596,7 @@ void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { return; } - const CSharpScript *top = script.ptr(); - while (top != nullptr) { - for (const CSharpScript::CSharpMethodInfo &E : top->methods) { - p_list->push_back(E.method_info); - } - - top = top->base_script.ptr(); - } + script->get_script_method_list(p_list); } bool CSharpInstance::has_method(const StringName &p_method) const { @@ -1771,16 +1764,19 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f } void CSharpInstance::connect_event_signals() { - // The script signals list includes the signals declared in base scripts. - for (CSharpScript::EventSignalInfo &signal : script->get_script_event_signals()) { - String signal_name = signal.name; + const CSharpScript *top = script.ptr(); + while (top != nullptr && top->valid) { + for (const CSharpScript::EventSignalInfo &signal : top->event_signals) { + String signal_name = signal.name; - // TODO: Use pooling for ManagedCallable instances. - EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); + // TODO: Use pooling for ManagedCallable instances. + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); + } + top = top->base_script.ptr(); } } @@ -2624,25 +2620,33 @@ bool CSharpScript::has_script_signal(const StringName &p_signal) const { } } + if (base_script.is_valid()) { + return base_script->has_script_signal(p_signal); + } + return false; } -void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { +void CSharpScript::_get_script_signal_list(List<MethodInfo> *r_signals, bool p_include_base) const { if (!valid) { return; } - for (const EventSignalInfo &signal : get_script_event_signals()) { + for (const EventSignalInfo &signal : event_signals) { r_signals->push_back(signal.method_info); } -} -Vector<CSharpScript::EventSignalInfo> CSharpScript::get_script_event_signals() const { - if (!valid) { - return Vector<EventSignalInfo>(); + if (!p_include_base) { + return; + } + + if (base_script.is_valid()) { + base_script->get_script_signal_list(r_signals); } +} - return event_signals; +void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + _get_script_signal_list(r_signals, true); } bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 17df3988ee..c48e1a95c9 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -215,6 +215,8 @@ private: // Do not use unless you know what you are doing static void update_script_class_info(Ref<CSharpScript> p_script); + void _get_script_signal_list(List<MethodInfo> *r_signals, bool p_include_base) const; + protected: static void _bind_methods(); @@ -251,8 +253,6 @@ public: bool has_script_signal(const StringName &p_signal) const override; void get_script_signal_list(List<MethodInfo> *r_signals) const override; - Vector<EventSignalInfo> get_script_event_signals() const; - bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; void get_script_property_list(List<PropertyInfo> *r_list) const override; void update_exports() override; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 21223654f3..a25a2c2f68 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -590,6 +590,11 @@ namespace Godot.SourceGenerators if (variantType == VariantType.Object && type is INamedTypeSymbol memberNamedType) { + if (TryGetNodeOrResourceType(exportAttr, out hint, out hintString)) + { + return true; + } + if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource")) { hint = PropertyHint.ResourceType; @@ -607,6 +612,37 @@ namespace Godot.SourceGenerators } } + static bool TryGetNodeOrResourceType(AttributeData exportAttr, out PropertyHint hint, out string? hintString) + { + hint = PropertyHint.None; + hintString = null; + + if (exportAttr.ConstructorArguments.Length <= 1) return false; + + var hintValue = exportAttr.ConstructorArguments[0].Value; + + var hintEnum = hintValue switch + { + null => PropertyHint.None, + int intValue => (PropertyHint)intValue, + _ => (PropertyHint)(long)hintValue + }; + + if (!hintEnum.HasFlag(PropertyHint.NodeType) && !hintEnum.HasFlag(PropertyHint.ResourceType)) + return false; + + var hintStringValue = exportAttr.ConstructorArguments[1].Value?.ToString(); + if (string.IsNullOrWhiteSpace(hintStringValue)) + { + return false; + } + + hint = hintEnum; + hintString = hintStringValue; + + return true; + } + static string GetTypeName(INamedTypeSymbol memberSymbol) { if (memberSymbol.GetAttributes() diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 1b23276bbd..eef26cdd4e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -798,17 +798,17 @@ namespace Godot.Bridge GetScriptTypeInfo(scriptType, outTypeInfo); + Type native = GodotObject.InternalGetClassNativeBase(scriptType); + // Methods // Performance is not critical here as this will be replaced with source generators. using var methods = new Collections.Array(); Type? top = scriptType; - Type native = GodotObject.InternalGetClassNativeBase(top); - - while (top != null && top != native) + if (scriptType != native) { - var methodList = GetMethodListForType(top); + var methodList = GetMethodListForType(scriptType); if (methodList != null) { @@ -859,8 +859,6 @@ namespace Godot.Bridge methods.Add(methodInfo); } } - - top = top.BaseType; } *outMethodsDest = NativeFuncs.godotsharp_array_new_copy( @@ -912,11 +910,9 @@ namespace Godot.Bridge // Performance is not critical here as this will be replaced with source generators. using var signals = new Collections.Dictionary(); - top = scriptType; - - while (top != null && top != native) + if (scriptType != native) { - var signalList = GetSignalListForType(top); + var signalList = GetSignalListForType(scriptType); if (signalList != null) { @@ -951,8 +947,6 @@ namespace Godot.Bridge signals.Add(signalName, signalParams); } } - - top = top.BaseType; } *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index c805b68c94..ad75195f0f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -426,7 +426,9 @@ namespace Godot /// <returns>An integer that indicates the lexical relationship between the two comparands.</returns> public static int CasecmpTo(this string instance, string to) { - return instance.CompareTo(to, caseSensitive: true); +#pragma warning disable CA1309 // Use ordinal string comparison + return string.Compare(instance, to, ignoreCase: false, null); +#pragma warning restore CA1309 } /// <summary> @@ -441,7 +443,9 @@ namespace Godot [Obsolete("Use string.Compare instead.")] public static int CompareTo(this string instance, string to, bool caseSensitive = true) { - return string.Compare(instance, to, !caseSensitive); +#pragma warning disable CA1309 // Use ordinal string comparison + return string.Compare(instance, to, ignoreCase: !caseSensitive, null); +#pragma warning restore CA1309 } /// <summary> @@ -1261,7 +1265,9 @@ namespace Godot /// <returns>An integer that indicates the lexical relationship between the two comparands.</returns> public static int NocasecmpTo(this string instance, string to) { - return instance.CompareTo(to, caseSensitive: false); +#pragma warning disable CA1309 // Use ordinal string comparison + return string.Compare(instance, to, ignoreCase: true, null); +#pragma warning restore CA1309 } /// <summary> diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index 219f176479..50cc7b9e7b 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -38,7 +38,7 @@ #include "scene/3d/xr_nodes.h" #include "scene/main/viewport.h" -HashSet<SubViewport *> OpenXRCompositionLayer::viewports_in_use; +Vector<OpenXRCompositionLayer *> OpenXRCompositionLayer::composition_layer_nodes; static const char *HOLE_PUNCH_SHADER_CODE = "shader_type spatial;\n" @@ -73,9 +73,7 @@ OpenXRCompositionLayer::~OpenXRCompositionLayer() { openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRCompositionLayer::_on_openxr_session_stopping)); } - if (layer_viewport) { - viewports_in_use.erase(layer_viewport); - } + composition_layer_nodes.erase(this); if (openxr_layer_provider != nullptr) { memdelete(openxr_layer_provider); @@ -153,22 +151,25 @@ void OpenXRCompositionLayer::update_fallback_mesh() { should_update_fallback_mesh = true; } +bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) { + for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) { + if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) { + return true; + } + } + return false; +} + void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) { if (layer_viewport == p_viewport) { return; } - ERR_FAIL_COND_EDMSG(viewports_in_use.has(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); - - if (layer_viewport) { - viewports_in_use.erase(layer_viewport); - } + ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first.")); layer_viewport = p_viewport; if (layer_viewport) { - viewports_in_use.insert(layer_viewport); - SubViewport::UpdateMode update_mode = layer_viewport->get_update_mode(); if (update_mode == SubViewport::UPDATE_WHEN_VISIBLE || update_mode == SubViewport::UPDATE_WHEN_PARENT_VISIBLE) { WARN_PRINT_ONCE("OpenXR composition layers cannot use SubViewports with UPDATE_WHEN_VISIBLE or UPDATE_WHEN_PARENT_VISIBLE. Switching to UPDATE_ALWAYS."); @@ -306,6 +307,8 @@ void OpenXRCompositionLayer::_reset_fallback_material() { void OpenXRCompositionLayer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { + composition_layer_nodes.push_back(this); + if (openxr_layer_provider) { for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) { extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults()); @@ -340,7 +343,9 @@ void OpenXRCompositionLayer::_notification(int p_what) { composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider); } - if (!fallback && layer_viewport && openxr_session_running && is_visible()) { + if (is_viewport_in_use(layer_viewport)) { + set_layer_viewport(nullptr); + } else if (!fallback && layer_viewport && openxr_session_running && is_visible()) { openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size()); } } break; @@ -349,12 +354,7 @@ void OpenXRCompositionLayer::_notification(int p_what) { composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider); } - // When a node is removed in the editor, we need to clear the layer viewport, because otherwise - // there will be issues with the tracking in viewports_in_use, since nodes deleted in the editor - // aren't really deleted in order to support undo. - if (Engine::get_singleton()->is_editor_hint() && layer_viewport) { - set_layer_viewport(nullptr); - } else if (!fallback) { + if (!fallback) { // This will clean up existing resources. openxr_layer_provider->set_viewport(RID(), Size2i()); } diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h index 7998d096b6..6792364295 100644 --- a/modules/openxr/scene/openxr_composition_layer.h +++ b/modules/openxr/scene/openxr_composition_layer.h @@ -77,7 +77,8 @@ protected: void update_fallback_mesh(); - static HashSet<SubViewport *> viewports_in_use; + static Vector<OpenXRCompositionLayer *> composition_layer_nodes; + bool is_viewport_in_use(SubViewport *p_viewport); public: void set_layer_viewport(SubViewport *p_viewport); diff --git a/platform/SCsub b/platform/SCsub index ca282e3e68..b24c189848 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -1,27 +1,64 @@ #!/usr/bin/env python import methods +from glob import glob +from pathlib import Path Import("env") env.platform_sources = [] + +# Generate export icons +def export_icon_builder(target, source, env): + src_path = Path(str(source[0])) + src_name = src_path.stem + platform = src_path.parent.parent.stem + with open(str(source[0]), "rb") as file: + svg = "".join([f"\\{hex(x)[1:]}" for x in file.read()]) + with methods.generated_wrapper(target, prefix=platform) as file: + file.write( + f"""\ +static const char *_{platform}_{src_name}_svg = "{svg}"; +""" + ) + + +for platform in env.platform_exporters: + for path in glob(f"{platform}/export/*.svg"): + env.CommandNoCache(path.replace(".svg", "_svg.gen.h"), path, env.Run(export_icon_builder)) + + # Register platform-exclusive APIs -reg_apis_inc = '#include "register_platform_apis.h"\n' -reg_apis = "void register_platform_apis() {\n" -unreg_apis = "void unregister_platform_apis() {\n" +def register_platform_apis_builder(target, source, env): + platforms = source[0].read() + api_inc = "\n".join([f'#include "{p}/api/api.h"' for p in platforms]) + api_reg = "\n".join([f"\tregister_{p}_api();" for p in platforms]) + api_unreg = "\n".join([f"\tunregister_{p}_api();" for p in platforms]) + with methods.generated_wrapper(target) as file: + file.write( + f"""\ +#include "register_platform_apis.h" + +{api_inc} + +void register_platform_apis() {{ +{api_reg} +}} + +void unregister_platform_apis() {{ +{api_unreg} +}} +""" + ) + + +register_platform_apis = env.CommandNoCache( + "register_platform_apis.gen.cpp", env.Value(env.platform_apis), env.Run(register_platform_apis_builder) +) +env.add_source_files(env.platform_sources, register_platform_apis) for platform in env.platform_apis: - platform_dir = env.Dir(platform) - env.add_source_files(env.platform_sources, platform + "/api/api.cpp") - reg_apis += "\tregister_" + platform + "_api();\n" - unreg_apis += "\tunregister_" + platform + "_api();\n" - reg_apis_inc += '#include "' + platform + '/api/api.h"\n' -reg_apis_inc += "\n" -reg_apis += "}\n\n" -unreg_apis += "}\n" - -methods.write_file_if_needed("register_platform_apis.gen.cpp", reg_apis_inc + reg_apis + unreg_apis) -env.add_source_files(env.platform_sources, "register_platform_apis.gen.cpp") + env.add_source_files(env.platform_sources, f"{platform}/api/api.cpp") lib = env.add_library("platform", env.platform_sources) env.Prepend(LIBS=[lib]) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index ccf889f1a3..93eb34001e 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -18,30 +18,36 @@ def get_name(): return "Windows" -def get_mingw_tool(tool, prefix="", arch="", test="--version"): - if not prefix: - prefix = os.getenv("MINGW_PREFIX", "") - supported_arches = ["x86_64", "x86_32", "arm64", "arm32"] - if arch in supported_arches: - arches = [arch, ""] - else: - arches = ["x86_64", "x86_32", "arm64", "arm32", ""] - for a in arches: +def try_cmd(test, prefix, arch): + if arch: try: - path = f"{get_mingw_bin_prefix(prefix, a)}{tool}" out = subprocess.Popen( - f"{path} {test}", + get_mingw_bin_prefix(prefix, arch) + test, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) out.communicate() if out.returncode == 0: - return path + return True except Exception: pass + else: + for a in ["x86_64", "x86_32", "arm64", "arm32"]: + try: + out = subprocess.Popen( + get_mingw_bin_prefix(prefix, a) + test, + shell=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + out.communicate() + if out.returncode == 0: + return True + except Exception: + pass - return "" + return False def can_build(): @@ -59,7 +65,9 @@ def can_build(): if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) - if get_mingw_tool("gcc") or get_mingw_tool("clang"): + prefix = os.getenv("MINGW_PREFIX", "") + + if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""): return True return False @@ -247,26 +255,36 @@ def get_flags(): def build_res_file(target, source, env: "SConsEnvironment"): - cmdbase = get_mingw_tool("windres", env["mingw_prefix"], env["arch"]) - if not cmdbase: - return -1 - arch_aliases = { "x86_32": "pe-i386", "x86_64": "pe-x86-64", "arm32": "armv7-w64-mingw32", "arm64": "aarch64-w64-mingw32", } - cmdbase += " --include-dir . --target=" + arch_aliases[env["arch"]] + cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]] + + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) for x in range(len(source)): - cmd = f"{cmdbase} -i {source[x]} -o {target[x]}" + ok = True + # Try prefixed executable (MinGW on Linux). + cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): - return -1 + ok = False except Exception: - return -1 + ok = False + + # Try generic executable (MSYS2). + if not ok: + cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) + try: + out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() + if len(out[1]): + return -1 + except Exception: + return -1 return 0 @@ -340,8 +358,8 @@ def setup_mingw(env: "SConsEnvironment"): ) sys.exit(255) - if not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) and not get_mingw_tool( - "clang", env["mingw_prefix"], env["arch"] + if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd( + "clang --version", env["mingw_prefix"], env["arch"] ): print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.") sys.exit(255) @@ -582,10 +600,10 @@ def configure_mingw(env: "SConsEnvironment"): ## Build type - if not env["use_llvm"] and not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]): + if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = True - if env["use_llvm"] and not get_mingw_tool("clang", env["mingw_prefix"], env["arch"]): + if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = False # TODO: Re-evaluate the need for this / streamline with common config. @@ -620,26 +638,27 @@ def configure_mingw(env: "SConsEnvironment"): if env["arch"] in ["x86_32", "x86_64"]: env["x86_libtheora_opt_gcc"] = True + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + if env["use_llvm"]: - env["CC"] = get_mingw_tool("clang", env["mingw_prefix"], env["arch"]) - env["CXX"] = get_mingw_tool("clang++", env["mingw_prefix"], env["arch"]) - tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) - tool_ar = get_mingw_tool("ar", env["mingw_prefix"], env["arch"]) - tool_ranlib = get_mingw_tool("ranlib", env["mingw_prefix"], env["arch"]) + env["CC"] = mingw_bin_prefix + "clang" + env["CXX"] = mingw_bin_prefix + "clang++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "ar" + if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "ranlib" env.extra_suffix = ".llvm" + env.extra_suffix else: - env["CC"] = get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) - env["CXX"] = get_mingw_tool("g++", env["mingw_prefix"], env["arch"]) - tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) - tool_ar = get_mingw_tool("gcc-ar", env["mingw_prefix"], env["arch"]) - tool_ranlib = get_mingw_tool("gcc-ranlib", env["mingw_prefix"], env["arch"]) - - if tool_as: - env["AS"] = tool_as - if tool_ar: - env["AR"] = tool_ar - if tool_ranlib: - env["RANLIB"] = tool_ranlib + env["CC"] = mingw_bin_prefix + "gcc" + env["CXX"] = mingw_bin_prefix + "g++" + if try_cmd("as --version", env["mingw_prefix"], env["arch"]): + env["AS"] = mingw_bin_prefix + "as" + if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]): + env["AR"] = mingw_bin_prefix + "gcc-ar" + if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]): + env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib" ## LTO diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index dd480571ba..729d55cea6 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -1,20 +1,24 @@ """Functions used to generate source files during build time""" import os -from detect import get_mingw_tool +from detect import get_mingw_bin_prefix +from detect import try_cmd def make_debug_mingw(target, source, env): dst = str(target[0]) # Force separate debug symbols if executable size is larger than 1.9 GB. if env["separate_debug_symbols"] or os.stat(dst).st_size >= 2040109465: - objcopy = get_mingw_tool("objcopy", env["mingw_prefix"], env["arch"]) - strip = get_mingw_tool("strip", env["mingw_prefix"], env["arch"]) - - if not objcopy or not strip: - print('`separate_debug_symbols` requires both "objcopy" and "strip" to function.') - return - - os.system("{0} --only-keep-debug {1} {1}.debugsymbols".format(objcopy, dst)) - os.system("{0} --strip-debug --strip-unneeded {1}".format(strip, dst)) - os.system("{0} --add-gnu-debuglink={1}.debugsymbols {1}".format(objcopy, dst)) + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) + else: + os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) + if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(dst)) + else: + os.system("strip --strip-debug --strip-unneeded {0}".format(dst)) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) + else: + os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) diff --git a/platform_methods.py b/platform_methods.py index 57b11d1a47..5326e36077 100644 --- a/platform_methods.py +++ b/platform_methods.py @@ -43,33 +43,6 @@ def detect_arch(): return "x86_64" -def generate_export_icons(platform_path, platform_name): - """ - Generate headers for logo and run icon for the export plugin. - """ - export_path = platform_path + "/export" - svg_names = [] - if os.path.isfile(export_path + "/logo.svg"): - svg_names.append("logo") - if os.path.isfile(export_path + "/run_icon.svg"): - svg_names.append("run_icon") - - for name in svg_names: - with open(export_path + "/" + name + ".svg", "rb") as svgf: - b = svgf.read(1) - svg_str = " /* AUTOGENERATED FILE, DO NOT EDIT */ \n" - svg_str += " static const char *_" + platform_name + "_" + name + '_svg = "' - while len(b) == 1: - svg_str += "\\" + hex(ord(b))[1:] - b = svgf.read(1) - - svg_str += '";\n' - - wf = export_path + "/" + name + "_svg.gen.h" - - methods.write_file_if_needed(wf, svg_str) - - def get_build_version(short): import version diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 165d4d5a67..fbe7563e6b 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1073,6 +1073,7 @@ TileMap::TileMap() { TileMapLayer *defaults = memnew(TileMapLayer); base_property_helper.set_prefix("layer_"); + base_property_helper.set_array_length_getter(&TileMap::get_layers_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled); base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate); diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp index 96e3e33841..8d806ef5fc 100644 --- a/scene/3d/skeleton_modifier_3d.cpp +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -110,7 +110,7 @@ void SkeletonModifier3D::process_modification() { } void SkeletonModifier3D::_process_modification() { - // + GDVIRTUAL_CALL(_process_modification); } void SkeletonModifier3D::_notification(int p_what) { @@ -133,6 +133,7 @@ void SkeletonModifier3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "influence", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_influence", "get_influence"); ADD_SIGNAL(MethodInfo("modification_processed")); + GDVIRTUAL_BIND(_process_modification); } SkeletonModifier3D::SkeletonModifier3D() { diff --git a/scene/3d/skeleton_modifier_3d.h b/scene/3d/skeleton_modifier_3d.h index 25c09f3b93..d00a1e94a9 100644 --- a/scene/3d/skeleton_modifier_3d.h +++ b/scene/3d/skeleton_modifier_3d.h @@ -60,6 +60,7 @@ protected: virtual void _set_active(bool p_active); virtual void _process_modification(); + GDVIRTUAL0(_process_modification); public: virtual PackedStringArray get_configuration_warnings() const override; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 88f65ca1bc..d8fcbbb883 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -243,8 +243,8 @@ Size2 BoxContainer::get_minimum_size() const { bool first = true; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); - if (!c) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c || !c->is_visible() || c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 97a2917dc1..91424c0ffd 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1344,6 +1344,7 @@ void FileDialog::_bind_methods() { Option defaults; base_property_helper.set_prefix("option_"); + base_property_helper.set_array_length_getter(&FileDialog::get_option_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 8376ef48b6..1dd13f2ebf 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1882,6 +1882,7 @@ void ItemList::_bind_methods() { Item defaults(true); base_property_helper.set_prefix("item_"); + base_property_helper.set_array_length_getter(&ItemList::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &ItemList::set_item_text, &ItemList::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable); diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 91e6c1f092..06e4a7cc13 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -36,8 +36,8 @@ Size2 MarginContainer::get_minimum_size() const { Size2 max; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); - if (!c) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c || !c->is_visible() || c->is_set_as_top_level()) { continue; } diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index e83d9c7c1b..998f99b2f9 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -190,6 +190,7 @@ void MenuButton::_bind_methods() { PopupMenu::Item defaults(true); base_property_helper.set_prefix("popup/item_"); + base_property_helper.set_array_length_getter(&MenuButton::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 509c6aca99..0f161a014a 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -571,6 +571,7 @@ void OptionButton::_bind_methods() { PopupMenu::Item defaults(true); base_property_helper.set_prefix("popup/item_"); + base_property_helper.set_array_length_getter(&OptionButton::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 9b991972be..5865fe141b 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2808,6 +2808,7 @@ void PopupMenu::_bind_methods() { Item defaults(true); base_property_helper.set_prefix("item_"); + base_property_helper.set_array_length_getter(&PopupMenu::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &PopupMenu::set_item_text, &PopupMenu::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &PopupMenu::set_item_icon, &PopupMenu::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"), defaults.checkable_type, &PopupMenu::_set_item_checkable_type, &PopupMenu::_get_item_checkable_type); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 0e130d60af..8a04974479 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1868,6 +1868,7 @@ void TabBar::_bind_methods() { Tab defaults(true); base_property_helper.set_prefix("tab_"); + base_property_helper.set_array_length_getter(&TabBar::get_tab_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title); base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index b666e4c52d..152ecaf89d 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -36,14 +36,17 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri return nullptr; } - { - const String index_string = components[0].trim_prefix(prefix); - if (!index_string.is_valid_int()) { - return nullptr; - } - *r_index = index_string.to_int(); + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return nullptr; + } + + int index = index_string.to_int(); + if (index < 0 || index >= _call_array_length_getter()) { + return nullptr; } + *r_index = index; return property_list.getptr(components[1]); } @@ -66,6 +69,11 @@ Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index return p_property->getter->call(object, argptrs, 1, ce); } +int PropertyListHelper::_call_array_length_getter() const { + Callable::CallError ce; + return array_length_getter->call(object, nullptr, 0, ce); +} + void PropertyListHelper::set_prefix(const String &p_prefix) { prefix = p_prefix; } @@ -83,7 +91,13 @@ bool PropertyListHelper::is_initialized() const { } void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) { + DEV_ASSERT(!p_base.prefix.is_empty()); + DEV_ASSERT(p_base.array_length_getter != nullptr); + DEV_ASSERT(!p_base.property_list.is_empty()); + DEV_ASSERT(p_object != nullptr); + prefix = p_base.prefix; + array_length_getter = p_base.array_length_getter; property_list = p_base.property_list; object = p_object; } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index eac6b03d47..e19e7cd22e 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -43,15 +43,22 @@ class PropertyListHelper { }; String prefix; + MethodBind *array_length_getter = nullptr; HashMap<String, Property> property_list; Object *object = nullptr; const Property *_get_property(const String &p_property, int *r_index) const; void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const; Variant _call_getter(const Property *p_property, int p_index) const; + int _call_array_length_getter() const; public: void set_prefix(const String &p_prefix); + template <typename G> + void set_array_length_getter(G p_array_length_getter) { + array_length_getter = create_method_bind(p_array_length_getter); + } + // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic. void register_property(const PropertyInfo &p_info, const Variant &p_default); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 6fac096c93..de94327d79 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -589,7 +589,7 @@ void register_scene_types() { GDREGISTER_CLASS(CPUParticles3D); GDREGISTER_CLASS(Marker3D); GDREGISTER_CLASS(RootMotionView); - GDREGISTER_ABSTRACT_CLASS(SkeletonModifier3D); + GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); OS::get_singleton()->yield(); // may take time to init diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 051ed59632..6966c243b5 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -711,6 +711,7 @@ void AudioStreamRandomizer::_bind_methods() { PoolEntry defaults; base_property_helper.set_prefix("stream_"); + base_property_helper.set_array_length_getter(&AudioStreamRandomizer::get_streams_count); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream); base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight); } |