diff options
95 files changed, 1196 insertions, 488 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index d714ec42c2..9f4bff3779 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -266,6 +266,12 @@ void Engine::print_header(const String &p_string) const { } } +void Engine::print_header_rich(const String &p_string) const { + if (_print_header) { + print_line_rich(p_string); + } +} + void Engine::add_singleton(const Singleton &p_singleton) { ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), vformat("Can't register singleton '%s' because it already exists.", p_singleton.name)); singletons.push_back(p_singleton); diff --git a/core/config/engine.h b/core/config/engine.h index be7cd62f66..d1495b36c2 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -126,6 +126,7 @@ public: void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; void print_header(const String &p_string) const; + void print_header_rich(const String &p_string) const; void set_frame_delay(uint32_t p_msec); uint32_t get_frame_delay() const; diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 0248b04034..fbd01be86d 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -153,17 +153,17 @@ public: class ResourceFormatLoaderCrypto : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverCrypto : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // CRYPTO_H diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 5d43dceece..0ed4c3380c 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -735,7 +735,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb bool library_copied = false; if (Engine::get_singleton()->is_editor_hint()) { if (!FileAccess::exists(abs_path)) { - ERR_PRINT("GDExtension library not found: " + library_path); + ERR_PRINT("GDExtension library not found: " + abs_path); return ERR_FILE_NOT_FOUND; } @@ -750,7 +750,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb Error copy_err = DirAccess::copy_absolute(abs_path, copy_path); if (copy_err) { - ERR_PRINT("Error copying GDExtension library: " + library_path); + ERR_PRINT("Error copying GDExtension library: " + abs_path); return ERR_CANT_CREATE; } FileAccess::set_hidden_attribute(copy_path, true); diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index a2b948a38a..2d0cb6a5ba 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -176,10 +176,10 @@ class GDExtensionResourceLoader : public ResourceFormatLoader { public: static Error load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension); - virtual Ref<Resource> load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; #ifdef TOOLS_ENABLED diff --git a/core/input/input_builders.py b/core/input/input_builders.py index eabdefe543..ae848f4e7c 100644 --- a/core/input/input_builders.py +++ b/core/input/input_builders.py @@ -13,7 +13,7 @@ def make_default_controller_mappings(target, source, env): # ensure mappings have a consistent order platform_mappings: dict = OrderedDict() for src_path in source: - with open(str(src_path), "r") as f: + with open(str(src_path), "r", encoding="utf-8") as f: # read mapping file and skip header mapping_file_lines = f.readlines()[2:] diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 5d6de1ad9a..7fd1806b31 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -383,6 +383,7 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = { { "ui_text_select_all", TTRC("Select All") }, { "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") }, { "ui_text_add_selection_for_next_occurrence", TTRC("Add Selection for Next Occurrence") }, + { "ui_text_skip_selection_for_next_occurrence", TTRC("Skip Selection for Next Occurrence") }, { "ui_text_clear_carets_and_selection", TTRC("Clear Carets and Selection") }, { "ui_text_toggle_insert_mode", TTRC("Toggle Insert Mode") }, { "ui_text_submit", TTRC("Submit Text") }, @@ -722,6 +723,10 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { default_builtin_cache.insert("ui_text_add_selection_for_next_occurrence", inputs); inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::D | KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT)); + default_builtin_cache.insert("ui_text_skip_selection_for_next_occurrence", inputs); + + inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::ESCAPE)); default_builtin_cache.insert("ui_text_clear_carets_and_selection", inputs); diff --git a/core/io/image_loader.h b/core/io/image_loader.h index e9b434522d..26af650344 100644 --- a/core/io/image_loader.h +++ b/core/io/image_loader.h @@ -103,10 +103,10 @@ public: class ResourceFormatLoaderImage : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; #endif // IMAGE_LOADER_H diff --git a/core/io/json.h b/core/io/json.h index a21cc542fd..801fa29b4b 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -98,17 +98,17 @@ public: class ResourceFormatLoaderJSON : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverJSON : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // JSON_H diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index e01c5fa467..222e633e58 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -110,16 +110,16 @@ public: class ResourceFormatLoaderBinary : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; - virtual String get_resource_script_class(const String &p_path) const; - virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes); - virtual ResourceUID::ID get_resource_uid(const String &p_path) const; - virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); - virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; + virtual String get_resource_script_class(const String &p_path) const override; + virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; + virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override; }; class ResourceFormatSaverBinaryInstance { @@ -181,10 +181,10 @@ public: class ResourceFormatSaverBinary : public ResourceFormatSaver { public: static ResourceFormatSaverBinary *singleton; - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid); - virtual bool recognize(const Ref<Resource> &p_resource) const; - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid) override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; ResourceFormatSaverBinary(); }; diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index e17644058a..dbd9e70d16 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -59,22 +59,22 @@ class ResourceFormatImporter : public ResourceFormatLoader { public: static ResourceFormatImporter *get_singleton() { return singleton; } - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; - virtual ResourceUID::ID get_resource_uid(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; + virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const override; virtual Variant get_resource_metadata(const String &p_path) const; - virtual bool is_import_valid(const String &p_path) const; - virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); - virtual bool is_imported(const String &p_path) const { return recognize_path(p_path); } - virtual String get_import_group_file(const String &p_path) const; - virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes); - virtual bool exists(const String &p_path) const; - - virtual int get_import_order(const String &p_path) const; + virtual bool is_import_valid(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; + virtual bool is_imported(const String &p_path) const override { return recognize_path(p_path); } + virtual String get_import_group_file(const String &p_path) const override; + virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override; + virtual bool exists(const String &p_path) const override; + + virtual int get_import_order(const String &p_path) const override; Error get_import_order_threads_and_importer(const String &p_path, int &r_order, bool &r_can_threads, String &r_importer) const; diff --git a/core/io/translation_loader_po.h b/core/io/translation_loader_po.h index 16e7c28cbe..a695826e59 100644 --- a/core/io/translation_loader_po.h +++ b/core/io/translation_loader_po.h @@ -38,10 +38,10 @@ class TranslationLoaderPO : public ResourceFormatLoader { public: static Ref<Resource> load_translation(Ref<FileAccess> f, Error *r_error = nullptr); - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; TranslationLoaderPO() {} }; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 93a08fd8b5..bdef1b9bbe 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1043,12 +1043,11 @@ signed char String::naturalnocasecmp_to(const String &p_str) const { static _FORCE_INLINE_ signed char file_cmp_common(const char32_t *&r_this_str, const char32_t *&r_that_str) { // Compare leading `_` sequences. - while (*r_this_str && *r_that_str) { + while ((*r_this_str == '_' && *r_that_str) || (*r_this_str && *r_that_str == '_')) { // Sort `_` lower than everything except `.` - if (*r_this_str != '_' && *r_that_str == '_') { + if (*r_this_str != '_') { return *r_this_str == '.' ? -1 : 1; - } - if (*r_this_str == '_' && *r_that_str != '_') { + } else if (*r_that_str != '_') { return *r_that_str == '.' ? 1 : -1; } r_this_str++; diff --git a/core/variant/type_info.h b/core/variant/type_info.h index 32c410463b..9c52db3345 100644 --- a/core/variant/type_info.h +++ b/core/variant/type_info.h @@ -296,6 +296,7 @@ public: _FORCE_INLINE_ constexpr BitField(T p_value) { value = (int64_t)p_value; } _FORCE_INLINE_ operator int64_t() const { return value; } _FORCE_INLINE_ operator Variant() const { return value; } + _FORCE_INLINE_ BitField<T> operator^(const BitField<T> &p_b) const { return BitField<T>(value ^ p_b.value); } }; #define TEMPL_MAKE_BITFIELD_TYPE_INFO(m_enum, m_impl) \ diff --git a/doc/classes/AudioEffectHardLimiter.xml b/doc/classes/AudioEffectHardLimiter.xml new file mode 100644 index 0000000000..7616b91f97 --- /dev/null +++ b/doc/classes/AudioEffectHardLimiter.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="AudioEffectHardLimiter" inherits="AudioEffect" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Adds a hard limiter audio effect to an Audio bus. + </brief_description> + <description> + A limiter is an effect designed to disallow sound from going over a given dB threshold. Hard limiters predict volume peaks, and will smoothly apply gain reduction when a peak crosses the ceiling threshold to prevent clipping and distortion. It preserves the waveform and prevents it from crossing the ceiling threshold. Adding one in the Master bus is recommended as a safety measure to prevent sudden volume peaks from occurring, and to prevent distortion caused by clipping. + </description> + <tutorials> + <link title="Audio buses">$DOCS_URL/tutorials/audio/audio_buses.html</link> + </tutorials> + <members> + <member name="ceiling_db" type="float" setter="set_ceiling_db" getter="get_ceiling_db" default="-0.3"> + The waveform's maximum allowed value, in decibels. This value can range from [code]-24.0[/code] to [code]0.0[/code]. + The default value of [code]-0.3[/code] prevents potential inter-sample peaks (ISP) from crossing over 0 dB, which can cause slight distortion on some older hardware. + </member> + <member name="pre_gain_db" type="float" setter="set_pre_gain_db" getter="get_pre_gain_db" default="0.0"> + Gain to apply before limiting, in decibels. + </member> + <member name="release" type="float" setter="set_release" getter="get_release" default="0.1"> + Time it takes in seconds for the gain reduction to fully release. + </member> + </members> +</class> diff --git a/doc/classes/AudioEffectLimiter.xml b/doc/classes/AudioEffectLimiter.xml index 57861d0485..b1a80cd9ef 100644 --- a/doc/classes/AudioEffectLimiter.xml +++ b/doc/classes/AudioEffectLimiter.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="AudioEffectLimiter" inherits="AudioEffect" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="AudioEffectLimiter" inherits="AudioEffect" deprecated="Use [AudioEffectHardLimiter] instead." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> Adds a soft-clip limiter audio effect to an Audio bus. </brief_description> diff --git a/doc/classes/CollisionPolygon2D.xml b/doc/classes/CollisionPolygon2D.xml index 12f7024518..29535ffd64 100644 --- a/doc/classes/CollisionPolygon2D.xml +++ b/doc/classes/CollisionPolygon2D.xml @@ -4,7 +4,7 @@ A node that provides a polygon shape to a [CollisionObject2D] parent. </brief_description> <description> - A node that provides a thickened polygon shape (a prism) to a [CollisionObject2D] parent and allows to edit it. The polygon can be concave or convex. This can give a detection shape to an [Area2D] or turn [PhysicsBody2D] into a solid object. + A node that provides a polygon shape to a [CollisionObject2D] parent and allows to edit it. The polygon can be concave or convex. This can give a detection shape to an [Area2D], turn [PhysicsBody2D] into a solid object, or give a hollow shape to a [StaticBody2D]. [b]Warning:[/b] A non-uniformly scaled [CollisionShape2D] will likely not behave as expected. Make sure to keep its scale the same on all axes and adjust its shape resource instead. </description> <tutorials> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 697afed636..cc32964e87 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1182,6 +1182,14 @@ - One of the node's theme property overrides is changed. - The node enters the scene tree. [b]Note:[/b] As an optimization, this notification won't be sent from changes that occur while this node is outside of the scene tree. Instead, all of the theme item updates can be applied at once when the node enters the scene tree. + [b]Note:[/b] This notification is received alongside [constant Node.NOTIFICATION_ENTER_TREE], so if you are instantiating a scene, the child nodes will not be initialized yet. You can use it to setup theming for this node, child nodes created from script, or if you want to access child nodes added in the editor, make sure the node is ready using [method Node.is_node_ready]. + [codeblock] + func _notification(what): + if what == NOTIFICATION_THEME_CHANGED: + if not is_node_ready(): + await ready # Wait until ready signal. + $Label.add_theme_color_override("font_color", Color.YELLOW) + [/codeblock] </constant> <constant name="NOTIFICATION_SCROLL_BEGIN" value="47"> Sent when this node is inside a [ScrollContainer] which has begun being scrolled when dragging the scrollable area [i]with a touch event[/i]. This notification is [i]not[/i] sent when scrolling by dragging the scrollbar, scrolling with the mouse wheel or scrolling with keyboard/gamepad events. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index ecb8438edb..ae0dc53f5d 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -553,7 +553,7 @@ <param index="0" name="menu_root" type="String" /> <param index="1" name="tag" type="Variant" /> <description> - Returns the index of the item with the specified [param tag]. Index is automatically assigned to each item by the engine. Index can not be set manually. + Returns the index of the item with the specified [param tag]. Indices are automatically assigned to each item by the engine, and cannot be set manually. [b]Note:[/b] This method is implemented only on macOS. </description> </method> @@ -562,7 +562,7 @@ <param index="0" name="menu_root" type="String" /> <param index="1" name="text" type="String" /> <description> - Returns the index of the item with the specified [param text]. Index is automatically assigned to each item by the engine. Index can not be set manually. + Returns the index of the item with the specified [param text]. Indices are automatically assigned to each item by the engine, and cannot be set manually. [b]Note:[/b] This method is implemented only on macOS. </description> </method> diff --git a/doc/classes/NativeMenu.xml b/doc/classes/NativeMenu.xml index cfe3f3f5c5..475874dee7 100644 --- a/doc/classes/NativeMenu.xml +++ b/doc/classes/NativeMenu.xml @@ -211,12 +211,21 @@ [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> + <method name="find_item_index_with_submenu" qualifiers="const"> + <return type="int" /> + <param index="0" name="rid" type="RID" /> + <param index="1" name="submenu_rid" type="RID" /> + <description> + Returns the index of the item with the submenu specified by [param submenu_rid]. Indices are automatically assigned to each item by the engine, and cannot be set manually. + [b]Note:[/b] This method is implemented on macOS and Windows. + </description> + </method> <method name="find_item_index_with_tag" qualifiers="const"> <return type="int" /> <param index="0" name="rid" type="RID" /> <param index="1" name="tag" type="Variant" /> <description> - Returns the index of the item with the specified [param tag]. Index is automatically assigned to each item by the engine. Index can not be set manually. + Returns the index of the item with the specified [param tag]. Indices are automatically assigned to each item by the engine, and cannot be set manually. [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> @@ -225,7 +234,7 @@ <param index="0" name="rid" type="RID" /> <param index="1" name="text" type="String" /> <description> - Returns the index of the item with the specified [param text]. Index is automatically assigned to each item by the engine. Index can not be set manually. + Returns the index of the item with the specified [param text]. Indices are automatically assigned to each item by the engine, and cannot be set manually. [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index c69e5edf0c..e6fdd229bf 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -100,7 +100,7 @@ <return type="void" /> <param index="0" name="event" type="InputEvent" /> <description> - Called when an [InputEventKey] or [InputEventShortcut] hasn't been consumed by [method _input] or any GUI [Control] item. It is called before [method _unhandled_key_input] and [method _unhandled_input]. The input event propagates up through the node tree until a node consumes it. + Called when an [InputEventKey], [InputEventShortcut], or [InputEventJoypadButton] hasn't been consumed by [method _input] or any GUI [Control] item. It is called before [method _unhandled_key_input] and [method _unhandled_input]. The input event propagates up through the node tree until a node consumes it. It is only called if shortcut processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_shortcut_input]. To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called. This method can be used to handle shortcuts. For generic GUI events, use [method _input] instead. Gameplay events should usually be handled with either [method _unhandled_input] or [method _unhandled_key_input]. @@ -187,7 +187,7 @@ <param index="0" name="message" type="String" /> <param index="1" name="context" type="StringName" default="""" /> <description> - Translates a [param message], using the translation catalogs configured in the Project Settings. Further [param context] can be specified to help with the translation. + Translates a [param message], using the translation catalogs configured in the Project Settings. Further [param context] can be specified to help with the translation. Note that most [Control] nodes automatically translate their strings, so this method is mostly useful for formatted strings or custom drawn text. This method works the same as [method Object.tr], with the addition of respecting the [member auto_translate_mode] state. If [method Object.can_translate_messages] is [code]false[/code], or no translation is available, this method returns the [param message] without changes. See [method Object.set_message_translation]. For detailed examples, see [url=$DOCS_URL/tutorials/i18n/internationalizing_games.html]Internationalizing games[/url]. @@ -1201,7 +1201,15 @@ Implemented only on iOS. </constant> <constant name="NOTIFICATION_TRANSLATION_CHANGED" value="2010"> - Notification received when translations may have changed. Can be triggered by the user changing the locale. Can be used to respond to language changes, for example to change the UI strings on the fly. Useful when working with the built-in translation support, like [method Object.tr]. + Notification received when translations may have changed. Can be triggered by the user changing the locale, changing [member auto_translate_mode] or when the node enters the scene tree. Can be used to respond to language changes, for example to change the UI strings on the fly. Useful when working with the built-in translation support, like [method Object.tr]. + [b]Note:[/b] This notification is received alongside [constant NOTIFICATION_ENTER_TREE], so if you are instantiating a scene, the child nodes will not be initialized yet. You can use it to setup translations for this node, child nodes created from script, or if you want to access child nodes added in the editor, make sure the node is ready using [method is_node_ready]. + [codeblock] + func _notification(what): + if what == NOTIFICATION_TRANSLATION_CHANGED: + if not is_node_ready(): + await ready # Wait until ready signal. + $Label.text = atr("%d Bananas") % banana_counter + [/codeblock] </constant> <constant name="NOTIFICATION_WM_ABOUT" value="2011"> Notification received from the OS when a request for "About" information is sent. diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 85b9cf16f2..b69326b6e0 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -1024,7 +1024,7 @@ <param index="0" name="message" type="StringName" /> <param index="1" name="context" type="StringName" default="&""" /> <description> - Translates a [param message], using the translation catalogs configured in the Project Settings. Further [param context] can be specified to help with the translation. + Translates a [param message], using the translation catalogs configured in the Project Settings. Further [param context] can be specified to help with the translation. Note that most [Control] nodes automatically translate their strings, so this method is mostly useful for formatted strings or custom drawn text. If [method can_translate_messages] is [code]false[/code], or no translation is available, this method returns the [param message] without changes. See [method set_message_translation]. For detailed examples, see [url=$DOCS_URL/tutorials/i18n/internationalizing_games.html]Internationalizing games[/url]. </description> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index d39722cdd0..48ac96a25f 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1334,6 +1334,12 @@ <member name="input/ui_text_select_word_under_caret.macos" type="Dictionary" setter="" getter=""> macOS specific override for the shortcut to select the word currently under the caret. </member> + <member name="input/ui_text_skip_selection_for_next_occurrence" type="Dictionary" setter="" getter=""> + If no selection is currently active with the last caret in text fields, searches for the next occurrence of the the word currently under the caret and moves the caret to the next occurrence. The action can be performed sequentially for other occurrences of the word under the last caret. + If a selection is currently active with the last caret in text fields, searches for the next occurrence of the selection, adds a caret, selects the next occurrence then deselects the previous selection and its associated caret. The action can be performed sequentially for other occurrences of the selection of the last caret. + The viewport is adjusted to the latest newly added caret. + [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. + </member> <member name="input/ui_text_submit" type="Dictionary" setter="" getter=""> Default [InputEventAction] to submit a text field. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 04d05e7860..92b54eef21 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1070,6 +1070,12 @@ Provide custom tooltip text. The callback method must take the following args: [code]hovered_word: String[/code]. </description> </method> + <method name="skip_selection_for_next_occurrence"> + <return type="void" /> + <description> + Moves a selection and a caret for the next occurrence of the current selection. If there is no active selection, moves to the next occurrence of the word under caret. + </description> + </method> <method name="start_action"> <return type="void" /> <param index="0" name="action" type="int" enum="TextEdit.EditAction" /> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 13d84d96d6..dcc817427b 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -497,10 +497,16 @@ Represents the size of the [enum RenderInfo] enum. </constant> <constant name="RENDER_INFO_TYPE_VISIBLE" value="0" enum="RenderInfoType"> + Visible render pass (excluding shadows). </constant> <constant name="RENDER_INFO_TYPE_SHADOW" value="1" enum="RenderInfoType"> + Shadow render pass. Objects will be rendered several times depending on the number of amounts of lights with shadows and the number of directional shadow splits. </constant> - <constant name="RENDER_INFO_TYPE_MAX" value="2" enum="RenderInfoType"> + <constant name="RENDER_INFO_TYPE_CANVAS" value="2" enum="RenderInfoType"> + Canvas item rendering. This includes all 2D rendering. + </constant> + <constant name="RENDER_INFO_TYPE_MAX" value="3" enum="RenderInfoType"> + Represents the size of the [enum RenderInfoType] enum. </constant> <constant name="DEBUG_DRAW_DISABLED" value="0" enum="DebugDraw"> Objects are displayed normally. diff --git a/drivers/png/resource_saver_png.h b/drivers/png/resource_saver_png.h index 2193b4a39b..2c24008074 100644 --- a/drivers/png/resource_saver_png.h +++ b/drivers/png/resource_saver_png.h @@ -39,9 +39,9 @@ public: static Error save_image(const String &p_path, const Ref<Image> &p_img); static Vector<uint8_t> save_image_to_buffer(const Ref<Image> &p_img); - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual bool recognize(const Ref<Resource> &p_resource) const; - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; ResourceSaverPNG(); }; diff --git a/editor/SCsub b/editor/SCsub index 442d0a3b75..f4d30b68b1 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -104,6 +104,7 @@ if env.editor_build: # Extractable translations tlist = glob.glob(env.Dir("#editor/translations/extractable").abspath + "/*.po") + tlist.extend(glob.glob(env.Dir("#editor/translations/extractable").abspath + "/extractable.pot")) env.Depends("#editor/extractable_translations.gen.h", tlist) env.CommandNoCache( "#editor/extractable_translations.gen.h", diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index ffff362a94..da75715b6d 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -84,7 +84,7 @@ String EditorPerformanceProfiler::_create_label(float p_value, Performance::Moni return String::humanize_size(p_value); } case Performance::MONITOR_TYPE_TIME: { - return TS->format_number(rtos(p_value * 1000).pad_decimals(2)) + " " + RTR("ms"); + return TS->format_number(rtos(p_value * 1000).pad_decimals(2)) + " " + TTR("ms"); } default: { return TS->format_number(rtos(p_value)); diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index c1500631fe..ce08d40634 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -125,12 +125,12 @@ String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_ca const int dmode = display_mode->get_selected(); if (dmode == DISPLAY_FRAME_TIME) { - return TS->format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + RTR("ms"); + return TS->format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + TTR("ms"); } else if (dmode == DISPLAY_AVERAGE_TIME) { if (p_calls == 0) { - return TS->format_number("0.00") + " " + RTR("ms"); + return TS->format_number("0.00") + " " + TTR("ms"); } else { - return TS->format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + RTR("ms"); + return TS->format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + TTR("ms"); } } else if (dmode == DISPLAY_FRAME_PERCENT) { return _get_percent_txt(p_time, m.frame_time); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 72f1060589..8a5c464c2f 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -116,7 +116,7 @@ String EditorVisualProfiler::_get_time_as_text(float p_time) { int dmode = display_mode->get_selected(); if (dmode == DISPLAY_FRAME_TIME) { - return TS->format_number(String::num(p_time, 2)) + " " + RTR("ms"); + return TS->format_number(String::num(p_time, 2)) + " " + TTR("ms"); } else if (dmode == DISPLAY_FRAME_PERCENT) { return TS->format_number(String::num(p_time * 100 / graph_limit, 2)) + " " + TS->percent_sign(); } diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 52b0d485cf..db45478d21 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -76,241 +76,275 @@ static String _translate_doc_string(const String &p_text) { return translated.indent(indent); } -void DocTools::merge_from(const DocTools &p_data) { - for (KeyValue<String, DocData::ClassDoc> &E : class_list) { - DocData::ClassDoc &c = E.value; +// Comparator for constructors, based on `MetodDoc` operator. +struct ConstructorCompare { + _FORCE_INLINE_ bool operator()(const DocData::MethodDoc &p_lhs, const DocData::MethodDoc &p_rhs) const { + // Must be a constructor (i.e. assume named for the class) + // We want this arbitrary order for a class "Foo": + // - 1. Default constructor: Foo() + // - 2. Copy constructor: Foo(Foo) + // - 3+. Other constructors Foo(Bar, ...) based on first argument's name + if (p_lhs.arguments.is_empty() || p_rhs.arguments.is_empty()) { // 1. + return p_lhs.arguments.size() < p_rhs.arguments.size(); + } + if (p_lhs.arguments[0].type == p_lhs.return_type || p_rhs.arguments[0].type == p_lhs.return_type) { // 2. + return (p_lhs.arguments[0].type == p_lhs.return_type) || (p_rhs.arguments[0].type != p_lhs.return_type); + } + return p_lhs.arguments[0] < p_rhs.arguments[0]; + } +}; - if (!p_data.class_list.has(c.name)) { - continue; +// Comparator for operators, compares on name and type. +struct OperatorCompare { + _FORCE_INLINE_ bool operator()(const DocData::MethodDoc &p_lhs, const DocData::MethodDoc &p_rhs) const { + if (p_lhs.name == p_rhs.name) { + if (p_lhs.arguments.size() == p_rhs.arguments.size()) { + if (p_lhs.arguments.is_empty()) { + return false; + } + return p_lhs.arguments[0].type < p_rhs.arguments[0].type; + } + return p_lhs.arguments.size() < p_rhs.arguments.size(); } + return p_lhs.name.naturalcasecmp_to(p_rhs.name) < 0; + } +}; - const DocData::ClassDoc &cf = p_data.class_list[c.name]; +// Comparator for methods, compares on names. +struct MethodCompare { + _FORCE_INLINE_ bool operator()(const DocData::MethodDoc &p_lhs, const DocData::MethodDoc &p_rhs) const { + return p_lhs.name.naturalcasecmp_to(p_rhs.name) < 0; + } +}; - c.is_deprecated = cf.is_deprecated; - c.deprecated_message = cf.deprecated_message; - c.is_experimental = cf.is_experimental; - c.experimental_message = cf.experimental_message; - c.keywords = cf.keywords; +static void merge_constructors(Vector<DocData::MethodDoc> &p_to, const Vector<DocData::MethodDoc> &p_from) { + // Get data from `p_from`, to avoid mutation checks. + const DocData::MethodDoc *from_ptr = p_from.ptr(); + int64_t from_size = p_from.size(); - c.description = cf.description; - c.brief_description = cf.brief_description; - c.tutorials = cf.tutorials; + // TODO: Improve constructor merging. + for (DocData::MethodDoc &to : p_to) { + for (int64_t from_i = 0; from_i < from_size; ++from_i) { + const DocData::MethodDoc &from = from_ptr[from_i]; - for (int i = 0; i < c.constructors.size(); i++) { - DocData::MethodDoc &m = c.constructors.write[i]; + // Compare argument count first. + if (from.arguments.size() != to.arguments.size()) { + continue; + } - for (int j = 0; j < cf.constructors.size(); j++) { - if (cf.constructors[j].name != m.name) { - continue; - } + if (from.name != to.name) { + continue; + } - { - // Since constructors can repeat, we need to check the type of - // the arguments so we make sure they are different. - if (cf.constructors[j].arguments.size() != m.arguments.size()) { - continue; - } - int arg_count = cf.constructors[j].arguments.size(); - Vector<bool> arg_used; - arg_used.resize(arg_count); - for (int l = 0; l < arg_count; ++l) { - arg_used.write[l] = false; - } - // also there is no guarantee that argument ordering will match, so we - // have to check one by one so we make sure we have an exact match - for (int k = 0; k < arg_count; ++k) { - for (int l = 0; l < arg_count; ++l) { - if (cf.constructors[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { - arg_used.write[l] = true; - break; - } - } - } - bool not_the_same = false; - for (int l = 0; l < arg_count; ++l) { - if (!arg_used[l]) { // at least one of the arguments was different - not_the_same = true; + { + // Since constructors can repeat, we need to check the type of + // the arguments so we make sure they are different. + int64_t arg_count = from.arguments.size(); + Vector<bool> arg_used; + arg_used.resize_zeroed(arg_count); + // Also there is no guarantee that argument ordering will match, + // so we have to check one by one so we make sure we have an exact match. + for (int64_t arg_i = 0; arg_i < arg_count; ++arg_i) { + for (int64_t arg_j = 0; arg_j < arg_count; ++arg_j) { + if (from.arguments[arg_i].type == to.arguments[arg_j].type && !arg_used[arg_j]) { + arg_used.write[arg_j] = true; + break; } } - if (not_the_same) { - continue; + } + bool not_the_same = false; + for (int64_t arg_i = 0; arg_i < arg_count; ++arg_i) { + if (!arg_used[arg_i]) { // At least one of the arguments was different. + not_the_same = true; + break; } } - - const DocData::MethodDoc &mf = cf.constructors[j]; - - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - break; + if (not_the_same) { + continue; + } } + + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; + break; } + } +} - for (int i = 0; i < c.methods.size(); i++) { - DocData::MethodDoc &m = c.methods.write[i]; +static void merge_methods(Vector<DocData::MethodDoc> &p_to, const Vector<DocData::MethodDoc> &p_from) { + // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. + DocData::MethodDoc *to_ptrw = p_to.ptrw(); + int64_t to_size = p_to.size(); - for (int j = 0; j < cf.methods.size(); j++) { - if (cf.methods[j].name != m.name) { - continue; - } + SearchArray<DocData::MethodDoc, MethodCompare> search_array; - const DocData::MethodDoc &mf = cf.methods[j]; + for (const DocData::MethodDoc &from : p_from) { + int64_t found = search_array.bisect(to_ptrw, to_size, from, true); - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - m.keywords = mf.keywords; - break; - } + if (found >= to_size) { + continue; } - for (int i = 0; i < c.signals.size(); i++) { - DocData::MethodDoc &m = c.signals.write[i]; + DocData::MethodDoc &to = to_ptrw[found]; - for (int j = 0; j < cf.signals.size(); j++) { - if (cf.signals[j].name != m.name) { - continue; - } - const DocData::MethodDoc &mf = cf.signals[j]; - - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - m.keywords = mf.keywords; - break; - } + // Check found entry on name. + if (to.name == from.name) { + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; + to.keywords = from.keywords; } + } +} - for (int i = 0; i < c.constants.size(); i++) { - DocData::ConstantDoc &m = c.constants.write[i]; +static void merge_constants(Vector<DocData::ConstantDoc> &p_to, const Vector<DocData::ConstantDoc> &p_from) { + // Get data from `p_from`, to avoid mutation checks. Searching will be done in the sorted `p_from` from the unsorted `p_to`. + const DocData::ConstantDoc *from_ptr = p_from.ptr(); + int64_t from_size = p_from.size(); - for (int j = 0; j < cf.constants.size(); j++) { - if (cf.constants[j].name != m.name) { - continue; - } - const DocData::ConstantDoc &mf = cf.constants[j]; - - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - m.keywords = mf.keywords; - break; - } + SearchArray<DocData::ConstantDoc> search_array; + + for (DocData::ConstantDoc &to : p_to) { + int64_t found = search_array.bisect(from_ptr, from_size, to, true); + + if (found >= from_size) { + continue; } - for (int i = 0; i < c.annotations.size(); i++) { - DocData::MethodDoc &m = c.annotations.write[i]; + // Check found entry on name. + const DocData::ConstantDoc &from = from_ptr[found]; - for (int j = 0; j < cf.annotations.size(); j++) { - if (cf.annotations[j].name != m.name) { - continue; - } - const DocData::MethodDoc &mf = cf.annotations[j]; - - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - m.keywords = mf.keywords; - break; - } + if (from.name == to.name) { + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; + to.keywords = from.keywords; } + } +} - for (int i = 0; i < c.properties.size(); i++) { - DocData::PropertyDoc &p = c.properties.write[i]; +static void merge_properties(Vector<DocData::PropertyDoc> &p_to, const Vector<DocData::PropertyDoc> &p_from) { + // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. + DocData::PropertyDoc *to_ptrw = p_to.ptrw(); + int64_t to_size = p_to.size(); - for (int j = 0; j < cf.properties.size(); j++) { - if (cf.properties[j].name != p.name) { - continue; - } - const DocData::PropertyDoc &pf = cf.properties[j]; - - p.description = pf.description; - p.is_deprecated = pf.is_deprecated; - p.deprecated_message = pf.deprecated_message; - p.is_experimental = pf.is_experimental; - p.experimental_message = pf.experimental_message; - p.keywords = pf.keywords; - break; - } + SearchArray<DocData::PropertyDoc> search_array; + + for (const DocData::PropertyDoc &from : p_from) { + int64_t found = search_array.bisect(to_ptrw, to_size, from, true); + + if (found >= to_size) { + continue; } - for (int i = 0; i < c.theme_properties.size(); i++) { - DocData::ThemeItemDoc &ti = c.theme_properties.write[i]; + DocData::PropertyDoc &to = to_ptrw[found]; - for (int j = 0; j < cf.theme_properties.size(); j++) { - if (cf.theme_properties[j].name != ti.name || cf.theme_properties[j].data_type != ti.data_type) { - continue; - } - const DocData::ThemeItemDoc &pf = cf.theme_properties[j]; + // Check found entry on name. + if (to.name == from.name) { + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; + to.keywords = from.keywords; + } + } +} - ti.description = pf.description; - ti.keywords = pf.keywords; - break; - } +static void merge_theme_properties(Vector<DocData::ThemeItemDoc> &p_to, const Vector<DocData::ThemeItemDoc> &p_from) { + // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. + DocData::ThemeItemDoc *to_ptrw = p_to.ptrw(); + int64_t to_size = p_to.size(); + + SearchArray<DocData::ThemeItemDoc> search_array; + + for (const DocData::ThemeItemDoc &from : p_from) { + int64_t found = search_array.bisect(to_ptrw, to_size, from, true); + + if (found >= to_size) { + continue; } - for (int i = 0; i < c.operators.size(); i++) { - DocData::MethodDoc &m = c.operators.write[i]; + DocData::ThemeItemDoc &to = to_ptrw[found]; - for (int j = 0; j < cf.operators.size(); j++) { - if (cf.operators[j].name != m.name) { - continue; - } + // Check found entry on name and data type. + if (to.name == from.name && to.data_type == from.data_type) { + to.description = from.description; + to.keywords = from.keywords; + } + } +} - { - // Since operators can repeat, we need to check the type of - // the arguments so we make sure they are different. - if (cf.operators[j].arguments.size() != m.arguments.size()) { - continue; - } - int arg_count = cf.operators[j].arguments.size(); - Vector<bool> arg_used; - arg_used.resize(arg_count); - for (int l = 0; l < arg_count; ++l) { - arg_used.write[l] = false; - } - // also there is no guarantee that argument ordering will match, so we - // have to check one by one so we make sure we have an exact match - for (int k = 0; k < arg_count; ++k) { - for (int l = 0; l < arg_count; ++l) { - if (cf.operators[j].arguments[k].type == m.arguments[l].type && !arg_used[l]) { - arg_used.write[l] = true; - break; - } - } - } - bool not_the_same = false; - for (int l = 0; l < arg_count; ++l) { - if (!arg_used[l]) { // at least one of the arguments was different - not_the_same = true; - } - } - if (not_the_same) { - continue; - } - } +static void merge_operators(Vector<DocData::MethodDoc> &p_to, const Vector<DocData::MethodDoc> &p_from) { + // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. + DocData::MethodDoc *to_ptrw = p_to.ptrw(); + int64_t to_size = p_to.size(); - const DocData::MethodDoc &mf = cf.operators[j]; + SearchArray<DocData::MethodDoc, OperatorCompare> search_array; - m.description = mf.description; - m.is_deprecated = mf.is_deprecated; - m.deprecated_message = mf.deprecated_message; - m.is_experimental = mf.is_experimental; - m.experimental_message = mf.experimental_message; - break; - } + for (const DocData::MethodDoc &from : p_from) { + int64_t found = search_array.bisect(to_ptrw, to_size, from, true); + + if (found >= to_size) { + continue; + } + + DocData::MethodDoc &to = to_ptrw[found]; + + // Check found entry on name and argument. + if (to.name == from.name && to.arguments.size() == from.arguments.size() && (to.arguments.is_empty() || to.arguments[0].type == from.arguments[0].type)) { + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; + } + } +} + +void DocTools::merge_from(const DocTools &p_data) { + for (KeyValue<String, DocData::ClassDoc> &E : class_list) { + DocData::ClassDoc &c = E.value; + + if (!p_data.class_list.has(c.name)) { + continue; } + const DocData::ClassDoc &cf = p_data.class_list[c.name]; + + c.is_deprecated = cf.is_deprecated; + c.deprecated_message = cf.deprecated_message; + c.is_experimental = cf.is_experimental; + c.experimental_message = cf.experimental_message; + c.keywords = cf.keywords; + + c.description = cf.description; + c.brief_description = cf.brief_description; + c.tutorials = cf.tutorials; + + merge_constructors(c.constructors, cf.constructors); + + merge_methods(c.methods, cf.methods); + + merge_methods(c.signals, cf.signals); + + merge_constants(c.constants, cf.constants); + + merge_methods(c.annotations, cf.annotations); + + merge_properties(c.properties, cf.properties); + + merge_theme_properties(c.theme_properties, cf.theme_properties); + + merge_operators(c.operators, cf.operators); + #ifndef MODULE_MONO_ENABLED // The Mono module defines some properties that we want to keep when // re-generating docs with a non-Mono build, to prevent pointless diffs @@ -323,6 +357,8 @@ void DocTools::merge_from(const DocTools &p_data) { for (int j = 0; j < cf.properties.size(); j++) { if (cf.properties[j].name == "GodotSharp") { c.properties.push_back(cf.properties[j]); + c.properties.sort(); + break; } } } @@ -620,7 +656,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { c.methods.push_back(method); } - c.methods.sort(); + c.methods.sort_custom<MethodCompare>(); List<MethodInfo> signal_list; ClassDB::get_signal_list(name, &signal_list, true); @@ -639,6 +675,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { c.signals.push_back(signal); } + + c.signals.sort_custom<MethodCompare>(); } List<String> constant_list; @@ -865,7 +903,9 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { } } - c.methods.sort(); + c.constructors.sort_custom<ConstructorCompare>(); + c.operators.sort_custom<OperatorCompare>(); + c.methods.sort_custom<MethodCompare>(); List<PropertyInfo> properties; v.get_property_list(&properties); @@ -878,6 +918,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { c.properties.push_back(property); } + c.properties.sort(); + List<StringName> constants; Variant::get_constants_for_type(Variant::Type(i), &constants); @@ -933,10 +975,11 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { c.properties.push_back(pd); } + c.properties.sort(); + // Variant utility functions. List<StringName> utility_functions; Variant::get_utility_function_list(&utility_functions); - utility_functions.sort_custom<StringName::AlphCompare>(); for (const StringName &E : utility_functions) { DocData::MethodDoc md; md.name = E; @@ -971,6 +1014,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { c.methods.push_back(md); } + + c.methods.sort_custom<MethodCompare>(); } // Add scripting language built-ins. @@ -1066,6 +1111,9 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { continue; } + c.methods.sort_custom<MethodCompare>(); + c.annotations.sort_custom<MethodCompare>(); + class_list[cname] = c; } } @@ -1468,6 +1516,9 @@ Error DocTools::_load(Ref<XMLParser> parser) { break; // End of <class>. } } + + // Sort loaded constants for merging. + c.constants.sort(); } return OK; @@ -1483,7 +1534,6 @@ static void _write_string(Ref<FileAccess> f, int p_tablevel, const String &p_str static void _write_method_doc(Ref<FileAccess> f, const String &p_name, Vector<DocData::MethodDoc> &p_method_docs) { if (!p_method_docs.is_empty()) { - p_method_docs.sort(); _write_string(f, 1, "<" + p_name + "s>"); for (int i = 0; i < p_method_docs.size(); i++) { const DocData::MethodDoc &m = p_method_docs[i]; @@ -1615,8 +1665,6 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String, if (!c.properties.is_empty()) { _write_string(f, 1, "<members>"); - c.properties.sort(); - for (int i = 0; i < c.properties.size(); i++) { String additional_attributes; if (!c.properties[i].enumeration.is_empty()) { @@ -1696,8 +1744,6 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String, _write_method_doc(f, "annotation", c.annotations); if (!c.theme_properties.is_empty()) { - c.theme_properties.sort(); - _write_string(f, 1, "<theme_items>"); for (int i = 0; i < c.theme_properties.size(); i++) { const DocData::ThemeItemDoc &ti = c.theme_properties[i]; diff --git a/editor/editor_builders.py b/editor/editor_builders.py index 68595047fe..a25f4949c4 100644 --- a/editor/editor_builders.py +++ b/editor/editor_builders.py @@ -61,7 +61,9 @@ def make_translations_header(target, source, env, category): xl_names = [] for i in range(len(sorted_paths)): - if msgfmt_available: + name = os.path.splitext(os.path.basename(sorted_paths[i]))[0] + # msgfmt erases non-translated messages, so avoid using it if exporting the POT. + if msgfmt_available and name != category: mo_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".mo") cmd = "msgfmt " + sorted_paths[i] + " --no-hash -o " + mo_path try: @@ -79,7 +81,7 @@ def make_translations_header(target, source, env, category): try: os.remove(mo_path) except OSError as e: - # Do not fail the entire build if it cannot delete a temporary file + # Do not fail the entire build if it cannot delete a temporary file. print( "WARNING: Could not delete temporary .mo file: path=%r; [%s] %s" % (mo_path, e.__class__.__name__, e) @@ -88,11 +90,13 @@ def make_translations_header(target, source, env, category): with open(sorted_paths[i], "rb") as f: buf = f.read() + if name == category: + name = "source" + decomp_size = len(buf) # Use maximum zlib compression level to further reduce file size # (at the cost of initial build times). buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) - name = os.path.splitext(os.path.basename(sorted_paths[i]))[0] g.write("static const unsigned char _{}_translation_{}_compressed[] = {{\n".format(category, name)) for j in range(len(buf)): diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 0e3b408996..b7380c9fc2 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2630,16 +2630,22 @@ void EditorPropertyColor::_set_read_only(bool p_read_only) { } void EditorPropertyColor::_color_changed(const Color &p_color) { + if (!live_changes_enabled) { + return; + } + // Cancel the color change if the current color is identical to the new one. - if (get_edited_property_value() == p_color) { + if (((Color)get_edited_property_value()).is_equal_approx(p_color)) { return; } - emit_changed(get_edited_property(), p_color, "", true); + // Preview color change, bypassing undo/redo. + get_edited_object()->set(get_edited_property(), p_color); } void EditorPropertyColor::_popup_closed() { - if (picker->get_pick_color() != last_color) { + get_edited_object()->set(get_edited_property(), last_color); + if (!picker->get_pick_color().is_equal_approx(last_color)) { emit_changed(get_edited_property(), picker->get_pick_color(), "", false); } } @@ -2682,6 +2688,10 @@ void EditorPropertyColor::setup(bool p_show_alpha) { picker->set_edit_alpha(p_show_alpha); } +void EditorPropertyColor::set_live_changes_enabled(bool p_enabled) { + live_changes_enabled = p_enabled; +} + EditorPropertyColor::EditorPropertyColor() { picker = memnew(ColorPickerButton); add_child(picker); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index fa759d5d19..ce164733fe 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -621,10 +621,10 @@ class EditorPropertyColor : public EditorProperty { ColorPickerButton *picker = nullptr; void _color_changed(const Color &p_color); void _popup_closed(); - void _picker_created(); void _picker_opening(); Color last_color; + bool live_changes_enabled = true; protected: virtual void _set_read_only(bool p_read_only) override; @@ -633,6 +633,7 @@ protected: public: virtual void update_property() override; void setup(bool p_show_alpha); + void set_live_changes_enabled(bool p_enabled); EditorPropertyColor(); }; diff --git a/editor/editor_translation.cpp b/editor/editor_translation.cpp index 302a81669d..194d78326d 100644 --- a/editor/editor_translation.cpp +++ b/editor/editor_translation.cpp @@ -160,20 +160,22 @@ 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."); + if (!strcmp(etl->lang, "source")) { + 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<FileAccessMemory> fa; + fa.instantiate(); + fa->open_custom(data.ptr(), data.size()); - Ref<Translation> tr = TranslationLoaderPO::load_translation(fa); + Ref<Translation> tr = TranslationLoaderPO::load_translation(fa); - if (tr.is_valid()) { - tr->get_message_list(&msgids); - break; + if (tr.is_valid()) { + tr->get_message_list(&msgids); + break; + } } etl++; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 2d3f1a5b90..3bddc91b81 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -566,6 +566,7 @@ void FindInFilesDialog::_bind_methods() { //----------------------------------------------------------------------------- const char *FindInFilesPanel::SIGNAL_RESULT_SELECTED = "result_selected"; const char *FindInFilesPanel::SIGNAL_FILES_MODIFIED = "files_modified"; +const char *FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED = "close_button_clicked"; FindInFilesPanel::FindInFilesPanel() { _finder = memnew(FindInFiles); @@ -611,6 +612,11 @@ FindInFilesPanel::FindInFilesPanel() { _cancel_button->hide(); hbc->add_child(_cancel_button); + _close_button = memnew(Button); + _close_button->set_text(TTR("Close")); + _close_button->connect("pressed", callable_mp(this, &FindInFilesPanel::_on_close_button_clicked)); + hbc->add_child(_close_button); + vbc->add_child(hbc); } @@ -843,6 +849,10 @@ void FindInFilesPanel::_on_cancel_button_clicked() { stop_search(); } +void FindInFilesPanel::_on_close_button_clicked() { + emit_signal(SNAME(SIGNAL_CLOSE_BUTTON_CLICKED)); +} + void FindInFilesPanel::_on_result_selected() { TreeItem *item = _results_display->get_selected(); HashMap<TreeItem *, Result>::Iterator E = _result_items.find(item); @@ -1010,4 +1020,6 @@ void FindInFilesPanel::_bind_methods() { PropertyInfo(Variant::INT, "end"))); ADD_SIGNAL(MethodInfo(SIGNAL_FILES_MODIFIED, PropertyInfo(Variant::STRING, "paths"))); + + ADD_SIGNAL(MethodInfo(SIGNAL_CLOSE_BUTTON_CLICKED)); } diff --git a/editor/find_in_files.h b/editor/find_in_files.h index 7885931514..ac336b4e35 100644 --- a/editor/find_in_files.h +++ b/editor/find_in_files.h @@ -159,6 +159,7 @@ class FindInFilesPanel : public Control { public: static const char *SIGNAL_RESULT_SELECTED; static const char *SIGNAL_FILES_MODIFIED; + static const char *SIGNAL_CLOSE_BUTTON_CLICKED; FindInFilesPanel(); @@ -180,6 +181,7 @@ private: void _on_finished(); void _on_refresh_button_clicked(); void _on_cancel_button_clicked(); + void _on_close_button_clicked(); void _on_result_selected(); void _on_item_edited(); void _on_replace_text_changed(const String &text); @@ -207,6 +209,7 @@ private: Label *_status_label = nullptr; Button *_refresh_button = nullptr; Button *_cancel_button = nullptr; + Button *_close_button = nullptr; ProgressBar *_progress_bar = nullptr; HashMap<String, TreeItem *> _file_items; HashMap<TreeItem *, Result> _result_items; diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 2dc43098f7..edd0ddbdad 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -77,10 +77,10 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project.")); debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); debug_menu->set_item_tooltip(-1, - TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project.")); + TTR("When this option is enabled, navigation meshes, and polygons will be visible in the running project.")); debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_avoidance", TTR("Visible Avoidance")), RUN_DEBUG_AVOIDANCE); debug_menu->set_item_tooltip(-1, - TTR("When this option is enabled, avoidance objects shapes, radius and velocities will be visible in the running project.")); + TTR("When this option is enabled, avoidance object shapes, radiuses, and velocities will be visible in the running project.")); debug_menu->add_separator(); debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_canvas_redraw", TTR("Debug CanvasItem Redraws")), RUN_DEBUG_CANVAS_REDRAW); debug_menu->set_item_tooltip(-1, diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp index 70911ea364..be7ff6ba3e 100644 --- a/editor/plugins/font_config_plugin.cpp +++ b/editor/plugins/font_config_plugin.cpp @@ -716,7 +716,7 @@ void EditorPropertyOTFeatures::update_property() { } for (int i = 0; i < FGRP_MAX; i++) { if (have_sub[i]) { - menu->add_submenu_node_item(RTR(group_names[i]), menu_sub[i]); + menu->add_submenu_node_item(TTRGET(group_names[i]), menu_sub[i]); } } @@ -848,15 +848,15 @@ EditorPropertyOTFeatures::EditorPropertyOTFeatures() { menu_sub[i]->connect("id_pressed", callable_mp(this, &EditorPropertyOTFeatures::_add_feature)); } - group_names[FGRP_STYLISTIC_SET] = "Stylistic Sets"; - group_names[FGRP_CHARACTER_VARIANT] = "Character Variants"; - group_names[FGRP_CAPITLS] = "Capitals"; - group_names[FGRP_LIGATURES] = "Ligatures"; - group_names[FGRP_ALTERNATES] = "Alternates"; - group_names[FGRP_EAL] = "East Asian Language"; - group_names[FGRP_EAW] = "East Asian Widths"; - group_names[FGRP_NUMAL] = "Numeral Alignment"; - group_names[FGRP_CUSTOM] = "Custom"; + group_names[FGRP_STYLISTIC_SET] = TTRC("Stylistic Sets"); + group_names[FGRP_CHARACTER_VARIANT] = TTRC("Character Variants"); + group_names[FGRP_CAPITLS] = TTRC("Capitals"); + group_names[FGRP_LIGATURES] = TTRC("Ligatures"); + group_names[FGRP_ALTERNATES] = TTRC("Alternates"); + group_names[FGRP_EAL] = TTRC("East Asian Language"); + group_names[FGRP_EAW] = TTRC("East Asian Widths"); + group_names[FGRP_NUMAL] = TTRC("Numeral Alignment"); + group_names[FGRP_CUSTOM] = TTRC("Custom"); } /*************************************************************************/ diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 86df57c469..e266a3241b 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -136,6 +136,10 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, if (property_name == "script" && property_value.get_type() == Variant::OBJECT && !property_value.is_null()) { // Parse built-in script. Ref<Script> s = Object::cast_to<Script>(property_value); + if (!s->is_built_in()) { + continue; + } + String extension = s->get_language()->get_extension(); if (EditorTranslationParser::get_singleton()->can_parse(extension)) { Vector<String> temp; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 9121ca09b4..6f1eef62de 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -1716,18 +1716,6 @@ void ScriptEditor::_notification(int p_what) { _test_script_times_on_disk(); _update_modified_scripts_for_external_editor(); } break; - - case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { - if (is_visible()) { - find_in_files_button->show(); - } else { - if (find_in_files->is_visible_in_tree()) { - EditorNode::get_bottom_panel()->hide_bottom_panel(); - } - find_in_files_button->hide(); - } - - } break; } } @@ -3776,6 +3764,7 @@ void ScriptEditor::_on_find_in_files_result_selected(const String &fpath, int li ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor()); if (ste) { + EditorInterface::get_singleton()->set_main_screen_editor("Script"); ste->goto_line_selection(line_number, begin, end); } } @@ -3790,6 +3779,7 @@ void ScriptEditor::_on_find_in_files_result_selected(const String &fpath, int li ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor()); if (ste) { + EditorInterface::get_singleton()->set_main_screen_editor("Script"); ste->goto_line_selection(line_number - 1, begin, end); } return; @@ -3823,6 +3813,13 @@ void ScriptEditor::_start_find_in_files(bool with_replace) { find_in_files->set_replace_text(find_in_files_dialog->get_replace_text()); find_in_files->start_search(); + if (find_in_files_button->get_index() != find_in_files_button->get_parent()->get_child_count()) { + find_in_files_button->get_parent()->move_child(find_in_files_button, -1); + } + if (!find_in_files_button->is_visible()) { + find_in_files_button->show(); + } + EditorNode::get_bottom_panel()->make_item_visible(find_in_files); } @@ -3849,6 +3846,11 @@ void ScriptEditor::_set_zoom_factor(float p_zoom_factor) { } } +void ScriptEditor::_on_find_in_files_close_button_clicked() { + EditorNode::get_bottom_panel()->hide_bottom_panel(); + find_in_files_button->hide(); +} + void ScriptEditor::_window_changed(bool p_visible) { make_floating->set_visible(!p_visible); is_floating = p_visible; @@ -4203,6 +4205,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE); find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected)); find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files)); + find_in_files->connect(FindInFilesPanel::SIGNAL_CLOSE_BUTTON_CLICKED, callable_mp(this, &ScriptEditor::_on_find_in_files_close_button_clicked)); find_in_files->hide(); find_in_files_button->hide(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index d5c33c73b4..4fff724866 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -496,6 +496,7 @@ class ScriptEditor : public PanelContainer { void _on_find_in_files_result_selected(const String &fpath, int line_number, int begin, int end); void _start_find_in_files(bool with_replace); void _on_find_in_files_modified_files(const PackedStringArray &paths); + void _on_find_in_files_close_button_clicked(); void _set_zoom_factor(float p_zoom_factor); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c093f556ea..640c755ccf 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1164,11 +1164,22 @@ void ScriptTextEditor::_update_connected_methods() { // Add override icons to methods. methods_found.clear(); for (int i = 0; i < functions.size(); i++) { - StringName name = StringName(functions[i].get_slice(":", 0)); + String raw_name = functions[i].get_slice(":", 0); + StringName name = StringName(raw_name); if (methods_found.has(name)) { continue; } + // Account for inner classes + if (raw_name.contains(".")) { + // Strip inner class name from the method, and start from the right since + // our inner class might be inside another inner class + int pos = raw_name.rfind("."); + if (pos != -1) { + name = raw_name.substr(pos + 1); + } + } + String found_base_class; StringName base_class = script->get_instance_base_type(); Ref<Script> inherited_script = script->get_base_script(); @@ -1217,7 +1228,7 @@ void ScriptTextEditor::_update_connected_methods() { text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("MethodOverrideAndSlot"))); } - methods_found.insert(name); + methods_found.insert(StringName(raw_name)); } } } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 964558ee78..c83c47577d 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -6922,6 +6922,8 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par } else if (Object::cast_to<EditorPropertyEnum>(prop)) { prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); Object::cast_to<EditorPropertyEnum>(prop)->set_option_button_clip(false); + } else if (Object::cast_to<EditorPropertyColor>(prop)) { + Object::cast_to<EditorPropertyColor>(prop)->set_live_changes_enabled(false); } editors.push_back(prop); diff --git a/gles3_builders.py b/gles3_builders.py index 985e9d547c..cf7c74f32d 100644 --- a/gles3_builders.py +++ b/gles3_builders.py @@ -31,7 +31,7 @@ class GLES3HeaderStruct: def include_file_in_gles3_header(filename: str, header_data: GLES3HeaderStruct, depth: int): - with open(filename, "r") as fs: + with open(filename, "r", encoding="utf-8") as fs: line = fs.readline() while line: diff --git a/glsl_builders.py b/glsl_builders.py index fd90e9a6c0..5a17e3ca7f 100644 --- a/glsl_builders.py +++ b/glsl_builders.py @@ -38,7 +38,7 @@ class RDHeaderStruct: def include_file_in_rd_header(filename: str, header_data: RDHeaderStruct, depth: int) -> RDHeaderStruct: - with open(filename, "r") as fs: + with open(filename, "r", encoding="utf-8") as fs: line = fs.readline() while line: @@ -172,7 +172,7 @@ class RAWHeaderStruct: def include_file_in_raw_header(filename: str, header_data: RAWHeaderStruct, depth: int) -> None: - with open(filename, "r") as fs: + with open(filename, "r", encoding="utf-8") as fs: line = fs.readline() while line: diff --git a/main/main.cpp b/main/main.cpp index 41f82177bc..cf40f764cf 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -398,15 +398,15 @@ void Main::print_header(bool p_rich) { if (VERSION_TIMESTAMP > 0) { // Version timestamp available. if (p_rich) { - print_line_rich("\u001b[38;5;39m" + String(VERSION_NAME) + "\u001b[0m v" + get_full_version_string() + " (" + Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC) - \u001b[4m" + String(VERSION_WEBSITE)); + Engine::get_singleton()->print_header_rich("\u001b[38;5;39m" + String(VERSION_NAME) + "\u001b[0m v" + get_full_version_string() + " (" + Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC) - \u001b[4m" + String(VERSION_WEBSITE)); } else { - print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " (" + Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC) - " + String(VERSION_WEBSITE)); + Engine::get_singleton()->print_header(String(VERSION_NAME) + " v" + get_full_version_string() + " (" + Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC) - " + String(VERSION_WEBSITE)); } } else { if (p_rich) { - print_line_rich("\u001b[38;5;39m" + String(VERSION_NAME) + "\u001b[0m v" + get_full_version_string() + " - \u001b[4m" + String(VERSION_WEBSITE)); + Engine::get_singleton()->print_header_rich("\u001b[38;5;39m" + String(VERSION_NAME) + "\u001b[0m v" + get_full_version_string() + " - \u001b[4m" + String(VERSION_WEBSITE)); } else { - print_line(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE)); + Engine::get_singleton()->print_header(String(VERSION_NAME) + " v" + get_full_version_string() + " - " + String(VERSION_WEBSITE)); } } } diff --git a/methods.py b/methods.py index 5aa34888eb..c0d129f93e 100644 --- a/methods.py +++ b/methods.py @@ -179,7 +179,7 @@ def get_version_info(module_version_string="", silent=False): gitfolder = ".git" if os.path.isfile(".git"): - with open(".git", "r") as file: + with open(".git", "r", encoding="utf-8") as file: module_folder = file.readline().strip() if module_folder.startswith("gitdir: "): gitfolder = module_folder[8:] @@ -196,12 +196,12 @@ def get_version_info(module_version_string="", silent=False): head = os.path.join(gitfolder, ref) packedrefs = os.path.join(gitfolder, "packed-refs") if os.path.isfile(head): - with open(head, "r") as file: + with open(head, "r", encoding="utf-8") as file: githash = file.readline().strip() elif os.path.isfile(packedrefs): # Git may pack refs into a single file. This code searches .git/packed-refs file for the current ref's hash. # https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-pack-refs.html - for line in open(packedrefs, "r").read().splitlines(): + for line in open(packedrefs, "r", encoding="utf-8").read().splitlines(): if line.startswith("#"): continue (line_hash, line_ref) = line.split(" ") @@ -270,7 +270,7 @@ const uint64_t VERSION_TIMESTAMP = {git_timestamp}; def parse_cg_file(fname, uniforms, sizes, conditionals): - with open(fname, "r") as fs: + with open(fname, "r", encoding="utf-8") as fs: line = fs.readline() while line: @@ -1243,7 +1243,7 @@ def generate_vs_project(env, original_args, project_name="godot"): ).hexdigest() if os.path.exists(f"{project_name}.vcxproj.filters"): - with open(f"{project_name}.vcxproj.filters", "r") as file: + with open(f"{project_name}.vcxproj.filters", "r", encoding="utf-8") as file: existing_filters = file.read() match = re.search(r"(?ms)^<!-- CHECKSUM$.([0-9a-f]{32})", existing_filters) if match is not None and md5 == match.group(1): @@ -1255,7 +1255,7 @@ def generate_vs_project(env, original_args, project_name="godot"): if not skip_filters: print(f"Regenerating {project_name}.vcxproj.filters") - with open("misc/msvs/vcxproj.filters.template", "r") as file: + with open("misc/msvs/vcxproj.filters.template", "r", encoding="utf-8") as file: filters_template = file.read() for i in range(1, 10): filters_template = filters_template.replace(f"%%UUID{i}%%", str(uuid.uuid4())) @@ -1409,7 +1409,7 @@ def generate_vs_project(env, original_args, project_name="godot"): ) output = f'bin\\godot{env["PROGSUFFIX"]}' - with open("misc/msvs/props.template", "r") as file: + with open("misc/msvs/props.template", "r", encoding="utf-8") as file: props_template = file.read() props_template = props_template.replace("%%VSCONF%%", vsconf) @@ -1478,7 +1478,7 @@ def generate_vs_project(env, original_args, project_name="godot"): sln_uuid = str(uuid.uuid4()) if os.path.exists(f"{project_name}.sln"): - for line in open(f"{project_name}.sln", "r").read().splitlines(): + for line in open(f"{project_name}.sln", "r", encoding="utf-8").read().splitlines(): if line.startswith('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}")'): proj_uuid = re.search( r"\"{(\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b)}\"$", @@ -1567,7 +1567,7 @@ def generate_vs_project(env, original_args, project_name="godot"): section2 = sorted(section2) if not get_bool(original_args, "vsproj_props_only", False): - with open("misc/msvs/vcxproj.template", "r") as file: + with open("misc/msvs/vcxproj.template", "r", encoding="utf-8") as file: proj_template = file.read() proj_template = proj_template.replace("%%UUID%%", proj_uuid) proj_template = proj_template.replace("%%CONFS%%", "\n ".join(configurations)) @@ -1579,7 +1579,7 @@ def generate_vs_project(env, original_args, project_name="godot"): f.write(proj_template) if not get_bool(original_args, "vsproj_props_only", False): - with open("misc/msvs/sln.template", "r") as file: + with open("misc/msvs/sln.template", "r", encoding="utf-8") as file: sln_template = file.read() sln_template = sln_template.replace("%%NAME%%", project_name) sln_template = sln_template.replace("%%UUID%%", proj_uuid) diff --git a/misc/scripts/check_ci_log.py b/misc/scripts/check_ci_log.py index d979d373de..d024a3e375 100755 --- a/misc/scripts/check_ci_log.py +++ b/misc/scripts/check_ci_log.py @@ -9,7 +9,7 @@ if len(sys.argv) < 2: fname = sys.argv[1] -with open(fname.strip(), "r") as fileread: +with open(fname.strip(), "r", encoding="utf-8") as fileread: file_contents = fileread.read() # If find "ERROR: AddressSanitizer:", then happens invalid read or write diff --git a/misc/scripts/copyright_headers.py b/misc/scripts/copyright_headers.py index 82a4477cc0..169795921f 100755 --- a/misc/scripts/copyright_headers.py +++ b/misc/scripts/copyright_headers.py @@ -69,7 +69,7 @@ for f in sys.argv[1:]: # In a second pass, we skip all consecutive comment lines starting with "/*", # then we can append the rest (step 2). - with open(fname.strip(), "r") as fileread: + with open(fname.strip(), "r", encoding="utf-8") as fileread: line = fileread.readline() header_done = False diff --git a/modules/dds/texture_loader_dds.h b/modules/dds/texture_loader_dds.h index 3763700ff1..ce5b7f4045 100644 --- a/modules/dds/texture_loader_dds.h +++ b/modules/dds/texture_loader_dds.h @@ -35,10 +35,10 @@ class ResourceFormatDDS : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; virtual ~ResourceFormatDDS() {} }; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index fd5ad837f9..781e284bfc 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -629,18 +629,18 @@ public: class ResourceFormatLoaderGDScript : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; - virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; }; class ResourceFormatSaverGDScript : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // GDSCRIPT_H diff --git a/modules/ktx/texture_loader_ktx.h b/modules/ktx/texture_loader_ktx.h index 0ea676be6b..0de458a742 100644 --- a/modules/ktx/texture_loader_ktx.h +++ b/modules/ktx/texture_loader_ktx.h @@ -36,10 +36,10 @@ class ResourceFormatKTX : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; virtual ~ResourceFormatKTX() {} ResourceFormatKTX(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 8d24582ba1..8679f32e1a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -212,7 +212,7 @@ namespace Godot private void Rotate(Quaternion quaternion) { - this *= new Basis(quaternion); + this = new Basis(quaternion) * this; } private void SetDiagonal(Vector3 diagonal) diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp index 599926ec99..29ebc38edf 100644 --- a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp @@ -149,7 +149,7 @@ void MultiplayerEditorPlugin::_node_removed(Node *p_node) { } void MultiplayerEditorPlugin::_pinned() { - if (!repl_editor->get_pin()->is_pressed()) { + if (!repl_editor->get_pin()->is_pressed() && repl_editor->get_current() == nullptr) { if (repl_editor->is_visible_in_tree()) { EditorNode::get_bottom_panel()->hide_bottom_panel(); } diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index f6df212d35..58803124cf 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -270,6 +270,7 @@ ReplicationEditor::ReplicationEditor() { pin = memnew(Button); pin->set_theme_type_variation("FlatButton"); pin->set_toggle_mode(true); + pin->set_tooltip_text(TTR("Pin replication editor")); hb->add_child(pin); tree = memnew(Tree); diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index 0e62824adc..f7af937c8b 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -187,7 +187,7 @@ with open(os.path.join(dest_dir, "kernels/config.h"), "w", encoding="utf-8", new ) -with open("CMakeLists.txt", "r") as cmake_file: +with open("CMakeLists.txt", "r", encoding="utf-8") as cmake_file: cmake_content = cmake_file.read() major_version = int(re.compile(r"EMBREE_VERSION_MAJOR\s(\d+)").findall(cmake_content)[0]) minor_version = int(re.compile(r"EMBREE_VERSION_MINOR\s(\d+)").findall(cmake_content)[0]) diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index fc06e24bee..f5174d7d46 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -1234,7 +1234,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, } if (p_font_data->embolden != 0.f) { - FT_Pos strength = p_font_data->embolden * p_size.x * 4; // 26.6 fractional units (1 / 64). + FT_Pos strength = p_font_data->embolden * p_size.x * fd->oversampling * 4; // 26.6 fractional units (1 / 64). FT_Outline_Embolden(&fd->face->glyph->outline, strength); } diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 4c0ed084d8..302bd677fe 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -669,7 +669,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, } if (p_font_data->embolden != 0.f) { - FT_Pos strength = p_font_data->embolden * p_size.x * 4; // 26.6 fractional units (1 / 64). + FT_Pos strength = p_font_data->embolden * p_size.x * fd->oversampling * 4; // 26.6 fractional units (1 / 64). FT_Outline_Embolden(&fd->face->glyph->outline, strength); } diff --git a/modules/theora/video_stream_theora.h b/modules/theora/video_stream_theora.h index 21d4caef45..1e7fd088cc 100644 --- a/modules/theora/video_stream_theora.h +++ b/modules/theora/video_stream_theora.h @@ -173,10 +173,10 @@ public: class ResourceFormatLoaderTheora : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; #endif // VIDEO_STREAM_THEORA_H diff --git a/modules/webp/resource_saver_webp.h b/modules/webp/resource_saver_webp.h index 5546c69e6d..65cd4377db 100644 --- a/modules/webp/resource_saver_webp.h +++ b/modules/webp/resource_saver_webp.h @@ -39,9 +39,9 @@ public: static Error save_image(const String &p_path, const Ref<Image> &p_img, const bool p_lossy = false, const float p_quality = 0.75f); static Vector<uint8_t> save_image_to_buffer(const Ref<Image> &p_img, const bool p_lossy = false, const float p_quality = 0.75f); - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual bool recognize(const Ref<Resource> &p_resource) const; - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; ResourceSaverWebP(); }; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 9a6b6d5037..9df890e6bd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -293,15 +293,15 @@ public final class PermissionsUtil { /** * Returns the permissions defined in the AndroidManifest.xml file. * @param context the caller context for this method. - * @return manifest permissions list + * @return mutable copy of manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - public static List<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException { + public static ArrayList<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) - return Collections.emptyList(); - return Arrays.asList(packageInfo.requestedPermissions); + return new ArrayList<String>(); + return new ArrayList<>(Arrays.asList(packageInfo.requestedPermissions)); } /** diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 85d5cf2796..2215f706c5 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -250,7 +250,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, } if (step.get() == 1) { - if (!Main::start()) { + if (Main::start() != EXIT_SUCCESS) { return true; // should exit instead and print the error } diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 4057587db7..7f9008e952 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -1563,7 +1563,7 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point } if (old_pd.pressed_button_mask != pd.pressed_button_mask) { - BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((uint32_t)old_pd.pressed_button_mask ^ (uint32_t)pd.pressed_button_mask); + BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask ^ pd.pressed_button_mask; const MouseButton buttons_to_test[] = { MouseButton::LEFT, diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index c00a510fd5..1d9feb64a7 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -33,7 +33,7 @@ #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "servers/native_menu.h" +#include "servers/display/native_menu.h" #import <AppKit/AppKit.h> #import <ApplicationServices/ApplicationServices.h> diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 6eab56d8b6..74fd231903 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -33,7 +33,7 @@ #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "servers/native_menu.h" +#include "servers/display/native_menu.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp index 96ef346d23..72ee4d52c5 100644 --- a/scene/2d/physics/collision_polygon_2d.cpp +++ b/scene/2d/physics/collision_polygon_2d.cpp @@ -132,17 +132,16 @@ void CollisionPolygon2D::_notification(int p_what) { } if (polygon.size() > 2) { -#define DEBUG_DECOMPOSE -#if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE) - Vector<Vector<Vector2>> decomp = _decompose_in_convex(); - - Color c(0.4, 0.9, 0.1); - for (int i = 0; i < decomp.size(); i++) { - c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); - draw_colored_polygon(decomp[i], c); +#ifdef TOOLS_ENABLED + if (build_mode == BUILD_SOLIDS) { + Vector<Vector<Vector2>> decomp = _decompose_in_convex(); + + Color c(0.4, 0.9, 0.1); + for (int i = 0; i < decomp.size(); i++) { + c.set_hsv(Math::fmod(c.get_h() + 0.738, 1), c.get_s(), c.get_v(), 0.5); + draw_colored_polygon(decomp[i], c); + } } -#else - draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color()); #endif const Color stroke_color = Color(0.9, 0.2, 0.0); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index ee2122f269..3b62f6c23d 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -714,6 +714,10 @@ Color ColorPicker::get_pick_color() const { return color; } +Color ColorPicker::get_old_color() const { + return old_color; +} + void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); if (p_shape == current_shape) { @@ -1514,7 +1518,7 @@ void ColorPicker::_pick_finished() { return; } - if (Input::get_singleton()->is_key_pressed(Key::ESCAPE)) { + if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"))) { set_pick_color(old_color); } else { emit_signal(SNAME("color_changed"), color); @@ -1627,7 +1631,12 @@ void ColorPicker::_html_focus_exit() { if (c_text->is_menu_visible()) { return; } - _html_submitted(c_text->get_text()); + + if (is_visible_in_tree()) { + _html_submitted(c_text->get_text()); + } else { + _update_text_value(); + } } void ColorPicker::set_can_add_swatches(bool p_enabled) { @@ -2026,6 +2035,15 @@ ColorPicker::~ColorPicker() { ///////////////// +void ColorPickerPopupPanel::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event->is_action_pressed(SNAME("ui_accept"), false, true)) { + _close_pressed(); + } + PopupPanel::_input_from_window(p_event); +} + +///////////////// + void ColorPickerButton::_about_to_popup() { set_pressed(true); if (picker) { @@ -2040,6 +2058,10 @@ void ColorPickerButton::_color_changed(const Color &p_color) { } void ColorPickerButton::_modal_closed() { + if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"))) { + set_pick_color(picker->get_old_color()); + emit_signal(SNAME("color_changed"), color); + } emit_signal(SNAME("popup_closed")); set_pressed(false); } @@ -2137,7 +2159,7 @@ PopupPanel *ColorPickerButton::get_popup() { void ColorPickerButton::_update_picker() { if (!picker) { - popup = memnew(PopupPanel); + popup = memnew(ColorPickerPopupPanel); popup->set_wrap_controls(true); picker = memnew(ColorPicker); picker->set_anchors_and_offsets_preset(PRESET_FULL_RECT); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 282926d1ff..ad028584b1 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -317,12 +317,11 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; - int get_preset_size(); - void _set_pick_color(const Color &p_color, bool p_update_sliders); void set_pick_color(const Color &p_color); Color get_pick_color() const; void set_old_color(const Color &p_color); + Color get_old_color() const; void set_display_old_color(bool p_enabled); bool is_displaying_old_color() const; @@ -375,6 +374,10 @@ public: ~ColorPicker(); }; +class ColorPickerPopupPanel : public PopupPanel { + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; +}; + class ColorPickerButton : public Button { GDCLASS(ColorPickerButton, Button); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 40ac87160c..c344272f38 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -197,15 +197,15 @@ void FileDialog::_notification(int p_what) { dir_up->end_bulk_theme_override(); dir_prev->begin_bulk_theme_override(); - dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); - dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_prev->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_prev->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); dir_prev->end_bulk_theme_override(); dir_next->begin_bulk_theme_override(); - dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); - dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_next->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_next->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); dir_next->end_bulk_theme_override(); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 8eb455d0ac..2ce12794a7 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -236,12 +236,12 @@ void MenuBar::bind_global_menu() { RID submenu_rid = popups[i]->bind_global_menu(); if (!popups[i]->is_system_menu()) { int index = nmenu->add_submenu_item(main_menu, menu_cache[i].name, submenu_rid, global_menu_tag + "#" + itos(i), global_start_idx + i); - menu_cache.write[i].global_index = index; + menu_cache.write[i].submenu_rid = submenu_rid; nmenu->set_item_hidden(main_menu, index, menu_cache[i].hidden); nmenu->set_item_disabled(main_menu, index, menu_cache[i].disabled); nmenu->set_item_tooltip(main_menu, index, menu_cache[i].tooltip); } else { - menu_cache.write[i].global_index = -1; + menu_cache.write[i].submenu_rid = RID(); } } } @@ -257,11 +257,14 @@ void MenuBar::unbind_global_menu() { Vector<PopupMenu *> popups = _get_popups(); for (int i = menu_cache.size() - 1; i >= 0; i--) { if (!popups[i]->is_system_menu()) { - popups[i]->unbind_global_menu(); - if (menu_cache[i].global_index >= 0) { - nmenu->remove_item(main_menu, menu_cache[i].global_index); + if (menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } - menu_cache.write[i].global_index = -1; + popups[i]->unbind_global_menu(); + menu_cache.write[i].submenu_rid = RID(); } } @@ -292,8 +295,11 @@ void MenuBar::_notification(int p_what) { RID main_menu = is_global ? nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID) : RID(); for (int i = 0; i < menu_cache.size(); i++) { shape(menu_cache.write[i]); - if (is_global && menu_cache[i].global_index >= 0) { - nmenu->set_item_text(main_menu, menu_cache[i].global_index, atr(menu_cache[i].name)); + if (is_global && menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name)); + } } } } break; @@ -500,8 +506,11 @@ void MenuBar::_refresh_menu_names() { if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) { menu_cache.write[i].name = popups[i]->get_name(); shape(menu_cache.write[i]); - if (is_global && menu_cache[i].global_index >= 0) { - nmenu->set_item_text(main_menu, menu_cache[i].global_index, atr(menu_cache[i].name)); + if (is_global && menu_cache[i].submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name)); + } } } } @@ -554,8 +563,8 @@ void MenuBar::add_child_notify(Node *p_child) { RID submenu_rid = pm->bind_global_menu(); if (!pm->is_system_menu()) { - int index = nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(menu_cache.size() - 1), _find_global_start_index() + menu_cache.size() - 1); - menu_cache.write[menu_cache.size() - 1].global_index = index; + nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(menu_cache.size() - 1), _find_global_start_index() + menu_cache.size() - 1); + menu_cache.write[menu_cache.size() - 1].submenu_rid = submenu_rid; } } update_minimum_size(); @@ -589,13 +598,14 @@ void MenuBar::move_child_notify(Node *p_child) { RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); int global_start = _find_global_start_index(); - if (menu.global_index >= 0) { - nmenu->remove_item(main_menu, menu.global_index); + if (menu.submenu_rid.is_valid()) { + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu.submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } if (new_idx != -1) { - RID submenu_rid = pm->bind_global_menu(); - int index = nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(new_idx), global_start + new_idx); - menu_cache.write[new_idx].global_index = index; + nmenu->add_submenu_item(main_menu, atr(menu.name), menu.submenu_rid, global_menu_tag + "#" + itos(new_idx), global_start + new_idx); } } } @@ -611,20 +621,22 @@ void MenuBar::remove_child_notify(Node *p_child) { int idx = get_menu_idx_from_control(pm); - menu_cache.remove_at(idx); - if (!global_menu_tag.is_empty()) { if (!pm->is_system_menu()) { - pm->unbind_global_menu(); - if (menu_cache[idx].global_index >= 0) { + if (menu_cache[idx].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->remove_item(main_menu, menu_cache[idx].global_index); - menu_cache.write[idx].global_index = -1; + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[idx].submenu_rid); + if (item_idx >= 0) { + nmenu->remove_item(main_menu, item_idx); + } } + pm->unbind_global_menu(); } } + menu_cache.remove_at(idx); + p_child->remove_meta("_menu_name"); p_child->remove_meta("_menu_tooltip"); @@ -817,10 +829,13 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) { } menu_cache.write[p_menu].name = p_title; shape(menu_cache.write[p_menu]); - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_text(main_menu, menu_cache[p_menu].global_index, atr(menu_cache[p_menu].name)); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[p_menu].name)); + } } update_minimum_size(); } @@ -835,10 +850,13 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) { PopupMenu *pm = get_menu_popup(p_menu); pm->set_meta("_menu_tooltip", p_tooltip); menu_cache.write[p_menu].tooltip = p_tooltip; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_tooltip(main_menu, menu_cache[p_menu].global_index, p_tooltip); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_tooltip(main_menu, item_idx, p_tooltip); + } } } @@ -850,10 +868,13 @@ String MenuBar::get_menu_tooltip(int p_menu) const { void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].disabled = p_disabled; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_disabled(main_menu, menu_cache[p_menu].global_index, p_disabled); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_disabled(main_menu, item_idx, p_disabled); + } } } @@ -865,10 +886,13 @@ bool MenuBar::is_menu_disabled(int p_menu) const { void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { ERR_FAIL_INDEX(p_menu, menu_cache.size()); menu_cache.write[p_menu].hidden = p_hidden; - if (!global_menu_tag.is_empty() && menu_cache[p_menu].global_index >= 0) { + if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) { NativeMenu *nmenu = NativeMenu::get_singleton(); RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID); - nmenu->set_item_hidden(main_menu, menu_cache[p_menu].global_index, p_hidden); + int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid); + if (item_idx >= 0) { + nmenu->set_item_hidden(main_menu, item_idx, p_hidden); + } } update_minimum_size(); } diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index 631b791e1b..04f6afc2fa 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -55,7 +55,7 @@ class MenuBar : public Control { Ref<TextLine> text_buf; bool hidden = false; bool disabled = false; - int global_index = -1; + RID submenu_rid; Menu(const String &p_name) { name = p_name; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 2d7a66d4c0..a7cd18e1a8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1113,7 +1113,7 @@ void TextEdit::_notification(int p_what) { Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); // Show selection at the end of line. - if (line < get_selection_to_line(c)) { + if (line_wrap_index == line_wrap_amount && line < get_selection_to_line(c)) { if (rtl) { sel.push_back(Vector2(-char_w, 0)); } else { @@ -1123,7 +1123,7 @@ void TextEdit::_notification(int p_what) { } for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y - sel[j].x), row_height); + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y) - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { continue; } @@ -2115,7 +2115,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (is_shortcut_keys_enabled()) { - // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, + // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, SKIP SELECTION FOR NEXT OCCURRENCE, // CLEAR CARETS AND SELECTIONS, CUT, COPY, PASTE. if (k->is_action("ui_text_select_all", true)) { select_all(); @@ -2132,6 +2132,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + if (k->is_action("ui_text_skip_selection_for_next_occurrence", true)) { + skip_selection_for_next_occurrence(); + accept_event(); + return; + } if (k->is_action("ui_text_clear_carets_and_selection", true)) { // Since the default shortcut is ESC, accepts the event only if it's actually performed. if (_clear_carets_and_selection()) { @@ -5185,6 +5190,54 @@ void TextEdit::add_selection_for_next_occurrence() { } } +void TextEdit::skip_selection_for_next_occurrence() { + if (!selecting_enabled) { + return; + } + + if (text.size() == 1 && text[0].length() == 0) { + return; + } + + // Always use the last caret, to correctly search for + // the next occurrence that comes after this caret. + int caret = get_caret_count() - 1; + + // Supports getting the text under caret without selecting it. + // It allows to use this shortcut to simply jump to the next (under caret) word. + // Due to const and &(reference) presence, ternary operator is a way to avoid errors and warnings. + const String &searched_text = has_selection(caret) ? get_selected_text(caret) : get_word_under_caret(caret); + + int column = (has_selection(caret) ? get_selection_from_column(caret) : get_caret_column(caret)) + 1; + int line = get_caret_line(caret); + + const Point2i next_occurrence = search(searched_text, SEARCH_MATCH_CASE, line, column); + + if (next_occurrence.x == -1 || next_occurrence.y == -1) { + return; + } + + int to_column = (has_selection(caret) ? get_selection_to_column(caret) : get_caret_column(caret)) + 1; + int end = next_occurrence.x + (to_column - column); + int new_caret = add_caret(next_occurrence.y, end); + + if (new_caret != -1) { + select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + adjust_viewport_to_caret(new_caret); + merge_overlapping_carets(); + } + + // Deselect word under previous caret. + if (has_selection(caret)) { + select_word_under_caret(caret); + } + + // Remove previous caret. + if (get_caret_count() > 1) { + remove_caret(caret); + } +} + void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) { ERR_FAIL_INDEX(p_caret, carets.size()); if (!selecting_enabled) { @@ -6351,6 +6404,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence); + ClassDB::bind_method(D_METHOD("skip_selection_for_next_occurrence"), &TextEdit::skip_selection_for_next_occurrence); ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1)); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b8e30c7900..1099295d3b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -890,6 +890,7 @@ public: void select_all(); void select_word_under_caret(int p_caret = -1); void add_selection_for_next_occurrence(); + void skip_selection_for_next_occurrence(); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0); bool has_selection(int p_caret = -1) const; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 41f3ff108c..4e1d2b3983 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -4841,6 +4841,7 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_VISIBLE); BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_SHADOW); + BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_CANVAS); BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_MAX); BIND_ENUM_CONSTANT(DEBUG_DRAW_DISABLED); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 29ccdc5426..21832a454c 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -138,6 +138,7 @@ public: enum RenderInfoType { RENDER_INFO_TYPE_VISIBLE, RENDER_INFO_TYPE_SHADOW, + RENDER_INFO_TYPE_CANVAS, RENDER_INFO_TYPE_MAX }; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index bced493d98..586a88ea85 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1213,7 +1213,7 @@ void Window::set_force_native(bool p_force_native) { return; } force_native = p_force_native; - if (is_visible()) { + if (is_visible() && !is_in_edited_scene_root()) { WARN_PRINT("Can't change \"force_native\" while a window is displayed. Consider hiding window before changing this value."); } } @@ -1224,7 +1224,7 @@ bool Window::get_force_native() const { Viewport *Window::get_embedder() const { ERR_READ_THREAD_GUARD_V(nullptr); - if (force_native && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS)) { + if (force_native && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS) && !is_in_edited_scene_root()) { return nullptr; } diff --git a/scene/resources/compressed_texture.h b/scene/resources/compressed_texture.h index 5297d79cfe..439f7c097e 100644 --- a/scene/resources/compressed_texture.h +++ b/scene/resources/compressed_texture.h @@ -113,10 +113,10 @@ public: class ResourceFormatLoaderCompressedTexture2D : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class CompressedTextureLayered : public TextureLayered { @@ -178,10 +178,10 @@ public: class ResourceFormatLoaderCompressedTextureLayered : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class CompressedTexture2DArray : public CompressedTextureLayered { @@ -264,10 +264,10 @@ public: class ResourceFormatLoaderCompressedTexture3D : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; #endif // COMPRESSED_TEXTURE_H diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 02898ce984..c05b7a24e1 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -139,17 +139,17 @@ public: class ResourceFormatLoaderText : public ResourceFormatLoader { public: static ResourceFormatLoaderText *singleton; - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const; - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes); - - virtual String get_resource_type(const String &p_path) const; - virtual String get_resource_script_class(const String &p_path) const; - virtual ResourceUID::ID get_resource_uid(const String &p_path) const; - virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false); - virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map); + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override; + + virtual String get_resource_type(const String &p_path) const override; + virtual String get_resource_script_class(const String &p_path) const override; + virtual ResourceUID::ID get_resource_uid(const String &p_path) const override; + virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false) override; + virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) override; static Error convert_file_to_binary(const String &p_src_path, const String &p_dst_path); @@ -199,10 +199,10 @@ public: class ResourceFormatSaverText : public ResourceFormatSaver { public: static ResourceFormatSaverText *singleton; - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid); - virtual bool recognize(const Ref<Resource> &p_resource) const; - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual Error set_uid(const String &p_path, ResourceUID::ID p_uid) override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; ResourceFormatSaverText(); }; diff --git a/scene/resources/shader.h b/scene/resources/shader.h index ca889940ef..921143c219 100644 --- a/scene/resources/shader.h +++ b/scene/resources/shader.h @@ -96,17 +96,17 @@ VARIANT_ENUM_CAST(Shader::Mode); class ResourceFormatLoaderShader : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverShader : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // SHADER_H diff --git a/scene/resources/shader_include.h b/scene/resources/shader_include.h index a8949b327e..9fb4271623 100644 --- a/scene/resources/shader_include.h +++ b/scene/resources/shader_include.h @@ -58,17 +58,17 @@ public: class ResourceFormatLoaderShaderInclude : public ResourceFormatLoader { public: - virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); - virtual void get_recognized_extensions(List<String> *p_extensions) const; - virtual bool handles_type(const String &p_type) const; - virtual String get_resource_type(const String &p_path) const; + virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List<String> *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; }; class ResourceFormatSaverShaderInclude : public ResourceFormatSaver { public: - virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0); - virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; - virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual Error save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags = 0) override; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const override; + virtual bool recognize(const Ref<Resource> &p_resource) const override; }; #endif // SHADER_INCLUDE_H diff --git a/servers/SCsub b/servers/SCsub index 2e4430f030..736bed68ec 100644 --- a/servers/SCsub +++ b/servers/SCsub @@ -7,7 +7,6 @@ env.servers_sources = [] env.add_source_files(env.servers_sources, "audio_server.cpp") env.add_source_files(env.servers_sources, "camera_server.cpp") env.add_source_files(env.servers_sources, "display_server.cpp") -env.add_source_files(env.servers_sources, "native_menu.cpp") env.add_source_files(env.servers_sources, "navigation_server_2d.cpp") env.add_source_files(env.servers_sources, "navigation_server_3d.cpp") env.add_source_files(env.servers_sources, "physics_server_2d.cpp") @@ -19,6 +18,7 @@ env.add_source_files(env.servers_sources, "text_server.cpp") SConscript("audio/SCsub") SConscript("camera/SCsub") SConscript("debugger/SCsub") +SConscript("display/SCsub") SConscript("extensions/SCsub") SConscript("movie_writer/SCsub") SConscript("navigation/SCsub") diff --git a/servers/audio/effects/audio_effect_hard_limiter.cpp b/servers/audio/effects/audio_effect_hard_limiter.cpp new file mode 100644 index 0000000000..e717557af2 --- /dev/null +++ b/servers/audio/effects/audio_effect_hard_limiter.cpp @@ -0,0 +1,161 @@ +/**************************************************************************/ +/* audio_effect_hard_limiter.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "audio_effect_hard_limiter.h" + +#include "servers/audio_server.h" + +void AudioEffectHardLimiterInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) { + float sample_rate = AudioServer::get_singleton()->get_mix_rate(); + + float ceiling = Math::db_to_linear(base->ceiling); + float release = base->release; + float attack = base->attack; + float pre_gain = Math::db_to_linear(base->pre_gain); + + for (int i = 0; i < p_frame_count; i++) { + float sample_left = p_src_frames[i].left; + float sample_right = p_src_frames[i].right; + + sample_left *= pre_gain; + sample_right *= pre_gain; + + float largest_sample = MAX(ABS(sample_left), ABS(sample_right)); + + release_factor = MAX(0.0, release_factor - 1.0 / sample_rate); + release_factor = MIN(release_factor, release); + + if (release_factor > 0.0) { + gain = Math::lerp(gain_target, 1.0f, 1.0f - release_factor / release); + } + + if (largest_sample * gain > ceiling) { + gain_target = ceiling / largest_sample; + release_factor = release; + attack_factor = attack; + } + + // Lerp gain over attack time to avoid distortion. + attack_factor = MAX(0.0f, attack_factor - 1.0f / sample_rate); + if (attack_factor > 0.0) { + gain = Math::lerp(gain_target, gain, 1.0f - attack_factor / attack); + } + + int bucket_id = gain_bucket_cursor / gain_bucket_size; + + // If first item within the current bucket, reset the bucket. + if (gain_bucket_cursor % gain_bucket_size == 0) { + gain_buckets[bucket_id] = 1.0f; + } + + gain_buckets[bucket_id] = MIN(gain_buckets[bucket_id], gain); + + gain_bucket_cursor = (gain_bucket_cursor + 1) % gain_samples_to_store; + + for (int j = 0; j < (int)gain_buckets.size(); j++) { + gain = MIN(gain, gain_buckets[j]); + } + + // Introduce latency by grabbing the AudioFrame stored previously, + // then overwrite it with current audioframe, then update circular + // buffer cursor. + float dst_buffer_left = sample_buffer_left[sample_cursor]; + float dst_buffer_right = sample_buffer_right[sample_cursor]; + + sample_buffer_left[sample_cursor] = sample_left; + sample_buffer_right[sample_cursor] = sample_right; + + sample_cursor = (sample_cursor + 1) % sample_buffer_left.size(); + + p_dst_frames[i].left = dst_buffer_left * gain; + p_dst_frames[i].right = dst_buffer_right * gain; + } +} + +Ref<AudioEffectInstance> AudioEffectHardLimiter::instantiate() { + Ref<AudioEffectHardLimiterInstance> ins; + ins.instantiate(); + ins->base = Ref<AudioEffectHardLimiter>(this); + + float mix_rate = AudioServer::get_singleton()->get_mix_rate(); + + for (int i = 0; i < (int)Math::ceil(mix_rate * attack) + 1; i++) { + ins->sample_buffer_left.push_back(0.0f); + ins->sample_buffer_right.push_back(0.0f); + } + + ins->gain_samples_to_store = (int)Math::ceil(mix_rate * (attack + sustain) + 1); + ins->gain_bucket_size = (int)(mix_rate * attack); + + for (int i = 0; i < ins->gain_samples_to_store; i += ins->gain_bucket_size) { + ins->gain_buckets.push_back(1.0f); + } + + return ins; +} + +void AudioEffectHardLimiter::set_ceiling_db(float p_ceiling) { + ceiling = p_ceiling; +} + +float AudioEffectHardLimiter::get_ceiling_db() const { + return ceiling; +} + +float AudioEffectHardLimiter::get_pre_gain_db() const { + return pre_gain; +} + +void AudioEffectHardLimiter::set_pre_gain_db(const float p_pre_gain) { + pre_gain = p_pre_gain; +} + +float AudioEffectHardLimiter::get_release() const { + return release; +} + +void AudioEffectHardLimiter::set_release(const float p_release) { + release = p_release; +} + +void AudioEffectHardLimiter::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_ceiling_db", "ceiling"), &AudioEffectHardLimiter::set_ceiling_db); + ClassDB::bind_method(D_METHOD("get_ceiling_db"), &AudioEffectHardLimiter::get_ceiling_db); + + ClassDB::bind_method(D_METHOD("set_pre_gain_db", "p_pre_gain"), &AudioEffectHardLimiter::set_pre_gain_db); + ClassDB::bind_method(D_METHOD("get_pre_gain_db"), &AudioEffectHardLimiter::get_pre_gain_db); + + ClassDB::bind_method(D_METHOD("set_release", "p_release"), &AudioEffectHardLimiter::set_release); + ClassDB::bind_method(D_METHOD("get_release"), &AudioEffectHardLimiter::get_release); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pre_gain_db", PROPERTY_HINT_RANGE, "-24,24,0.01,suffix:dB"), "set_pre_gain_db", "get_pre_gain_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ceiling_db", PROPERTY_HINT_RANGE, "-24,0.0,0.01,suffix:dB"), "set_ceiling_db", "get_ceiling_db"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "release", PROPERTY_HINT_RANGE, "0.01,3,0.01"), "set_release", "get_release"); +} diff --git a/servers/audio/effects/audio_effect_hard_limiter.h b/servers/audio/effects/audio_effect_hard_limiter.h new file mode 100644 index 0000000000..7e28480797 --- /dev/null +++ b/servers/audio/effects/audio_effect_hard_limiter.h @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* audio_effect_hard_limiter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef AUDIO_EFFECT_HARD_LIMITER_H +#define AUDIO_EFFECT_HARD_LIMITER_H + +#include "servers/audio/audio_effect.h" + +class AudioEffectHardLimiter; + +class AudioEffectHardLimiterInstance : public AudioEffectInstance { + GDCLASS(AudioEffectHardLimiterInstance, AudioEffectInstance); + friend class AudioEffectHardLimiter; + Ref<AudioEffectHardLimiter> base; + +private: + int sample_cursor = 0; + + float release_factor = 0; + float attack_factor = 0; + float gain = 1; + float gain_target = 1; + + LocalVector<float> sample_buffer_left; + LocalVector<float> sample_buffer_right; + + int gain_samples_to_store = 0; + int gain_bucket_cursor = 0; + int gain_bucket_size = 0; + LocalVector<float> gain_buckets; + +public: + virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) override; +}; + +class AudioEffectHardLimiter : public AudioEffect { + GDCLASS(AudioEffectHardLimiter, AudioEffect); + + friend class AudioEffectHardLimiterInstance; + float pre_gain = 0.0f; + float ceiling = -0.3f; + float sustain = 0.02f; + float release = 0.1f; + const float attack = 0.002; + +protected: + static void _bind_methods(); + +public: + void set_ceiling_db(float p_ceiling); + float get_ceiling_db() const; + + void set_release(float p_release); + float get_release() const; + + void set_pre_gain_db(float p_pre_gain); + float get_pre_gain_db() const; + + Ref<AudioEffectInstance> instantiate() override; +}; + +#endif // AUDIO_EFFECT_HARD_LIMITER_H diff --git a/servers/display/SCsub b/servers/display/SCsub new file mode 100644 index 0000000000..86681f9c74 --- /dev/null +++ b/servers/display/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.servers_sources, "*.cpp") diff --git a/servers/native_menu.cpp b/servers/display/native_menu.cpp index d1894ba6c3..ca46560c7c 100644 --- a/servers/native_menu.cpp +++ b/servers/display/native_menu.cpp @@ -68,6 +68,7 @@ void NativeMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("find_item_index_with_text", "rid", "text"), &NativeMenu::find_item_index_with_text); ClassDB::bind_method(D_METHOD("find_item_index_with_tag", "rid", "tag"), &NativeMenu::find_item_index_with_tag); + ClassDB::bind_method(D_METHOD("find_item_index_with_submenu", "rid", "submenu_rid"), &NativeMenu::find_item_index_with_submenu); ClassDB::bind_method(D_METHOD("is_item_checked", "rid", "idx"), &NativeMenu::is_item_checked); ClassDB::bind_method(D_METHOD("is_item_checkable", "rid", "idx"), &NativeMenu::is_item_checkable); @@ -263,6 +264,19 @@ int NativeMenu::find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) return -1; } +int NativeMenu::find_item_index_with_submenu(const RID &p_rid, const RID &p_submenu_rid) const { + if (!has_menu(p_rid) || !has_menu(p_submenu_rid)) { + return -1; + } + int count = get_item_count(p_rid); + for (int i = 0; i < count; i++) { + if (p_submenu_rid == get_item_submenu(p_rid, i)) { + return i; + } + } + return -1; +} + bool NativeMenu::is_item_checked(const RID &p_rid, int p_idx) const { WARN_PRINT("Global menus are not supported on this platform."); return false; diff --git a/servers/native_menu.h b/servers/display/native_menu.h index f65e193972..2bc061a216 100644 --- a/servers/native_menu.h +++ b/servers/display/native_menu.h @@ -102,6 +102,7 @@ public: virtual int find_item_index_with_text(const RID &p_rid, const String &p_text) const; virtual int find_item_index_with_tag(const RID &p_rid, const Variant &p_tag) const; + virtual int find_item_index_with_submenu(const RID &p_rid, const RID &p_submenu_rid) const; virtual bool is_item_checked(const RID &p_rid, int p_idx) const; virtual bool is_item_checkable(const RID &p_rid, int p_idx) const; diff --git a/servers/display_server.h b/servers/display_server.h index 85b791ca69..39a80ae7c1 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -36,7 +36,7 @@ #include "core/os/os.h" #include "core/variant/callable.h" -#include "native_menu.h" +#include "display/native_menu.h" class Texture2D; class Image; diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 4e49129941..c03c0b7a40 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -43,6 +43,7 @@ #include "audio/effects/audio_effect_distortion.h" #include "audio/effects/audio_effect_eq.h" #include "audio/effects/audio_effect_filter.h" +#include "audio/effects/audio_effect_hard_limiter.h" #include "audio/effects/audio_effect_limiter.h" #include "audio/effects/audio_effect_panner.h" #include "audio/effects/audio_effect_phaser.h" @@ -56,11 +57,11 @@ #include "camera/camera_feed.h" #include "camera_server.h" #include "debugger/servers_debugger.h" +#include "display/native_menu.h" #include "display_server.h" #include "movie_writer/movie_writer.h" #include "movie_writer/movie_writer_mjpeg.h" #include "movie_writer/movie_writer_pngwav.h" -#include "native_menu.h" #include "rendering/renderer_compositor.h" #include "rendering/renderer_rd/framebuffer_cache_rd.h" #include "rendering/renderer_rd/storage_rd/render_data_rd.h" @@ -210,6 +211,7 @@ void register_server_types() { GDREGISTER_CLASS(AudioEffectDelay); GDREGISTER_CLASS(AudioEffectCompressor); GDREGISTER_CLASS(AudioEffectLimiter); + GDREGISTER_CLASS(AudioEffectHardLimiter); GDREGISTER_CLASS(AudioEffectPitchShift); GDREGISTER_CLASS(AudioEffectPhaser); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 33bb5459f2..d78f3ba05b 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -546,6 +546,8 @@ void RenderForwardClustered::_render_list(RenderingDevice::DrawListID p_draw_lis VALID_FLAG_COMBINATION(COLOR_PASS_FLAG_MULTIVIEW); VALID_FLAG_COMBINATION(COLOR_PASS_FLAG_MULTIVIEW | COLOR_PASS_FLAG_MOTION_VECTORS); VALID_FLAG_COMBINATION(COLOR_PASS_FLAG_MOTION_VECTORS); + VALID_FLAG_COMBINATION(COLOR_PASS_FLAG_SEPARATE_SPECULAR | COLOR_PASS_FLAG_MULTIVIEW | COLOR_PASS_FLAG_MOTION_VECTORS); + VALID_FLAG_COMBINATION(COLOR_PASS_FLAG_TRANSPARENT | COLOR_PASS_FLAG_MULTIVIEW | COLOR_PASS_FLAG_MOTION_VECTORS); default: { ERR_FAIL_MSG("Invalid color pass flag combination " + itos(p_params->color_pass_flags)); } diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 027dcac92d..64f03e5879 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -338,10 +338,12 @@ TEST_CASE("[String] Natural compare function test") { TEST_CASE("[String] File compare function test") { String a = "_img2.png"; + String b = "img2.png"; CHECK(a.nocasecmp_to("img10.png") > 0); CHECK_MESSAGE(a.filenocasecmp_to("img10.png") < 0, "Should sort before letters."); CHECK_MESSAGE(a.filenocasecmp_to(".img10.png") > 0, "Should sort after period."); + CHECK(b.filenocasecmp_to("img3.png") < 0); } TEST_CASE("[String] hex_encode_buffer") { diff --git a/tests/create_test.py b/tests/create_test.py index 4d1f1d656e..deb53aca20 100644 --- a/tests/create_test.py +++ b/tests/create_test.py @@ -101,7 +101,7 @@ TEST_CASE("[{name_pascal_case}] Example test case") {{ if args.invasive: print("Trying to insert include directive in test_main.cpp...") - with open("test_main.cpp", "r") as file: + with open("test_main.cpp", "r", encoding="utf-8") as file: contents = file.read() match = re.search(r'#include "tests.*\n', contents) diff --git a/tests/python_build/test_gles3_builder.py b/tests/python_build/test_gles3_builder.py index 861e0b84c4..6f16139eb9 100644 --- a/tests/python_build/test_gles3_builder.py +++ b/tests/python_build/test_gles3_builder.py @@ -17,15 +17,15 @@ def test_gles3_builder(shader_files, builder, header_struct): builder(shader_files["path_input"], "drivers/gles3/shader_gles3.h", "GLES3", header_data=header) - with open(shader_files["path_expected_parts"], "r") as f: + with open(shader_files["path_expected_parts"], "r", encoding="utf-8") as f: expected_parts = json.load(f) assert expected_parts == header.__dict__ - with open(shader_files["path_output"], "r") as f: + with open(shader_files["path_output"], "r", encoding="utf-8") as f: actual_output = f.read() assert actual_output - with open(shader_files["path_expected_full"], "r") as f: + with open(shader_files["path_expected_full"], "r", encoding="utf-8") as f: expected_output = f.read() assert actual_output == expected_output diff --git a/tests/python_build/test_glsl_builder.py b/tests/python_build/test_glsl_builder.py index b9dcef48ac..348ef8441c 100644 --- a/tests/python_build/test_glsl_builder.py +++ b/tests/python_build/test_glsl_builder.py @@ -23,15 +23,15 @@ def test_glsl_builder(shader_files, builder, header_struct): header = header_struct() builder(shader_files["path_input"], header_data=header) - with open(shader_files["path_expected_parts"], "r") as f: + with open(shader_files["path_expected_parts"], "r", encoding="utf-8") as f: expected_parts = json.load(f) assert expected_parts == header.__dict__ - with open(shader_files["path_output"], "r") as f: + with open(shader_files["path_output"], "r", encoding="utf-8") as f: actual_output = f.read() assert actual_output - with open(shader_files["path_expected_full"], "r") as f: + with open(shader_files["path_expected_full"], "r", encoding="utf-8") as f: expected_output = f.read() assert actual_output == expected_output diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 8f603c698d..8577dd7148 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -802,6 +802,153 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_selected_text(3) == "test"); } + SUBCASE("[TextEdit] skip selection for next occurrence") { + text_edit->set_text("\ntest other_test\nrandom test\nword test word nonrandom"); + text_edit->set_caret_column(0); + text_edit->set_caret_line(1); + + // Without selection on the current caret, the caret as 'jumped' to the next occurrence of the word under the caret. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 13); + + // Repeating previous action. + // This time caret is in 'other_test' (other_|test) + // so the searched term will be 'other_test' or not just 'test' + // => no occurrence, as a side effect, the caret will move to start of the term. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 7); + + // Repeating action again should do nothing now + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK_FALSE(text_edit->has_selection(0)); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 7); + + // Moving back to the first 'test' occurrence. + text_edit->set_caret_column(0); + text_edit->set_caret_line(1); + + // But this time, create a selection of it. + text_edit->add_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 1); + CHECK(text_edit->get_selection_from_column(0) == 0); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 4); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + + // Then, skipping it, but this time, selection has been made on the next occurrence. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 1); + CHECK(text_edit->get_selection_from_column(0) == 13); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 17); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 17); + + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 2); + CHECK(text_edit->get_selection_from_column(0) == 9); + CHECK(text_edit->get_selection_to_line(0) == 2); + CHECK(text_edit->get_selection_to_column(0) == 13); + CHECK(text_edit->get_caret_line(0) == 2); + CHECK(text_edit->get_caret_column(0) == 13); + + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 3); + CHECK(text_edit->get_selection_from_column(0) == 5); + CHECK(text_edit->get_selection_to_line(0) == 3); + CHECK(text_edit->get_selection_to_column(0) == 9); + CHECK(text_edit->get_caret_line(0) == 3); + CHECK(text_edit->get_caret_column(0) == 9); + + // Last skip, we are back to the first occurrence. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 1); + CHECK(text_edit->get_selection_from_column(0) == 0); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 4); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + + // Adding first occurrence to selections/carets list + // and select occurrence on 'other_test'. + text_edit->add_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + + CHECK(text_edit->has_selection(1)); + CHECK(text_edit->get_selected_text(1) == "test"); + CHECK(text_edit->get_selection_from_line(1) == 1); + CHECK(text_edit->get_selection_from_column(1) == 13); + CHECK(text_edit->get_selection_to_line(1) == 1); + CHECK(text_edit->get_selection_to_column(1) == 17); + CHECK(text_edit->get_caret_line(1) == 1); + CHECK(text_edit->get_caret_column(1) == 17); + + // We don't want this occurrence. + // Let's skip it. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + + CHECK(text_edit->get_selected_text(1) == "test"); + CHECK(text_edit->get_selection_from_line(1) == 2); + CHECK(text_edit->get_selection_from_column(1) == 9); + CHECK(text_edit->get_selection_to_line(1) == 2); + CHECK(text_edit->get_selection_to_column(1) == 13); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 13); + + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + + CHECK(text_edit->get_selected_text(1) == "test"); + CHECK(text_edit->get_selection_from_line(1) == 3); + CHECK(text_edit->get_selection_from_column(1) == 5); + CHECK(text_edit->get_selection_to_line(1) == 3); + CHECK(text_edit->get_selection_to_column(1) == 9); + CHECK(text_edit->get_caret_line(1) == 3); + CHECK(text_edit->get_caret_column(1) == 9); + + // We are back the first occurrence. + text_edit->skip_selection_for_next_occurrence(); + CHECK(text_edit->get_caret_count() == 1); + CHECK(text_edit->has_selection(0)); + CHECK(text_edit->get_selected_text(0) == "test"); + CHECK(text_edit->get_selection_from_line(0) == 1); + CHECK(text_edit->get_selection_from_column(0) == 0); + CHECK(text_edit->get_selection_to_line(0) == 1); + CHECK(text_edit->get_selection_to_column(0) == 4); + CHECK(text_edit->get_caret_line(0) == 1); + CHECK(text_edit->get_caret_column(0) == 4); + } + SUBCASE("[TextEdit] deselect on focus loss") { text_edit->set_text("test"); |