diff options
140 files changed, 1480 insertions, 875 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3493219ea7..6f77645584 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v16.0.6 + rev: v17.0.6 hooks: - id: clang-format files: \.(c|h|cpp|hpp|cc|cxx|m|mm|inc|java|glsl)$ @@ -14,7 +14,7 @@ repos: ) - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.3.0 + rev: 24.2.0 hooks: - id: black files: (\.py$|SConstruct|SCsub) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index d9a5a5094a..d1c90ed7e6 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1514,6 +1514,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_INTERNAL("internationalization/locale/translation_remaps", PackedStringArray()); GLOBAL_DEF_INTERNAL("internationalization/locale/translations", PackedStringArray()); GLOBAL_DEF_INTERNAL("internationalization/locale/translations_pot_files", PackedStringArray()); + GLOBAL_DEF_INTERNAL("internationalization/locale/translation_add_builtin_strings_to_pot", false); ProjectSettings::get_singleton()->add_hidden_prefix("input/"); } diff --git a/core/core_bind.cpp b/core/core_bind.cpp index f5c69b9b98..8ccf7d1f51 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -145,6 +145,8 @@ void ResourceLoader::_bind_methods() { BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE_DEEP); + BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE_DEEP); } ////// ResourceSaver ////// diff --git a/core/core_bind.h b/core/core_bind.h index f884426881..3440531124 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -63,9 +63,11 @@ public: }; enum CacheMode { - CACHE_MODE_IGNORE, // Resource and subresources do not use path cache, no path is set into resource. - CACHE_MODE_REUSE, // Resource and subresources use patch cache, reuse existing loaded resources instead of loading from disk when available. - CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk. + CACHE_MODE_IGNORE, + CACHE_MODE_REUSE, + CACHE_MODE_REPLACE, + CACHE_MODE_IGNORE_DEEP, + CACHE_MODE_REPLACE_DEEP, }; static ResourceLoader *get_singleton() { return singleton; } diff --git a/core/core_builders.py b/core/core_builders.py index 8b6b87ad83..61b7bd695c 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -2,6 +2,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import zlib from platform_methods import subprocess_main diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 0c96c32187..f96a8873ca 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1415,7 +1415,7 @@ static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data) #endif } -#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name) +#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr) & gdextension_##m_name) void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(get_godot_version); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index daa57be76f..7e8d0b43cd 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -41,7 +41,12 @@ #include <stdio.h> void Resource::emit_changed() { - emit_signal(CoreStringNames::get_singleton()->changed); + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + // Let the connection happen on the main thread, later, since signals are not thread-safe. + call_deferred("emit_signal", CoreStringNames::get_singleton()->changed); + } else { + emit_signal(CoreStringNames::get_singleton()->changed); + } } void Resource::_resource_path_changed() { @@ -152,12 +157,22 @@ bool Resource::editor_can_reload_from_file() { } void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + // Let the check and connection happen on the main thread, later, since signals are not thread-safe. + callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags); + return; + } if (!is_connected(CoreStringNames::get_singleton()->changed, p_callable) || p_flags & CONNECT_REFERENCE_COUNTED) { connect(CoreStringNames::get_singleton()->changed, p_callable, p_flags); } } void Resource::disconnect_changed(const Callable &p_callable) { + if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { + // Let the check and disconnection happen on the main thread, later, since signals are not thread-safe. + callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable); + return; + } if (is_connected(CoreStringNames::get_singleton()->changed, p_callable)) { disconnect(CoreStringNames::get_singleton()->changed, p_callable); } diff --git a/core/io/resource.h b/core/io/resource.h index b885b773ac..f0f686af57 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -106,7 +106,7 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; - void set_path_cache(const String &p_path); // Set raw path without involving resource cache. + virtual void set_path_cache(const String &p_path); // Set raw path without involving resource cache. _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } static String generate_scene_unique_id(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 20c494516b..17cffb878e 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -430,7 +430,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { path = remaps[path]; } - Ref<Resource> res = ResourceLoader::load(path, exttype); + Ref<Resource> res = ResourceLoader::load(path, exttype, cache_mode_for_external); if (res.is_null()) { WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data()); @@ -683,7 +683,7 @@ Error ResourceLoaderBinary::load() { } external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap - external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, cache_mode_for_external); if (!external_resources[i].load_token.is_valid()) { if (!ResourceLoader::get_abort_on_missing_resources()) { ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); @@ -772,10 +772,12 @@ Error ResourceLoaderBinary::load() { } res = Ref<Resource>(r); - if (!path.is_empty() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it - } else if (!path.is_resource_file()) { - r->set_path_cache(path); + if (!path.is_empty()) { + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); // If got here because the resource with same path has different type, replace it. + } else { + r->set_path_cache(path); + } } r->set_scene_unique_id(id); } @@ -1187,7 +1189,22 @@ Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const Strin ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'."); ResourceLoaderBinary loader; - loader.cache_mode = p_cache_mode; + switch (p_cache_mode) { + case CACHE_MODE_IGNORE: + case CACHE_MODE_REUSE: + case CACHE_MODE_REPLACE: + loader.cache_mode = p_cache_mode; + loader.cache_mode_for_external = CACHE_MODE_REUSE; + break; + case CACHE_MODE_IGNORE_DEEP: + loader.cache_mode = CACHE_MODE_IGNORE; + loader.cache_mode_for_external = p_cache_mode; + break; + case CACHE_MODE_REPLACE_DEEP: + loader.cache_mode = CACHE_MODE_REPLACE; + loader.cache_mode_for_external = p_cache_mode; + break; + } loader.use_sub_threads = p_use_sub_threads; loader.progress = r_progress; String path = !p_original_path.is_empty() ? p_original_path : p_path; diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index e64485d404..e01c5fa467 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -85,6 +85,7 @@ class ResourceLoaderBinary { Error error = OK; ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; + ResourceFormatLoader::CacheMode cache_mode_for_external = ResourceFormatLoader::CACHE_MODE_REUSE; friend class ResourceFormatLoaderBinary; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index a3fc7bc370..ff563a35b2 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -188,6 +188,8 @@ void ResourceFormatLoader::_bind_methods() { BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE); BIND_ENUM_CONSTANT(CACHE_MODE_REUSE); BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE); + BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE_DEEP); + BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE_DEEP); GDVIRTUAL_BIND(_get_recognized_extensions); GDVIRTUAL_BIND(_recognize_path, "path", "type"); @@ -339,9 +341,11 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.cond_var = nullptr; } + bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; + bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP; if (load_task.resource.is_valid()) { - if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - if (load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE) { + if (!ignoring) { + if (replacing) { Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path); if (old_res.is_valid() && old_res != load_task.resource) { // If resource is already loaded, only replace its data, to avoid existing invalidating instances. @@ -349,8 +353,8 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { load_task.resource = old_res; } } - load_task.resource->set_path(load_task.local_path, load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); - } else if (!load_task.local_path.is_resource_file()) { + load_task.resource->set_path(load_task.local_path, replacing); + } else { load_task.resource->set_path_cache(load_task.local_path); } @@ -370,7 +374,7 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { if (_loaded_callback) { _loaded_callback(load_task.resource, load_task.local_path); } - } else if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + } else if (!ignoring) { Ref<Resource> existing = ResourceCache::get_ref(load_task.local_path); if (existing.is_valid()) { load_task.resource = existing; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 1f79b83f11..5caf699d32 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -47,9 +47,11 @@ class ResourceFormatLoader : public RefCounted { public: enum CacheMode { - CACHE_MODE_IGNORE, // Resource and subresources do not use path cache, no path is set into resource. - CACHE_MODE_REUSE, // Resource and subresources use patch cache, reuse existing loaded resources instead of loading from disk when available. - CACHE_MODE_REPLACE, // Resource and subresource use path cache, but replace existing loaded resources when available with information from disk. + CACHE_MODE_IGNORE, + CACHE_MODE_REUSE, + CACHE_MODE_REPLACE, + CACHE_MODE_IGNORE_DEEP, + CACHE_MODE_REPLACE_DEEP, }; protected: diff --git a/core/math/geometry_3d.cpp b/core/math/geometry_3d.cpp index e2edf8b23e..4d55455166 100644 --- a/core/math/geometry_3d.cpp +++ b/core/math/geometry_3d.cpp @@ -393,7 +393,7 @@ static inline void _build_faces(uint8_t ***p_cell_status, int x, int y, int z, i return; } -#define vert(m_idx) Vector3(((m_idx)&4) >> 2, ((m_idx)&2) >> 1, (m_idx)&1) +#define vert(m_idx) Vector3(((m_idx) & 4) >> 2, ((m_idx) & 2) >> 1, (m_idx) & 1) static const uint8_t indices[6][4] = { { 7, 6, 4, 5 }, diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 63adc5b502..231a8e4d68 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -599,7 +599,9 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) placeholder_extension->property_can_revert = &PlaceholderExtensionInstance::placeholder_instance_property_can_revert; placeholder_extension->property_get_revert = &PlaceholderExtensionInstance::placeholder_instance_property_get_revert; placeholder_extension->validate_property = &PlaceholderExtensionInstance::placeholder_instance_validate_property; +#ifndef DISABLE_DEPRECATED placeholder_extension->notification = nullptr; +#endif // DISABLE_DEPRECATED placeholder_extension->notification2 = &PlaceholderExtensionInstance::placeholder_instance_notification; placeholder_extension->to_string = &PlaceholderExtensionInstance::placeholder_instance_to_string; placeholder_extension->reference = &PlaceholderExtensionInstance::placeholder_instance_reference; diff --git a/core/object/object.cpp b/core/object/object.cpp index 5db1d2534f..d9a5713c20 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1483,10 +1483,15 @@ String Object::tr(const StringName &p_message, const StringName &p_context) cons } if (Engine::get_singleton()->is_editor_hint()) { + String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context); + if (!tr_msg.is_empty()) { + return tr_msg; + } + return TranslationServer::get_singleton()->tool_translate(p_message, p_context); - } else { - return TranslationServer::get_singleton()->translate(p_message, p_context); } + + return TranslationServer::get_singleton()->translate(p_message, p_context); } String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -1499,10 +1504,15 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu } if (Engine::get_singleton()->is_editor_hint()) { + String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context); + if (!tr_msg.is_empty()) { + return tr_msg; + } + return TranslationServer::get_singleton()->tool_translate_plural(p_message, p_message_plural, p_n, p_context); - } else { - return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); } + + return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); } void Object::_clear_internal_resource_paths(const Variant &p_var) { diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp index 32e4564c5e..8ae2efb787 100644 --- a/core/string/node_path.cpp +++ b/core/string/node_path.cpp @@ -92,6 +92,14 @@ StringName NodePath::get_subname(int p_idx) const { return data->subpath[p_idx]; } +int NodePath::get_total_name_count() const { + if (!data) { + return 0; + } + + return data->path.size() + data->subpath.size(); +} + void NodePath::unref() { if (data && data->refcount.unref()) { memdelete(data); @@ -229,6 +237,27 @@ StringName NodePath::get_concatenated_subnames() const { return data->concatenated_subpath; } +NodePath NodePath::slice(int p_begin, int p_end) const { + const int name_count = get_name_count(); + const int total_count = get_total_name_count(); + + int begin = CLAMP(p_begin, -total_count, total_count); + if (begin < 0) { + begin += total_count; + } + int end = CLAMP(p_end, -total_count, total_count); + if (end < 0) { + end += total_count; + } + const int sub_begin = MAX(begin - name_count - 1, 0); + const int sub_end = MAX(end - name_count, 0); + + const Vector<StringName> names = get_names().slice(begin, end); + const Vector<StringName> sub_names = get_subnames().slice(sub_begin, sub_end); + const bool absolute = is_absolute() && (begin == 0); + return NodePath(names, sub_names, absolute); +} + NodePath NodePath::rel_path_to(const NodePath &p_np) const { ERR_FAIL_COND_V(!is_absolute(), NodePath()); ERR_FAIL_COND_V(!p_np.is_absolute(), NodePath()); @@ -331,7 +360,7 @@ NodePath NodePath::simplified() const { } NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) { - if (p_path.size() == 0) { + if (p_path.size() == 0 && !p_absolute) { return; } @@ -343,7 +372,7 @@ NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) { } NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) { - if (p_path.size() == 0 && p_subpath.size() == 0) { + if (p_path.size() == 0 && p_subpath.size() == 0 && !p_absolute) { return; } diff --git a/core/string/node_path.h b/core/string/node_path.h index 876d69924e..56799839d7 100644 --- a/core/string/node_path.h +++ b/core/string/node_path.h @@ -57,10 +57,12 @@ public: StringName get_name(int p_idx) const; int get_subname_count() const; StringName get_subname(int p_idx) const; + int get_total_name_count() const; Vector<StringName> get_names() const; Vector<StringName> get_subnames() const; StringName get_concatenated_names() const; StringName get_concatenated_subnames() const; + NodePath slice(int p_begin, int p_end = INT_MAX) const; NodePath rel_path_to(const NodePath &p_np) const; NodePath get_as_property_path() const; diff --git a/core/string/translation.cpp b/core/string/translation.cpp index db0c3f6006..2f25f56eac 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -772,6 +772,20 @@ StringName TranslationServer::tool_translate_plural(const StringName &p_message, return p_message_plural; } +void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) { + property_translation = p_translation; +} + +StringName TranslationServer::property_translate(const StringName &p_message) const { + if (property_translation.is_valid()) { + StringName r = property_translation->get_message(p_message); + if (r) { + return r; + } + } + return p_message; +} + void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) { doc_translation = p_translation; } @@ -800,13 +814,13 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message, return p_message_plural; } -void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) { - property_translation = p_translation; +void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) { + extractable_translation = p_translation; } -StringName TranslationServer::property_translate(const StringName &p_message) const { - if (property_translation.is_valid()) { - StringName r = property_translation->get_message(p_message); +StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const { + if (extractable_translation.is_valid()) { + StringName r = extractable_translation->get_message(p_message, p_context); if (r) { return r; } @@ -814,6 +828,20 @@ StringName TranslationServer::property_translate(const StringName &p_message) co return p_message; } +StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (extractable_translation.is_valid()) { + StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); + if (r) { + return r; + } + } + + if (p_n == 1) { + return p_message; + } + return p_message_plural; +} + bool TranslationServer::is_pseudolocalization_enabled() const { return pseudolocalization_enabled; } diff --git a/core/string/translation.h b/core/string/translation.h index 4eca37f6be..bd7082dc9d 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -82,8 +82,9 @@ class TranslationServer : public Object { HashSet<Ref<Translation>> translations; Ref<Translation> tool_translation; - Ref<Translation> doc_translation; Ref<Translation> property_translation; + Ref<Translation> doc_translation; + Ref<Translation> extractable_translation; bool enabled = true; @@ -181,11 +182,14 @@ public: Ref<Translation> get_tool_translation() const; StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const; StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; + void set_property_translation(const Ref<Translation> &p_translation); + StringName property_translate(const StringName &p_message) const; void set_doc_translation(const Ref<Translation> &p_translation); StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const; StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; - void set_property_translation(const Ref<Translation> &p_translation); - StringName property_translate(const StringName &p_message) const; + void set_extractable_translation(const Ref<Translation> &p_translation); + StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const; + StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; void setup(); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index a8a96e6e3f..f4b00255a1 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -5391,9 +5391,7 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St /** * "Run-time TRanslate". Performs string replacement for internationalization - * within a running project. The translation string must be supplied by the - * project, as Godot does not provide built-in translations for `RTR()` strings - * to keep binary size low. A translation context can optionally be specified to + * without the editor. A translation context can optionally be specified to * disambiguate between identical source strings in translations. When * placeholders are desired, use `vformat(RTR("Example: %s"), some_string)`. * If a string mentions a quantity (and may therefore need a dynamic plural form), @@ -5407,9 +5405,8 @@ String RTR(const String &p_text, const String &p_context) { String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context); if (rtr.is_empty() || rtr == p_text) { return TranslationServer::get_singleton()->translate(p_text, p_context); - } else { - return rtr; } + return rtr; } return p_text; @@ -5417,13 +5414,10 @@ String RTR(const String &p_text, const String &p_context) { /** * "Run-time TRanslate for N items". Performs string replacement for - * internationalization within a running project. The translation string must be - * supplied by the project, as Godot does not provide built-in translations for - * `RTRN()` strings to keep binary size low. A translation context can - * optionally be specified to disambiguate between identical source strings in - * translations. Use `RTR()` if the string doesn't need dynamic plural form. - * When placeholders are desired, use - * `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`. + * internationalization without the editor. A translation context can optionally + * be specified to disambiguate between identical source strings in translations. + * Use `RTR()` if the string doesn't need dynamic plural form. When placeholders + * are desired, use `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`. * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`. * * NOTE: Do not use `RTRN()` in editor-only code (typically within the `editor/` @@ -5434,9 +5428,8 @@ String RTRN(const String &p_text, const String &p_text_plural, int p_n, const St String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context); if (rtr.is_empty() || rtr == p_text || rtr == p_text_plural) { return TranslationServer::get_singleton()->translate_plural(p_text, p_text_plural, p_n, p_context); - } else { - return rtr; } + return rtr; } // Return message based on English plural rule if translation is not possible. diff --git a/core/string/ustring.h b/core/string/ustring.h index b1348ceb48..468a015302 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -556,6 +556,43 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St String RTR(const String &p_text, const String &p_context = ""); String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = ""); +/** + * "Extractable TRanslate". Used for strings that can appear inside an exported + * project (such as the ones in nodes like `FileDialog`), which are made possible + * to add in the POT generator. A translation context can optionally be specified + * to disambiguate between identical source strings in translations. + * When placeholders are desired, use vformat(ETR("Example: %s"), some_string)`. + * If a string mentions a quantity (and may therefore need a dynamic plural form), + * use `ETRN()` instead of `ETR()`. + * + * NOTE: This function is for string extraction only, and will just return the + * string it was given. The translation itself should be done internally by nodes + * with `atr()` instead. + */ +_FORCE_INLINE_ String ETR(const String &p_text, const String &p_context = "") { + return p_text; +} + +/** + * "Extractable TRanslate for N items". Used for strings that can appear inside an + * exported project (such as the ones in nodes like `FileDialog`), which are made + * possible to add in the POT generator. A translation context can optionally be + * specified to disambiguate between identical source strings in translations. + * Use `ETR()` if the string doesn't need dynamic plural form. When placeholders + * are desired, use `vformat(ETRN("%d item", "%d items", some_integer), some_integer)`. + * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`. + * + * NOTE: This function is for string extraction only, and will just return the + * string it was given. The translation itself should be done internally by nodes + * with `atr()` instead. + */ +_FORCE_INLINE_ String ETRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "") { + if (p_n == 1) { + return p_text; + } + return p_text_plural; +} + bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end); _FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) { diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 55f687bdf9..6bad6f5a5b 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -30,10 +30,11 @@ #include "callable.h" -#include "callable_bind.h" #include "core/object/object.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" +#include "core/variant/callable_bind.h" +#include "core/variant/variant_callable.h" void Callable::call_deferredp(const Variant **p_arguments, int p_argcount) const { MessageQueue::get_singleton()->push_callablep(*this, p_arguments, p_argcount, true); @@ -327,14 +328,27 @@ Callable::operator String() const { } } +Callable Callable::create(const Variant &p_variant, const StringName &p_method) { + ERR_FAIL_COND_V_MSG(p_method == StringName(), Callable(), "Method argument to Callable::create method must be a non-empty string."); + + switch (p_variant.get_type()) { + case Variant::NIL: + return Callable(ObjectID(), p_method); + case Variant::OBJECT: + return Callable(p_variant.operator ObjectID(), p_method); + default: + return Callable(memnew(VariantCallable(p_variant, p_method))); + } +} + Callable::Callable(const Object *p_object, const StringName &p_method) { - if (p_method == StringName()) { + if (unlikely(p_method == StringName())) { object = 0; - ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string"); + ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string."); } - if (p_object == nullptr) { + if (unlikely(p_object == nullptr)) { object = 0; - ERR_FAIL_MSG("Object argument to Callable constructor must be non-null"); + ERR_FAIL_MSG("Object argument to Callable constructor must be non-null."); } object = p_object->get_instance_id(); @@ -342,9 +356,9 @@ Callable::Callable(const Object *p_object, const StringName &p_method) { } Callable::Callable(ObjectID p_object, const StringName &p_method) { - if (p_method == StringName()) { + if (unlikely(p_method == StringName())) { object = 0; - ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string"); + ERR_FAIL_MSG("Method argument to Callable constructor must be a non-empty string."); } object = p_object; @@ -352,9 +366,9 @@ Callable::Callable(ObjectID p_object, const StringName &p_method) { } Callable::Callable(CallableCustom *p_custom) { - if (p_custom->referenced) { + if (unlikely(p_custom->referenced)) { object = 0; - ERR_FAIL_MSG("Callable custom is already referenced"); + ERR_FAIL_MSG("Callable custom is already referenced."); } p_custom->referenced = true; object = 0; //ensure object is all zero, since pointer may be 32 bits diff --git a/core/variant/callable.h b/core/variant/callable.h index 38872b71ef..bba69d453e 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -125,6 +125,8 @@ public: operator String() const; + static Callable create(const Variant &p_variant, const StringName &p_method); + Callable(const Object *p_object, const StringName &p_method); Callable(ObjectID p_object, const StringName &p_method); Callable(CallableCustom *p_custom); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index b551a7059e..543ee1135f 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2033,11 +2033,13 @@ static void _register_variant_builtin_methods() { bind_method(NodePath, get_subname, sarray("idx"), varray()); bind_method(NodePath, get_concatenated_names, sarray(), varray()); bind_method(NodePath, get_concatenated_subnames, sarray(), varray()); + bind_method(NodePath, slice, sarray("begin", "end"), varray(INT_MAX)); bind_method(NodePath, get_as_property_path, sarray(), varray()); bind_method(NodePath, is_empty, sarray(), varray()); /* Callable */ + bind_static_method(Callable, create, sarray("variant", "method"), varray()); bind_method(Callable, callv, sarray("arguments"), varray()); bind_method(Callable, is_null, sarray(), varray()); bind_method(Callable, is_custom, sarray(), varray()); diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 171074188f..79bed9be33 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -810,7 +810,7 @@ struct VariantInternalAccessor<bool> { #define VARIANT_ACCESSOR_NUMBER(m_type) \ template <> \ struct VariantInternalAccessor<m_type> { \ - static _FORCE_INLINE_ m_type get(const Variant *v) { return (m_type)*VariantInternal::get_int(v); } \ + static _FORCE_INLINE_ m_type get(const Variant *v) { return (m_type) * VariantInternal::get_int(v); } \ static _FORCE_INLINE_ void set(Variant *v, m_type p_value) { *VariantInternal::get_int(v) = p_value; } \ }; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 50c9c10987..20941b944f 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -433,9 +433,9 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { } \ m_assign_type num; \ if (value->get_type() == Variant::INT) { \ - num = (m_assign_type)*VariantGetInternalPtr<int64_t>::get_ptr(value); \ + num = (m_assign_type) * VariantGetInternalPtr<int64_t>::get_ptr(value); \ } else if (value->get_type() == Variant::FLOAT) { \ - num = (m_assign_type)*VariantGetInternalPtr<double>::get_ptr(value); \ + num = (m_assign_type) * VariantGetInternalPtr<double>::get_ptr(value); \ } else { \ *oob = false; \ *valid = false; \ @@ -495,9 +495,9 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { } \ m_assign_type num; \ if (value->get_type() == Variant::INT) { \ - num = (m_assign_type)*VariantGetInternalPtr<int64_t>::get_ptr(value); \ + num = (m_assign_type) * VariantGetInternalPtr<int64_t>::get_ptr(value); \ } else if (value->get_type() == Variant::FLOAT) { \ - num = (m_assign_type)*VariantGetInternalPtr<double>::get_ptr(value); \ + num = (m_assign_type) * VariantGetInternalPtr<double>::get_ptr(value); \ } else { \ *oob = false; \ *valid = false; \ diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml index a028d0bb4f..d7fb735b4d 100644 --- a/doc/classes/AnimationNode.xml +++ b/doc/classes/AnimationNode.xml @@ -27,7 +27,7 @@ <method name="_get_child_nodes" qualifiers="virtual const"> <return type="Dictionary" /> <description> - When inheriting from [AnimationRootNode], implement this virtual method to return all children animation nodes in order as a [code]name: node[/code] dictionary. + When inheriting from [AnimationRootNode], implement this virtual method to return all child animation nodes in order as a [code]name: node[/code] dictionary. </description> </method> <method name="_get_parameter_default_value" qualifiers="virtual const"> @@ -115,7 +115,7 @@ <param index="7" name="sync" type="bool" default="true" /> <param index="8" name="test_only" type="bool" default="false" /> <description> - Blend another animation node (in case this animation node contains children animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, else editors will not display your animation node for addition. + Blend another animation node (in case this animation node contains child animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, otherwise editors will not display your animation node for addition. </description> </method> <method name="find_input" qualifiers="const"> diff --git a/doc/classes/AnimationNodeTimeScale.xml b/doc/classes/AnimationNodeTimeScale.xml index d7d4814506..4cb8ccb962 100644 --- a/doc/classes/AnimationNodeTimeScale.xml +++ b/doc/classes/AnimationNodeTimeScale.xml @@ -4,7 +4,7 @@ A time-scaling animation node used in [AnimationTree]. </brief_description> <description> - Allows to scale the speed of the animation (or reverse it) in any children [AnimationNode]s. Setting it to [code]0.0[/code] will pause the animation. + Allows to scale the speed of the animation (or reverse it) in any child [AnimationNode]s. Setting it to [code]0.0[/code] will pause the animation. </description> <tutorials> <link title="Using AnimationTree">$DOCS_URL/tutorials/animation/animation_tree.html</link> diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index 7ba328c8b8..e5b47ffb89 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -94,7 +94,7 @@ <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Text [Color] used when the [Button] is being hovered and pressed. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the [Button]. </theme_item> <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index 8dd0cfa135..3965fd4f8f 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -4,7 +4,7 @@ A built-in type representing a method or a standalone function. </brief_description> <description> - [Callable] is a built-in [Variant] type that represents a function. It can either be a method within an [Object] instance, or a standalone function not related to any object, like a lambda function. Like all [Variant] types, it can be stored in variables and passed to other functions. It is most commonly used for signal callbacks. + [Callable] is a built-in [Variant] type that represents a function. It can either be a method within an [Object] instance, or a custom callable used for different purposes (see [method is_custom]). Like all [Variant] types, it can be stored in variables and passed to other functions. It is most commonly used for signal callbacks. [b]Example:[/b] [codeblocks] [gdscript] @@ -46,6 +46,22 @@ # Prints "Attack!", when the button_pressed signal is emitted. button_pressed.connect(func(): print("Attack!")) [/codeblock] + In GDScript, you can access methods and global functions as [Callable]s: + [codeblock] + tween.tween_callback(node.queue_free) # Object methods. + tween.tween_callback(array.clear) # Methods of built-in types. + tween.tween_callback(print.bind("Test")) # Global functions. + [/codeblock] + [b]Note:[/b] [Dictionary] does not support the above due to ambiguity with keys. + [codeblock] + var dictionary = {"hello": "world"} + + # This will not work, `clear` is treated as a key. + tween.tween_callback(dictionary.clear) + + # This will work. + tween.tween_callback(Callable.create(dictionary, "clear")) + [/codeblock] </description> <tutorials> </tutorials> @@ -69,6 +85,7 @@ <param index="1" name="method" type="StringName" /> <description> Creates a new [Callable] for the method named [param method] in the specified [param object]. + [b]Note:[/b] For methods of built-in [Variant] types, use [method create] instead. </description> </constructor> </constructors> @@ -110,6 +127,7 @@ } [/csharp] [/codeblocks] + [b]Note:[/b] Deferred calls are processed at idle time. Idle time happens mainly at the end of process and physics frames. In it, deferred calls will be run until there are none left, which means you can defer calls from other deferred calls and they'll still be run in the current idle time cycle. This means you should not call a method deferred from itself (or from a method called by it), as this causes infinite recursion the same way as if you had called the method directly. See also [method Object.call_deferred]. </description> </method> @@ -120,6 +138,15 @@ Calls the method represented by this [Callable]. Unlike [method call], this method expects all arguments to be contained inside the [param arguments] [Array]. </description> </method> + <method name="create" qualifiers="static"> + <return type="Callable" /> + <param index="0" name="variant" type="Variant" /> + <param index="1" name="method" type="StringName" /> + <description> + Creates a new [Callable] for the method named [param method] in the specified [param variant]. To represent a method of a built-in [Variant] type, a custom callable is used (see [method is_custom]). If [param variant] is [Object], then a standard callable will be created instead. + [b]Note:[/b] This method is always necessary for the [Dictionary] type, as property syntax is used to access its entries. You may also use this method when [param variant]'s type is not known in advance (for polymorphism). + </description> + </method> <method name="get_bound_arguments" qualifiers="const"> <return type="Array" /> <description> @@ -160,7 +187,11 @@ <method name="is_custom" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this [Callable] is a custom callable. Custom callables are created from [method bind] or [method unbind]. In GDScript, lambda functions are also custom callables. + Returns [code]true[/code] if this [Callable] is a custom callable. Custom callables are used: + - for binding/unbinding arguments (see [method bind] and [method unbind]); + - for representing methods of built-in [Variant] types (see [method create]); + - for representing global, lambda, and RPC functions in GDScript; + - for other purposes in the core, GDExtension, and C#. </description> </method> <method name="is_null" qualifiers="const"> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 608a47d416..3cb74a9cc9 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -528,7 +528,7 @@ <return type="void" /> <description> Moves this node to display on top of its siblings. - Internally, the node is moved to the bottom of parent's children list. The method has no effect on nodes without a parent. + Internally, the node is moved to the bottom of parent's child list. The method has no effect on nodes without a parent. </description> </method> <method name="queue_redraw"> @@ -568,7 +568,7 @@ </methods> <members> <member name="clip_children" type="int" setter="set_clip_children_mode" getter="get_clip_children_mode" enum="CanvasItem.ClipChildrenMode" default="0"> - Allows the current node to clip children nodes, essentially acting as a mask. + Allows the current node to clip child nodes, essentially acting as a mask. </member> <member name="light_mask" type="int" setter="set_light_mask" getter="get_light_mask" default="1"> The rendering layers in which this [CanvasItem] responds to [Light2D] nodes. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 44f24a97bb..d5fc52ab12 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -78,6 +78,7 @@ <param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" /> <description> Sets a custom mouse cursor image for the defined [param shape]. This means the user's operating system and mouse cursor theme will no longer influence the mouse cursor's appearance. The image must be [code]256x256[/code] or smaller for correct appearance. [param hotspot] can optionally be set to define the area where the cursor will click. By default, [param hotspot] is set to [code]Vector2(0, 0)[/code], which is the top-left corner of the image. See also [method cursor_set_shape]. + [param cursor] can be either [Texture2D] or [Image]. </description> </method> <method name="cursor_set_shape"> diff --git a/doc/classes/FlowContainer.xml b/doc/classes/FlowContainer.xml index e70e76f191..5e767acf7d 100644 --- a/doc/classes/FlowContainer.xml +++ b/doc/classes/FlowContainer.xml @@ -43,10 +43,10 @@ </constants> <theme_items> <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal separation of children nodes. + The horizontal separation of child nodes. </theme_item> <theme_item name="v_separation" data_type="constant" type="int" default="4"> - The vertical separation of children nodes. + The vertical separation of child nodes. </theme_item> </theme_items> </class> diff --git a/doc/classes/GridContainer.xml b/doc/classes/GridContainer.xml index cd96bd49d9..d69f4a10d2 100644 --- a/doc/classes/GridContainer.xml +++ b/doc/classes/GridContainer.xml @@ -18,10 +18,10 @@ </members> <theme_items> <theme_item name="h_separation" data_type="constant" type="int" default="4"> - The horizontal separation of children nodes. + The horizontal separation of child nodes. </theme_item> <theme_item name="v_separation" data_type="constant" type="int" default="4"> - The vertical separation of children nodes. + The vertical separation of child nodes. </theme_item> </theme_items> </class> diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index d9b2ef59e3..6c1ae1b0ef 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -325,7 +325,7 @@ <param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" /> <description> Sets a custom mouse cursor image, which is only visible inside the game window. The hotspot can also be specified. Passing [code]null[/code] to the image parameter resets to the system cursor. See [enum CursorShape] for the list of shapes. - [param image]'s size must be lower than or equal to 256×256. To avoid rendering issues, sizes lower than or equal to 128×128 are recommended. + [param image] can be either [Texture2D] or [Image] and its size must be lower than or equal to 256×256. To avoid rendering issues, sizes lower than or equal to 128×128 are recommended. [param hotspot] must be within [param image]'s size. [b]Note:[/b] [AnimatedTexture]s aren't supported as custom mouse cursors. If using an [AnimatedTexture], only the first frame will be displayed. [b]Note:[/b] The [b]Lossless[/b], [b]Lossy[/b] or [b]Uncompressed[/b] compression modes are recommended. The [b]Video RAM[/b] compression mode can be used, but it will be decompressed on the CPU, which means loading times are slowed down and no memory is saved compared to lossless modes. diff --git a/doc/classes/ItemList.xml b/doc/classes/ItemList.xml index 593f41bc70..a1e5d9cbd9 100644 --- a/doc/classes/ItemList.xml +++ b/doc/classes/ItemList.xml @@ -461,7 +461,7 @@ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> Text [Color] used when the item is hovered and not selected yet. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the item. </theme_item> <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> @@ -483,7 +483,7 @@ The size of the item text outline. [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. </theme_item> - <theme_item name="v_separation" data_type="constant" type="int" default="2"> + <theme_item name="v_separation" data_type="constant" type="int" default="4"> The vertical spacing between items. </theme_item> <theme_item name="font" data_type="font" type="Font"> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index edd8a3e436..f39f5616f0 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -115,7 +115,7 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Default text [Color] of the [Label]. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The color of text outline. </theme_item> <theme_item name="font_shadow_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 1f25e926c9..77fff22157 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -454,7 +454,7 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> Default font color. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the [LineEdit]. </theme_item> <theme_item name="font_placeholder_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.6)"> diff --git a/doc/classes/LinkButton.xml b/doc/classes/LinkButton.xml index aa81b6fde0..bcdffcd1ee 100644 --- a/doc/classes/LinkButton.xml +++ b/doc/classes/LinkButton.xml @@ -74,7 +74,7 @@ <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> Text [Color] used when the [LinkButton] is being hovered and pressed. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the [LinkButton]. </theme_item> <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml index 8812017e49..9e4287331c 100644 --- a/doc/classes/MenuBar.xml +++ b/doc/classes/MenuBar.xml @@ -132,7 +132,7 @@ <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Text [Color] used when the menu item is being hovered and pressed. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the menu item. </theme_item> <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 29e144e4b9..b786b67933 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -977,7 +977,7 @@ Set the process thread group for this node (basically, whether it receives [constant NOTIFICATION_PROCESS], [constant NOTIFICATION_PHYSICS_PROCESS], [method _process] or [method _physics_process] (and the internal versions) on the main thread or in a sub-thread. By default, the thread group is [constant PROCESS_THREAD_GROUP_INHERIT], which means that this node belongs to the same thread group as the parent node. The thread groups means that nodes in a specific thread group will process together, separate to other thread groups (depending on [member process_thread_group_order]). If the value is set is [constant PROCESS_THREAD_GROUP_SUB_THREAD], this thread group will occur on a sub thread (not the main thread), otherwise if set to [constant PROCESS_THREAD_GROUP_MAIN_THREAD] it will process on the main thread. If there is not a parent or grandparent node set to something other than inherit, the node will belong to the [i]default thread group[/i]. This default group will process on the main thread and its group order is 0. During processing in a sub-thread, accessing most functions in nodes outside the thread group is forbidden (and it will result in an error in debug mode). Use [method Object.call_deferred], [method call_thread_safe], [method call_deferred_thread_group] and the likes in order to communicate from the thread groups to the main thread (or to other thread groups). - To better understand process thread groups, the idea is that any node set to any other value than [constant PROCESS_THREAD_GROUP_INHERIT] will include any children (and grandchildren) nodes set to inherit into its process thread group. this means that the processing of all the nodes in the group will happen together, at the same time as the node including them. + To better understand process thread groups, the idea is that any node set to any other value than [constant PROCESS_THREAD_GROUP_INHERIT] will include any child (and grandchild) nodes set to inherit into its process thread group. This means that the processing of all the nodes in the group will happen together, at the same time as the node including them. </member> <member name="process_thread_group_order" type="int" setter="set_process_thread_group_order" getter="get_process_thread_group_order"> Change the process thread group order. Groups with a lesser order will process before groups with a greater order. This is useful when a large amount of nodes process in sub thread and, afterwards, another group wants to collect their result in the main thread, as an example. @@ -1220,13 +1220,13 @@ Never process. Completely disables processing, ignoring [member SceneTree.paused]. This is the inverse of [constant PROCESS_MODE_ALWAYS]. </constant> <constant name="PROCESS_THREAD_GROUP_INHERIT" value="0" enum="ProcessThreadGroup"> - If the [member process_thread_group] property is sent to this, the node will belong to any parent (or grandparent) node that has a thread group mode that is not inherit. See [member process_thread_group] for more information. + Process this node based on the thread group mode of the first parent (or grandparent) node that has a thread group mode that is not inherit. See [member process_thread_group] for more information. </constant> <constant name="PROCESS_THREAD_GROUP_MAIN_THREAD" value="1" enum="ProcessThreadGroup"> - Process this node (and children nodes set to inherit) on the main thread. See [member process_thread_group] for more information. + Process this node (and child nodes set to inherit) on the main thread. See [member process_thread_group] for more information. </constant> <constant name="PROCESS_THREAD_GROUP_SUB_THREAD" value="2" enum="ProcessThreadGroup"> - Process this node (and children nodes set to inherit) on a sub-thread. See [member process_thread_group] for more information. + Process this node (and child nodes set to inherit) on a sub-thread. See [member process_thread_group] for more information. </constant> <constant name="FLAG_PROCESS_THREAD_MESSAGES" value="1" enum="ProcessThreadMessages" is_bitfield="true"> Allows this node to process threaded messages created with [method call_deferred_thread_group] right before [method _process] is called. @@ -1253,10 +1253,10 @@ The node will not be internal. </constant> <constant name="INTERNAL_MODE_FRONT" value="1" enum="InternalMode"> - The node will be placed at the beginning of the parent's children list, before any non-internal sibling. + The node will be placed at the beginning of the parent's children, before any non-internal sibling. </constant> <constant name="INTERNAL_MODE_BACK" value="2" enum="InternalMode"> - The node will be placed at the end of the parent's children list, after any non-internal sibling. + The node will be placed at the end of the parent's children, after any non-internal sibling. </constant> <constant name="AUTO_TRANSLATE_MODE_INHERIT" value="0" enum="AutoTranslateMode"> Inherits [member auto_translate_mode] from the node's parent. This is the default for any newly created node. diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml index fa6f158b6e..6e0799e796 100644 --- a/doc/classes/NodePath.xml +++ b/doc/classes/NodePath.xml @@ -199,6 +199,16 @@ Returns [code]true[/code] if the node path has been constructed from an empty [String] ([code]""[/code]). </description> </method> + <method name="slice" qualifiers="const"> + <return type="NodePath" /> + <param index="0" name="begin" type="int" /> + <param index="1" name="end" type="int" default="2147483647" /> + <description> + Returns the slice of the [NodePath], from [param begin] (inclusive) to [param end] (exclusive), as a new [NodePath]. + The absolute value of [param begin] and [param end] will be clamped to the sum of [method get_name_count] and [method get_subname_count], so the default value for [param end] makes it slice to the end of the [NodePath] by default (i.e. [code]path.slice(1)[/code] is a shorthand for [code]path.slice(1, path.get_name_count() + path.get_subname_count())[/code]). + If either [param begin] or [param end] are negative, they will be relative to the end of the [NodePath] (i.e. [code]path.slice(0, -2)[/code] is a shorthand for [code]path.slice(0, path.get_name_count() + path.get_subname_count() - 2)[/code]). + </description> + </method> </methods> <operators> <operator name="operator !="> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 307eaa5e00..724c9a38d8 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -406,7 +406,7 @@ <param index="0" name="method" type="StringName" /> <description> Calls the [param method] on the object during idle time. Always returns null, [b]not[/b] the method's result. - Idle time happens mainly at the end of process and physics frames. In it, deferred calls will be run until there are none left, which means you can defer calls from other deferred calls and they'll still be run in the current idle time cycle. If not done carefully, this can result in infinite recursion without causing a stack overflow, which will hang the game similarly to an infinite loop. + Idle time happens mainly at the end of process and physics frames. In it, deferred calls will be run until there are none left, which means you can defer calls from other deferred calls and they'll still be run in the current idle time cycle. This means you should not call a method deferred from itself (or from a method called by it), as this causes infinite recursion the same way as if you had called the method directly. This method supports a variable number of arguments, so parameters can be passed as a comma separated list. [codeblocks] [gdscript] diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml index fc9ef7ddb8..9324f99535 100644 --- a/doc/classes/PackedScene.xml +++ b/doc/classes/PackedScene.xml @@ -106,7 +106,7 @@ <members> <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{ "conn_count": 0, "conns": PackedInt32Array(), "editable_instances": [], "names": PackedStringArray(), "node_count": 0, "node_paths": [], "nodes": PackedInt32Array(), "variants": [], "version": 3 }"> A dictionary representation of the scene contents. - Available keys include "rnames" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for base scene children overrides, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene. + Available keys include "rnames" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for paths to overridden nodes, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene. </member> </members> <constants> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index d884c7806f..266a0940eb 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -680,13 +680,13 @@ <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> [Color] used for the hovered text. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the menu item. </theme_item> <theme_item name="font_separator_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> [Color] used for labeled separators' text. See [method add_separator]. </theme_item> - <theme_item name="font_separator_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_separator_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the labeled separator. </theme_item> <theme_item name="h_separation" data_type="constant" type="int" default="4"> diff --git a/doc/classes/ProgressBar.xml b/doc/classes/ProgressBar.xml index 02f82f6a9a..e562d39bb7 100644 --- a/doc/classes/ProgressBar.xml +++ b/doc/classes/ProgressBar.xml @@ -40,7 +40,7 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> The color of the text. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the [ProgressBar]. </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 8bf299e3e7..75897bd85d 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2286,14 +2286,16 @@ [b]Note:[/b] This property is only read when the project starts. To change the 2D shadow atlas size at runtime, use [method RenderingServer.canvas_set_shadow_texture_size] instead. </member> <member name="rendering/2d/snap/snap_2d_transforms_to_pixel" type="bool" setter="" getter="" default="false"> - If [code]true[/code], [CanvasItem] nodes will internally snap to full pixels. Useful for low-resolution pixel art games. Their position can still be sub-pixel, but the decimals will not have effect. This can lead to a crisper appearance at the cost of less smooth movement, especially when [Camera2D] smoothing is enabled. + If [code]true[/code], [CanvasItem] nodes will internally snap to full pixels. Useful for low-resolution pixel art games. Their position can still be sub-pixel, but the decimals will not have effect as the position is rounded. This can lead to a crisper appearance at the cost of less smooth movement, especially when [Camera2D] smoothing is enabled. [b]Note:[/b] This property is only read when the project starts. To toggle 2D transform snapping at runtime, use [method RenderingServer.viewport_set_snap_2d_transforms_to_pixel] on the root [Viewport] instead. [b]Note:[/b] [Control] nodes are snapped to the nearest pixel by default. This is controlled by [member gui/common/snap_controls_to_pixels]. + [b]Note:[/b] It is not recommended to use this setting together with [member rendering/2d/snap/snap_2d_vertices_to_pixel], as movement may appear even less smooth. Prefer only enabling this setting instead. </member> <member name="rendering/2d/snap/snap_2d_vertices_to_pixel" type="bool" setter="" getter="" default="false"> If [code]true[/code], vertices of [CanvasItem] nodes will snap to full pixels. Useful for low-resolution pixel art games. Only affects the final vertex positions, not the transforms. This can lead to a crisper appearance at the cost of less smooth movement, especially when [Camera2D] smoothing is enabled. [b]Note:[/b] This property is only read when the project starts. To toggle 2D vertex snapping at runtime, use [method RenderingServer.viewport_set_snap_2d_vertices_to_pixel] on the root [Viewport] instead. [b]Note:[/b] [Control] nodes are snapped to the nearest pixel by default. This is controlled by [member gui/common/snap_controls_to_pixels]. + [b]Note:[/b] It is not recommended to use this setting together with [member rendering/2d/snap/snap_2d_transforms_to_pixel], as movement may appear even less smooth. Prefer only enabling that setting instead. </member> <member name="rendering/anti_aliasing/quality/msaa_2d" type="int" setter="" getter="" default="0"> Sets the number of MSAA samples to use for 2D/Canvas rendering (as a power of two). MSAA is used to reduce aliasing around the edges of polygons. A higher MSAA value results in smoother edges but can be significantly slower on some hardware, especially integrated graphics due to their limited memory bandwidth. This has no effect on shader-induced aliasing or texture aliasing. diff --git a/doc/classes/ResourceFormatLoader.xml b/doc/classes/ResourceFormatLoader.xml index 61eafb4527..4e4adc86c4 100644 --- a/doc/classes/ResourceFormatLoader.xml +++ b/doc/classes/ResourceFormatLoader.xml @@ -99,10 +99,19 @@ </methods> <constants> <constant name="CACHE_MODE_IGNORE" value="0" enum="CacheMode"> + Neither the main resource (the one requested to be loaded) nor any of its subresources are retrieved from cache nor stored into it. Dependencies (external resources) are loaded with [constant CACHE_MODE_REUSE]. </constant> <constant name="CACHE_MODE_REUSE" value="1" enum="CacheMode"> + The main resource (the one requested to be loaded), its subresources, and its dependencies (external resources) are retrieved from cache if present, instead of loaded. Those not cached are loaded and then stored into the cache. The same rules are propagated recursively down the tree of dependencies (external resources). </constant> <constant name="CACHE_MODE_REPLACE" value="2" enum="CacheMode"> + Like [constant CACHE_MODE_REUSE], but the cache is checked for the main resource (the one requested to be loaded) as well as for each of its subresources. Those already in the cache, as long as the loaded and cached types match, have their data refreshed from storage into the already existing instances. Otherwise, they are recreated as completely new objects. + </constant> + <constant name="CACHE_MODE_IGNORE_DEEP" value="3" enum="CacheMode"> + Like [constant CACHE_MODE_IGNORE], but propagated recursively down the tree of dependencies (external resources). + </constant> + <constant name="CACHE_MODE_REPLACE_DEEP" value="4" enum="CacheMode"> + Like [constant CACHE_MODE_REPLACE], but propagated recursively down the tree of dependencies (external resources). </constant> </constants> </class> diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml index 54e95d8c30..95fbee4a24 100644 --- a/doc/classes/ResourceLoader.xml +++ b/doc/classes/ResourceLoader.xml @@ -138,13 +138,19 @@ The resource was loaded successfully and can be accessed via [method load_threaded_get]. </constant> <constant name="CACHE_MODE_IGNORE" value="0" enum="CacheMode"> - The resource is always loaded from disk, even if a cache entry exists for its path, and the newly loaded copy will not be cached. Instances loaded with this mode will exist independently. + Neither the main resource (the one requested to be loaded) nor any of its subresources are retrieved from cache nor stored into it. Dependencies (external resources) are loaded with [constant CACHE_MODE_REUSE]. </constant> <constant name="CACHE_MODE_REUSE" value="1" enum="CacheMode"> - If a resource is cached, returns the cached reference. Otherwise it's loaded from disk. + The main resource (the one requested to be loaded), its subresources, and its dependencies (external resources) are retrieved from cache if present, instead of loaded. Those not cached are loaded and then stored into the cache. The same rules are propagated recursively down the tree of dependencies (external resources). </constant> <constant name="CACHE_MODE_REPLACE" value="2" enum="CacheMode"> - The resource is always loaded from disk, even if a cache entry exists for its path. The cached entry will be replaced by the newly loaded copy. + Like [constant CACHE_MODE_REUSE], but the cache is checked for the main resource (the one requested to be loaded) as well as for each of its subresources. Those already in the cache, as long as the loaded and cached types match, have their data refreshed from storage into the already existing instances. Otherwise, they are recreated as completely new objects. + </constant> + <constant name="CACHE_MODE_IGNORE_DEEP" value="3" enum="CacheMode"> + Like [constant CACHE_MODE_IGNORE], but propagated recursively down the tree of dependencies (external resources). + </constant> + <constant name="CACHE_MODE_REPLACE_DEEP" value="4" enum="CacheMode"> + Like [constant CACHE_MODE_REPLACE], but propagated recursively down the tree of dependencies (external resources). </constant> </constants> </class> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 85dea1485a..b294b37b8f 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -69,7 +69,7 @@ <return type="int" /> <param index="0" name="character" type="int" /> <description> - Returns the line number of the character position provided. + Returns the line number of the character position provided. Line and character numbers are both zero-indexed. [b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded. </description> </method> @@ -77,7 +77,7 @@ <return type="int" /> <param index="0" name="character" type="int" /> <description> - Returns the paragraph number of the character position provided. + Returns the paragraph number of the character position provided. Paragraph and character numbers are both zero-indexed. [b]Note:[/b] If [member threaded] is enabled, this method returns a value for the loaded part of the document. Use [method is_ready] or [signal finished] to determine whether document is fully loaded. </description> </method> @@ -430,6 +430,7 @@ <param index="0" name="data" type="Variant" /> <description> Adds a meta tag to the tag stack. Similar to the BBCode [code skip-lint][url=something]{text}[/url][/code], but supports non-[String] metadata types. + [b]Note:[/b] Meta tags do nothing by default when clicked. To assign behavior when clicked, connect [signal meta_clicked] to a function that is called when the meta tag is clicked. </description> </method> <method name="push_mono"> @@ -616,7 +617,7 @@ Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. </member> <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true"> - If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code]. + If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code]. These tags can call a function when clicked if [signal meta_clicked] is connected to a function. </member> <member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000"> The delay after which the loading progress bar is displayed, in milliseconds. Set to [code]-1[/code] to disable progress bar entirely. @@ -674,7 +675,17 @@ <signal name="meta_clicked"> <param index="0" name="meta" type="Variant" /> <description> - Triggered when the user clicks on content between meta tags. If the meta is defined in text, e.g. [code skip-lint][url={"data"="hi"}]hi[/url][/code], then the parameter for this signal will be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack. + Triggered when the user clicks on content between meta (URL) tags. If the meta is defined in BBCode, e.g. [code skip-lint][url={"key": "value"}]Text[/url][/code], then the parameter for this signal will always be a [String] type. If a particular type or an object is desired, the [method push_meta] method must be used to manually insert the data into the tag stack. Alternatively, you can convert the [String] input to the desired type based on its contents (such as calling [method JSON.parse] on it). + For example, the following method can be connected to [signal meta_clicked] to open clicked URLs using the user's default web browser: + [codeblocks] + [gdscript] + # This assumes RichTextLabel's `meta_clicked` signal was connected to + # the function below using the signal connection dialog. + func _richtextlabel_on_meta_clicked(meta): + # `meta` is of Variant type, so convert it to a String to avoid script errors at run-time. + OS.shell_open(str(meta)) + [/gdscript] + [/codeblocks] </description> </signal> <signal name="meta_hover_ended"> @@ -741,7 +752,7 @@ <theme_item name="default_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> The default text color. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The default tint of text outline. </theme_item> <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index b6be0e92a3..31c608f3a1 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -62,7 +62,7 @@ <return type="PackedInt32Array" /> <param index="0" name="bone_idx" type="int" /> <description> - Returns an array containing the bone indexes of all the children node of the passed in bone, [param bone_idx]. + Returns an array containing the bone indexes of all the child node of the passed in bone, [param bone_idx]. </description> </method> <method name="get_bone_count" qualifiers="const"> diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index c59a336bc2..a2beef81eb 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -360,7 +360,7 @@ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> Font color of the currently hovered tab. Does not apply to the selected tab. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the tab name. </theme_item> <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml index 0c1feae6e0..b0e3f67791 100644 --- a/doc/classes/TabContainer.xml +++ b/doc/classes/TabContainer.xml @@ -209,7 +209,7 @@ If [code]true[/code], tabs are visible. If [code]false[/code], tabs' content and titles are hidden. </member> <member name="use_hidden_tabs_for_min_size" type="bool" setter="set_use_hidden_tabs_for_min_size" getter="get_use_hidden_tabs_for_min_size" default="false"> - If [code]true[/code], children [Control] nodes that are hidden have their minimum size take into account in the total, instead of only the currently visible one. + If [code]true[/code], child [Control] nodes that are hidden have their minimum size take into account in the total, instead of only the currently visible one. </member> </members> <signals> @@ -276,7 +276,7 @@ <theme_item name="font_hovered_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> Font color of the currently hovered tab. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the tab name. </theme_item> <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index bbf86cf84d..b8a838208b 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1428,7 +1428,7 @@ <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> Sets the font [Color]. </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the [TextEdit]. </theme_item> <theme_item name="font_placeholder_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.6)"> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 88cda5ae10..0f318efbd1 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -509,7 +509,7 @@ <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> Text [Color] for a [constant TreeItem.CELL_MODE_CHECK] mode cell when it's non-editable (see [method TreeItem.set_editable]). </theme_item> - <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The tint of text outline of the item. </theme_item> <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 7c593fefd9..d5dc92e16a 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -4,7 +4,7 @@ Abstract base class for viewports. Encapsulates drawing and interaction with a game world. </brief_description> <description> - A Viewport creates a different view into the screen, or a sub-view inside another viewport. Children 2D Nodes will display on it, and children Camera3D 3D nodes will render on it too. + A [Viewport] creates a different view into the screen, or a sub-view inside another viewport. Child 2D nodes will display on it, and child Camera3D 3D nodes will render on it too. Optionally, a viewport can have its own 2D or 3D world, so it doesn't share what it draws with other viewports. Viewports can also choose to be audio listeners, so they generate positional audio depending on a 2D or 3D camera child of it. Also, viewports can be assigned to different screens in case the devices have multiple screens. diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 7a60c23e58..2acf0532e8 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -97,7 +97,7 @@ <method name="get_contents_minimum_size" qualifiers="const"> <return type="Vector2" /> <description> - Returns the combined minimum size from the child [Control] nodes of the window. Use [method child_controls_changed] to update it when children nodes have changed. + Returns the combined minimum size from the child [Control] nodes of the window. Use [method child_controls_changed] to update it when child nodes have changed. The value returned by this method can be overridden with [method _get_contents_minimum_size]. </description> </method> @@ -902,7 +902,7 @@ <theme_item name="title_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> The color of the title's text. </theme_item> - <theme_item name="title_outline_modulate" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + <theme_item name="title_outline_modulate" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> The color of the title's text outline. </theme_item> <theme_item name="close_h_offset" data_type="constant" type="int" default="18"> diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index aae06505cd..dd8bceb573 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -47,7 +47,7 @@ #include <wchar.h> #ifdef _MSC_VER -#define S_ISREG(m) ((m)&_S_IFREG) +#define S_ISREG(m) ((m) & _S_IFREG) #endif void FileAccessWindows::check_errors() const { diff --git a/editor/SCsub b/editor/SCsub index aa240a1e01..bbb40ff303 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -104,6 +104,15 @@ if env.editor_build: env.Run(editor_builders.make_doc_translations_header, "Generating translations header."), ) + # Extractable translations + tlist = glob.glob(env.Dir("#editor/translations/extractable").abspath + "/*.po") + env.Depends("#editor/extractable_translations.gen.h", tlist) + env.CommandNoCache( + "#editor/extractable_translations.gen.h", + tlist, + env.Run(editor_builders.make_extractable_translations_header, "Generating extractable translations header."), + ) + env.add_source_files(env.editor_sources, "*.cpp") env.add_source_files(env.editor_sources, "register_exporters.gen.cpp") diff --git a/editor/editor_builders.py b/editor/editor_builders.py index 25004da877..98a64cfd23 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -140,5 +140,9 @@ def make_doc_translations_header(target, source, env): make_translations_header(target, source, env, "doc") +def make_extractable_translations_header(target, source, env): + make_translations_header(target, source, env, "extractable") + + if __name__ == "__main__": subprocess_main(globals()) diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index fa6a02f9d4..3e11b4a5ff 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -2016,6 +2016,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector } Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) { + print_verbose(vformat("EditorFileSystem: Importing file: %s", p_file)); + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + EditorFileSystemDirectory *fs = nullptr; int cpos = -1; bool found = _find_file(p_file, &fs, cpos); @@ -2268,6 +2271,8 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); + print_verbose(vformat("EditorFileSystem: \"%s\" import took %d ms.", p_file, OS::get_singleton()->get_ticks_msec() - start_time)); + return OK; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2cee2c2198..dc90256490 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1285,7 +1285,14 @@ void EditorNode::save_resource(const Ref<Resource> &p_resource) { if (p_resource->is_built_in()) { const String scene_path = p_resource->get_path().get_slice("::", 0); if (!scene_path.is_empty()) { - save_scene_if_open(scene_path); + if (ResourceLoader::exists(scene_path) && ResourceLoader::get_resource_type(scene_path) == "PackedScene") { + save_scene_if_open(scene_path); + } else { + // Not a packed scene, so save it as regular resource. + Ref<Resource> parent_resource = ResourceCache::get_ref(scene_path); + ERR_FAIL_COND_MSG(parent_resource.is_null(), "Parent resource not loaded, can't save."); + save_resource(parent_resource); + } return; } } diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index d76f55564b..b6d8d7b8d6 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -1060,6 +1060,9 @@ void EditorSettings::setup_language() { // Load class reference translation. load_doc_translations(lang); + + // Load extractable translation for projects. + load_extractable_translations(lang); } void EditorSettings::setup_network() { diff --git a/editor/editor_translation.cpp b/editor/editor_translation.cpp index 54a9d2f068..302a81669d 100644 --- a/editor/editor_translation.cpp +++ b/editor/editor_translation.cpp @@ -35,6 +35,7 @@ #include "core/io/translation_loader_po.h" #include "editor/doc_translations.gen.h" #include "editor/editor_translations.gen.h" +#include "editor/extractable_translations.gen.h" #include "editor/property_translations.gen.h" Vector<String> get_editor_locales() { @@ -77,6 +78,32 @@ void load_editor_translations(const String &p_locale) { } } +void load_property_translations(const String &p_locale) { + PropertyTranslationList *etl = _property_translations; + while (etl->data) { + if (etl->lang == p_locale) { + Vector<uint8_t> data; + data.resize(etl->uncomp_size); + int ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE); + ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt."); + + Ref<FileAccessMemory> fa; + fa.instantiate(); + fa->open_custom(data.ptr(), data.size()); + + Ref<Translation> tr = TranslationLoaderPO::load_translation(fa); + + if (tr.is_valid()) { + tr->set_locale(etl->lang); + TranslationServer::get_singleton()->set_property_translation(tr); + break; + } + } + + etl++; + } +} + void load_doc_translations(const String &p_locale) { DocTranslationList *dtl = _doc_translations; while (dtl->data) { @@ -103,8 +130,8 @@ void load_doc_translations(const String &p_locale) { } } -void load_property_translations(const String &p_locale) { - PropertyTranslationList *etl = _property_translations; +void load_extractable_translations(const String &p_locale) { + ExtractableTranslationList *etl = _extractable_translations; while (etl->data) { if (etl->lang == p_locale) { Vector<uint8_t> data; @@ -120,7 +147,7 @@ void load_property_translations(const String &p_locale) { if (tr.is_valid()) { tr->set_locale(etl->lang); - TranslationServer::get_singleton()->set_property_translation(tr); + TranslationServer::get_singleton()->set_extractable_translation(tr); break; } } @@ -128,3 +155,29 @@ void load_property_translations(const String &p_locale) { etl++; } } + +List<StringName> get_extractable_message_list() { + ExtractableTranslationList *etl = _extractable_translations; + List<StringName> msgids; + while (etl->data) { + Vector<uint8_t> data; + data.resize(etl->uncomp_size); + int ret = Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE); + ERR_FAIL_COND_V_MSG(ret == -1, msgids, "Compressed file is corrupt."); + + Ref<FileAccessMemory> fa; + fa.instantiate(); + fa->open_custom(data.ptr(), data.size()); + + Ref<Translation> tr = TranslationLoaderPO::load_translation(fa); + + if (tr.is_valid()) { + tr->get_message_list(&msgids); + break; + } + + etl++; + } + + return msgids; +} diff --git a/editor/editor_translation.h b/editor/editor_translation.h index bd6db32536..4785495629 100644 --- a/editor/editor_translation.h +++ b/editor/editor_translation.h @@ -32,11 +32,14 @@ #define EDITOR_TRANSLATION_H #include "core/string/ustring.h" +#include "core/templates/list.h" #include "core/templates/vector.h" Vector<String> get_editor_locales(); void load_editor_translations(const String &p_locale); -void load_doc_translations(const String &p_locale); void load_property_translations(const String &p_locale); +void load_doc_translations(const String &p_locale); +void load_extractable_translations(const String &p_locale); +List<StringName> get_extractable_message_list(); #endif // EDITOR_TRANSLATION_H diff --git a/editor/localization_editor.cpp b/editor/localization_editor.cpp index 8bdd5a3102..eb7f508695 100644 --- a/editor/localization_editor.cpp +++ b/editor/localization_editor.cpp @@ -45,6 +45,7 @@ void LocalizationEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { translation_list->connect("button_clicked", callable_mp(this, &LocalizationEditor::_translation_delete)); translation_pot_list->connect("button_clicked", callable_mp(this, &LocalizationEditor::_pot_delete)); + translation_pot_add_builtin->set_pressed(GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot")); List<String> tfn; ResourceLoader::get_recognized_extensions_for_type("Translation", &tfn); @@ -377,6 +378,11 @@ void LocalizationEditor::_pot_generate_open() { pot_generate_dialog->popup_file_dialog(); } +void LocalizationEditor::_pot_add_builtin_toggled() { + ProjectSettings::get_singleton()->set_setting("internationalization/locale/translation_add_builtin_strings_to_pot", translation_pot_add_builtin->is_pressed()); + ProjectSettings::get_singleton()->save(); +} + void LocalizationEditor::_pot_generate(const String &p_file) { POTGenerator::get_singleton()->generate_pot(p_file); } @@ -730,13 +736,14 @@ LocalizationEditor::LocalizationEditor() { pot_generate_button->connect("pressed", callable_mp(this, &LocalizationEditor::_pot_generate_open)); thb->add_child(pot_generate_button); - VBoxContainer *tmc = memnew(VBoxContainer); - tmc->set_v_size_flags(Control::SIZE_EXPAND_FILL); - tvb->add_child(tmc); - translation_pot_list = memnew(Tree); translation_pot_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); - tmc->add_child(translation_pot_list); + tvb->add_child(translation_pot_list); + + translation_pot_add_builtin = memnew(CheckBox(TTR("Add Built-in Strings to POT"))); + translation_pot_add_builtin->set_tooltip_text(TTR("Add strings from built-in components such as certain Control nodes.")); + translation_pot_add_builtin->connect("pressed", callable_mp(this, &LocalizationEditor::_pot_add_builtin_toggled)); + tvb->add_child(translation_pot_add_builtin); pot_generate_dialog = memnew(EditorFileDialog); pot_generate_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); diff --git a/editor/localization_editor.h b/editor/localization_editor.h index b9a78a3c82..eb6e5b93c7 100644 --- a/editor/localization_editor.h +++ b/editor/localization_editor.h @@ -32,6 +32,7 @@ #define LOCALIZATION_EDITOR_H #include "editor/editor_locale_dialog.h" +#include "scene/gui/check_box.h" #include "scene/gui/tree.h" class EditorFileDialog; @@ -52,6 +53,7 @@ class LocalizationEditor : public VBoxContainer { Tree *translation_remap_options = nullptr; Tree *translation_pot_list = nullptr; + CheckBox *translation_pot_add_builtin = nullptr; EditorFileDialog *pot_file_open_dialog = nullptr; EditorFileDialog *pot_generate_dialog = nullptr; Button *pot_generate_button = nullptr; @@ -78,6 +80,7 @@ class LocalizationEditor : public VBoxContainer { void _pot_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button); void _pot_file_open(); void _pot_generate_open(); + void _pot_add_builtin_toggled(); void _pot_generate(const String &p_file); void _update_pot_file_extensions(); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 5ff8cd17ea..e986510895 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1001,6 +1001,11 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { } } + if (p_res.is_valid()) { + // In case the Resource has built-in scripts. + _mark_built_in_scripts_as_saved(p_res->get_path()); + } + _update_script_names(); Ref<Script> scr = p_res; if (scr.is_valid()) { @@ -1010,6 +1015,10 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { void ScriptEditor::_scene_saved_callback(const String &p_path) { // If scene was saved, mark all built-in scripts from that scene as saved. + _mark_built_in_scripts_as_saved(p_path); +} + +void ScriptEditor::_mark_built_in_scripts_as_saved(const String &p_parent_path) { for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { @@ -1022,7 +1031,7 @@ void ScriptEditor::_scene_saved_callback(const String &p_path) { continue; // External script, who cares. } - if (edited_res->get_path().get_slice("::", 0) == p_path) { + if (edited_res->get_path().get_slice("::", 0) == p_parent_path) { se->tag_saved_version(); } diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 6d13c6af09..d5c33c73b4 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -401,6 +401,7 @@ class ScriptEditor : public PanelContainer { void _add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args); void _res_saved_callback(const Ref<Resource> &p_res); void _scene_saved_callback(const String &p_path); + void _mark_built_in_scripts_as_saved(const String &p_parent_path); bool open_textfile_after_create = true; bool trim_trailing_whitespace_on_save; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 9321c6dafc..213a332bab 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -625,12 +625,37 @@ void ShaderEditorPlugin::_file_removed(const String &p_removed_file) { } } +void ShaderEditorPlugin::_res_saved_callback(const Ref<Resource> &p_res) { + if (p_res.is_null()) { + return; + } + const String &path = p_res->get_path(); + + for (EditedShader &edited : edited_shaders) { + Ref<Resource> shader_res = edited.shader; + if (shader_res.is_null()) { + shader_res = edited.shader_inc; + } + ERR_FAIL_COND(shader_res.is_null()); + + if (!edited.shader_editor || !shader_res->is_built_in()) { + continue; + } + + if (shader_res->get_path().get_slice("::", 0) == path) { + edited.shader_editor->tag_saved_version(); + _update_shader_list(); + } + } +} + void ShaderEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_resource_saved), CONNECT_DEFERRED); EditorNode::get_singleton()->connect("scene_closed", callable_mp(this, &ShaderEditorPlugin::_close_builtin_shaders_from_scene)); FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ShaderEditorPlugin::_file_removed)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ShaderEditorPlugin::_res_saved_callback)); } break; } } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 84c5a620a7..2558184982 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -96,6 +96,7 @@ class ShaderEditorPlugin : public EditorPlugin { void _close_shader(int p_index); void _close_builtin_shaders_from_scene(const String &p_scene); void _file_removed(const String &p_removed_file); + void _res_saved_callback(const Ref<Resource> &p_res); void _shader_created(Ref<Shader> p_shader); void _shader_include_created(Ref<ShaderInclude> p_shader_inc); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index f5fadc2f1b..7617359823 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -6206,9 +6206,9 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Multiplies two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Multiplies two integer scalars."), { VisualShaderNodeIntOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); add_options.push_back(AddOption("Multiply (*)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Multiplies two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); - add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), { VisualShaderNodeIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Remainder", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the remainder of the two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); + add_options.push_back(AddOption("Remainder (%)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Remainder (%)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), { VisualShaderNodeIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Remainder (%)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Returns the remainder of the two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeFloatOp", TTR("Subtracts two floating-point scalars."), { VisualShaderNodeFloatOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeIntOp", TTR("Subtracts two integer scalars."), { VisualShaderNodeIntOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); add_options.push_back(AddOption("Subtract (-)", "Scalar/Operators", "VisualShaderNodeUIntOp", TTR("Subtracts two unsigned integer scalars."), { VisualShaderNodeUIntOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR_UINT)); @@ -6478,9 +6478,9 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 2D vector by 2D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 3D vector by 3D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); add_options.push_back(AddOption("Multiply (*)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 4D vector by 4D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); - add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 2D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); - add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 3D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Remainder", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 4D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Remainder (%)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 2D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Remainder (%)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 3D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Remainder (%)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 4D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 2D vector from 2D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 3D vector from 3D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); add_options.push_back(AddOption("Subtract (-)", "Vector/Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 4D vector from 4D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp index 3804bd8d5b..b85099ca2e 100644 --- a/editor/pot_generator.cpp +++ b/editor/pot_generator.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/error/error_macros.h" +#include "editor/editor_translation.h" #include "editor/editor_translation_parser.h" #include "plugins/packed_scene_translation_parser_plugin.h" @@ -65,6 +66,8 @@ void POTGenerator::generate_pot(const String &p_file) { // Clear all_translation_strings of the previous round. all_translation_strings.clear(); + List<StringName> extractable_msgids = get_extractable_message_list(); + // Collect all translatable strings according to files order in "POT Generation" setting. for (int i = 0; i < files.size(); i++) { Vector<String> msgids; @@ -88,6 +91,12 @@ void POTGenerator::generate_pot(const String &p_file) { } } + if (GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot")) { + for (int i = 0; i < extractable_msgids.size(); i++) { + _add_new_msgid(extractable_msgids[i], "", "", ""); + } + } + _write_to_pot(p_file); } @@ -136,7 +145,9 @@ void POTGenerator::_write_to_pot(const String &p_file) { // Write file locations. for (const String &E : locations) { - file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n")); + if (!E.is_empty()) { + file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n")); + } } // Write context. diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index f55ac6f59a..6849d87923 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1036,7 +1036,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_color("font_selected_color", "ItemList", p_config.mono_color); p_theme->set_color("font_outline_color", "ItemList", p_config.font_outline_color); p_theme->set_color("guide_color", "ItemList", Color(1, 1, 1, 0)); - p_theme->set_constant("v_separation", "ItemList", p_config.forced_even_separation * 0.5 * EDSCALE); + p_theme->set_constant("v_separation", "ItemList", p_config.forced_even_separation * EDSCALE); p_theme->set_constant("h_separation", "ItemList", (p_config.increased_margin + 2) * EDSCALE); p_theme->set_constant("icon_margin", "ItemList", (p_config.increased_margin + 2) * EDSCALE); p_theme->set_constant("line_separation", "ItemList", p_config.separation_margin); diff --git a/gles3_builders.py b/gles3_builders.py index 055451f298..f1cdf7ac8c 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import os.path from typing import Optional diff --git a/glsl_builders.py b/glsl_builders.py index a6ca9aa2f3..ce52ec247d 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import os.path from typing import Optional, Iterable diff --git a/main/main_builders.py b/main/main_builders.py index c880bfa3c4..59d9d7dc3d 100644 --- a/main/main_builders.py +++ b/main/main_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + from platform_methods import subprocess_main diff --git a/methods.py b/methods.py index c85e6825da..88c8e7043e 100644 --- a/methods.py +++ b/methods.py @@ -1155,6 +1155,11 @@ def generate_vs_project(env, original_args, project_name="godot"): # This lets projects be regenerated even if there are build errors. filtered_args.pop("vsproj_gen_only", None) + # This flag allows users to regenerate only the props file without touching the sln or vcxproj files. + # This preserves any customizations users have done to the solution, while still updating the file list + # and build commands. + filtered_args.pop("vsproj_props_only", None) + # The "progress" option is ignored as the current compilation progress indication doesn't work in VS filtered_args.pop("progress", None) @@ -1347,27 +1352,30 @@ def generate_vs_project(env, original_args, project_name="godot"): set_sources = set(sources_active) set_others = set(others_active) for file in headers: + base_path = os.path.dirname(file).replace("\\", "_") all_items.append(f'<ClInclude Include="{file}">') all_items.append( - f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList_{base_path}.Contains(';{file};'))\">true</ExcludedFromBuild>" ) all_items.append("</ClInclude>") if file in set_headers: activeItems.append(file) for file in sources: + base_path = os.path.dirname(file).replace("\\", "_") all_items.append(f'<ClCompile Include="{file}">') all_items.append( - f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList_{base_path}.Contains(';{file};'))\">true</ExcludedFromBuild>" ) all_items.append("</ClCompile>") if file in set_sources: activeItems.append(file) for file in others: + base_path = os.path.dirname(file).replace("\\", "_") all_items.append(f'<None Include="{file}">') all_items.append( - f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList.Contains(';{file};'))\">true</ExcludedFromBuild>" + f" <ExcludedFromBuild Condition=\"!$(ActiveProjectItemList_{base_path}.Contains(';{file};'))\">true</ExcludedFromBuild>" ) all_items.append("</None>") if file in set_others: @@ -1381,7 +1389,18 @@ def generate_vs_project(env, original_args, project_name="godot"): break condition = "'$(GodotConfiguration)|$(GodotPlatform)'=='" + vsconf + "'" - properties.append("<ActiveProjectItemList>;" + ";".join(activeItems) + ";</ActiveProjectItemList>") + itemlist = {} + for item in activeItems: + key = os.path.dirname(item).replace("\\", "_") + if not key in itemlist: + itemlist[key] = [item] + else: + itemlist[key] += [item] + + for x in itemlist.keys(): + properties.append( + "<ActiveProjectItemList_%s>;%s;</ActiveProjectItemList_%s>" % (x, ";".join(itemlist[x]), x) + ) output = f'bin\\godot{env["PROGSUFFIX"]}' props_template = open("misc/msvs/props.template", "r").read() @@ -1419,6 +1438,8 @@ def generate_vs_project(env, original_args, project_name="godot"): cmd_rebuild = [ "vsproj=yes", + "vsproj_props_only=yes", + "vsproj_gen_only=no", f"vsproj_name={project_name}", ] + common_build_postfix @@ -1536,25 +1557,27 @@ def generate_vs_project(env, original_args, project_name="godot"): section1 = sorted(section1) section2 = sorted(section2) - proj_template = open("misc/msvs/vcxproj.template", "r").read() - - proj_template = proj_template.replace("%%UUID%%", proj_uuid) - proj_template = proj_template.replace("%%CONFS%%", "\n ".join(configurations)) - proj_template = proj_template.replace("%%IMPORTS%%", "\n ".join(imports)) - proj_template = proj_template.replace("%%DEFAULT_ITEMS%%", "\n ".join(all_items)) - proj_template = proj_template.replace("%%PROPERTIES%%", "\n ".join(properties)) - - with open(f"{project_name}.vcxproj", "w") as f: - f.write(proj_template) - - sln_template = open("misc/msvs/sln.template", "r").read() - sln_template = sln_template.replace("%%NAME%%", project_name) - sln_template = sln_template.replace("%%UUID%%", proj_uuid) - sln_template = sln_template.replace("%%SLNUUID%%", sln_uuid) - sln_template = sln_template.replace("%%SECTION1%%", "\n ".join(section1)) - sln_template = sln_template.replace("%%SECTION2%%", "\n ".join(section2)) - with open(f"{project_name}.sln", "w") as f: - f.write(sln_template) + if not get_bool(original_args, "vsproj_props_only", False): + proj_template = open("misc/msvs/vcxproj.template", "r").read() + proj_template = proj_template.replace("%%UUID%%", proj_uuid) + proj_template = proj_template.replace("%%CONFS%%", "\n ".join(configurations)) + proj_template = proj_template.replace("%%IMPORTS%%", "\n ".join(imports)) + proj_template = proj_template.replace("%%DEFAULT_ITEMS%%", "\n ".join(all_items)) + proj_template = proj_template.replace("%%PROPERTIES%%", "\n ".join(properties)) + + with open(f"{project_name}.vcxproj", "w") as f: + f.write(proj_template) + + if not get_bool(original_args, "vsproj_props_only", False): + sln_template = open("misc/msvs/sln.template", "r").read() + sln_template = sln_template.replace("%%NAME%%", project_name) + sln_template = sln_template.replace("%%UUID%%", proj_uuid) + sln_template = sln_template.replace("%%SLNUUID%%", sln_uuid) + sln_template = sln_template.replace("%%SECTION1%%", "\n ".join(section1)) + sln_template = sln_template.replace("%%SECTION2%%", "\n ".join(section2)) + + with open(f"{project_name}.sln", "w") as f: + f.write(sln_template) if get_bool(original_args, "vsproj_gen_only", True): sys.exit() diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index 1c643d780d..31df83efae 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -169,7 +169,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { r_img->set_data(width, height, mipmaps, target_format, dest_data); - print_verbose(vformat("astcenc: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); + print_verbose(vformat("astcenc: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } void _decompress_astc(Image *r_img) { @@ -286,5 +286,5 @@ void _decompress_astc(Image *r_img) { r_img->set_data(width, height, mipmaps, target_format, dest_data); - print_verbose(vformat("astcenc: Decompression took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); + print_verbose(vformat("astcenc: Decompression took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 087d3a9314..dcd73101c2 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -278,5 +278,5 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { // Replace original image with compressed one. r_img->set_data(width, height, mipmaps, target_format, dest_data); - print_verbose(vformat("etcpak: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); + print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index 3ef54ec0df..b615b5aed8 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -121,7 +121,7 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint bool remove_immutable = p_options.has("animation/remove_immutable_tracks") ? (bool)p_options["animation/remove_immutable_tracks"] : true; return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable); #else - return gltf->create_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); #endif } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 2d6c0c1d11..70f4411dec 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2817,7 +2817,8 @@ Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { Error err; - Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE); + bool ignoring = p_cache_mode == CACHE_MODE_IGNORE || p_cache_mode == CACHE_MODE_IGNORE_DEEP; + Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", ignoring); if (err && scr.is_valid()) { // If !scr.is_valid(), the error was likely from scr->load_source_code(), which already generates an error. diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd index e4016c0119..cb5ea827f6 100644 --- a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd +++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.gd @@ -1,6 +1,13 @@ func test(): var array: Array = [1, 2, 3] print(array) - var callable: Callable = array.clear - callable.call() + var array_clear: Callable = array.clear + array_clear.call() print(array) + + var dictionary: Dictionary = {1: 2, 3: 4} + print(dictionary) + # `dictionary.clear` is treated as a key. + var dictionary_clear := Callable.create(dictionary, &"clear") + dictionary_clear.call() + print(dictionary) diff --git a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out index c4182b38e9..c12984ca37 100644 --- a/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out +++ b/modules/gdscript/tests/scripts/runtime/features/builtin_method_as_callable.out @@ -1,3 +1,5 @@ GDTEST_OK [1, 2, 3] [] +{ 1: 2, 3: 4 } +{ } diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 7f33310c0b..4a7570e4bc 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -35,7 +35,7 @@ If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. </member> <member name="children" type="PackedInt32Array" setter="set_children" getter="get_children" default="PackedInt32Array()"> - The indices of the children nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. + The indices of the child nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. </member> <member name="height" type="int" setter="set_height" getter="get_height" default="-1"> How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated. diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 38d7f5521b..78beaac3ed 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -75,7 +75,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t bool remove_immutable = p_options.has("animation/remove_immutable_tracks") ? (bool)p_options["animation/remove_immutable_tracks"] : true; return gltf->generate_scene(state, (float)p_options["animation/fps"], trimming, remove_immutable); #else - return gltf->create_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]); #endif } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 88fe82c6b8..9ccaa27e84 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2854,6 +2854,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const Ref<Resource> existing = ResourceCache::get_ref(p_path); switch (p_cache_mode) { case ResourceFormatLoader::CACHE_MODE_IGNORE: + case ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP: break; case ResourceFormatLoader::CACHE_MODE_REUSE: if (existing.is_null()) { @@ -2863,6 +2864,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const } break; case ResourceFormatLoader::CACHE_MODE_REPLACE: + case ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP: scr->set_path(p_original_path, true); break; } diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index aa37df6752..f2d70db7a4 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -73,7 +73,7 @@ using namespace godot; /*************************************************************************/ -#define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff))) +#define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1) & 0xff) << 24) | (((uint32_t)(c2) & 0xff) << 16) | (((uint32_t)(c3) & 0xff) << 8) | ((uint32_t)(c4) & 0xff))) bool TextServerFallback::_has_feature(Feature p_feature) const { switch (p_feature) { diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 53182b8e2d..db0352db01 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -387,7 +387,7 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n"; } else if (p.begins_with("ro.opengles.version=")) { uint32_t opengl = p.get_slice("=", 1).to_int(); - d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl)&0xFF) + "\n"; + d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl) & 0xFF) + "\n"; } } diff --git a/platform/linuxbsd/platform_linuxbsd_builders.py b/platform/linuxbsd/platform_linuxbsd_builders.py index 58234f3748..85d55f0ac6 100644 --- a/platform/linuxbsd/platform_linuxbsd_builders.py +++ b/platform/linuxbsd/platform_linuxbsd_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import os from platform_methods import subprocess_main diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index c957dea32d..528c688a9c 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -990,36 +990,9 @@ void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor wayland_thread.cursor_shape_clear_custom_image(p_shape); } - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Size2i texture_size; - - Ref<AtlasTexture> atlas_texture = texture; - - if (atlas_texture.is_valid()) { - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - - // NOTE: The Wayland protocol says nothing about cursor size limits, yet if - // the texture is larger than 256x256 it won't show at least on sway. - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - ERR_FAIL_COND(texture_size.height == 0 || texture_size.width == 0); - - Ref<Image> image = texture->get_image(); - ERR_FAIL_COND(!image.is_valid()); - - if (image->is_compressed()) { - image = image->duplicate(true); - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } + Rect2 atlas_rect; + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); CustomCursor &cursor = custom_cursors[p_shape]; diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index e42967eb7b..5b8db1be47 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -59,8 +59,6 @@ #include "core/config/project_settings.h" #include "core/input/input.h" -#include "scene/resources/atlas_texture.h" -#include "scene/resources/texture.h" #include "servers/display_server.h" #include <limits.h> diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index ebb21722e2..7fa69a3bed 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -448,14 +448,11 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss); } -#if 0 - // FIXME: Broken. if (!ss->wp_tablet_seat && registry->wp_tablet_manager) { // Tablet. ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat); zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss); } -#endif registry->wl_seats.push_back(wl_seat); @@ -541,13 +538,11 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re return; } -#if 0 - // FIXME: Broken. if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) { registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1); registry->wp_tablet_manager_name = name; - // This global creates some seats data. Let's do that for the ones already available. + // This global creates some seat data. Let's do that for the ones already available. for (struct wl_seat *wl_seat : registry->wl_seats) { SeatState *ss = wl_seat_get_seat_state(wl_seat); ERR_FAIL_NULL(ss); @@ -558,7 +553,6 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re return; } -#endif } void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) { @@ -820,8 +814,6 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry return; } -#if 0 - // FIXME: Broken. if (name == registry->wp_tablet_manager_name) { if (registry->wp_tablet_manager) { zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager); @@ -835,25 +827,27 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry SeatState *ss = wl_seat_get_seat_state(wl_seat); ERR_FAIL_NULL(ss); - List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front(); - - while (it) { - zwp_tablet_tool_v2_destroy(it->get()); - ss->tablet_tools.erase(it); + for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } - it = it->next(); + zwp_tablet_tool_v2_destroy(tool); } + + ss->tablet_tools.clear(); } return; } -#endif { // Iterate through all of the seats to find if any got removed. - List<struct wl_seat *>::Element *it = registry->wl_seats.front(); - while (it) { - struct wl_seat *wl_seat = it->get(); + List<struct wl_seat *>::Element *E = registry->wl_seats.front(); + while (E) { + struct wl_seat *wl_seat = E->get(); + List<struct wl_seat *>::Element *N = E->next(); SeatState *ss = wl_seat_get_seat_state(wl_seat); ERR_FAIL_NULL(ss); @@ -867,28 +861,26 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry wl_data_device_destroy(ss->wl_data_device); } -#if 0 - // FIXME: Broken. if (ss->wp_tablet_seat) { zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat); for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } + zwp_tablet_tool_v2_destroy(tool); } } - // Let's destroy all tools. - for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { - zwp_tablet_tool_v2_destroy(tool); - } - memdelete(ss); - registry->wl_seats.erase(it); -#endif + + registry->wl_seats.erase(E); return; } - it = it->next(); + E = N; } } @@ -2160,82 +2152,80 @@ void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct } void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tablet %x added", (size_t)zwp_tablet_seat_v2, (size_t)id)); } void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) { SeatState *ss = (SeatState *)data; ERR_FAIL_NULL(ss); - ss->tablet_tools.push_back(id); + TabletToolState *state = memnew(TabletToolState); + state->wl_seat = ss->wl_seat; - zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, ss); + wl_proxy_tag_godot((struct wl_proxy *)id); + zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, state); - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tool %x added", (size_t)zwp_tablet_seat_v2, (size_t)id)); + ss->tablet_tools.push_back(id); } void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on pad %x added", (size_t)zwp_tablet_seat_v2, (size_t)id)); } void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on type %d", (size_t)zwp_tablet_tool_v2, tool_type)); + TabletToolState *state = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (state && tool_type == ZWP_TABLET_TOOL_V2_TYPE_ERASER) { + state->is_eraser = true; + } } void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware serial %x%x", (size_t)zwp_tablet_tool_v2, hardware_serial_hi, hardware_serial_lo)); } void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware id wacom hardware id %x%x", (size_t)zwp_tablet_tool_v2, hardware_id_hi, hardware_id_lo)); } void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); - - if (capability == ZWP_TABLET_TOOL_V2_TYPE_ERASER) { - ss->tablet_tool_data_buffer.is_eraser = true; - } - - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on capability %d", (size_t)zwp_tablet_tool_v2, capability)); } void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on done", (size_t)zwp_tablet_tool_v2)); } void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); - List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front(); + if (!ts || !ss) { + return; + } - while (it) { - struct zwp_tablet_tool_v2 *tool = it->get(); + List<struct zwp_tablet_tool_v2 *>::Element *E = ss->tablet_tools.find(zwp_tablet_tool_v2); - if (tool == zwp_tablet_tool_v2) { - zwp_tablet_tool_v2_destroy(tool); - ss->tablet_tools.erase(it); - break; + if (E && E->get()) { + struct zwp_tablet_tool_v2 *tool = E->get(); + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); } - } - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on removed", (size_t)zwp_tablet_tool_v2)); + zwp_tablet_tool_v2_destroy(tool); + ss->tablet_tools.erase(E); + } } void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + + if (!ts || !ss) { + return; + } WaylandThread *wayland_thread = ss->wayland_thread; ERR_FAIL_NULL(wayland_thread); - ss->tablet_tool_data_buffer.in_proximity = true; - - ss->pointer_enter_serial = serial; - ss->pointed_surface = surface; - ss->last_pointed_surface = surface; + ts->data_pending.proximity_serial = serial; + ts->data_pending.proximal_surface = surface; + ts->last_surface = surface; Ref<WindowEventMessage> msg; msg.instantiate(); @@ -2243,21 +2233,20 @@ void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_table wayland_thread->push_message(msg); DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window."); - - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity in serial %d tablet %x surface %x", (size_t)zwp_tablet_tool_v2, serial, (size_t)tablet, (size_t)surface)); } void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + + if (!ts || !ss) { + return; + } WaylandThread *wayland_thread = ss->wayland_thread; ERR_FAIL_NULL(wayland_thread); - ss->pointed_surface = nullptr; - ss->tablet_tool_data_buffer.in_proximity = false; - - DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window."); + ts->data_pending.proximal_surface = nullptr; Ref<WindowEventMessage> msg; msg.instantiate(); @@ -2265,16 +2254,18 @@ void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tabl wayland_thread->push_message(msg); - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity out", (size_t)zwp_tablet_tool_v2)); + DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window."); } void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } - TabletToolData &td = ss->tablet_tool_data_buffer; + TabletToolData &td = ts->data_pending; - td.touching = true; td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT)); td.last_button_pressed = MouseButton::LEFT; td.double_click_begun = true; @@ -2282,45 +2273,53 @@ void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v // The protocol doesn't cover this, but we can use this funky hack to make // double clicking work. td.button_time = OS::get_singleton()->get_ticks_msec(); - - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on down serial %x", (size_t)zwp_tablet_tool_v2, serial)); } void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); - TabletToolData &td = ss->tablet_tool_data_buffer; + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; - td.touching = false; td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT)); // The protocol doesn't cover this, but we can use this funky hack to make // double clicking work. td.button_time = OS::get_singleton()->get_ticks_msec(); - - DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on up", (size_t)zwp_tablet_tool_v2)); } void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); - WindowState *ws = wl_surface_get_window_state(ss->pointed_surface); + if (!ts) { + return; + } + + WindowState *ws = wl_surface_get_window_state(ts->data_pending.proximal_surface); ERR_FAIL_NULL(ws); - double scale_factor = window_state_get_scale_factor(ws); + TabletToolData &td = ts->data_pending; - TabletToolData &td = ss->tablet_tool_data_buffer; + double scale_factor = window_state_get_scale_factor(ws); + td.position.x = wl_fixed_to_int(x); + td.position.y = wl_fixed_to_int(y); td.position = scale_vector2i(td.position, scale_factor); + + td.motion_time = OS::get_singleton()->get_ticks_msec(); } void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } - ss->tablet_tool_data_buffer.pressure = pressure; + ts->data_pending.pressure = pressure; } void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance) { @@ -2328,11 +2327,16 @@ void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_to } void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + + if (!ts) { + return; + } - ss->tablet_tool_data_buffer.tilt.x = wl_fixed_to_double(tilt_x); - ss->tablet_tool_data_buffer.tilt.y = wl_fixed_to_double(tilt_y); + TabletToolData &td = ts->data_pending; + + td.tilt.x = wl_fixed_to_double(tilt_x); + td.tilt.y = wl_fixed_to_double(tilt_y); } void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees) { @@ -2348,10 +2352,13 @@ void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_ } void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); - TabletToolData &td = ss->tablet_tool_data_buffer; + if (!ts) { + return; + } + + TabletToolData &td = ts->data_pending; MouseButton mouse_button = MouseButton::NONE; @@ -2381,14 +2388,18 @@ void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool } void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) { - SeatState *ss = (SeatState *)data; - ERR_FAIL_NULL(ss); + TabletToolState *ts = wp_tablet_tool_get_state(zwp_tablet_tool_v2); + SeatState *ss = wl_seat_get_seat_state(ts->wl_seat); + + if (!ts || !ss) { + return; + } WaylandThread *wayland_thread = ss->wayland_thread; ERR_FAIL_NULL(wayland_thread); - TabletToolData &old_td = ss->tablet_tool_data; - TabletToolData &td = ss->tablet_tool_data_buffer; + TabletToolData &old_td = ts->data; + TabletToolData &td = ts->data_pending; if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) { Ref<InputEventMouseMotion> mm; @@ -2411,25 +2422,25 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_ // straight from the compositor, so we have to normalize them here. // According to the tablet proto spec, tilt is expressed in degrees relative - // to the Z axis of the tablet, so it shouldn't go over 90 degrees, I think. - // TODO: Investigate whether the tilt can go over 90 degrees (it shouldn't). + // to the Z axis of the tablet, so it shouldn't go over 90 degrees either way, + // I think. We'll clamp it just in case. + td.tilt.x = CLAMP(td.tilt.x, -90, 90); + td.tilt.y = CLAMP(td.tilt.x, -90, 90); + mm->set_tilt(td.tilt / 90); // The tablet proto spec explicitly says that pressure is defined as a value // between 0 to 65535. mm->set_pressure(td.pressure / (float)65535); - // FIXME: Tool handling is broken. - mm->set_pen_inverted(td.is_eraser); + mm->set_pen_inverted(ts->is_eraser); mm->set_relative(td.position - old_td.position); mm->set_relative_screen_position(mm->get_relative()); - // FIXME: Stop doing this to calculate speed. - // FIXME2: It has been done, port this from the pointer logic once this works again. - Input::get_singleton()->set_mouse_position(td.position); - mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - mm->set_screen_velocity(mm->get_velocity()); + Vector2i pos_delta = td.position - old_td.position; + uint32_t time_delta = td.motion_time - td.motion_time; + mm->set_velocity((Vector2)pos_delta / time_delta); Ref<InputEventMessage> inputev_msg; inputev_msg.instantiate(); @@ -2618,6 +2629,15 @@ WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat * return nullptr; } +// Returns the wp_tablet_tool's `TabletToolState`, otherwise `nullptr`. +// NOTE: This will fail if the output isn't tagged as ours. +WaylandThread::TabletToolState *WaylandThread::wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool) { + if (p_tool && wl_proxy_is_godot((wl_proxy *)p_tool)) { + return (TabletToolState *)zwp_tablet_tool_v2_get_user_data(p_tool); + } + + return nullptr; +} // Returns the wl_data_offer's `OfferState`, otherwise `nullptr`. // NOTE: This will fail if the output isn't tagged as ours. WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) { @@ -4070,14 +4090,16 @@ void WaylandThread::destroy() { zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer); } -#if 0 - // FIXME: Broken. if (ss->wp_tablet_seat) { zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat); } -#endif for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) { + TabletToolState *state = wp_tablet_tool_get_state(tool); + if (state) { + memdelete(state); + } + zwp_tablet_tool_v2_destroy(tool); } diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index f3e3c3a2ac..b9e7e0437a 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -322,10 +322,20 @@ public: // be used as a mouse...), but we'll hack one in with the current ticks. uint64_t button_time = 0; + uint64_t motion_time = 0; + + uint32_t proximity_serial = 0; + struct wl_surface *proximal_surface = nullptr; + }; + + struct TabletToolState { + struct wl_seat *wl_seat = nullptr; + + struct wl_surface *last_surface = nullptr; bool is_eraser = false; - bool in_proximity = false; - bool touching = false; + TabletToolData data_pending; + TabletToolData data; }; struct OfferState { @@ -425,9 +435,6 @@ public: struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr; List<struct zwp_tablet_tool_v2 *> tablet_tools; - - TabletToolData tablet_tool_data_buffer; - TabletToolData tablet_tool_data; }; struct CustomCursor { @@ -855,6 +862,7 @@ public: static WindowState *wl_surface_get_window_state(struct wl_surface *p_surface); static ScreenState *wl_output_get_screen_state(struct wl_output *p_output); static SeatState *wl_seat_get_seat_state(struct wl_seat *p_seat); + static TabletToolState *wp_tablet_tool_get_state(struct zwp_tablet_tool_v2 *p_tool); static OfferState *wl_data_offer_get_offer_state(struct wl_data_offer *p_offer); static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 35bfe81827..d58b5b93d7 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -41,7 +41,6 @@ #include "core/string/ustring.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" -#include "scene/resources/atlas_texture.h" #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" @@ -3064,39 +3063,10 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu cursors_cache.erase(p_shape); } - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Ref<AtlasTexture> atlas_texture = p_cursor; - Size2i texture_size; - Rect2i atlas_rect; - - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - Ref<Image> image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - if (image->is_compressed()) { - image = image->duplicate(true); - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } + Rect2 atlas_rect; + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); + Vector2i texture_size = image->get_size(); // Create the cursor structure XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); @@ -3115,7 +3085,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu int row_index = floor(index / texture_size.width) + atlas_rect.position.y; int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; - if (atlas_texture.is_valid()) { + if (atlas_rect.has_area()) { column_index = MIN(column_index, atlas_rect.size.width - 1); row_index = MIN(row_index, atlas_rect.size.height - 1); } diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index b2014f2849..2d6e614a05 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -48,7 +48,6 @@ #include "core/os/keyboard.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" -#include "scene/resources/atlas_texture.h" #include "scene/resources/image_texture.h" #if defined(GLES3_ENABLED) @@ -2686,7 +2685,7 @@ Ref<Image> DisplayServerMacOS::clipboard_get_image() const { return image; } NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:data]; - NSData *pngData = [bitmap representationUsingType:NSPNGFileType properties:@{}]; + NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; image.instantiate(); PNGDriverCommon::png_to_image((const uint8_t *)pngData.bytes, pngData.length, false, image); return image; @@ -4037,39 +4036,10 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, cursors_cache.erase(p_shape); } - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Ref<AtlasTexture> atlas_texture = p_cursor; - Size2 texture_size; Rect2 atlas_rect; - - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - Ref<Image> image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - if (image->is_compressed()) { - image = image->duplicate(true); - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); + Vector2i texture_size = image->get_size(); NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nullptr @@ -4092,7 +4062,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, int row_index = floor(i / texture_size.width) + atlas_rect.position.y; int column_index = (i % int(texture_size.width)) + atlas_rect.position.x; - if (atlas_texture.is_valid()) { + if (atlas_rect.has_area()) { column_index = MIN(column_index, atlas_rect.size.width - 1); row_index = MIN(row_index, atlas_rect.size.height - 1); } @@ -4102,7 +4072,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, uint8_t alpha = (color >> 24) & 0xFF; pixels[i * 4 + 0] = ((color >> 16) & 0xFF) * alpha / 255; pixels[i * 4 + 1] = ((color >> 8) & 0xFF) * alpha / 255; - pixels[i * 4 + 2] = ((color)&0xFF) * alpha / 255; + pixels[i * 4 + 2] = ((color) & 0xFF) * alpha / 255; pixels[i * 4 + 3] = alpha; } diff --git a/platform/macos/godot_application.mm b/platform/macos/godot_application.mm index e3a744caa2..f5e1bb43bf 100644 --- a/platform/macos/godot_application.mm +++ b/platform/macos/godot_application.mm @@ -100,7 +100,7 @@ } - (void)sendEvent:(NSEvent *)event { - if ([event type] == NSSystemDefined && [event subtype] == 8) { + if ([event type] == NSEventTypeSystemDefined && [event subtype] == 8) { int keyCode = (([event data1] & 0xFFFF0000) >> 16); int keyFlags = ([event data1] & 0x0000FFFF); int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA; diff --git a/platform/macos/joypad_macos.mm b/platform/macos/joypad_macos.mm index 2420bd73fb..8cd5cdd9f2 100644 --- a/platform/macos/joypad_macos.mm +++ b/platform/macos/joypad_macos.mm @@ -136,6 +136,10 @@ JoypadMacOS::JoypadMacOS() { observer = [[JoypadMacOSObserver alloc] init]; [observer startObserving]; + + if (@available(macOS 11.3, *)) { + GCController.shouldMonitorBackgroundEvents = YES; + } } JoypadMacOS::~JoypadMacOS() { diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 29f433a51c..c5c95c9a70 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -601,7 +601,9 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum for (const String &arg : p_arguments) { [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]]; } +#if defined(__x86_64__) if (@available(macOS 10.15, *)) { +#endif NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init]; [configuration setArguments:arguments]; [configuration setCreatesNewApplicationInstance:YES]; @@ -630,6 +632,7 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum } return err; +#if defined(__x86_64__) } else { Error err = ERR_TIMEOUT; NSError *error = nullptr; @@ -645,6 +648,7 @@ Error OS_MacOS::create_process(const String &p_path, const List<String> &p_argum } return err; } +#endif } else { return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console); } diff --git a/platform/macos/platform_macos_builders.py b/platform/macos/platform_macos_builders.py index 3a1cc92bd2..a1228b96eb 100644 --- a/platform/macos/platform_macos_builders.py +++ b/platform/macos/platform_macos_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import os from platform_methods import subprocess_main diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index bc4c0d22f0..2e3afc49ca 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -36,7 +36,6 @@ #include "core/config/project_settings.h" #include "core/object/callable_method_pointer.h" -#include "scene/resources/atlas_texture.h" #include "servers/rendering/dummy/rasterizer_dummy.h" #ifdef GLES3_ENABLED @@ -511,43 +510,12 @@ DisplayServer::CursorShape DisplayServerWeb::cursor_get_shape() const { void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); if (p_cursor.is_valid()) { - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Ref<AtlasTexture> atlas_texture = p_cursor; - Size2 texture_size; Rect2 atlas_rect; + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); + Vector2i texture_size = image->get_size(); - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - Ref<Image> image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - - image = image->duplicate(true); - - if (image->is_compressed()) { - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } - - if (atlas_texture.is_valid()) { + if (atlas_rect.has_area()) { image->crop_from_point( atlas_rect.position.x, atlas_rect.position.y, diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 4bb6d2f3f5..ced8dce65a 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -38,7 +38,6 @@ #include "core/version.h" #include "drivers/png/png_driver_common.h" #include "main/main.h" -#include "scene/resources/atlas_texture.h" #if defined(VULKAN_ENABLED) #include "rendering_context_driver_vulkan_windows.h" @@ -2393,38 +2392,10 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor cursors_cache.erase(p_shape); } - Ref<Texture2D> texture = p_cursor; - ERR_FAIL_COND(!texture.is_valid()); - Ref<AtlasTexture> atlas_texture = p_cursor; - Size2 texture_size; Rect2 atlas_rect; - - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - - atlas_rect.size.width = texture->get_width(); - atlas_rect.size.height = texture->get_height(); - atlas_rect.position.x = atlas_texture->get_region().position.x; - atlas_rect.position.y = atlas_texture->get_region().position.y; - texture_size.width = atlas_texture->get_region().size.x; - texture_size.height = atlas_texture->get_region().size.y; - } else { - texture_size.width = texture->get_width(); - texture_size.height = texture->get_height(); - } - - ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0); - ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256); - ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height); - - Ref<Image> image = texture->get_image(); - - ERR_FAIL_COND(!image.is_valid()); - if (image->is_compressed()) { - image = image->duplicate(true); - Error err = image->decompress(); - ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); - } + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + ERR_FAIL_COND(image.is_null()); + Vector2i texture_size = image->get_size(); UINT image_size = texture_size.width * texture_size.height; @@ -2453,7 +2424,7 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor int row_index = floor(index / texture_size.width) + atlas_rect.position.y; int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; - if (atlas_texture.is_valid()) { + if (atlas_rect.has_area()) { column_index = MIN(column_index, atlas_rect.size.width - 1); row_index = MIN(row_index, atlas_rect.size.height - 1); } @@ -3094,9 +3065,9 @@ void DisplayServerWindows::set_context(Context p_context) { #define SIGNATURE_MASK 0xFFFFFF00 // Keeping the name suggested by Microsoft, but this macro really answers: // Is this mouse event emulated from touch or pen input? -#define IsPenEvent(dw) (((dw)&SIGNATURE_MASK) == MI_WP_SIGNATURE) +#define IsPenEvent(dw) (((dw) & SIGNATURE_MASK) == MI_WP_SIGNATURE) // This one tells whether the event comes from touchscreen (and not from pen). -#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw)&0x80)) +#define IsTouchEvent(dw) (IsPenEvent(dw) && ((dw) & 0x80)) void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx) { if (touch_state.has(idx) == p_pressed) { diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index 652dc06acf..99e9633800 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -3,6 +3,7 @@ All such functions are invoked in a subprocess on Windows to prevent build flakiness. """ + import os from detect import get_mingw_bin_prefix from detect import try_cmd diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 48fe2afccc..f8737730ba 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -105,7 +105,7 @@ void TileMap::set_rendering_quadrant_size(int p_size) { rendering_quadrant_size = p_size; for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE); + layer->set_rendering_quadrant_size(p_size); } _emit_changed(); } @@ -214,11 +214,10 @@ void TileMap::add_layer(int p_to_pos) { TileMapLayer *new_layer = memnew(TileMapLayer); layers.insert(p_to_pos, new_layer); add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->force_parent_owned(); new_layer->set_name(vformat("Layer%d", p_to_pos)); move_child(new_layer, p_to_pos); for (uint32_t i = 0; i < layers.size(); i++) { - layers[i]->set_layer_index_in_tile_map_node(i); + layers[i]->set_as_tile_map_internal_node(i); } new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); @@ -239,7 +238,7 @@ void TileMap::move_layer(int p_layer, int p_to_pos) { layers.remove_at(p_to_pos < p_layer ? p_layer + 1 : p_layer); for (uint32_t i = 0; i < layers.size(); i++) { move_child(layers[i], i); - layers[i]->set_layer_index_in_tile_map_node(i); + layers[i]->set_as_tile_map_internal_node(i); } notify_property_list_changed(); @@ -255,7 +254,7 @@ void TileMap::remove_layer(int p_layer) { layers[p_layer]->queue_free(); layers.remove_at(p_layer); for (uint32_t i = 0; i < layers.size(); i++) { - layers[i]->set_layer_index_in_tile_map_node(i); + layers[i]->set_as_tile_map_internal_node(i); } notify_property_list_changed(); @@ -350,7 +349,7 @@ void TileMap::set_collision_visibility_mode(TileMap::VisibilityMode p_show_colli } collision_visibility_mode = p_show_collision; for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE); + layer->set_collision_visibility_mode(TileMapLayer::VisibilityMode(p_show_collision)); } _emit_changed(); } @@ -365,7 +364,7 @@ void TileMap::set_navigation_visibility_mode(TileMap::VisibilityMode p_show_navi } navigation_visibility_mode = p_show_navigation; for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE); + layer->set_navigation_visibility_mode(TileMapLayer::VisibilityMode(p_show_navigation)); } _emit_changed(); } @@ -380,7 +379,7 @@ void TileMap::set_y_sort_enabled(bool p_enable) { } Node2D::set_y_sort_enabled(p_enable); for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED); + layer->set_y_sort_enabled(p_enable); } _emit_changed(); update_configuration_warnings(); @@ -497,10 +496,10 @@ void TileMap::update_internals() { void TileMap::notify_runtime_tile_data_update(int p_layer) { if (p_layer >= 0) { - TILEMAP_CALL_FOR_LAYER(p_layer, notify_tile_map_change, TileMapLayer::DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE); + TILEMAP_CALL_FOR_LAYER(p_layer, notify_runtime_tile_data_update); } else { for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE); + layer->notify_runtime_tile_data_update(); } } } @@ -539,9 +538,8 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { if (layers.size() == 0) { TileMapLayer *new_layer = memnew(TileMapLayer); add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->force_parent_owned(); + new_layer->set_as_tile_map_internal_node(0); new_layer->set_name("Layer0"); - new_layer->set_layer_index_in_tile_map_node(0); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); } @@ -565,9 +563,8 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->force_parent_owned(); + new_layer->set_as_tile_map_internal_node(index); new_layer->set_name(vformat("Layer%d", index)); - new_layer->set_layer_index_in_tile_map_node(index); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); } @@ -795,46 +792,34 @@ Rect2i TileMap::get_used_rect() const { // --- Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems --- void TileMap::set_light_mask(int p_light_mask) { - // Occlusion: set light mask. + // Set light mask for occlusion and applies it to all layers too. CanvasItem::set_light_mask(p_light_mask); for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_LIGHT_MASK); + layer->set_light_mask(p_light_mask); } } -void TileMap::set_material(const Ref<Material> &p_material) { - // Set material for the whole tilemap. - CanvasItem::set_material(p_material); - - // Update material for the whole tilemap. +void TileMap::set_self_modulate(const Color &p_self_modulate) { + // Set self_modulation and applies it to all layers too. + CanvasItem::set_self_modulate(p_self_modulate); for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_MATERIAL); - } -} - -void TileMap::set_use_parent_material(bool p_use_parent_material) { - // Set use_parent_material for the whole tilemap. - CanvasItem::set_use_parent_material(p_use_parent_material); - - // Update use_parent_material for the whole tilemap. - for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL); + layer->set_self_modulate(p_self_modulate); } } void TileMap::set_texture_filter(TextureFilter p_texture_filter) { - // Set a default texture filter for the whole tilemap. + // Set a default texture filter and applies it to all layers too. CanvasItem::set_texture_filter(p_texture_filter); for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER); + layer->set_texture_filter(p_texture_filter); } } void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { - // Set a default texture repeat for the whole tilemap. + // Set a default texture repeat and applies it to all layers too. CanvasItem::set_texture_repeat(p_texture_repeat); for (TileMapLayer *layer : layers) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT); + layer->set_texture_repeat(p_texture_repeat); } } @@ -1003,11 +988,10 @@ void TileMap::_bind_methods() { TileMap::TileMap() { TileMapLayer *new_layer = memnew(TileMapLayer); add_child(new_layer, false, INTERNAL_MODE_FRONT); + new_layer->set_as_tile_map_internal_node(0); new_layer->set_name("Layer0"); - new_layer->set_layer_index_in_tile_map_node(0); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); - default_layer = memnew(TileMapLayer); } diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index c9844f29af..edea90fa95 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -46,9 +46,10 @@ enum TileMapDataFormat { }; class TileMap : public TileMapLayerGroup { - GDCLASS(TileMap, TileMapLayerGroup); + GDCLASS(TileMap, TileMapLayerGroup) public: + // Kept for compatibility, but should use TileMapLayer::VisibilityMode instead. enum VisibilityMode { VISIBILITY_MODE_DEFAULT, VISIBILITY_MODE_FORCE_SHOW, @@ -187,8 +188,7 @@ public: // Override some methods of the CanvasItem class to pass the changes to the quadrants CanvasItems. virtual void set_light_mask(int p_light_mask) override; - virtual void set_material(const Ref<Material> &p_material) override; - virtual void set_use_parent_material(bool p_use_parent_material) override; + virtual void set_self_modulate(const Color &p_self_modulate) override; virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index c9431f53d8..c7aa4c2be4 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -40,10 +40,6 @@ #include "servers/navigation_server_3d.h" #endif // DEBUG_ENABLED -TileMap *TileMapLayer::_fetch_tilemap() const { - return TileMap::cast_to<TileMap>(get_parent()); -} - #ifdef DEBUG_ENABLED /////////////////////////////// Debug ////////////////////////////////////////// constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; @@ -183,7 +179,6 @@ void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList< /////////////////////////////// Rendering ////////////////////////////////////// void TileMapLayer::_rendering_update() { - const TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); RenderingServer *rs = RenderingServer::get_singleton(); @@ -192,24 +187,23 @@ void TileMapLayer::_rendering_update() { // ----------- Layer level processing ----------- if (!forced_cleanup) { - // Update the layer's CanvasItem. - set_use_parent_material(true); - set_light_mask(tile_map_node->get_light_mask()); - // Modulate the layer. Color layer_modulate = get_modulate(); #ifdef TOOLS_ENABLED - const Vector<StringName> selected_layers = tile_map_node->get_selected_layers(); - if (tile_map_node->is_highlighting_selected_layer() && selected_layers.size() == 1 && get_name() != selected_layers[0]) { - TileMapLayer *selected_layer = Object::cast_to<TileMapLayer>(tile_map_node->get_node_or_null(String(selected_layers[0]))); - if (selected_layer) { - int z_selected = selected_layer->get_z_index(); - int layer_z_index = get_z_index(); - if (layer_z_index < z_selected || (layer_z_index == z_selected && get_index() < selected_layer->get_index())) { - layer_modulate = layer_modulate.darkened(0.5); - } else if (layer_z_index > z_selected || (layer_z_index == z_selected && get_index() > selected_layer->get_index())) { - layer_modulate = layer_modulate.darkened(0.5); - layer_modulate.a *= 0.3; + const TileMapLayerGroup *tile_map_layer_group = Object::cast_to<TileMapLayerGroup>(get_parent()); + if (tile_map_layer_group) { + const Vector<StringName> selected_layers = tile_map_layer_group->get_selected_layers(); + if (tile_map_layer_group->is_highlighting_selected_layer() && selected_layers.size() == 1 && get_name() != selected_layers[0]) { + TileMapLayer *selected_layer = Object::cast_to<TileMapLayer>(tile_map_layer_group->get_node_or_null(String(selected_layers[0]))); + if (selected_layer) { + int z_selected = selected_layer->get_z_index(); + int layer_z_index = get_z_index(); + if (layer_z_index < z_selected || (layer_z_index == z_selected && get_index() < selected_layer->get_index())) { + layer_modulate = layer_modulate.darkened(0.5); + } else if (layer_z_index > z_selected || (layer_z_index == z_selected && get_index() > selected_layer->get_index())) { + layer_modulate = layer_modulate.darkened(0.5); + layer_modulate.a *= 0.3; + } } } } @@ -224,8 +218,8 @@ void TileMapLayer::_rendering_update() { // Check if anything changed that might change the quadrant shape. // If so, recreate everything. - bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE] || - (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET])); + bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] || + (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET])); // Free all quadrants. if (forced_cleanup || quandrant_shape_changed) { @@ -330,7 +324,7 @@ void TileMapLayer::_rendering_update() { Transform2D xform(0, rendering_quadrant->canvas_items_position); rs->canvas_item_set_transform(ci, xform); - rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + rs->canvas_item_set_light_mask(ci, get_light_mask()); rs->canvas_item_set_z_as_relative_to_parent(ci, true); rs->canvas_item_set_z_index(ci, tile_z_index); @@ -398,17 +392,15 @@ void TileMapLayer::_rendering_update() { } } - // Updates on TileMap changes. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_LIGHT_MASK] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_MATERIAL] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT] || + // Updates on rendering changes. + if (dirty.flags[DIRTY_FLAGS_LAYER_LIGHT_MASK] || + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_FILTER] || + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_REPEAT] || dirty.flags[DIRTY_FLAGS_LAYER_SELF_MODULATE]) { for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { Ref<RenderingQuadrant> &rendering_quadrant = kv.value; for (const RID &ci : rendering_quadrant->canvas_items) { - rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + rs->canvas_item_set_light_mask(ci, get_light_mask()); rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(get_texture_filter_in_tree())); rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(get_texture_repeat_in_tree())); rs->canvas_item_set_self_modulate(ci, get_self_modulate()); @@ -465,7 +457,6 @@ void TileMapLayer::_rendering_notification(int p_what) { } void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) { - const TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); // Check if the cell is valid and retrieve its y_sort_origin. @@ -495,14 +486,13 @@ void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfL canvas_items_position = Vector2(0, tile_set->map_to_local(r_cell_data.coords).y + tile_y_sort_origin + y_sort_origin); quadrant_coords = canvas_items_position * 100; } else { - int quad_size = tile_map_node->get_rendering_quadrant_size(); const Vector2i &coords = r_cell_data.coords; // Rounding down, instead of simply rounding towards zero (truncating). quadrant_coords = Vector2i( - coords.x > 0 ? coords.x / quad_size : (coords.x - (quad_size - 1)) / quad_size, - coords.y > 0 ? coords.y / quad_size : (coords.y - (quad_size - 1)) / quad_size); - canvas_items_position = tile_set->map_to_local(quad_size * quadrant_coords); + coords.x > 0 ? coords.x / rendering_quadrant_size : (coords.x - (rendering_quadrant_size - 1)) / rendering_quadrant_size, + coords.y > 0 ? coords.y / rendering_quadrant_size : (coords.y - (rendering_quadrant_size - 1)) / rendering_quadrant_size); + canvas_items_position = tile_set->map_to_local(rendering_quadrant_size * quadrant_coords); } Ref<RenderingQuadrant> rendering_quadrant; @@ -767,7 +757,6 @@ void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { - const TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); Transform2D gl_transform = get_global_transform(); RID space = get_world_2d()->get_space(); @@ -825,7 +814,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { body = ps->body_create(); } bodies_coords[body] = r_cell_data.coords; - ps->body_set_mode(body, tile_map_node->is_collision_animatable() ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_mode(body, use_kinematic_bodies ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); ps->body_set_space(body, space); Transform2D xform; @@ -833,7 +822,7 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { xform = gl_transform * xform; ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - ps->body_attach_object_instance_id(body, tile_map_node->get_instance_id()); + ps->body_attach_object_instance_id(body, tile_map_node ? tile_map_node->get_instance_id() : get_instance_id()); ps->body_set_collision_layer(body, physics_layer); ps->body_set_collision_mask(body, physics_mask); ps->body_set_pickable(body, false); @@ -885,7 +874,6 @@ void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { // Draw the debug collision shapes. - TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); ERR_FAIL_COND(!tile_set.is_valid()); @@ -894,14 +882,14 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect } bool show_collision = false; - switch (tile_map_node->get_collision_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: + switch (collision_visibility_mode) { + case TileMapLayer::VISIBILITY_MODE_DEFAULT: show_collision = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_collisions_hint(); break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: + case TileMapLayer::VISIBILITY_MODE_FORCE_HIDE: show_collision = false; break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: + case TileMapLayer::VISIBILITY_MODE_FORCE_SHOW: show_collision = true; break; } @@ -942,32 +930,32 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect void TileMapLayer::_navigation_update() { ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - const Ref<TileSet> &tile_set = get_effective_tile_set(); NavigationServer2D *ns = NavigationServer2D::get_singleton(); + const Ref<TileSet> &tile_set = get_effective_tile_set(); // Check if we should cleanup everything. bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !is_inside_tree() || !tile_set.is_valid(); // ----------- Layer level processing ----------- - if (forced_cleanup) { - if (navigation_map.is_valid() && !uses_world_navigation_map) { - ns->free(navigation_map); - navigation_map = RID(); - } - } else { - // Update navigation maps. - if (!navigation_map.is_valid()) { - if (layer_index_in_tile_map_node == 0) { - // Use the default World2D navigation map for the first layer when empty. - navigation_map = get_world_2d()->get_navigation_map(); - uses_world_navigation_map = true; - } else { - RID new_layer_map = ns->map_create(); - // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. - ns->map_set_cell_size(new_layer_map, 1.0); - ns->map_set_active(new_layer_map, true); - navigation_map = new_layer_map; - uses_world_navigation_map = false; + // All this processing is kept for compatibility with the TileMap node. + // Otherwise, layers shall use the World2D navigation map or define a custom one with set_navigation_map(...). + if (tile_map_node) { + if (forced_cleanup) { + if (navigation_map_override.is_valid()) { + ns->free(navigation_map_override); + navigation_map_override = RID(); + } + } else { + // Update navigation maps. + if (!navigation_map_override.is_valid()) { + if (layer_index_in_tile_map_node > 0) { + // Create a dedicated map for each layer. + RID new_layer_map = ns->map_create(); + // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. + ns->map_set_cell_size(new_layer_map, 1.0); + ns->map_set_active(new_layer_map, true); + navigation_map_override = new_layer_map; + } } } } @@ -979,7 +967,7 @@ void TileMapLayer::_navigation_update() { _navigation_clear_cell(kv.value); } } else { - if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { + if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP]) { // Update all cells. for (KeyValue<Vector2i, CellData> &kv : tile_map) { _navigation_update_cell(kv.value); @@ -1033,10 +1021,11 @@ void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { - const TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); NavigationServer2D *ns = NavigationServer2D::get_singleton(); Transform2D gl_xform = get_global_transform(); + RID navigation_map = navigation_map_override.is_valid() ? navigation_map_override : get_world_2d()->get_navigation_map(); + ERR_FAIL_COND(navigation_map.is_null()); // Get the navigation polygons and create regions. TileMapCell &c = r_cell_data.cell; @@ -1084,7 +1073,7 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { if (!region.is_valid()) { region = ns->region_create(); } - ns->region_set_owner_id(region, tile_map_node->get_instance_id()); + ns->region_set_owner_id(region, tile_map_node ? tile_map_node->get_instance_id() : get_instance_id()); ns->region_set_map(region, navigation_map); ns->region_set_transform(region, gl_xform * tile_transform); ns->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index)); @@ -1111,16 +1100,15 @@ void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { #ifdef DEBUG_ENABLED void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { // Draw the debug collision shapes. - const TileMap *tile_map_node = _fetch_tilemap(); bool show_navigation = false; - switch (tile_map_node->get_navigation_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: + switch (navigation_visibility_mode) { + case TileMapLayer::VISIBILITY_MODE_DEFAULT: show_navigation = !Engine::get_singleton()->is_editor_hint() && get_tree()->is_debugging_navigation_hint(); break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: + case TileMapLayer::VISIBILITY_MODE_FORCE_HIDE: show_navigation = false; break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: + case TileMapLayer::VISIBILITY_MODE_FORCE_SHOW: show_navigation = true; break; } @@ -1250,13 +1238,14 @@ void TileMapLayer::_scenes_update() { } void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { - const TileMap *tile_map_node = _fetch_tilemap(); - if (!tile_map_node) { - return; - } - // Cleanup existing scene. - Node *node = tile_map_node->get_node_or_null(r_cell_data.scene); + Node *node = nullptr; + if (tile_map_node) { + // Compatibility with TileMap. + node = tile_map_node->get_node_or_null(r_cell_data.scene); + } else { + node = get_node_or_null(r_cell_data.scene); + } if (node) { node->queue_free(); } @@ -1264,7 +1253,6 @@ void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { } void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { - TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); // Clear the scene in any case. @@ -1292,7 +1280,12 @@ void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); } - tile_map_node->add_child(scene); + if (tile_map_node) { + // Compatibility with TileMap. + tile_map_node->add_child(scene); + } else { + add_child(scene); + } r_cell_data.scene = scene->get_name(); } } @@ -1352,26 +1345,28 @@ void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vecto ///////////////////////////////////////////////////////////////////// void TileMapLayer::_build_runtime_update_tile_data() { - const TileMap *tile_map_node = _fetch_tilemap(); const Ref<TileSet> &tile_set = get_effective_tile_set(); // Check if we should cleanup everything. bool forced_cleanup = in_destructor || !enabled || !tile_set.is_valid() || !is_visible_in_tree(); if (!forced_cleanup) { - if (tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { + bool valid_runtime_update = GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update); + bool valid_runtime_update_for_tilemap = tile_map_node && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update); // For keeping compatibility. + if (valid_runtime_update || valid_runtime_update_for_tilemap) { + bool use_tilemap_for_runtime = valid_runtime_update_for_tilemap && !valid_runtime_update; if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_LAYER_GROUP_TILE_SET]) { _runtime_update_needs_all_cells_cleaned_up = true; for (KeyValue<Vector2i, CellData> &E : tile_map) { - _build_runtime_update_tile_data_for_cell(E.value); + _build_runtime_update_tile_data_for_cell(E.value, use_tilemap_for_runtime); } - } else if (dirty.flags[DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE]) { + } else if (dirty.flags[DIRTY_FLAGS_LAYER_RUNTIME_UPDATE]) { for (KeyValue<Vector2i, CellData> &E : tile_map) { - _build_runtime_update_tile_data_for_cell(E.value, true); + _build_runtime_update_tile_data_for_cell(E.value, use_tilemap_for_runtime, true); } } else { for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { CellData &cell_data = *cell_data_list_element->self(); - _build_runtime_update_tile_data_for_cell(cell_data); + _build_runtime_update_tile_data_for_cell(cell_data, use_tilemap_for_runtime); } } } @@ -1382,8 +1377,7 @@ void TileMapLayer::_build_runtime_update_tile_data() { _runtime_update_tile_data_was_cleaned_up = forced_cleanup; } -void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list) { - TileMap *tile_map_node = _fetch_tilemap(); +void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_use_tilemap_for_runtime, bool p_auto_add_to_dirty_list) { const Ref<TileSet> &tile_set = get_effective_tile_set(); TileMapCell &c = r_cell_data.cell; @@ -1395,18 +1389,37 @@ void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_dat TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { bool ret = false; - if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, ret) && ret) { - TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - // Create the runtime TileData. - TileData *tile_data_runtime_use = tile_data->duplicate(); - tile_data_runtime_use->set_allow_transform(true); - r_cell_data.runtime_tile_data_cache = tile_data_runtime_use; + if (p_use_tilemap_for_runtime) { + // Compatibility with TileMap. + if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, ret) && ret) { + TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + + // Create the runtime TileData. + TileData *tile_data_runtime_use = tile_data->duplicate(); + tile_data_runtime_use->set_allow_transform(true); + r_cell_data.runtime_tile_data_cache = tile_data_runtime_use; - tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, tile_data_runtime_use); + tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, tile_data_runtime_use); - if (p_auto_add_to_dirty_list) { - dirty.cell_list.add(&r_cell_data.dirty_list_element); + if (p_auto_add_to_dirty_list) { + dirty.cell_list.add(&r_cell_data.dirty_list_element); + } + } + } else { + if (GDVIRTUAL_CALL(_use_tile_data_runtime_update, r_cell_data.coords, ret) && ret) { + TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + + // Create the runtime TileData. + TileData *tile_data_runtime_use = tile_data->duplicate(); + tile_data_runtime_use->set_allow_transform(true); + r_cell_data.runtime_tile_data_cache = tile_data_runtime_use; + + GDVIRTUAL_CALL(_tile_data_runtime_update, r_cell_data.coords, tile_data_runtime_use); + + if (p_auto_add_to_dirty_list) { + dirty.cell_list.add(&r_cell_data.dirty_list_element); + } } } } @@ -1611,8 +1624,7 @@ void TileMapLayer::_renamed() { } void TileMapLayer::_update_notify_local_transform() { - TileMap *tile_map_node = _fetch_tilemap(); - bool notify = tile_map_node->is_collision_animatable() || is_y_sort_enabled(); + bool notify = is_using_kinematic_bodies() || is_y_sort_enabled(); if (!notify) { if (is_y_sort_enabled()) { notify = true; @@ -1727,16 +1739,23 @@ void TileMapLayer::_notification(int p_what) { void TileMapLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapLayer::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0)); + GDVIRTUAL_BIND(_use_tile_data_runtime_update, "coords"); + GDVIRTUAL_BIND(_tile_data_runtime_update, "coords", "tile_data"); + ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); } -void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) { - if (p_index == layer_index_in_tile_map_node) { - return; +void TileMapLayer::set_as_tile_map_internal_node(int p_index) { + // Compatibility with TileMap. + ERR_FAIL_NULL(get_parent()); + tile_map_node = Object::cast_to<TileMap>(get_parent()); + set_use_parent_material(true); + force_parent_owned(); + if (layer_index_in_tile_map_node != p_index) { + layer_index_in_tile_map_node = p_index; + dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true; + _queue_internal_update(); } - layer_index_in_tile_map_node = p_index; - dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true; - _queue_internal_update(); } Rect2 TileMapLayer::get_rect(bool &r_changed) const { @@ -2085,7 +2104,7 @@ void TileMapLayer::set_tile_data(TileMapDataFormat p_format, const Vector<int> & clear(); #ifdef DISABLE_DEPRECATED - ERR_FAIL_COND_MSG(p_format != TileMapDataFormat::FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); + ERR_FAIL_COND_MSG(p_format != TileMapDataFormat::FORMAT_3, vformat("Cannot handle deprecated TileMapLayer data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); #endif for (int i = 0; i < c; i += offset) { @@ -2176,7 +2195,7 @@ Vector<int> TileMapLayer::get_tile_data() const { return tile_data; } -void TileMapLayer::notify_tile_map_change(DirtyFlags p_what) { +void TileMapLayer::notify_tile_map_layer_group_change(DirtyFlags p_what) { if (p_what == DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS || p_what == DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED || p_what == DIRTY_FLAGS_LAYER_GROUP_TILE_SET) { @@ -2192,6 +2211,12 @@ void TileMapLayer::update_internals() { _deferred_internal_update(); } +void TileMapLayer::notify_runtime_tile_data_update() { + dirty.flags[TileMapLayer::DIRTY_FLAGS_LAYER_RUNTIME_UPDATE] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { // Set the current cell tile (using integer position). Vector2i pk(p_coords); @@ -2532,8 +2557,9 @@ void TileMapLayer::set_enabled(bool p_enabled) { _queue_internal_update(); emit_signal(CoreStringNames::get_singleton()->changed); - TileMap *tile_map_node = _fetch_tilemap(); - tile_map_node->update_configuration_warnings(); + if (tile_map_node) { + tile_map_node->update_configuration_warnings(); + } } bool TileMapLayer::is_enabled() const { @@ -2559,8 +2585,9 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { _queue_internal_update(); emit_signal(CoreStringNames::get_singleton()->changed); - TileMap *tile_map_node = _fetch_tilemap(); - tile_map_node->update_configuration_warnings(); + if (tile_map_node) { + tile_map_node->update_configuration_warnings(); + } _update_notify_local_transform(); } @@ -2587,8 +2614,51 @@ void TileMapLayer::set_z_index(int p_z_index) { _queue_internal_update(); emit_signal(CoreStringNames::get_singleton()->changed); - TileMap *tile_map_node = _fetch_tilemap(); - tile_map_node->update_configuration_warnings(); + if (tile_map_node) { + tile_map_node->update_configuration_warnings(); + } +} + +void TileMapLayer::set_light_mask(int p_light_mask) { + if (get_light_mask() == p_light_mask) { + return; + } + CanvasItem::set_light_mask(p_light_mask); + dirty.flags[DIRTY_FLAGS_LAYER_LIGHT_MASK] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void TileMapLayer::set_texture_filter(TextureFilter p_texture_filter) { + // Set a default texture filter for the whole tilemap. + CanvasItem::set_texture_filter(p_texture_filter); + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_FILTER] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void TileMapLayer::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { + // Set a default texture repeat for the whole tilemap. + CanvasItem::set_texture_repeat(p_texture_repeat); + dirty.flags[DIRTY_FLAGS_LAYER_TEXTURE_REPEAT] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +void TileMapLayer::set_rendering_quadrant_size(int p_size) { + if (rendering_quadrant_size == p_size) { + return; + } + dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] = true; + ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); + + rendering_quadrant_size = p_size; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +int TileMapLayer::get_rendering_quadrant_size() const { + return rendering_quadrant_size; } void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) { @@ -2602,6 +2672,20 @@ bool TileMapLayer::is_using_kinematic_bodies() const { return use_kinematic_bodies; } +void TileMapLayer::set_collision_visibility_mode(TileMapLayer::VisibilityMode p_show_collision) { + if (collision_visibility_mode == p_show_collision) { + return; + } + collision_visibility_mode = p_show_collision; + dirty.flags[DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +TileMapLayer::VisibilityMode TileMapLayer::get_collision_visibility_mode() const { + return collision_visibility_mode; +} + void TileMapLayer::set_navigation_enabled(bool p_enabled) { if (navigation_enabled == p_enabled) { return; @@ -2617,18 +2701,38 @@ bool TileMapLayer::is_navigation_enabled() const { } void TileMapLayer::set_navigation_map(RID p_map) { - ERR_FAIL_COND_MSG(!is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree."); - navigation_map = p_map; - uses_world_navigation_map = p_map == get_world_2d()->get_navigation_map(); + if (navigation_map_override == p_map) { + return; + } + navigation_map_override = p_map; + dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_MAP] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); } RID TileMapLayer::get_navigation_map() const { - if (navigation_map.is_valid()) { - return navigation_map; + if (navigation_map_override.is_valid()) { + return navigation_map_override; + } else if (is_inside_tree()) { + return get_world_2d()->get_navigation_map(); } return RID(); } +void TileMapLayer::set_navigation_visibility_mode(TileMapLayer::VisibilityMode p_show_navigation) { + if (navigation_visibility_mode == p_show_navigation) { + return; + } + navigation_visibility_mode = p_show_navigation; + dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE] = true; + _queue_internal_update(); + emit_signal(CoreStringNames::get_singleton()->changed); +} + +TileMapLayer::VisibilityMode TileMapLayer::get_navigation_visibility_mode() const { + return navigation_visibility_mode; +} + void TileMapLayer::fix_invalid_tiles() { Ref<TileSet> tileset = get_effective_tile_set(); ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 5a0c51d7e5..c58c72949c 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -218,6 +218,12 @@ class TileMapLayer : public Node2D { GDCLASS(TileMapLayer, Node2D); public: + enum VisibilityMode { + VISIBILITY_MODE_DEFAULT, + VISIBILITY_MODE_FORCE_SHOW, + VISIBILITY_MODE_FORCE_HIDE, + }; + enum DirtyFlags { DIRTY_FLAGS_LAYER_ENABLED = 0, DIRTY_FLAGS_LAYER_IN_TREE, @@ -228,24 +234,23 @@ public: DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, DIRTY_FLAGS_LAYER_Z_INDEX, + DIRTY_FLAGS_LAYER_LIGHT_MASK, + DIRTY_FLAGS_LAYER_TEXTURE_FILTER, + DIRTY_FLAGS_LAYER_TEXTURE_REPEAT, + DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE, DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES, + DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE, DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, - DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE, + DIRTY_FLAGS_LAYER_NAVIGATION_MAP, + DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE, + DIRTY_FLAGS_LAYER_RUNTIME_UPDATE, + + DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE, // For compatibility. DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS, DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED, DIRTY_FLAGS_LAYER_GROUP_TILE_SET, - DIRTY_FLAGS_TILE_MAP_LIGHT_MASK, - DIRTY_FLAGS_TILE_MAP_MATERIAL, - DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL, - DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER, - DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT, - DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE, - DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE, - DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE, - DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED, - DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE, DIRTY_FLAGS_MAX, }; @@ -253,16 +258,23 @@ private: // Exposed properties. bool enabled = true; int y_sort_origin = 0; + int rendering_quadrant_size = 16; + bool use_kinematic_bodies = false; + VisibilityMode collision_visibility_mode = VISIBILITY_MODE_DEFAULT; + bool navigation_enabled = true; - RID navigation_map; - bool uses_world_navigation_map = false; + RID navigation_map_override; + VisibilityMode navigation_visibility_mode = VISIBILITY_MODE_DEFAULT; // Internal. - int layer_index_in_tile_map_node = -1; HashMap<Vector2i, CellData> tile_map; bool pending_update = false; + // For keeping compatibility with TileMap. + TileMap *tile_map_node = nullptr; + int layer_index_in_tile_map_node = -1; + // Dirty flag. Allows knowing what was modified since the last update. struct { bool flags[DIRTY_FLAGS_MAX] = { false }; @@ -276,13 +288,10 @@ private: mutable Rect2i used_rect_cache; mutable bool used_rect_cache_dirty = true; - // Method to fetch the TileSet to use - TileMap *_fetch_tilemap() const; - // Runtime tile data. bool _runtime_update_tile_data_was_cleaned_up = false; void _build_runtime_update_tile_data(); - void _build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list = false); + void _build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_use_tilemap_for_runtime, bool p_auto_add_to_dirty_list = false); bool _runtime_update_needs_all_cells_cleaned_up = false; void _clear_runtime_update_tile_data(); void _clear_runtime_update_tile_data_for_cell(CellData &r_cell_data); @@ -353,7 +362,7 @@ protected: public: // TileMap node. - void set_layer_index_in_tile_map_node(int p_index); + void set_as_tile_map_internal_node(int p_index); // Rect caching. Rect2 get_rect(bool &r_changed) const; @@ -370,9 +379,10 @@ public: // For TileMap node's use. void set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data); Vector<int> get_tile_data() const; - void notify_tile_map_change(DirtyFlags p_what); + void notify_tile_map_layer_group_change(DirtyFlags p_what); void update_internals(); + void notify_runtime_tile_data_update(); // --- Exposed in TileMap --- // Cells manipulation. @@ -406,12 +416,23 @@ public: void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; virtual void set_z_index(int p_z_index) override; + virtual void set_light_mask(int p_light_mask) override; + virtual void set_texture_filter(CanvasItem::TextureFilter p_texture_filter) override; + virtual void set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) override; + void set_rendering_quadrant_size(int p_size); + int get_rendering_quadrant_size() const; + void set_use_kinematic_bodies(bool p_use_kinematic_bodies); bool is_using_kinematic_bodies() const; + void set_collision_visibility_mode(VisibilityMode p_show_collision); + VisibilityMode get_collision_visibility_mode() const; + void set_navigation_enabled(bool p_enabled); bool is_navigation_enabled() const; void set_navigation_map(RID p_map); RID get_navigation_map() const; + void set_navigation_visibility_mode(VisibilityMode p_show_navigation); + VisibilityMode get_navigation_visibility_mode() const; // Fixing and clearing methods. void fix_invalid_tiles(); @@ -423,6 +444,9 @@ public: // Helper. Ref<TileSet> get_effective_tile_set() const; + // Virtual function to modify the TileData at runtime. + GDVIRTUAL1R(bool, _use_tile_data_runtime_update, Vector2i); + GDVIRTUAL2(_tile_data_runtime_update, Vector2i, TileData *); // --- TileMapLayer(); diff --git a/scene/2d/tile_map_layer_group.cpp b/scene/2d/tile_map_layer_group.cpp index c596534474..132b4bbba5 100644 --- a/scene/2d/tile_map_layer_group.cpp +++ b/scene/2d/tile_map_layer_group.cpp @@ -53,7 +53,7 @@ void TileMapLayerGroup::_tile_set_changed() { for (int i = 0; i < get_child_count(); i++) { TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); if (layer) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); + layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); } } @@ -70,7 +70,7 @@ void TileMapLayerGroup::set_selected_layers(Vector<StringName> p_layer_names) { for (int i = 0; i < get_child_count(); i++) { TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); if (layer) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS); + layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_SELECTED_LAYERS); } } } @@ -89,7 +89,7 @@ void TileMapLayerGroup::set_highlight_selected_layer(bool p_highlight_selected_l for (int i = 0; i < get_child_count(); i++) { TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); if (layer) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED); + layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_HIGHLIGHT_SELECTED); } } } @@ -132,7 +132,7 @@ void TileMapLayerGroup::set_tileset(const Ref<TileSet> &p_tileset) { for (int i = 0; i < get_child_count(); i++) { TileMapLayer *layer = Object::cast_to<TileMapLayer>(get_child(i)); if (layer) { - layer->notify_tile_map_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); + layer->notify_tile_map_layer_group_change(TileMapLayer::DIRTY_FLAGS_LAYER_GROUP_TILE_SET); } } } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 73cbfceea4..b8af4c3f79 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -1824,14 +1824,14 @@ void CodeEdit::create_code_region() { } int to_line = get_selection_to_line(caret_idx); set_line(to_line, get_line(to_line) + "\n" + code_region_end_string); - insert_line_at(from_line, code_region_start_string + " " + RTR("New Code Region")); + insert_line_at(from_line, code_region_start_string + " " + atr(ETR("New Code Region"))); fold_line(from_line); } // Select name of the first region to allow quick edit. remove_secondary_carets(); set_caret_line(first_region_start); - int tag_length = code_region_start_string.length() + RTR("New Code Region").length() + 1; + int tag_length = code_region_start_string.length() + atr(ETR("New Code Region")).length() + 1; set_caret_column(tag_length); select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index cdb091cb32..ee2122f269 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -54,6 +54,22 @@ void ColorPicker::_notification(int p_what) { _update_color(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + List<BaseButton *> buttons; + preset_group->get_buttons(&buttons); + for (List<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { + Color preset_color = ((ColorPresetButton *)E->get())->get_preset_color(); + E->get()->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color\nRMB: Remove preset")), preset_color.to_html(preset_color.a < 1))); + } + + buttons.clear(); + recent_preset_group->get_buttons(&buttons); + for (List<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { + Color preset_color = ((ColorPresetButton *)E->get())->get_preset_color(); + E->get()->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color")), preset_color.to_html(preset_color.a < 1))); + } + } break; + case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(theme_cache.screen_picker); _update_drop_down_arrow(btn_preset->is_pressed(), btn_preset); @@ -689,7 +705,7 @@ void ColorPicker::_text_type_toggled() { text_type->set_icon(nullptr); c_text->set_editable(true); - c_text->set_tooltip_text(RTR("Enter a hex code (\"#ff0000\") or named color (\"red\").")); + c_text->set_tooltip_text(ETR("Enter a hex code (\"#ff0000\") or named color (\"red\").")); } _update_color(); } @@ -735,7 +751,7 @@ inline int ColorPicker::_get_preset_size() { void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); - btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color\nRMB: Remove preset")), p_color.to_html(p_color.a < 1))); SET_DRAG_FORWARDING_GCDU(btn_preset_new, ColorPicker); btn_preset_new->set_button_group(preset_group); preset_container->add_child(btn_preset_new); @@ -745,7 +761,7 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); - btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_tooltip_text(vformat(atr(ETR("Color: #%s\nLMB: Apply color")), p_color.to_html(p_color.a < 1))); btn_preset_new->set_button_group(recent_preset_group); recent_preset_hbc->add_child(btn_preset_new); recent_preset_hbc->move_child(btn_preset_new, 0); @@ -1807,11 +1823,11 @@ ColorPicker::ColorPicker() { btn_pick = memnew(Button); sample_hbc->add_child(btn_pick); if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE)) { - btn_pick->set_tooltip_text(RTR("Pick a color from the screen.")); + btn_pick->set_tooltip_text(ETR("Pick a color from the screen.")); btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed)); } else { // On unsupported platforms, use a legacy method for color picking. - btn_pick->set_tooltip_text(RTR("Pick a color from the application window.")); + btn_pick->set_tooltip_text(ETR("Pick a color from the application window.")); btn_pick->connect(SNAME("pressed"), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); } @@ -1825,7 +1841,7 @@ ColorPicker::ColorPicker() { btn_shape->set_flat(false); sample_hbc->add_child(btn_shape); btn_shape->set_toggle_mode(true); - btn_shape->set_tooltip_text(RTR("Select a picker shape.")); + btn_shape->set_tooltip_text(ETR("Select a picker shape.")); current_shape = SHAPE_HSV_RECTANGLE; @@ -1864,7 +1880,7 @@ ColorPicker::ColorPicker() { btn_mode->set_flat(false); mode_hbc->add_child(btn_mode); btn_mode->set_toggle_mode(true); - btn_mode->set_tooltip_text(RTR("Select a picker mode.")); + btn_mode->set_tooltip_text(ETR("Select a picker mode.")); current_mode = MODE_RGB; @@ -1918,8 +1934,8 @@ ColorPicker::ColorPicker() { hex_hbc->add_child(c_text); c_text->set_h_size_flags(SIZE_EXPAND_FILL); c_text->set_select_all_on_focus(true); - c_text->set_tooltip_text(RTR("Enter a hex code (\"#ff0000\") or named color (\"red\").")); - c_text->set_placeholder(RTR("Hex code or named color")); + c_text->set_tooltip_text(ETR("Enter a hex code (\"#ff0000\") or named color (\"red\").")); + c_text->set_placeholder(ETR("Hex code or named color")); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); @@ -1997,7 +2013,7 @@ ColorPicker::ColorPicker() { btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); - btn_add_preset->set_tooltip_text(RTR("Add current color as a preset.")); + btn_add_preset->set_tooltip_text(ETR("Add current color as a preset.")); btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); preset_container->add_child(btn_add_preset); } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 178af01a3e..abed3cf594 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -302,7 +302,7 @@ void FileDialog::update_dir() { if (drives->is_visible()) { if (dir_access->get_current_dir().is_network_share_path()) { _update_drives(false); - drives->add_item(RTR("Network")); + drives->add_item(ETR("Network")); drives->set_item_disabled(-1, true); drives->select(drives->get_item_count() - 1); } else { @@ -459,7 +459,7 @@ void FileDialog::_action_pressed() { } if (dir_access->file_exists(f)) { - confirm_save->set_text(vformat(RTR("File \"%s\" already exists.\nDo you want to overwrite it?"), f)); + confirm_save->set_text(vformat(atr(ETR("File \"%s\" already exists.\nDo you want to overwrite it?")), f)); confirm_save->popup_centered(Size2(250, 80)); } else { emit_signal(SNAME("file_selected"), f); @@ -539,10 +539,10 @@ void FileDialog::deselect_all() { switch (mode) { case FILE_MODE_OPEN_FILE: case FILE_MODE_OPEN_FILES: - set_ok_button_text(RTR("Open")); + set_ok_button_text(ETR("Open")); break; case FILE_MODE_OPEN_DIR: - set_ok_button_text(RTR("Select Current Folder")); + set_ok_button_text(ETR("Select Current Folder")); break; case FILE_MODE_OPEN_ANY: case FILE_MODE_SAVE_FILE: @@ -566,7 +566,7 @@ void FileDialog::_tree_selected() { if (!d["dir"]) { file->set_text(d["name"]); } else if (mode == FILE_MODE_OPEN_DIR) { - set_ok_button_text(RTR("Select This Folder")); + set_ok_button_text(ETR("Select This Folder")); } get_ok_button()->set_disabled(_is_open_should_be_disabled()); @@ -621,7 +621,7 @@ void FileDialog::update_file_list() { if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { message->hide(); } else { - message->set_text(RTR("You don't have permission to access contents of this folder.")); + message->set_text(ETR("You don't have permission to access contents of this folder.")); message->show(); } @@ -771,7 +771,7 @@ void FileDialog::update_filters() { all_filters += ", ..."; } - filter->add_item(RTR("All Recognized") + " (" + all_filters + ")"); + filter->add_item(atr(ETR("All Recognized")) + " (" + all_filters + ")"); } for (int i = 0; i < filters.size(); i++) { String flt = filters[i].get_slice(";", 0).strip_edges(); @@ -783,7 +783,7 @@ void FileDialog::update_filters() { } } - filter->add_item(RTR("All Files") + " (*)"); + filter->add_item(atr(ETR("All Files")) + " (*)"); } void FileDialog::clear_filters() { @@ -896,37 +896,37 @@ void FileDialog::set_file_mode(FileMode p_mode) { mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: - set_ok_button_text(RTR("Open")); + set_ok_button_text(ETR("Open")); if (mode_overrides_title) { - set_title(TTRC("Open a File")); + set_title(ETR("Open a File")); } makedir->hide(); break; case FILE_MODE_OPEN_FILES: - set_ok_button_text(RTR("Open")); + set_ok_button_text(ETR("Open")); if (mode_overrides_title) { - set_title(TTRC("Open File(s)")); + set_title(ETR("Open File(s)")); } makedir->hide(); break; case FILE_MODE_OPEN_DIR: - set_ok_button_text(RTR("Select Current Folder")); + set_ok_button_text(ETR("Select Current Folder")); if (mode_overrides_title) { - set_title(TTRC("Open a Directory")); + set_title(ETR("Open a Directory")); } makedir->show(); break; case FILE_MODE_OPEN_ANY: - set_ok_button_text(RTR("Open")); + set_ok_button_text(ETR("Open")); if (mode_overrides_title) { - set_title(TTRC("Open a File or Directory")); + set_title(ETR("Open a File or Directory")); } makedir->show(); break; case FILE_MODE_SAVE_FILE: - set_ok_button_text(RTR("Save")); + set_ok_button_text(ETR("Save")); if (mode_overrides_title) { - set_title(TTRC("Save a File")); + set_title(ETR("Save a File")); } makedir->show(); break; @@ -1389,13 +1389,13 @@ FileDialog::FileDialog() { dir_prev = memnew(Button); dir_prev->set_theme_type_variation("FlatButton"); - dir_prev->set_tooltip_text(RTR("Go to previous folder.")); + dir_prev->set_tooltip_text(ETR("Go to previous folder.")); dir_next = memnew(Button); dir_next->set_theme_type_variation("FlatButton"); - dir_next->set_tooltip_text(RTR("Go to next folder.")); + dir_next->set_tooltip_text(ETR("Go to next folder.")); dir_up = memnew(Button); dir_up->set_theme_type_variation("FlatButton"); - dir_up->set_tooltip_text(RTR("Go to parent folder.")); + dir_up->set_tooltip_text(ETR("Go to parent folder.")); hbc->add_child(dir_prev); hbc->add_child(dir_next); hbc->add_child(dir_up); @@ -1403,7 +1403,7 @@ FileDialog::FileDialog() { dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward)); dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up)); - hbc->add_child(memnew(Label(RTR("Path:")))); + hbc->add_child(memnew(Label(ETR("Path:")))); drives_container = memnew(HBoxContainer); hbc->add_child(drives_container); @@ -1419,7 +1419,7 @@ FileDialog::FileDialog() { refresh = memnew(Button); refresh->set_theme_type_variation("FlatButton"); - refresh->set_tooltip_text(RTR("Refresh files.")); + refresh->set_tooltip_text(ETR("Refresh files.")); refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); @@ -1427,7 +1427,7 @@ FileDialog::FileDialog() { show_hidden->set_theme_type_variation("FlatButton"); show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); - show_hidden->set_tooltip_text(RTR("Toggle the visibility of hidden files.")); + show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files.")); show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); @@ -1436,14 +1436,14 @@ FileDialog::FileDialog() { makedir = memnew(Button); makedir->set_theme_type_variation("FlatButton"); - makedir->set_tooltip_text(RTR("Create a new folder.")); + makedir->set_tooltip_text(ETR("Create a new folder.")); makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir)); hbc->add_child(makedir); vbox->add_child(hbc); tree = memnew(Tree); tree->set_hide_root(true); - vbox->add_margin_child(RTR("Directories & Files:"), tree, true); + vbox->add_margin_child(ETR("Directories & Files:"), tree, true); message = memnew(Label); message->hide(); @@ -1453,7 +1453,7 @@ FileDialog::FileDialog() { tree->add_child(message); file_box = memnew(HBoxContainer); - file_box->add_child(memnew(Label(RTR("File:")))); + file_box->add_child(memnew(Label(ETR("File:")))); file = memnew(LineEdit); file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); file->set_stretch_ratio(4); @@ -1489,22 +1489,22 @@ FileDialog::FileDialog() { confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); makedialog = memnew(ConfirmationDialog); - makedialog->set_title(RTR("Create Folder")); + makedialog->set_title(ETR("Create Folder")); VBoxContainer *makevb = memnew(VBoxContainer); makedialog->add_child(makevb); makedirname = memnew(LineEdit); makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); - makevb->add_margin_child(RTR("Name:"), makedirname); + makevb->add_margin_child(ETR("Name:"), makedirname); add_child(makedialog, false, INTERNAL_MODE_FRONT); makedialog->register_text_enter(makedirname); makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm)); mkdirerr = memnew(AcceptDialog); - mkdirerr->set_text(RTR("Could not create folder.")); + mkdirerr->set_text(ETR("Could not create folder.")); add_child(mkdirerr, false, INTERNAL_MODE_FRONT); exterr = memnew(AcceptDialog); - exterr->set_text(RTR("Invalid extension, or empty filename.")); + exterr->set_text(ETR("Invalid extension, or empty filename.")); add_child(exterr, false, INTERNAL_MODE_FRONT); update_filters(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index b7118d595f..056872a4fe 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -2435,7 +2435,7 @@ GraphEdit::GraphEdit() { zoom_minus_button = memnew(Button); zoom_minus_button->set_theme_type_variation("FlatButton"); zoom_minus_button->set_visible(show_zoom_buttons); - zoom_minus_button->set_tooltip_text(RTR("Zoom Out")); + zoom_minus_button->set_tooltip_text(ETR("Zoom Out")); zoom_minus_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_minus_button); zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus)); @@ -2443,7 +2443,7 @@ GraphEdit::GraphEdit() { zoom_reset_button = memnew(Button); zoom_reset_button->set_theme_type_variation("FlatButton"); zoom_reset_button->set_visible(show_zoom_buttons); - zoom_reset_button->set_tooltip_text(RTR("Zoom Reset")); + zoom_reset_button->set_tooltip_text(ETR("Zoom Reset")); zoom_reset_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_reset_button); zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset)); @@ -2451,7 +2451,7 @@ GraphEdit::GraphEdit() { zoom_plus_button = memnew(Button); zoom_plus_button->set_theme_type_variation("FlatButton"); zoom_plus_button->set_visible(show_zoom_buttons); - zoom_plus_button->set_tooltip_text(RTR("Zoom In")); + zoom_plus_button->set_tooltip_text(ETR("Zoom In")); zoom_plus_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(zoom_plus_button); zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus)); @@ -2463,7 +2463,7 @@ GraphEdit::GraphEdit() { toggle_grid_button->set_visible(show_grid_buttons); toggle_grid_button->set_toggle_mode(true); toggle_grid_button->set_pressed(true); - toggle_grid_button->set_tooltip_text(RTR("Toggle the visual grid.")); + toggle_grid_button->set_tooltip_text(ETR("Toggle the visual grid.")); toggle_grid_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(toggle_grid_button); toggle_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled)); @@ -2472,7 +2472,7 @@ GraphEdit::GraphEdit() { toggle_snapping_button->set_theme_type_variation("FlatButton"); toggle_snapping_button->set_visible(show_grid_buttons); toggle_snapping_button->set_toggle_mode(true); - toggle_snapping_button->set_tooltip_text(RTR("Toggle snapping to the grid.")); + toggle_snapping_button->set_tooltip_text(ETR("Toggle snapping to the grid.")); toggle_snapping_button->set_pressed(snapping_enabled); toggle_snapping_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(toggle_snapping_button); @@ -2484,7 +2484,7 @@ GraphEdit::GraphEdit() { snapping_distance_spinbox->set_max(GRID_MAX_SNAPPING_DISTANCE); snapping_distance_spinbox->set_step(1); snapping_distance_spinbox->set_value(snapping_distance); - snapping_distance_spinbox->set_tooltip_text(RTR("Change the snapping distance.")); + snapping_distance_spinbox->set_tooltip_text(ETR("Change the snapping distance.")); menu_hbox->add_child(snapping_distance_spinbox); snapping_distance_spinbox->connect("value_changed", callable_mp(this, &GraphEdit::_snapping_distance_changed)); @@ -2494,7 +2494,7 @@ GraphEdit::GraphEdit() { minimap_button->set_theme_type_variation("FlatButton"); minimap_button->set_visible(show_minimap_button); minimap_button->set_toggle_mode(true); - minimap_button->set_tooltip_text(RTR("Toggle the graph minimap.")); + minimap_button->set_tooltip_text(ETR("Toggle the graph minimap.")); minimap_button->set_pressed(show_grid); minimap_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(minimap_button); @@ -2506,7 +2506,7 @@ GraphEdit::GraphEdit() { arrange_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); arrange_button->set_focus_mode(FOCUS_NONE); menu_hbox->add_child(arrange_button); - arrange_button->set_tooltip_text(RTR("Automatically arrange selected nodes.")); + arrange_button->set_tooltip_text(ETR("Automatically arrange selected nodes.")); // Minimap. diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 162c9cf801..8376ef48b6 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1150,10 +1150,6 @@ void ItemList::_notification(int p_what) { if (should_draw_selected_bg || should_draw_hovered_bg || should_draw_custom_bg) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= theme_cache.v_separation / 2; - r.size.y += theme_cache.v_separation; - r.position.x -= theme_cache.h_separation / 2; - r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1186,6 +1182,12 @@ void ItemList::_notification(int p_what) { Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs; if (icon_mode == ICON_MODE_TOP) { + pos.y += theme_cache.v_separation / 2; + } else { + pos.x += theme_cache.h_separation / 2; + } + + if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); pos.y += theme_cache.icon_margin; text_ofs.y = icon_size.height + theme_cache.icon_margin * 2; @@ -1224,6 +1226,8 @@ void ItemList::_notification(int p_what) { if (items[i].tag_icon.is_valid()) { Point2 draw_pos = items[i].rect_cache.position; + draw_pos.x += theme_cache.h_separation / 2; + draw_pos.y += theme_cache.v_separation / 2; if (rtl) { draw_pos.x = size.width - draw_pos.x - items[i].tag_icon->get_width(); } @@ -1261,12 +1265,18 @@ void ItemList::_notification(int p_what) { text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; + text_ofs.x += theme_cache.h_separation / 2; + text_ofs.y += theme_cache.v_separation / 2; + if (rtl) { text_ofs.x = size.width - text_ofs.x - max_len; } items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); + float text_w = items[i].rect_cache.size.width - theme_cache.h_separation; + items.write[i].text_buf->set_width(text_w); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } @@ -1279,14 +1289,17 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; + text_ofs.x += theme_cache.h_separation / 2; + text_ofs.y += theme_cache.v_separation / 2; } else { text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; + text_ofs.x += theme_cache.h_separation / 2; } text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; - float text_w = width - text_ofs.x; + float text_w = width - text_ofs.x - theme_cache.h_separation; items.write[i].text_buf->set_width(text_w); if (rtl) { @@ -1309,10 +1322,6 @@ void ItemList::_notification(int p_what) { if (select_mode == SELECT_MULTI && i == current) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= theme_cache.v_separation / 2; - r.size.y += theme_cache.v_separation; - r.position.x -= theme_cache.h_separation / 2; - r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1382,9 +1391,10 @@ void ItemList::force_update_list_size() { } max_column_width = MAX(max_column_width, minsize.x); - // elements need to adapt to the selected size + // Elements need to adapt to the selected size. minsize.y += theme_cache.v_separation; minsize.x += theme_cache.h_separation; + items.write[i].rect_cache.size = minsize; items.write[i].min_rect_cache.size = minsize; } @@ -1415,18 +1425,18 @@ void ItemList::force_update_list_size() { } if (same_column_width) { - items.write[i].rect_cache.size.x = max_column_width; + items.write[i].rect_cache.size.x = max_column_width + theme_cache.h_separation; } items.write[i].rect_cache.position = ofs; max_h = MAX(max_h, items[i].rect_cache.size.y); - ofs.x += items[i].rect_cache.size.x + theme_cache.h_separation; + ofs.x += items[i].rect_cache.size.x; items.write[i].column = col; col++; if (col == current_columns) { if (i < items.size() - 1) { - separators.push_back(ofs.y + max_h + theme_cache.v_separation / 2); + separators.push_back(ofs.y + max_h); } for (int j = i; j >= 0 && col > 0; j--, col--) { @@ -1434,7 +1444,7 @@ void ItemList::force_update_list_size() { } ofs.x = 0; - ofs.y += max_h + theme_cache.v_separation; + ofs.y += max_h; col = 0; max_h = 0; } @@ -1496,12 +1506,6 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { for (int i = 0; i < items.size(); i++) { Rect2 rc = items[i].rect_cache; - // Grow the detection rectangle to match the grown selection indicator. - rc.position.y -= theme_cache.v_separation / 2; - rc.size.y += theme_cache.v_separation; - rc.position.x -= theme_cache.h_separation / 2; - rc.size.x += theme_cache.h_separation; - if (i % current_columns == current_columns - 1) { rc.size.width = get_size().width - rc.position.x; // Make sure you can still select the last item when clicking past the column. } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 75474c060e..aa6d52cc36 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -2406,45 +2406,45 @@ void LineEdit::_generate_context_menu() { add_child(menu, false, INTERNAL_MODE_FRONT); menu_dir = memnew(PopupMenu); - menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED); - menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO); - menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR); - menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL); + menu_dir->add_radio_check_item(ETR("Same as Layout Direction"), MENU_DIR_INHERITED); + menu_dir->add_radio_check_item(ETR("Auto-Detect Direction"), MENU_DIR_AUTO); + menu_dir->add_radio_check_item(ETR("Left-to-Right"), MENU_DIR_LTR); + menu_dir->add_radio_check_item(ETR("Right-to-Left"), MENU_DIR_RTL); menu_ctl = memnew(PopupMenu); - menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM); - menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM); - menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE); - menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE); - menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO); - menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO); - menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF); + menu_ctl->add_item(ETR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM); + menu_ctl->add_item(ETR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM); + menu_ctl->add_item(ETR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE); + menu_ctl->add_item(ETR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE); + menu_ctl->add_item(ETR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO); + menu_ctl->add_item(ETR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO); + menu_ctl->add_item(ETR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF); menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM); - menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI); - menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI); - menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI); - menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI); + menu_ctl->add_item(ETR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM); + menu_ctl->add_item(ETR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI); + menu_ctl->add_item(ETR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI); + menu_ctl->add_item(ETR("First Strong Isolate (FSI)"), MENU_INSERT_FSI); + menu_ctl->add_item(ETR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI); menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ); - menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ); - menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ); - menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY); - - menu->add_item(RTR("Cut"), MENU_CUT); - menu->add_item(RTR("Copy"), MENU_COPY); - menu->add_item(RTR("Paste"), MENU_PASTE); + menu_ctl->add_item(ETR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ); + menu_ctl->add_item(ETR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ); + menu_ctl->add_item(ETR("Word Joiner (WJ)"), MENU_INSERT_WJ); + menu_ctl->add_item(ETR("Soft Hyphen (SHY)"), MENU_INSERT_SHY); + + menu->add_item(ETR("Cut"), MENU_CUT); + menu->add_item(ETR("Copy"), MENU_COPY); + menu->add_item(ETR("Paste"), MENU_PASTE); menu->add_separator(); - menu->add_item(RTR("Select All"), MENU_SELECT_ALL); - menu->add_item(RTR("Clear"), MENU_CLEAR); + menu->add_item(ETR("Select All"), MENU_SELECT_ALL); + menu->add_item(ETR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO); - menu->add_item(RTR("Redo"), MENU_REDO); + menu->add_item(ETR("Undo"), MENU_UNDO); + menu->add_item(ETR("Redo"), MENU_REDO); menu->add_separator(); - menu->add_submenu_node_item(RTR("Text Writing Direction"), menu_dir, MENU_SUBMENU_TEXT_DIR); + menu->add_submenu_node_item(ETR("Text Writing Direction"), menu_dir, MENU_SUBMENU_TEXT_DIR); menu->add_separator(); - menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC); - menu->add_submenu_node_item(RTR("Insert Control Character"), menu_ctl, MENU_SUBMENU_INSERT_UCC); + menu->add_check_item(ETR("Display Control Characters"), MENU_DISPLAY_UCC); + menu->add_submenu_node_item(ETR("Insert Control Character"), menu_ctl, MENU_SUBMENU_INSERT_UCC); menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 4f25fed335..1b88caef15 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -6270,8 +6270,8 @@ void RichTextLabel::_generate_context_menu() { add_child(menu, false, INTERNAL_MODE_FRONT); menu->connect("id_pressed", callable_mp(this, &RichTextLabel::menu_option)); - menu->add_item(RTR("Copy"), MENU_COPY); - menu->add_item(RTR("Select All"), MENU_SELECT_ALL); + menu->add_item(ETR("Copy"), MENU_COPY); + menu->add_item(ETR("Select All"), MENU_SELECT_ALL); } void RichTextLabel::_update_context_menu() { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 79a5f2b557..c2716c9ef6 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -6873,45 +6873,45 @@ void TextEdit::_generate_context_menu() { add_child(menu, false, INTERNAL_MODE_FRONT); menu_dir = memnew(PopupMenu); - menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED); - menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO); - menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR); - menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL); + menu_dir->add_radio_check_item(ETR("Same as Layout Direction"), MENU_DIR_INHERITED); + menu_dir->add_radio_check_item(ETR("Auto-Detect Direction"), MENU_DIR_AUTO); + menu_dir->add_radio_check_item(ETR("Left-to-Right"), MENU_DIR_LTR); + menu_dir->add_radio_check_item(ETR("Right-to-Left"), MENU_DIR_RTL); menu_ctl = memnew(PopupMenu); - menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM); - menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM); - menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE); - menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE); - menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO); - menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO); - menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF); + menu_ctl->add_item(ETR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM); + menu_ctl->add_item(ETR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM); + menu_ctl->add_item(ETR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE); + menu_ctl->add_item(ETR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE); + menu_ctl->add_item(ETR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO); + menu_ctl->add_item(ETR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO); + menu_ctl->add_item(ETR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF); menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM); - menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI); - menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI); - menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI); - menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI); + menu_ctl->add_item(ETR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM); + menu_ctl->add_item(ETR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI); + menu_ctl->add_item(ETR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI); + menu_ctl->add_item(ETR("First Strong Isolate (FSI)"), MENU_INSERT_FSI); + menu_ctl->add_item(ETR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI); menu_ctl->add_separator(); - menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ); - menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ); - menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ); - menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY); - - menu->add_item(RTR("Cut"), MENU_CUT); - menu->add_item(RTR("Copy"), MENU_COPY); - menu->add_item(RTR("Paste"), MENU_PASTE); + menu_ctl->add_item(ETR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ); + menu_ctl->add_item(ETR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ); + menu_ctl->add_item(ETR("Word Joiner (WJ)"), MENU_INSERT_WJ); + menu_ctl->add_item(ETR("Soft Hyphen (SHY)"), MENU_INSERT_SHY); + + menu->add_item(ETR("Cut"), MENU_CUT); + menu->add_item(ETR("Copy"), MENU_COPY); + menu->add_item(ETR("Paste"), MENU_PASTE); menu->add_separator(); - menu->add_item(RTR("Select All"), MENU_SELECT_ALL); - menu->add_item(RTR("Clear"), MENU_CLEAR); + menu->add_item(ETR("Select All"), MENU_SELECT_ALL); + menu->add_item(ETR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO); - menu->add_item(RTR("Redo"), MENU_REDO); + menu->add_item(ETR("Undo"), MENU_UNDO); + menu->add_item(ETR("Redo"), MENU_REDO); menu->add_separator(); - menu->add_submenu_node_item(RTR("Text Writing Direction"), menu_dir, MENU_SUBMENU_TEXT_DIR); + menu->add_submenu_node_item(ETR("Text Writing Direction"), menu_dir, MENU_SUBMENU_TEXT_DIR); menu->add_separator(); - menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC); - menu->add_submenu_node_item(RTR("Insert Control Character"), menu_ctl, MENU_SUBMENU_INSERT_UCC); + menu->add_check_item(ETR("Display Control Characters"), MENU_DISPLAY_UCC); + menu->add_submenu_node_item(ETR("Insert Control Character"), menu_ctl, MENU_SUBMENU_INSERT_UCC); menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index ea6dbebd3b..0c841dabbe 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1949,7 +1949,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { int option = (int)p_item->cells[p_col].val; - valtext = RTR("(Other)"); + valtext = atr(ETR("(Other)")); Vector<String> strings = p_item->cells[p_col].text.split(","); for (int j = 0; j < strings.size(); j++) { int value = j; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 8d0bad6942..85422f22f9 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -1810,7 +1810,7 @@ SceneTree::SceneTree() { bool snap_2d_transforms = GLOBAL_DEF_BASIC("rendering/2d/snap/snap_2d_transforms_to_pixel", false); root->set_snap_2d_transforms_to_pixel(snap_2d_transforms); - bool snap_2d_vertices = GLOBAL_DEF_BASIC("rendering/2d/snap/snap_2d_vertices_to_pixel", false); + bool snap_2d_vertices = GLOBAL_DEF("rendering/2d/snap/snap_2d_vertices_to_pixel", false); root->set_snap_2d_vertices_to_pixel(snap_2d_vertices); // We setup VRS for the main viewport here, in the editor this will have little effect. diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1dcf9acda3..1ee6a8dca7 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1447,7 +1447,7 @@ String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Cont String tooltip; while (p_control) { - tooltip = p_control->get_tooltip(pos); + tooltip = p_control->atr(p_control->get_tooltip(pos)); // Temporary solution for PopupMenus. PopupMenu *menu = Object::cast_to<PopupMenu>(this); @@ -3158,9 +3158,6 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { gui.subwindow_over->_mouse_leave_viewport(); } gui.subwindow_over = sw; - if (!sw->is_input_disabled()) { - sw->_propagate_window_notification(sw, NOTIFICATION_WM_MOUSE_ENTER); - } } if (!sw->is_input_disabled()) { sw->_update_mouse_over(sw->get_final_transform().affine_inverse().xform(p_pos - sw->get_position())); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 03db0d4023..2904e3e156 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -352,7 +352,7 @@ private: struct GUI { bool forced_mouse_focus = false; //used for menu buttons - bool mouse_in_viewport = true; + bool mouse_in_viewport = false; bool key_event_accepted = false; HashMap<int, ObjectID> touch_focus; Control *mouse_focus = nullptr; diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index a7ce15c5ad..7a7bab636b 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -625,5 +625,3 @@ bool NavigationMesh::_get(const StringName &p_name, Variant &r_ret) const { return false; } #endif // DISABLE_DEPRECATED - -NavigationMesh::NavigationMesh() {} diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 1daebdda7f..136e1cf468 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -199,7 +199,8 @@ public: Ref<ArrayMesh> get_debug_mesh(); #endif // DEBUG_ENABLED - NavigationMesh(); + NavigationMesh() {} + ~NavigationMesh() {} }; VARIANT_ENUM_CAST(NavigationMesh::SamplePartitionType); diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp index 3dde6dbff6..fabe1659c6 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp @@ -129,10 +129,3 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines"); } - -NavigationMeshSourceGeometryData2D::NavigationMeshSourceGeometryData2D() { -} - -NavigationMeshSourceGeometryData2D::~NavigationMeshSourceGeometryData2D() { - clear(); -} diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/navigation_mesh_source_geometry_data_2d.h index f26a4e9a2e..985f90fb9e 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.h @@ -71,8 +71,8 @@ public: bool has_data() { return traversable_outlines.size(); }; void clear(); - NavigationMeshSourceGeometryData2D(); - ~NavigationMeshSourceGeometryData2D(); + NavigationMeshSourceGeometryData2D() {} + ~NavigationMeshSourceGeometryData2D() { clear(); } }; #endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp index 412455be73..e39ffab47a 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp @@ -182,10 +182,3 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices"); } - -NavigationMeshSourceGeometryData3D::NavigationMeshSourceGeometryData3D() { -} - -NavigationMeshSourceGeometryData3D::~NavigationMeshSourceGeometryData3D() { - clear(); -} diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/navigation_mesh_source_geometry_data_3d.h index ec8bddd4dd..10048773fe 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.h @@ -68,8 +68,8 @@ public: void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform); void add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); - NavigationMeshSourceGeometryData3D(); - ~NavigationMeshSourceGeometryData3D(); + NavigationMeshSourceGeometryData3D() {} + ~NavigationMeshSourceGeometryData3D() { clear(); } }; #endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/navigation_polygon.cpp index 150e4b924f..e830153330 100644 --- a/scene/resources/navigation_polygon.cpp +++ b/scene/resources/navigation_polygon.cpp @@ -541,5 +541,3 @@ void NavigationPolygon::_validate_property(PropertyInfo &p_property) const { } } } - -NavigationPolygon::NavigationPolygon() {} diff --git a/scene/resources/navigation_polygon.h b/scene/resources/navigation_polygon.h index f20d510b93..b9816f900c 100644 --- a/scene/resources/navigation_polygon.h +++ b/scene/resources/navigation_polygon.h @@ -153,7 +153,7 @@ public: void clear(); - NavigationPolygon(); + NavigationPolygon() {} ~NavigationPolygon() {} }; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index a59ac9b56d..2c92a3edb3 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -348,35 +348,16 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { //handle resources that are local to scene by duplicating them if needed Ref<Resource> res = value; if (res.is_valid()) { - if (res->is_local_to_scene()) { - if (n.instance >= 0) { // For the root node of a sub-scene, treat it as part of the sub-scene. - value = get_remap_resource(res, resources_local_to_sub_scene, node->get(snames[nprops[j].name]), node); - } else { - HashMap<Ref<Resource>, Ref<Resource>>::Iterator E = resources_local_to_scene.find(res); - Node *base = i == 0 ? node : ret_nodes[0]; - if (E) { - value = E->value; - } else { - if (p_edit_state == GEN_EDIT_STATE_MAIN) { - //for the main scene, use the resource as is - res->configure_for_local_scene(base, resources_local_to_scene); - resources_local_to_scene[res] = res; - } else { - //for instances, a copy must be made - Ref<Resource> local_dupe = res->duplicate_for_local_scene(base, resources_local_to_scene); - resources_local_to_scene[res] = local_dupe; - value = local_dupe; - } - } - } - //must make a copy, because this res is local to scene - } + value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); } } if (value.get_type() == Variant::ARRAY) { Array set_array = value; + value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); + bool is_get_valid = false; Variant get_value = node->get(snames[nprops[j].name], &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::ARRAY) { Array get_array = get_value; if (!set_array.is_same_typed(get_array)) { @@ -384,8 +365,26 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } } - if (p_edit_state == GEN_EDIT_STATE_INSTANCE && value.get_type() != Variant::OBJECT) { - value = value.duplicate(true); // Duplicate arrays and dictionaries for the editor + if (value.get_type() == Variant::DICTIONARY) { + Dictionary dictionary = value; + const Array keys = dictionary.keys(); + const Array values = dictionary.values(); + + if (has_local_resource(values) || has_local_resource(keys)) { + Array duplicated_keys = keys.duplicate(true); + Array duplicated_values = values.duplicate(true); + + duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); + duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); + + dictionary.clear(); + + for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) { + dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index]; + } + + value = dictionary; + } } bool set_valid = true; @@ -400,6 +399,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (set_valid) { node->set(snames[nprops[j].name], value, &valid); } + if (p_edit_state == GEN_EDIT_STATE_INSTANCE && value.get_type() != Variant::OBJECT) { + value = value.duplicate(true); // Duplicate arrays and dictionaries for the editor. + } } } if (!missing_resource_properties.is_empty()) { @@ -587,6 +589,50 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { return ret_nodes[0]; } +Variant SceneState::make_local_resource(Variant &p_value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const { + Ref<Resource> res = p_value; + if (res.is_null() || !res->is_local_to_scene()) { + return p_value; + } + + if (p_node_data.instance >= 0) { // For the root node of a sub-scene, treat it as part of the sub-scene. + return get_remap_resource(res, p_resources_local_to_sub_scene, p_node->get(p_sname), p_node); + } else { + HashMap<Ref<Resource>, Ref<Resource>>::Iterator E = p_resources_local_to_scene.find(res); + Node *base = p_i == 0 ? p_node : p_ret_nodes[0]; + if (E) { + return E->value; + } else if (p_edit_state == GEN_EDIT_STATE_MAIN) { // For the main scene, use the resource as is + res->configure_for_local_scene(base, p_resources_local_to_scene); + p_resources_local_to_scene[res] = res; + return res; + } else { // For instances, a copy must be made. + Ref<Resource> local_dupe = res->duplicate_for_local_scene(base, p_resources_local_to_scene); + p_resources_local_to_scene[res] = local_dupe; + return local_dupe; + } + } +} + +Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const { + for (int i = 0; i < p_array_to_scan.size(); i++) { + if (p_array_to_scan[i].get_type() == Variant::OBJECT) { + p_array_to_scan[i] = make_local_resource(p_array_to_scan[i], p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state); + } + } + return p_array_to_scan; +} + +bool SceneState::has_local_resource(const Array &p_array) const { + for (int i = 0; i < p_array.size(); i++) { + Ref<Resource> res = p_array[i]; + if (res.is_valid() && res->is_local_to_scene()) { + return true; + } + } + return false; +} + static int _nm_get_string(const String &p_string, HashMap<StringName, int> &name_map) { if (name_map.has(p_string)) { return name_map[p_string]; @@ -751,8 +797,18 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (!pinned_props.has(name)) { bool is_valid_default = false; Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &is_valid_default, &states_stack, true); + if (is_valid_default && !PropertyUtils::is_property_value_different(value, default_value)) { - continue; + if (value.get_type() == Variant::ARRAY && has_local_resource(value)) { + // Save anyway + } else if (value.get_type() == Variant::DICTIONARY) { + Dictionary dictionary = value; + if (!has_local_resource(dictionary.values()) && !has_local_resource(dictionary.keys())) { + continue; + } + } else { + continue; + } } } @@ -2045,6 +2101,11 @@ void PackedScene::set_path(const String &p_path, bool p_take_over) { Resource::set_path(p_path, p_take_over); } +void PackedScene::set_path_cache(const String &p_path) { + state->set_path(p_path); + Resource::set_path_cache(p_path); +} + void PackedScene::reset_state() { clear(); } diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index e6cbc3e16b..304e401602 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -157,6 +157,10 @@ public: bool can_instantiate() const; Node *instantiate(GenEditState p_edit_state) const; + Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const; + Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const; + bool has_local_resource(const Array &p_array) const; + Ref<SceneState> get_base_scene_state() const; void update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene); @@ -258,6 +262,8 @@ public: virtual void reload_from_file() override; virtual void set_path(const String &p_path, bool p_take_over = false) override; + virtual void set_path_cache(const String &p_path) override; + #ifdef TOOLS_ENABLED virtual void set_last_modified_time(uint64_t p_time) override { Resource::set_last_modified_time(p_time); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index d08919a8c6..6d0796f1b9 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -471,7 +471,7 @@ Error ResourceLoaderText::load() { ext_resources[id].path = path; ext_resources[id].type = type; - ext_resources[id].load_token = ResourceLoader::_load_start(path, type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + ext_resources[id].load_token = ResourceLoader::_load_start(path, type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, cache_mode_for_external); if (!ext_resources[id].load_token.is_valid()) { if (ResourceLoader::get_abort_on_missing_resources()) { error = ERR_FILE_CORRUPT; @@ -584,7 +584,7 @@ Error ResourceLoaderText::load() { if (do_assign) { if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { res->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); - } else if (!path.is_resource_file()) { + } else { res->set_path_cache(path); } res->set_scene_unique_id(id); @@ -721,6 +721,8 @@ Error ResourceLoaderText::load() { resource->set_path(res_path); } resource->set_as_translation_remapped(translation_remapped); + } else { + resource->set_path_cache(res_path); } } return error; @@ -805,8 +807,13 @@ Error ResourceLoaderText::load() { error = OK; //get it here resource = packed_scene; - if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && !ResourceCache::has(res_path)) { - packed_scene->set_path(res_path); + if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + if (!ResourceCache::has(res_path)) { + packed_scene->set_path(res_path); + } + } else { + packed_scene->get_state()->set_path(res_path); + packed_scene->set_path_cache(res_path); } resource_current++; @@ -1650,7 +1657,22 @@ Ref<Resource> ResourceFormatLoaderText::load(const String &p_path, const String ResourceLoaderText loader; String path = !p_original_path.is_empty() ? p_original_path : p_path; - loader.cache_mode = p_cache_mode; + switch (p_cache_mode) { + case CACHE_MODE_IGNORE: + case CACHE_MODE_REUSE: + case CACHE_MODE_REPLACE: + loader.cache_mode = p_cache_mode; + loader.cache_mode_for_external = CACHE_MODE_REUSE; + break; + case CACHE_MODE_IGNORE_DEEP: + loader.cache_mode = ResourceFormatLoader::CACHE_MODE_IGNORE; + loader.cache_mode_for_external = p_cache_mode; + break; + case CACHE_MODE_REPLACE_DEEP: + loader.cache_mode = ResourceFormatLoader::CACHE_MODE_REPLACE; + loader.cache_mode_for_external = p_cache_mode; + break; + } loader.use_sub_threads = p_use_sub_threads; loader.local_path = ProjectSettings::get_singleton()->localize_path(path); loader.progress = r_progress; diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 1734ccc98b..02898ce984 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -69,6 +69,7 @@ class ResourceLoaderText { VariantParser::Tag next_tag; ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; + ResourceFormatLoader::CacheMode cache_mode_for_external = ResourceFormatLoader::CACHE_MODE_REUSE; bool use_sub_threads = false; float *progress = nullptr; diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 634b32c9f6..e4c469b752 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -174,7 +174,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_focus_color", "Button", control_font_focus_color); theme->set_color("font_hover_pressed_color", "Button", control_font_pressed_color); theme->set_color("font_disabled_color", "Button", control_font_disabled_color); - theme->set_color("font_outline_color", "Button", Color(1, 1, 1)); + theme->set_color("font_outline_color", "Button", Color(0, 0, 0)); theme->set_color("icon_normal_color", "Button", Color(1, 1, 1, 1)); theme->set_color("icon_pressed_color", "Button", Color(1, 1, 1, 1)); @@ -202,7 +202,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_focus_color", "MenuBar", control_font_focus_color); theme->set_color("font_hover_pressed_color", "MenuBar", control_font_pressed_color); theme->set_color("font_disabled_color", "MenuBar", control_font_disabled_color); - theme->set_color("font_outline_color", "MenuBar", Color(1, 1, 1)); + theme->set_color("font_outline_color", "MenuBar", Color(0, 0, 0)); theme->set_constant("h_separation", "MenuBar", Math::round(4 * scale)); @@ -217,7 +217,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_pressed_color", "LinkButton", control_font_pressed_color); theme->set_color("font_hover_color", "LinkButton", control_font_hover_color); theme->set_color("font_focus_color", "LinkButton", control_font_focus_color); - theme->set_color("font_outline_color", "LinkButton", Color(1, 1, 1)); + theme->set_color("font_outline_color", "LinkButton", Color(0, 0, 0)); theme->set_constant("outline_size", "LinkButton", 0); theme->set_constant("underline_spacing", "LinkButton", Math::round(2 * scale)); @@ -256,7 +256,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hover_pressed_color", "OptionButton", control_font_pressed_color); theme->set_color("font_focus_color", "OptionButton", control_font_focus_color); theme->set_color("font_disabled_color", "OptionButton", control_font_disabled_color); - theme->set_color("font_outline_color", "OptionButton", Color(1, 1, 1)); + theme->set_color("font_outline_color", "OptionButton", Color(0, 0, 0)); theme->set_constant("h_separation", "OptionButton", Math::round(4 * scale)); theme->set_constant("arrow_margin", "OptionButton", Math::round(4 * scale)); @@ -279,7 +279,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hover_color", "MenuButton", control_font_hover_color); theme->set_color("font_focus_color", "MenuButton", control_font_focus_color); theme->set_color("font_disabled_color", "MenuButton", Color(1, 1, 1, 0.3)); - theme->set_color("font_outline_color", "MenuButton", Color(1, 1, 1)); + theme->set_color("font_outline_color", "MenuButton", Color(0, 0, 0)); theme->set_constant("h_separation", "MenuButton", Math::round(4 * scale)); theme->set_constant("outline_size", "MenuButton", 0); @@ -316,7 +316,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hover_pressed_color", "CheckBox", control_font_pressed_color); theme->set_color("font_focus_color", "CheckBox", control_font_focus_color); theme->set_color("font_disabled_color", "CheckBox", control_font_disabled_color); - theme->set_color("font_outline_color", "CheckBox", Color(1, 1, 1)); + theme->set_color("font_outline_color", "CheckBox", Color(0, 0, 0)); theme->set_constant("h_separation", "CheckBox", Math::round(4 * scale)); theme->set_constant("check_v_offset", "CheckBox", 0); @@ -353,7 +353,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hover_pressed_color", "CheckButton", control_font_pressed_color); theme->set_color("font_focus_color", "CheckButton", control_font_focus_color); theme->set_color("font_disabled_color", "CheckButton", control_font_disabled_color); - theme->set_color("font_outline_color", "CheckButton", Color(1, 1, 1)); + theme->set_color("font_outline_color", "CheckButton", Color(0, 0, 0)); theme->set_constant("h_separation", "CheckButton", Math::round(4 * scale)); theme->set_constant("check_v_offset", "CheckButton", 0); @@ -389,7 +389,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "Label", Color(1, 1, 1)); theme->set_color("font_shadow_color", "Label", Color(0, 0, 0, 0)); - theme->set_color("font_outline_color", "Label", Color(1, 1, 1)); + theme->set_color("font_outline_color", "Label", Color(0, 0, 0)); theme->set_constant("shadow_offset_x", "Label", Math::round(1 * scale)); theme->set_constant("shadow_offset_y", "Label", Math::round(1 * scale)); @@ -429,7 +429,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_selected_color", "LineEdit", control_font_pressed_color); theme->set_color("font_uneditable_color", "LineEdit", control_font_disabled_color); theme->set_color("font_placeholder_color", "LineEdit", control_font_placeholder_color); - theme->set_color("font_outline_color", "LineEdit", Color(1, 1, 1)); + theme->set_color("font_outline_color", "LineEdit", Color(0, 0, 0)); theme->set_color("caret_color", "LineEdit", control_font_hover_color); theme->set_color("selection_color", "LineEdit", control_selection_color); theme->set_color("clear_button_color", "LineEdit", control_font_color); @@ -450,7 +450,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("font_size", "ProgressBar", -1); theme->set_color("font_color", "ProgressBar", control_font_hover_color); - theme->set_color("font_outline_color", "ProgressBar", Color(1, 1, 1)); + theme->set_color("font_outline_color", "ProgressBar", Color(0, 0, 0)); theme->set_constant("outline_size", "ProgressBar", 0); @@ -471,7 +471,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_selected_color", "TextEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "TextEdit", control_font_disabled_color); theme->set_color("font_placeholder_color", "TextEdit", control_font_placeholder_color); - theme->set_color("font_outline_color", "TextEdit", Color(1, 1, 1)); + theme->set_color("font_outline_color", "TextEdit", Color(0, 0, 0)); theme->set_color("selection_color", "TextEdit", control_selection_color); theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8)); theme->set_color("caret_color", "TextEdit", control_font_color); @@ -515,7 +515,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "CodeEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_placeholder_color", "CodeEdit", control_font_placeholder_color); - theme->set_color("font_outline_color", "CodeEdit", Color(1, 1, 1)); + theme->set_color("font_outline_color", "CodeEdit", Color(0, 0, 0)); theme->set_color("selection_color", "CodeEdit", control_selection_color); theme->set_color("bookmark_color", "CodeEdit", Color(0.5, 0.64, 1, 0.8)); theme->set_color("breakpoint_color", "CodeEdit", Color(0.9, 0.29, 0.3)); @@ -626,7 +626,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font("title_font", "Window", Ref<Font>()); theme->set_font_size("title_font_size", "Window", -1); theme->set_color("title_color", "Window", control_font_color); - theme->set_color("title_outline_modulate", "Window", Color(1, 1, 1)); + theme->set_color("title_outline_modulate", "Window", Color(0, 0, 0)); theme->set_constant("title_outline_size", "Window", 0); theme->set_constant("title_height", "Window", 36 * scale); theme->set_constant("resize_margin", "Window", Math::round(4 * scale)); @@ -706,8 +706,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "PopupMenu", Color(0.4, 0.4, 0.4, 0.8)); theme->set_color("font_hover_color", "PopupMenu", control_font_color); theme->set_color("font_separator_color", "PopupMenu", control_font_color); - theme->set_color("font_outline_color", "PopupMenu", Color(1, 1, 1)); - theme->set_color("font_separator_outline_color", "PopupMenu", Color(1, 1, 1)); + theme->set_color("font_outline_color", "PopupMenu", Color(0, 0, 0)); + theme->set_color("font_separator_outline_color", "PopupMenu", Color(0, 0, 0)); theme->set_constant("indent", "PopupMenu", Math::round(10 * scale)); theme->set_constant("h_separation", "PopupMenu", Math::round(4 * scale)); @@ -750,7 +750,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("font_size", "GraphNodeTitleLabel", -1); theme->set_color("font_color", "GraphNodeTitleLabel", control_font_color); theme->set_color("font_shadow_color", "GraphNodeTitleLabel", Color(0, 0, 0, 0)); - theme->set_color("font_outline_color", "GraphNodeTitleLabel", control_font_color); + theme->set_color("font_outline_color", "GraphNodeTitleLabel", Color(0, 0, 0)); theme->set_constant("shadow_offset_x", "GraphNodeTitleLabel", Math::round(1 * scale)); theme->set_constant("shadow_offset_y", "GraphNodeTitleLabel", Math::round(1 * scale)); theme->set_constant("outline_size", "GraphNodeTitleLabel", 0); @@ -794,7 +794,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "Tree", control_font_low_color); theme->set_color("font_selected_color", "Tree", control_font_pressed_color); theme->set_color("font_disabled_color", "Tree", control_font_disabled_color); - theme->set_color("font_outline_color", "Tree", Color(1, 1, 1)); + theme->set_color("font_outline_color", "Tree", Color(0, 0, 0)); theme->set_color("guide_color", "Tree", Color(0.7, 0.7, 0.7, 0.25)); theme->set_color("drop_position_color", "Tree", Color(1, 1, 1)); theme->set_color("relationship_line_color", "Tree", Color(0.27, 0.27, 0.27)); @@ -832,7 +832,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("panel", "ItemList", make_flat_stylebox(style_normal_color)); theme->set_stylebox("focus", "ItemList", focus); theme->set_constant("h_separation", "ItemList", Math::round(4 * scale)); - theme->set_constant("v_separation", "ItemList", Math::round(2 * scale)); + theme->set_constant("v_separation", "ItemList", Math::round(4 * scale)); theme->set_constant("icon_margin", "ItemList", Math::round(4 * scale)); theme->set_constant("line_separation", "ItemList", Math::round(2 * scale)); @@ -842,7 +842,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "ItemList", control_font_lower_color); theme->set_color("font_hovered_color", "ItemList", control_font_hover_color); theme->set_color("font_selected_color", "ItemList", control_font_pressed_color); - theme->set_color("font_outline_color", "ItemList", Color(1, 1, 1)); + theme->set_color("font_outline_color", "ItemList", Color(0, 0, 0)); theme->set_color("guide_color", "ItemList", Color(0.7, 0.7, 0.7, 0.25)); theme->set_stylebox("hovered", "ItemList", make_flat_stylebox(Color(1, 1, 1, 0.07))); theme->set_stylebox("selected", "ItemList", make_flat_stylebox(style_selected_color)); @@ -891,7 +891,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hovered_color", "TabContainer", control_font_hover_color); theme->set_color("font_unselected_color", "TabContainer", control_font_low_color); theme->set_color("font_disabled_color", "TabContainer", control_font_disabled_color); - theme->set_color("font_outline_color", "TabContainer", Color(1, 1, 1)); + theme->set_color("font_outline_color", "TabContainer", Color(0, 0, 0)); theme->set_color("drop_mark_color", "TabContainer", Color(1, 1, 1)); theme->set_constant("side_margin", "TabContainer", Math::round(8 * scale)); @@ -923,7 +923,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hovered_color", "TabBar", control_font_hover_color); theme->set_color("font_unselected_color", "TabBar", control_font_low_color); theme->set_color("font_disabled_color", "TabBar", control_font_disabled_color); - theme->set_color("font_outline_color", "TabBar", Color(1, 1, 1)); + theme->set_color("font_outline_color", "TabBar", Color(0, 0, 0)); theme->set_color("drop_mark_color", "TabBar", Color(1, 1, 1)); theme->set_constant("h_separation", "TabBar", Math::round(4 * scale)); @@ -1035,7 +1035,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_hover_color", "ColorPickerButton", Color(1, 1, 1, 1)); theme->set_color("font_focus_color", "ColorPickerButton", Color(1, 1, 1, 1)); theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); - theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); + theme->set_color("font_outline_color", "ColorPickerButton", Color(0, 0, 0)); theme->set_constant("h_separation", "ColorPickerButton", Math::round(4 * scale)); theme->set_constant("outline_size", "ColorPickerButton", 0); @@ -1063,7 +1063,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_color", "TooltipLabel", control_font_color); theme->set_color("font_shadow_color", "TooltipLabel", Color(0, 0, 0, 0)); - theme->set_color("font_outline_color", "TooltipLabel", Color(0, 0, 0, 0)); + theme->set_color("font_outline_color", "TooltipLabel", Color(0, 0, 0)); theme->set_constant("shadow_offset_x", "TooltipLabel", 1); theme->set_constant("shadow_offset_y", "TooltipLabel", 1); @@ -1091,7 +1091,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_shadow_color", "RichTextLabel", Color(0, 0, 0, 0)); - theme->set_color("font_outline_color", "RichTextLabel", Color(1, 1, 1)); + theme->set_color("font_outline_color", "RichTextLabel", Color(0, 0, 0)); theme->set_constant("shadow_offset_x", "RichTextLabel", Math::round(1 * scale)); theme->set_constant("shadow_offset_y", "RichTextLabel", Math::round(1 * scale)); diff --git a/scu_builders.py b/scu_builders.py index 2579398ad6..8cd87a13d5 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -1,5 +1,6 @@ """Functions used to generate scu build source files during build time """ + import glob, os import math from pathlib import Path diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 6631d44f63..199152b5e8 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -31,6 +31,7 @@ #include "display_server.h" #include "core/input/input.h" +#include "scene/resources/atlas_texture.h" #include "scene/resources/texture.h" #include "servers/display_server_headless.h" @@ -987,6 +988,43 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(TTS_UTTERANCE_BOUNDARY); } +Ref<Image> DisplayServer::_get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect) { + Ref<Image> image; + ERR_FAIL_COND_V_MSG(p_hotspot.x < 0 || p_hotspot.y < 0, image, "Hotspot outside cursor image."); + + Size2 texture_size; + + Ref<Texture2D> texture = p_cursor; + if (texture.is_valid()) { + Ref<AtlasTexture> atlas_texture = p_cursor; + + if (atlas_texture.is_valid()) { + texture = atlas_texture->get_atlas(); + r_atlas_rect.size = texture->get_size(); + r_atlas_rect.position = atlas_texture->get_region().position; + texture_size = atlas_texture->get_region().size; + } else { + texture_size = texture->get_size(); + } + image = texture->get_image(); + } else { + image = p_cursor; + ERR_FAIL_COND_V(image.is_null(), image); + texture_size = image->get_size(); + } + + ERR_FAIL_COND_V_MSG(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height, image, "Hotspot outside cursor image."); + ERR_FAIL_COND_V_MSG(texture_size.width > 256 || texture_size.height > 256, image, "Cursor image too big. Max supported size is 256x256."); + + ERR_FAIL_COND_V(image.is_null(), image); + if (image->is_compressed()) { + image = image->duplicate(true); + Error err = image->decompress(); + ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock."); + } + return image; +} + void DisplayServer::register_create_function(const char *p_name, CreateFunction p_function, GetRenderingDriversFunction p_get_drivers) { ERR_FAIL_COND(server_create_count == MAX_SERVERS); // Headless display server is always last diff --git a/servers/display_server.h b/servers/display_server.h index 0f6c43ebe0..7608d76b30 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -37,6 +37,7 @@ #include "core/variant/callable.h" class Texture2D; +class Image; class DisplayServer : public Object { GDCLASS(DisplayServer, Object) @@ -86,6 +87,8 @@ private: protected: static void _bind_methods(); + static Ref<Image> _get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect); + enum { MAX_SERVERS = 64 }; diff --git a/servers/physics_3d/godot_soft_body_3d.cpp b/servers/physics_3d/godot_soft_body_3d.cpp index 0c2b935554..e621977326 100644 --- a/servers/physics_3d/godot_soft_body_3d.cpp +++ b/servers/physics_3d/godot_soft_body_3d.cpp @@ -595,7 +595,7 @@ void GodotSoftBody3D::generate_bending_constraints(int p_distance) { const uint32_t adj_size = n * n; unsigned *adj = memnew_arr(unsigned, adj_size); -#define IDX(_x_, _y_) ((_y_)*n + (_x_)) +#define IDX(_x_, _y_) ((_y_) * n + (_x_)) for (j = 0; j < n; ++j) { for (i = 0; i < n; ++i) { int idx_ij = j * n + i; diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h index e0ec42a4d1..66106d7eb7 100644 --- a/servers/rendering/shader_compiler.h +++ b/servers/rendering/shader_compiler.h @@ -91,8 +91,8 @@ public: HashMap<StringName, String> render_mode_defines; HashMap<StringName, String> usage_defines; HashMap<StringName, String> custom_samplers; - ShaderLanguage::TextureFilter default_filter = ShaderLanguage::TextureFilter::FILTER_DEFAULT; - ShaderLanguage::TextureRepeat default_repeat = ShaderLanguage::TextureRepeat::REPEAT_DEFAULT; + ShaderLanguage::TextureFilter default_filter = ShaderLanguage::TextureFilter::FILTER_NEAREST; + ShaderLanguage::TextureRepeat default_repeat = ShaderLanguage::TextureRepeat::REPEAT_DISABLE; int base_texture_binding_index = 0; int texture_layout_set = 0; String base_uniform_string; diff --git a/tests/core/string/test_node_path.h b/tests/core/string/test_node_path.h index 031a33c570..bdbc578e85 100644 --- a/tests/core/string/test_node_path.h +++ b/tests/core/string/test_node_path.h @@ -167,6 +167,59 @@ TEST_CASE("[NodePath] Empty path") { node_path_empty.is_empty(), "The node path should be considered empty."); } + +TEST_CASE("[NodePath] Slice") { + const NodePath node_path_relative = NodePath("Parent/Child:prop"); + const NodePath node_path_absolute = NodePath("/root/Parent/Child:prop"); + CHECK_MESSAGE( + node_path_relative.slice(0, 2) == NodePath("Parent/Child"), + "The slice lower bound should be inclusive and the slice upper bound should be exclusive."); + CHECK_MESSAGE( + node_path_relative.slice(3) == NodePath(":prop"), + "Slicing on the length of the path should return the last entry."); + CHECK_MESSAGE( + node_path_relative.slice(1, 3) == NodePath("Child:prop"), + "Slicing should include names and subnames."); + CHECK_MESSAGE( + node_path_relative.slice(-1) == NodePath(":prop"), + "Slicing on -1 should return the last entry."); + CHECK_MESSAGE( + node_path_relative.slice(0, -1) == NodePath("Parent/Child"), + "Slicing up to -1 should include the second-to-last entry."); + CHECK_MESSAGE( + node_path_relative.slice(-2, -1) == NodePath("Child"), + "Slicing from negative to negative should treat lower bound as inclusive and upper bound as exclusive."); + CHECK_MESSAGE( + node_path_relative.slice(0, 10) == NodePath("Parent/Child:prop"), + "Slicing past the length of the path should work like slicing up to the last entry."); + CHECK_MESSAGE( + node_path_relative.slice(-10, 2) == NodePath("Parent/Child"), + "Slicing negatively past the length of the path should work like slicing from the first entry."); + CHECK_MESSAGE( + node_path_relative.slice(1, 1) == NodePath(""), + "Slicing with a lower bound equal to upper bound should return empty path."); + + CHECK_MESSAGE( + node_path_absolute.slice(0, 2) == NodePath("/root/Parent"), + "Slice from beginning of an absolute path should be an absolute path."); + CHECK_MESSAGE( + node_path_absolute.slice(1, 4) == NodePath("Parent/Child:prop"), + "Slice of an absolute path that does not start at the beginning should be a relative path."); + CHECK_MESSAGE( + node_path_absolute.slice(3, 4) == NodePath(":prop"), + "Slice of an absolute path that does not start at the beginning should be a relative path."); + + CHECK_MESSAGE( + NodePath("").slice(0, 1) == NodePath(""), + "Slice of an empty path should be an empty path."); + CHECK_MESSAGE( + NodePath("").slice(-1, 2) == NodePath(""), + "Slice of an empty path should be an empty path."); + CHECK_MESSAGE( + NodePath("/").slice(-1, 2) == NodePath("/"), + "Slice of an empty absolute path should be an empty absolute path."); +} + } // namespace TestNodePath #endif // TEST_NODE_PATH_H diff --git a/tests/test_macros.h b/tests/test_macros.h index bc85ec6ddc..a173b37a2d 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -161,11 +161,11 @@ int register_test_command(String p_command, TestFunc p_function); MessageQueue::get_singleton()->flush(); \ } -#define _UPDATE_EVENT_MODIFERS(m_event, m_modifers) \ - m_event->set_shift_pressed(((m_modifers)&KeyModifierMask::SHIFT) != Key::NONE); \ - m_event->set_alt_pressed(((m_modifers)&KeyModifierMask::ALT) != Key::NONE); \ - m_event->set_ctrl_pressed(((m_modifers)&KeyModifierMask::CTRL) != Key::NONE); \ - m_event->set_meta_pressed(((m_modifers)&KeyModifierMask::META) != Key::NONE); +#define _UPDATE_EVENT_MODIFERS(m_event, m_modifers) \ + m_event->set_shift_pressed(((m_modifers) & KeyModifierMask::SHIFT) != Key::NONE); \ + m_event->set_alt_pressed(((m_modifers) & KeyModifierMask::ALT) != Key::NONE); \ + m_event->set_ctrl_pressed(((m_modifers) & KeyModifierMask::CTRL) != Key::NONE); \ + m_event->set_meta_pressed(((m_modifers) & KeyModifierMask::META) != Key::NONE); #define _CREATE_GUI_MOUSE_EVENT(m_screen_pos, m_input, m_mask, m_modifers) \ Ref<InputEventMouseButton> event; \ |