diff options
84 files changed, 1362 insertions, 668 deletions
diff --git a/.github/actions/godot-deps/action.yml b/.github/actions/godot-deps/action.yml index 07a364cd79..607666ec5f 100644 --- a/.github/actions/godot-deps/action.yml +++ b/.github/actions/godot-deps/action.yml @@ -3,7 +3,7 @@ description: Setup Python, install the pip version of SCons. inputs: python-version: description: The Python version to use. - default: "3.x" + default: "3.12.3" python-arch: description: The Python architecture. default: "x64" diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 0996db9d89..e8a6a5075b 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" #include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/io/file_access_compressed.h" #include "core/io/file_access_encrypted.h" #include "core/io/marshalls.h" @@ -1919,6 +1920,16 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) { ::EngineDebugger::get_singleton()->send_message(p_msg, p_data); } +void EngineDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { + ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't send debug. No active debugger"); + ::EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint); +} + +void EngineDebugger::script_debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't send debug. No active debugger"); + ::EngineDebugger::get_script_debugger()->debug(p_lang, p_can_continue, p_is_error_breakpoint); +} + Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { Callable &capture = *(Callable *)p_user; if (!capture.is_valid()) { @@ -1935,6 +1946,56 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra return OK; } +void EngineDebugger::line_poll() { + ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't poll. No active debugger"); + ::EngineDebugger::get_singleton()->line_poll(); +} + +void EngineDebugger::set_lines_left(int p_lines) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set lines left. No active debugger"); + ::EngineDebugger::get_script_debugger()->set_lines_left(p_lines); +} + +int EngineDebugger::get_lines_left() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get lines left. No active debugger"); + return ::EngineDebugger::get_script_debugger()->get_lines_left(); +} + +void EngineDebugger::set_depth(int p_depth) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set depth. No active debugger"); + ::EngineDebugger::get_script_debugger()->set_depth(p_depth); +} + +int EngineDebugger::get_depth() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get depth. No active debugger"); + return ::EngineDebugger::get_script_debugger()->get_depth(); +} + +bool EngineDebugger::is_breakpoint(int p_line, const StringName &p_source) const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check breakpoint. No active debugger"); + return ::EngineDebugger::get_script_debugger()->is_breakpoint(p_line, p_source); +} + +bool EngineDebugger::is_skipping_breakpoints() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check skipping breakpoint. No active debugger"); + return ::EngineDebugger::get_script_debugger()->is_skipping_breakpoints(); +} + +void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't insert breakpoint. No active debugger"); + ::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source); +} + +void EngineDebugger::remove_breakpoint(int p_line, const StringName &p_source) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't remove breakpoint. No active debugger"); + ::EngineDebugger::get_script_debugger()->remove_breakpoint(p_line, p_source); +} + +void EngineDebugger::clear_breakpoints() { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't clear breakpoints. No active debugger"); + ::EngineDebugger::get_script_debugger()->clear_breakpoints(); +} + EngineDebugger::~EngineDebugger() { for (const KeyValue<StringName, Callable> &E : captures) { ::EngineDebugger::unregister_message_capture(E.key); @@ -1960,7 +2021,23 @@ void EngineDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EngineDebugger::unregister_message_capture); ClassDB::bind_method(D_METHOD("has_capture", "name"), &EngineDebugger::has_capture); + ClassDB::bind_method(D_METHOD("line_poll"), &EngineDebugger::line_poll); + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EngineDebugger::send_message); + ClassDB::bind_method(D_METHOD("debug", "can_continue", "is_error_breakpoint"), &EngineDebugger::debug, DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("script_debug", "language", "can_continue", "is_error_breakpoint"), &EngineDebugger::script_debug, DEFVAL(true), DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("set_lines_left", "lines"), &EngineDebugger::set_lines_left); + ClassDB::bind_method(D_METHOD("get_lines_left"), &EngineDebugger::get_lines_left); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &EngineDebugger::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &EngineDebugger::get_depth); + + ClassDB::bind_method(D_METHOD("is_breakpoint", "line", "source"), &EngineDebugger::is_breakpoint); + ClassDB::bind_method(D_METHOD("is_skipping_breakpoints"), &EngineDebugger::is_skipping_breakpoints); + ClassDB::bind_method(D_METHOD("insert_breakpoint", "line", "source"), &EngineDebugger::insert_breakpoint); + ClassDB::bind_method(D_METHOD("remove_breakpoint", "line", "source"), &EngineDebugger::remove_breakpoint); + ClassDB::bind_method(D_METHOD("clear_breakpoints"), &EngineDebugger::clear_breakpoints); } } // namespace core_bind diff --git a/core/core_bind.h b/core/core_bind.h index 148e0ad83e..febc33a9c1 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -576,9 +576,25 @@ public: bool has_capture(const StringName &p_name); void send_message(const String &p_msg, const Array &p_data); + void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false); + void script_debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false); static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); + void line_poll(); + + void set_lines_left(int p_lines); + int get_lines_left() const; + + void set_depth(int p_depth); + int get_depth() const; + + bool is_breakpoint(int p_line, const StringName &p_source) const; + bool is_skipping_breakpoints() const; + void insert_breakpoint(int p_line, const StringName &p_source); + void remove_breakpoint(int p_line, const StringName &p_source); + void clear_breakpoints(); + EngineDebugger() { singleton = this; } ~EngineDebugger(); }; diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 1cf388b33a..a762e24ff3 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -59,11 +59,9 @@ bool FileAccess::exists(const String &p_name) { return true; } - Ref<FileAccess> f = open(p_name, READ); - if (f.is_null()) { - return false; - } - return true; + // Using file_exists because it's faster then trying to open the file. + Ref<FileAccess> ret = create_for_path(p_name); + return ret->file_exists(p_name); } void FileAccess::_set_access_type(AccessType p_access) { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index fcf4a727ca..49ba7b0358 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -362,6 +362,24 @@ Variant ResourceFormatImporter::get_resource_metadata(const String &p_path) cons return pat.metadata; } + +Error ResourceFormatImporter::get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const { + PathAndType pat; + Error err = _get_path_and_type(p_path, pat); + + if (err == OK) { + r_type = pat.type; + r_uid = pat.uid; + r_import_group_file = pat.group_file; + } else { + r_type = ""; + r_uid = ResourceUID::INVALID_ID; + r_import_group_file = ""; + } + + return err; +} + void ResourceFormatImporter::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) { PathAndType pat; Error err = _get_path_and_type(p_path, pat); diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index dbd9e70d16..4a38a7fb4a 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -93,6 +93,9 @@ public: String get_import_settings_hash() const; String get_import_base_path(const String &p_for_file) const; + + Error get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const; + ResourceFormatImporter(); }; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index c3c37aa89d..7299631fec 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -957,36 +957,39 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem new_path = path_remaps[new_path]; } else { // Try file remap. - Error err; - Ref<FileAccess> f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); - if (f.is_valid()) { - VariantParser::StreamFile stream; - stream.f = f; - - String assign; - Variant value; - VariantParser::Tag next_tag; - - int lines = 0; - String error_text; - while (true) { - assign = Variant(); - next_tag.fields.clear(); - next_tag.name = String(); - - err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); - if (err == ERR_FILE_EOF) { - break; - } else if (err != OK) { - ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); - break; - } + // Usually, there's no remap file and FileAccess::exists() is faster then FileAccess::open(). + if (FileAccess::exists(new_path + ".remap")) { + Error err; + Ref<FileAccess> f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err); + if (f.is_valid()) { + VariantParser::StreamFile stream; + stream.f = f; + + String assign; + Variant value; + VariantParser::Tag next_tag; + + int lines = 0; + String error_text; + while (true) { + assign = Variant(); + next_tag.fields.clear(); + next_tag.name = String(); + + err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true); + if (err == ERR_FILE_EOF) { + break; + } else if (err != OK) { + ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + "."); + break; + } - if (assign == "path") { - new_path = value; - break; - } else if (next_tag.name != "remap") { - break; + if (assign == "path") { + new_path = value; + break; + } else if (next_tag.name != "remap") { + break; + } } } } diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index a18ef8d4d7..7b643e4637 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -132,6 +132,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_debug_get_stack_level_line, "level"); GDVIRTUAL_BIND(_debug_get_stack_level_function, "level"); + GDVIRTUAL_BIND(_debug_get_stack_level_source, "level"); GDVIRTUAL_BIND(_debug_get_stack_level_locals, "level", "max_subitems", "max_depth"); GDVIRTUAL_BIND(_debug_get_stack_level_members, "level", "max_subitems", "max_depth"); GDVIRTUAL_BIND(_debug_get_stack_level_instance, "level"); diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 26ed881502..3b7a6e66fe 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -360,9 +360,12 @@ <param index="1" name="time" type="float" /> <param index="2" name="find_mode" type="int" enum="Animation.FindMode" default="0" /> <param index="3" name="limit" type="bool" default="false" /> + <param index="4" name="backward" type="bool" default="false" /> <description> Finds the key index by time in a given track. Optionally, only find it if the approx/exact time is given. If [param limit] is [code]true[/code], it does not return keys outside the animation range. + If [param backward] is [code]true[/code], the direction is reversed in methods that rely on one directional processing. + For example, in case [param find_mode] is [constant FIND_MODE_NEAREST], if there is no key in the current position just after seeked, the first key found is retrieved by searching before the position, but if [param backward] is [code]true[/code], the first key found is retrieved after the position. </description> </method> <method name="track_get_interpolation_loop_wrap" qualifiers="const"> @@ -583,6 +586,7 @@ <param index="2" name="backward" type="bool" default="false" /> <description> Returns the interpolated value at the given time (in seconds). The [param track_idx] must be the index of a value track. + A [param backward] mainly affects the direction of key retrieval of the track with [constant UPDATE_DISCRETE] converted by [constant AnimationMixer.ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS] to match the result with [method track_find_key]. </description> </method> <method name="value_track_set_update_mode"> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 338d9523fa..41bda1033d 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -444,12 +444,14 @@ Transforms (multiplies) the [param right] vector by this basis, returning a [Vector3]. [codeblocks] [gdscript] - var my_basis = Basis(Vector3(1, 1, 1), Vector3(1, 1, 1), Vector3(0, 2, 5)) - print(my_basis * Vector3(1, 2, 3)) # Prints (7, 3, 16) + # Basis that swaps the X/Z axes and doubles the scale. + var my_basis = Basis(Vector3(0, 2, 0), Vector3(2, 0, 0), Vector3(0, 0, 2)) + print(my_basis * Vector3(1, 2, 3)) # Prints (4, 2, 6) [/gdscript] [csharp] - var myBasis = new Basis(new Vector3(1, 1, 1), new Vector3(1, 1, 1), new Vector3(0, 2, 5)); - GD.Print(my_basis * new Vector3(1, 2, 3)); // Prints (7, 3, 16) + // Basis that swaps the X/Z axes and doubles the scale. + var myBasis = new Basis(new Vector3(0, 2, 0), new Vector3(2, 0, 0), new Vector3(0, 0, 2)); + GD.Print(myBasis * new Vector3(1, 2, 3)); // Prints (4, 2, 6) [/csharp] [/codeblocks] </description> diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index d2af6179d9..98f25ed573 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -118,6 +118,9 @@ <theme_item name="icon_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Icon modulate [Color] used when the [Button] is being pressed. </theme_item> + <theme_item name="align_to_largest_stylebox" data_type="constant" type="int" default="0"> + This constant acts as a boolean. If [code]true[/code], text and icon are always aligned to the largest stylebox margins, otherwise it's aligned to the current button state stylebox margins. + </theme_item> <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used. </theme_item> diff --git a/doc/classes/EditorDebuggerPlugin.xml b/doc/classes/EditorDebuggerPlugin.xml index 4c602b8359..a519e43bc6 100644 --- a/doc/classes/EditorDebuggerPlugin.xml +++ b/doc/classes/EditorDebuggerPlugin.xml @@ -47,6 +47,21 @@ <tutorials> </tutorials> <methods> + <method name="_breakpoint_set_in_tree" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="script" type="Script" /> + <param index="1" name="line" type="int" /> + <param index="2" name="enabled" type="bool" /> + <description> + Override this method to be notified when a breakpoint is set in the editor. + </description> + </method> + <method name="_breakpoints_cleared_in_tree" qualifiers="virtual"> + <return type="void" /> + <description> + Override this method to be notified when all breakpoints are cleared in the editor. + </description> + </method> <method name="_capture" qualifiers="virtual"> <return type="bool" /> <param index="0" name="message" type="String" /> @@ -56,6 +71,14 @@ Override this method to process incoming messages. The [param session_id] is the ID of the [EditorDebuggerSession] that received the message (which you can retrieve via [method get_session]). </description> </method> + <method name="_goto_script_line" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="script" type="Script" /> + <param index="1" name="line" type="int" /> + <description> + Override this method to be notified when a breakpoint line has been clicked in the debugger breakpoint panel. + </description> + </method> <method name="_has_capture" qualifiers="virtual const"> <return type="bool" /> <param index="0" name="capture" type="String" /> diff --git a/doc/classes/EditorDebuggerSession.xml b/doc/classes/EditorDebuggerSession.xml index c6c632be01..b4e754cc7e 100644 --- a/doc/classes/EditorDebuggerSession.xml +++ b/doc/classes/EditorDebuggerSession.xml @@ -50,6 +50,15 @@ Sends the given [param message] to the attached remote instance, optionally passing additionally [param data]. See [EngineDebugger] for how to retrieve those messages. </description> </method> + <method name="set_breakpoint"> + <return type="void" /> + <param index="0" name="path" type="String" /> + <param index="1" name="line" type="int" /> + <param index="2" name="enabled" type="bool" /> + <description> + Enables or disables a specific breakpoint based on [param enabled], updating the Editor Breakpoint Panel accordingly. + </description> + </method> <method name="toggle_profiler"> <return type="void" /> <param index="0" name="profiler" type="String" /> diff --git a/doc/classes/EditorFileSystem.xml b/doc/classes/EditorFileSystem.xml index 08b40c7800..f3129ede65 100644 --- a/doc/classes/EditorFileSystem.xml +++ b/doc/classes/EditorFileSystem.xml @@ -90,6 +90,18 @@ Emitted if at least one resource is reloaded when the filesystem is scanned. </description> </signal> + <signal name="scan_started"> + <param index="0" name="complete_scan" type="bool" /> + <description> + Emitted when a new scan of the project files has started. + </description> + </signal> + <signal name="scan_stopped"> + <param index="0" name="complete_scan" type="bool" /> + <description> + Emitted when a scan of the project files has ended. + </description> + </signal> <signal name="script_classes_updated"> <description> Emitted when the list of global script classes gets updated. diff --git a/doc/classes/EngineDebugger.xml b/doc/classes/EngineDebugger.xml index 29ac04f097..7583520da0 100644 --- a/doc/classes/EngineDebugger.xml +++ b/doc/classes/EngineDebugger.xml @@ -9,6 +9,32 @@ <tutorials> </tutorials> <methods> + <method name="clear_breakpoints"> + <return type="void" /> + <description> + Clears all breakpoints. + </description> + </method> + <method name="debug"> + <return type="void" /> + <param index="0" name="can_continue" type="bool" default="true" /> + <param index="1" name="is_error_breakpoint" type="bool" default="false" /> + <description> + Starts a debug break in script execution, optionally specifying whether the program can continue based on [param can_continue] and whether the break was due to a breakpoint. + </description> + </method> + <method name="get_depth" qualifiers="const" experimental=""> + <return type="int" /> + <description> + Returns the current debug depth. + </description> + </method> + <method name="get_lines_left" qualifiers="const" experimental=""> + <return type="int" /> + <description> + Returns the number of lines that remain. + </description> + </method> <method name="has_capture"> <return type="bool" /> <param index="0" name="name" type="StringName" /> @@ -23,12 +49,28 @@ Returns [code]true[/code] if a profiler with the given name is present otherwise [code]false[/code]. </description> </method> + <method name="insert_breakpoint"> + <return type="void" /> + <param index="0" name="line" type="int" /> + <param index="1" name="source" type="StringName" /> + <description> + Inserts a new breakpoint with the given [param source] and [param line]. + </description> + </method> <method name="is_active"> <return type="bool" /> <description> Returns [code]true[/code] if the debugger is active otherwise [code]false[/code]. </description> </method> + <method name="is_breakpoint" qualifiers="const"> + <return type="bool" /> + <param index="0" name="line" type="int" /> + <param index="1" name="source" type="StringName" /> + <description> + Returns [code]true[/code] if the given [param source] and [param line] represent an existing breakpoint. + </description> + </method> <method name="is_profiling"> <return type="bool" /> <param index="0" name="name" type="StringName" /> @@ -36,6 +78,18 @@ Returns [code]true[/code] if a profiler with the given name is present and active otherwise [code]false[/code]. </description> </method> + <method name="is_skipping_breakpoints" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the debugger is skipping breakpoints otherwise [code]false[/code]. + </description> + </method> + <method name="line_poll"> + <return type="void" /> + <description> + Forces a processing loop of debugger events. The purpose of this method is just processing events every now and then when the script might get too busy, so that bugs like infinite loops can be caught + </description> + </method> <method name="profiler_add_frame_data"> <return type="void" /> <param index="0" name="name" type="StringName" /> @@ -70,6 +124,23 @@ Registers a profiler with the given [param name]. See [EngineProfiler] for more information. </description> </method> + <method name="remove_breakpoint"> + <return type="void" /> + <param index="0" name="line" type="int" /> + <param index="1" name="source" type="StringName" /> + <description> + Removes a breakpoint with the given [param source] and [param line]. + </description> + </method> + <method name="script_debug"> + <return type="void" /> + <param index="0" name="language" type="ScriptLanguage" /> + <param index="1" name="can_continue" type="bool" default="true" /> + <param index="2" name="is_error_breakpoint" type="bool" default="false" /> + <description> + Starts a debug break in script execution, optionally specifying whether the program can continue based on [param can_continue] and whether the break was due to a breakpoint. + </description> + </method> <method name="send_message"> <return type="void" /> <param index="0" name="message" type="String" /> @@ -78,6 +149,20 @@ Sends a message with given [param message] and [param data] array. </description> </method> + <method name="set_depth" experimental=""> + <return type="void" /> + <param index="0" name="depth" type="int" /> + <description> + Sets the current debugging depth. + </description> + </method> + <method name="set_lines_left" experimental=""> + <return type="void" /> + <param index="0" name="lines" type="int" /> + <description> + Sets the current debugging lines that remain. + </description> + </method> <method name="unregister_message_capture"> <return type="void" /> <param index="0" name="name" type="StringName" /> diff --git a/doc/classes/ScriptLanguageExtension.xml b/doc/classes/ScriptLanguageExtension.xml index a453866e27..cc47ca274d 100644 --- a/doc/classes/ScriptLanguageExtension.xml +++ b/doc/classes/ScriptLanguageExtension.xml @@ -108,6 +108,13 @@ <description> </description> </method> + <method name="_debug_get_stack_level_source" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="level" type="int" /> + <description> + Returns the source associated with a given debug stack position. + </description> + </method> <method name="_debug_parse_stack_level_expression" qualifiers="virtual"> <return type="String" /> <param index="0" name="level" type="int" /> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 610550d8bd..1167b70c8d 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -155,6 +155,13 @@ Returns the rest transform for a bone [param bone_idx]. </description> </method> + <method name="get_concatenated_bone_names" qualifiers="const"> + <return type="StringName" /> + <description> + Returns all bone names concatenated with commas ([code],[/code]) as a single [StringName]. + It is useful to set it as a hint for the enum property. + </description> + </method> <method name="get_parentless_bones" qualifiers="const"> <return type="PackedInt32Array" /> <description> diff --git a/doc/classes/SkeletonModifier3D.xml b/doc/classes/SkeletonModifier3D.xml index c0b1b6fd53..620eed9b70 100644 --- a/doc/classes/SkeletonModifier3D.xml +++ b/doc/classes/SkeletonModifier3D.xml @@ -18,6 +18,12 @@ [method _process_modification] must not apply [member influence] to bone poses because the [Skeleton3D] automatically applies influence to all bone poses set by the modifier. </description> </method> + <method name="get_skeleton" qualifiers="const"> + <return type="Skeleton3D" /> + <description> + Get parent [Skeleton3D] node if found. + </description> + </method> </methods> <members> <member name="active" type="bool" setter="set_active" getter="is_active" default="true"> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 687c7194cd..3f70810a7f 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -486,8 +486,8 @@ Show or hide the TileMap's navigation meshes. If set to [constant VISIBILITY_MODE_DEFAULT], this depends on the show navigation debug settings. </member> <member name="rendering_quadrant_size" type="int" setter="set_rendering_quadrant_size" getter="get_rendering_quadrant_size" default="16"> - The TileMap's quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quandrant size groups together [code]16 * 16 = 256[/code] tiles. - The quadrant size does not apply on Y-sorted layers, as tiles are be grouped by Y position instead in that case. + The TileMap's quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles. + The quadrant size does not apply on Y-sorted layers, as tiles are grouped by Y position instead in that case. [b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the TileMap's local coordinate system. </member> <member name="tile_set" type="TileSet" setter="set_tileset" getter="get_tileset"> diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml index 1bff6d911b..b9acef2095 100644 --- a/doc/classes/TileMapLayer.xml +++ b/doc/classes/TileMapLayer.xml @@ -264,8 +264,8 @@ Show or hide the [TileMapLayer]'s navigation meshes. If set to [constant DEBUG_VISIBILITY_MODE_DEFAULT], this depends on the show navigation debug settings. </member> <member name="rendering_quadrant_size" type="int" setter="set_rendering_quadrant_size" getter="get_rendering_quadrant_size" default="16"> - The [TileMapLayer]'s quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quandrant size groups together [code]16 * 16 = 256[/code] tiles. - The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are be grouped by Y position instead in that case. + The [TileMapLayer]'s quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles. + The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are grouped by Y position instead in that case. [b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the [TileMapLayer]'s local coordinate system. </member> <member name="tile_map_data" type="PackedByteArray" setter="set_tile_map_data_from_array" getter="get_tile_map_data_as_array" default="PackedByteArray()"> @@ -277,6 +277,9 @@ <member name="use_kinematic_bodies" type="bool" setter="set_use_kinematic_bodies" getter="is_using_kinematic_bodies" default="false"> If [code]true[/code], this [TileMapLayer] collision shapes will be instantiated as kinematic bodies. This can be needed for moving [TileMapLayer] nodes (i.e. moving platforms). </member> + <member name="x_draw_order_reversed" type="bool" setter="set_x_draw_order_reversed" getter="is_x_draw_order_reversed" default="false"> + If [member CanvasItem.y_sort_enabled] is enabled, setting this to [code]true[/code] will reverse the order the tiles are drawn on the X-axis. + </member> <member name="y_sort_origin" type="int" setter="set_y_sort_origin" getter="get_y_sort_origin" default="0"> This Y-sort origin value is added to each tile's Y-sort origin value. This allows, for example, to fake a different height level. This can be useful for top-down view games. </member> diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 3adff84e40..035e2dcd65 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -316,8 +316,6 @@ void EditorFileSystem::_scan_filesystem() { EditorProgressBG scan_progress("efs", "ScanFS", 1000); ScanProgress sp; - sp.low = 0; - sp.hi = 1; sp.progress = &scan_progress; new_filesystem = memnew(EditorFileSystemDirectory); @@ -325,7 +323,8 @@ void EditorFileSystem::_scan_filesystem() { Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); d->change_dir("res://"); - _scan_new_dir(new_filesystem, d, sp); + + _scan_new_dir(new_filesystem, d, &sp, 1); dep_update_list.clear(); file_cache.clear(); //clear caches, no longer needed @@ -560,15 +559,27 @@ bool EditorFileSystem::_scan_import_support(const Vector<String> &reimports) { return false; } -bool EditorFileSystem::_update_scan_actions() { +bool EditorFileSystem::_update_scan_actions(bool p_show_progress) { sources_changed.clear(); bool fs_changed = false; Vector<String> reimports; Vector<String> reloads; + HashMap<String, String> import_group_files; + + EditorProgress *editor_progress = nullptr; + if (p_show_progress && scan_actions.size() > 0) { + editor_progress = memnew(EditorProgress("_update_scan_actions", TTR("Scanning actions..."), scan_actions.size())); + editor_progress->step("", 0, true); + } + int idx_action = 0; for (const ItemAction &ia : scan_actions) { + if (editor_progress) { + editor_progress->step(ia.file, idx_action, false); + } + idx_action++; switch (ia.action) { case ItemAction::ACTION_NONE: { } break; @@ -643,10 +654,17 @@ bool EditorFileSystem::_update_scan_actions() { if (_test_for_reimport(full_path, false)) { //must reimport reimports.push_back(full_path); + if (!ia.import_group_file.is_empty()) { + import_group_files[full_path] = ia.import_group_file; + } Vector<String> dependencies = _get_dependencies(full_path); for (const String &dependency_path : dependencies) { if (import_extensions.has(dependency_path.get_extension())) { reimports.push_back(dependency_path); + String import_groupe_file = ResourceFormatImporter::get_singleton()->get_import_group_file(dependency_path); + if (!ia.import_group_file.is_empty()) { + import_group_files[dependency_path] = import_groupe_file; + } } } } else { @@ -676,6 +694,8 @@ bool EditorFileSystem::_update_scan_actions() { } } + memdelete_notnull(editor_progress); + if (_scan_extensions()) { //needs editor restart //extensions also may provide filetypes to be imported, so they must run before importing @@ -694,7 +714,7 @@ bool EditorFileSystem::_update_scan_actions() { return true; } - reimport_files(reimports); + _reimport_files_internal(reimports, &import_group_files); } else { //reimport files will update the uid cache file so if nothing was reimported, update it manually ResourceUID::get_singleton()->update_cache(); @@ -725,10 +745,10 @@ void EditorFileSystem::scan() { } _update_extensions(); - if (!use_threads) { scanning = true; scan_total = 0; + emit_signal(SNAME("scan_started"), true); _scan_filesystem(); if (filesystem) { memdelete(filesystem); @@ -736,7 +756,7 @@ void EditorFileSystem::scan() { //file_type_cache.clear(); filesystem = new_filesystem; new_filesystem = nullptr; - _update_scan_actions(); + _update_scan_actions(true); scanning = false; _update_pending_script_classes(); _update_pending_scene_groups(); @@ -749,28 +769,50 @@ void EditorFileSystem::scan() { Thread::Settings s; scanning = true; scan_total = 0; + emit_signal(SNAME("scan_started"), true); s.priority = Thread::PRIORITY_LOW; thread.start(_thread_func, this, s); - //tree->hide(); - //progress->show(); } } -void EditorFileSystem::ScanProgress::update(int p_current, int p_total) const { - float ratio = low + ((hi - low) / p_total) * p_current; - progress->step(ratio * 1000); - EditorFileSystem::singleton->scan_total = ratio; +void EditorFileSystem::ScanProgress::increment() { + real++; + nb_file_to_scan--; + current = CLAMP(real / estimated, current, 1); + progress->step(current * 1000); + EditorFileSystem::singleton->scan_total = current; } -EditorFileSystem::ScanProgress EditorFileSystem::ScanProgress::get_sub(int p_current, int p_total) const { - ScanProgress sp = *this; - float slice = (sp.hi - sp.low) / p_total; - sp.low += slice * p_current; - sp.hi = slice; - return sp; +void EditorFileSystem::ScanProgress::update_estimated(int p_depth, int p_nb_sub_dirs, int p_nb_files) { + // The algorithm calculates an estimated value for the progress of scanning directories and files. + // It adjusts the estimate based on the number of directories and files discovered so far, + // and ensures the estimated value does not decrease drastically between updates. + float average = 10; + directories_processed++; + if (nb_dir_to_scan > 0) { + nb_dir_to_scan--; + } + nb_dir_to_scan += p_nb_sub_dirs; + nb_file_to_scan += p_nb_files; + + if (directories_processed > 2) { + average = (real + nb_file_to_scan) / directories_processed; + if (average < 10) { + average = 10; + } + } + + float old_estimated = estimated; + estimated = real + nb_file_to_scan + (nb_dir_to_scan * average * p_depth); + if (estimated < old_estimated * 0.95) { + estimated = old_estimated * 0.95; + } + if (estimated <= real + average) { + estimated = real + average; + } } -void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress) { +void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, ScanProgress *p_progress, int p_depth) { List<String> dirs; List<String> files; @@ -810,11 +852,12 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc dirs.sort_custom<FileNoCaseComparator>(); files.sort_custom<FileNoCaseComparator>(); - int total = dirs.size() + files.size(); - int idx = 0; + p_progress->update_estimated(p_depth, dirs.size(), files.size()); + int idx = 0; for (List<String>::Element *E = dirs.front(); E; E = E->next(), idx++) { - if (da->change_dir(E->get()) == OK) { + String name = E->get(); + if (da->change_dir(name) == OK) { String d = da->get_current_dir(); if (d == cd || !d.begins_with(cd)) { @@ -825,38 +868,27 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc efd->parent = p_dir; efd->name = E->get(); - _scan_new_dir(efd, da, p_progress.get_sub(idx, total)); + _scan_new_dir(efd, da, p_progress, p_depth + 1); - int idx2 = 0; - for (int i = 0; i < p_dir->subdirs.size(); i++) { - if (efd->name.filenocasecmp_to(p_dir->subdirs[i]->name) < 0) { - break; - } - idx2++; - } - if (idx2 == p_dir->subdirs.size()) { - p_dir->subdirs.push_back(efd); - } else { - p_dir->subdirs.insert(idx2, efd); - } + // Folders are already sorted with FileNoCaseComparator. + p_dir->subdirs.push_back(efd); da->change_dir(".."); } } else { ERR_PRINT("Cannot go into subdir '" + E->get() + "'."); } - - p_progress.update(idx, total); } for (List<String>::Element *E = files.front(); E; E = E->next(), idx++) { - String ext = E->get().get_extension().to_lower(); + String name = E->get(); + String ext = name.get_extension().to_lower(); if (!valid_extensions.has(ext)) { continue; //invalid } EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); - fi->file = E->get(); + fi->file = name; String path = cd.path_join(fi->file); @@ -884,14 +916,6 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc fi->script_class_extends = fc->script_class_extends; fi->script_class_icon_path = fc->script_class_icon_path; - if (revalidate_import_files && !ResourceFormatImporter::get_singleton()->are_import_settings_valid(path)) { - ItemAction ia; - ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; - ia.dir = p_dir; - ia.file = E->get(); - scan_actions.push_back(ia); - } - if (fc->type.is_empty()) { fi->type = ResourceLoader::get_resource_type(path); fi->resource_script_class = ResourceLoader::get_resource_script_class(path); @@ -900,15 +924,23 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc //note: I think this should not happen any longer.. } + if (revalidate_import_files && !ResourceFormatImporter::get_singleton()->are_import_settings_valid(path)) { + ItemAction ia; + ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; + ia.dir = p_dir; + ia.file = E->get(); + ia.import_group_file = fi->import_group_file; + scan_actions.push_back(ia); + } + if (fc->uid == ResourceUID::INVALID_ID) { // imported files should always have a UID, so attempt to fetch it. fi->uid = ResourceLoader::get_resource_uid(path); } } else { - fi->type = ResourceFormatImporter::get_singleton()->get_resource_type(path); - fi->uid = ResourceFormatImporter::get_singleton()->get_resource_uid(path); - fi->import_group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(path); + // Using get_resource_import_info() to prevent calling 3 times ResourceFormatImporter::_get_path_and_type. + ResourceFormatImporter::get_singleton()->get_resource_import_info(path, fi->type, fi->uid, fi->import_group_file); fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->modified_time = 0; fi->import_modified_time = 0; @@ -918,6 +950,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; ia.dir = p_dir; ia.file = E->get(); + ia.import_group_file = fi->import_group_file; scan_actions.push_back(ia); } } else { @@ -968,11 +1001,11 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc } p_dir->files.push_back(fi); - p_progress.update(idx, total); + p_progress->increment(); } } -void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress) { +void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress *p_progress) { uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path()); bool updated_dir = false; @@ -1028,7 +1061,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const efd->name = f; Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); d->change_dir(cd.path_join(f)); - _scan_new_dir(efd, d, p_progress.get_sub(1, 1)); + _scan_new_dir(efd, d, p_progress, 1); ItemAction ia; ia.action = ItemAction::ACTION_DIR_ADD; @@ -1080,6 +1113,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; ia.dir = p_dir; ia.file = f; + ia.import_group_file = fi->import_group_file; scan_actions.push_back(ia); } @@ -1130,6 +1164,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; ia.dir = p_dir; ia.file = p_dir->files[i]->file; + ia.import_group_file = p_dir->files[i]->import_group_file; scan_actions.push_back(ia); } } else if (ResourceCache::has(path)) { //test for potential reload @@ -1179,9 +1214,7 @@ void EditorFileSystem::_thread_func_sources(void *_userdata) { EditorProgressBG pr("sources", TTR("ScanSources"), 1000); ScanProgress sp; sp.progress = ≺ - sp.hi = 1; - sp.low = 0; - efs->_scan_fs_changes(efs->filesystem, sp); + efs->_scan_fs_changes(efs->filesystem, &sp); } efs->scanning_changes_done.set(); } @@ -1204,10 +1237,9 @@ void EditorFileSystem::scan_changes() { EditorProgressBG pr("sources", TTR("ScanSources"), 1000); ScanProgress sp; sp.progress = ≺ - sp.hi = 1; - sp.low = 0; scan_total = 0; - _scan_fs_changes(filesystem, sp); + emit_signal(SNAME("scan_started"), false); + _scan_fs_changes(filesystem, &sp); bool changed = _update_scan_actions(); _update_pending_script_classes(); _update_pending_scene_groups(); @@ -1222,6 +1254,7 @@ void EditorFileSystem::scan_changes() { ERR_FAIL_COND(thread_sources.is_started()); set_process(true); scan_total = 0; + emit_signal(SNAME("scan_started"), false); Thread::Settings s; s.priority = Thread::PRIORITY_LOW; thread_sources.start(_thread_func_sources, this, s); @@ -1275,6 +1308,7 @@ void EditorFileSystem::_notification(int p_what) { if (thread_sources.is_started()) { thread_sources.wait_to_finish(); } + emit_signal(SNAME("scan_stopped"), false); bool changed = _update_scan_actions(); _update_pending_script_classes(); _update_pending_scene_groups(); @@ -1295,7 +1329,8 @@ void EditorFileSystem::_notification(int p_what) { filesystem = new_filesystem; new_filesystem = nullptr; thread.wait_to_finish(); - _update_scan_actions(); + emit_signal(SNAME("scan_stopped"), true); + _update_scan_actions(true); _update_pending_script_classes(); _update_pending_scene_groups(); emit_signal(SNAME("filesystem_changed")); @@ -1578,10 +1613,20 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St return String(); } -void EditorFileSystem::_update_script_classes() { +void EditorFileSystem::_update_script_classes(bool p_show_progress) { update_script_mutex.lock(); + EditorProgress *ep = nullptr; + if (p_show_progress && update_script_paths.size() > 0) { + ep = memnew(EditorProgress("update_scripts_classes", TTR("(Re)importing Scripts"), update_script_paths.size() * 2)); + ep->step("", 0, true); + } + + int step_count = 0; for (const String &path : update_script_paths) { + if (ep) { + ep->step(TTR("Registering global classes..."), step_count++, false); + } EditorFileSystem::get_singleton()->register_global_class_script(path, path); } @@ -1590,6 +1635,10 @@ void EditorFileSystem::_update_script_classes() { int index = -1; EditorFileSystemDirectory *efd = find_file(path, &index); + if (ep) { + ep->step(TTR("Updating documentation..."), step_count++, false); + } + if (!efd || index < 0) { // The file was removed continue; @@ -1610,6 +1659,8 @@ void EditorFileSystem::_update_script_classes() { } } + memdelete_notnull(ep); + update_script_paths.clear(); update_script_mutex.unlock(); @@ -1626,9 +1677,9 @@ void EditorFileSystem::_update_script_classes() { ResourceSaver::add_custom_savers(); } -void EditorFileSystem::_update_pending_script_classes() { +void EditorFileSystem::_update_pending_script_classes(bool p_show_progress) { if (!update_script_paths.is_empty()) { - _update_script_classes(); + _update_script_classes(p_show_progress); } else { // In case the class cache file was removed somehow, regenerate it. if (!FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) { @@ -1668,7 +1719,7 @@ void EditorFileSystem::_update_scene_groups() { } if (ep) { - ep->step(TTR("Updating Scene Groups..."), step_count++); + ep->step(TTR("Updating Scene Groups..."), step_count++, false); } } @@ -1714,110 +1765,125 @@ HashSet<StringName> EditorFileSystem::_get_scene_groups(const String &p_path) { void EditorFileSystem::update_file(const String &p_file) { ERR_FAIL_COND(p_file.is_empty()); - EditorFileSystemDirectory *fs = nullptr; - int cpos = -1; + update_files({ p_file }); +} - if (!_find_file(p_file, &fs, cpos)) { - if (!fs) { - return; +void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { + for (const String &file : p_script_paths) { + ERR_CONTINUE(file.is_empty()); + EditorFileSystemDirectory *fs = nullptr; + int cpos = -1; + + if (!_find_file(file, &fs, cpos)) { + if (!fs) { + return; + } } - } - if (!FileAccess::exists(p_file)) { - //was removed - _delete_internal_files(p_file); - if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog). - if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) { - if (ResourceUID::get_singleton()->has_id(fs->files[cpos]->uid)) { - ResourceUID::get_singleton()->remove_id(fs->files[cpos]->uid); + if (!FileAccess::exists(file)) { + //was removed + _delete_internal_files(file); + if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog). + if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(fs->files[cpos]->uid)) { + ResourceUID::get_singleton()->remove_id(fs->files[cpos]->uid); + } } - } - if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(p_file); - } - if (fs->files[cpos]->type == SNAME("PackedScene")) { - _queue_update_scene_groups(p_file); + if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { + _queue_update_script_class(file); + } + if (fs->files[cpos]->type == SNAME("PackedScene")) { + _queue_update_scene_groups(file); + } + + memdelete(fs->files[cpos]); + fs->files.remove_at(cpos); } - memdelete(fs->files[cpos]); - fs->files.remove_at(cpos); + // Preventing update of scripts and scene groups while first scan. + // Some ResourceLoader resave files which invoke update_file while loading (ex: ResourceImporterCSVTranslation). + if (!first_scan) { + _update_pending_script_classes(); + _update_pending_scene_groups(); + call_deferred(SNAME("emit_signal"), "filesystem_changed"); // Update later. + } + return; } - _update_pending_script_classes(); - _update_pending_scene_groups(); - call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later - return; - } - - String type = ResourceLoader::get_resource_type(p_file); - if (type.is_empty() && textfile_extensions.has(p_file.get_extension())) { - type = "TextFile"; - } - String script_class = ResourceLoader::get_resource_script_class(p_file); + String type = ResourceLoader::get_resource_type(file); + if (type.is_empty() && textfile_extensions.has(file.get_extension())) { + type = "TextFile"; + } + String script_class = ResourceLoader::get_resource_script_class(file); - ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file); + ResourceUID::ID uid = ResourceLoader::get_resource_uid(file); - if (cpos == -1) { - // The file did not exist, it was added. - int idx = 0; - String file_name = p_file.get_file(); + if (cpos == -1) { + // The file did not exist, it was added. + int idx = 0; + String file_name = file.get_file(); - for (int i = 0; i < fs->files.size(); i++) { - if (p_file.filenocasecmp_to(fs->files[i]->file) < 0) { - break; + for (int i = 0; i < fs->files.size(); i++) { + if (file.filenocasecmp_to(fs->files[i]->file) < 0) { + break; + } + idx++; } - idx++; - } - EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); - fi->file = file_name; - fi->import_modified_time = 0; - fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); + fi->file = file_name; + fi->import_modified_time = 0; + fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); - if (idx == fs->files.size()) { - fs->files.push_back(fi); + if (idx == fs->files.size()) { + fs->files.push_back(fi); + } else { + fs->files.insert(idx, fi); + } + cpos = idx; } else { - fs->files.insert(idx, fi); + //the file exists and it was updated, and was not added in this step. + //this means we must force upon next restart to scan it again, to get proper type and dependencies + late_update_files.insert(file); + _save_late_updated_files(); //files need to be updated in the re-scan } - cpos = idx; - } else { - //the file exists and it was updated, and was not added in this step. - //this means we must force upon next restart to scan it again, to get proper type and dependencies - late_update_files.insert(p_file); - _save_late_updated_files(); //files need to be updated in the re-scan - } - fs->files[cpos]->type = type; - fs->files[cpos]->resource_script_class = script_class; - fs->files[cpos]->uid = uid; - fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path); - fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(p_file); - fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); - fs->files[cpos]->deps = _get_dependencies(p_file); - fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + fs->files[cpos]->type = type; + fs->files[cpos]->resource_script_class = script_class; + fs->files[cpos]->uid = uid; + fs->files[cpos]->script_class_name = _get_global_script_class(type, file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path); + fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file); + fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); + fs->files[cpos]->deps = _get_dependencies(file); + fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); - if (uid != ResourceUID::INVALID_ID) { - if (ResourceUID::get_singleton()->has_id(uid)) { - ResourceUID::get_singleton()->set_id(uid, p_file); - } else { - ResourceUID::get_singleton()->add_id(uid, p_file); + if (uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(uid)) { + ResourceUID::get_singleton()->set_id(uid, file); + } else { + ResourceUID::get_singleton()->add_id(uid, file); + } + + ResourceUID::get_singleton()->update_cache(); } + // Update preview + EditorResourcePreview::get_singleton()->check_for_invalidation(file); - ResourceUID::get_singleton()->update_cache(); + if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { + _queue_update_script_class(file); + } + if (fs->files[cpos]->type == SNAME("PackedScene")) { + _queue_update_scene_groups(file); + } } - // Update preview - EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); - if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(p_file); - } - if (fs->files[cpos]->type == SNAME("PackedScene")) { - _queue_update_scene_groups(p_file); + // Preventing update of scripts and scene groups while first scan. + // Some ResourceLoader resave files which invoke update_file while loading (ex: ResourceImporterCSVTranslation). + if (!first_scan) { + _update_pending_script_classes(); + _update_pending_scene_groups(); + call_deferred(SNAME("emit_signal"), "filesystem_changed"); // Update later. } - - _update_pending_script_classes(); - _update_pending_scene_groups(); - call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later } HashSet<String> EditorFileSystem::get_valid_extensions() const { @@ -2338,132 +2404,152 @@ void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_im } void EditorFileSystem::reimport_files(const Vector<String> &p_files) { + _reimport_files_internal(p_files, nullptr); +} + +void EditorFileSystem::_reimport_files_internal(const Vector<String> &p_files, const HashMap<String, String> *p_import_group_files) { ERR_FAIL_COND_MSG(importing, "Attempted to call reimport_files() recursively, this is not allowed."); importing = true; Vector<String> reloads; - EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size()); + { + EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size()); + pr.step("", 0, true); - Vector<ImportFile> reimport_files; + Vector<ImportFile> reimport_files; + HashMap<String, String> import_group_files = *p_import_group_files; - HashSet<String> groups_to_reimport; + HashSet<String> groups_to_reimport; - for (int i = 0; i < p_files.size(); i++) { - String file = p_files[i]; - - ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(file); - if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) { - file = ResourceUID::get_singleton()->get_id_path(uid); - } - - String group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(file); - - if (group_file_cache.has(file)) { - // Maybe the file itself is a group! - groups_to_reimport.insert(file); - // Groups do not belong to groups. - group_file = String(); - } else if (groups_to_reimport.has(file)) { - // Groups do not belong to groups. - group_file = String(); - } else if (!group_file.is_empty()) { - // It's a group file, add group to import and skip this file. - groups_to_reimport.insert(group_file); - } else { - // It's a regular file. - ImportFile ifile; - ifile.path = file; - ResourceFormatImporter::get_singleton()->get_import_order_threads_and_importer(file, ifile.order, ifile.threaded, ifile.importer); - reloads.push_back(file); - reimport_files.push_back(ifile); - } + for (int i = 0; i < p_files.size(); i++) { + String file = p_files[i]; - // Group may have changed, so also update group reference. - EditorFileSystemDirectory *fs = nullptr; - int cpos = -1; - if (_find_file(file, &fs, cpos)) { - fs->files.write[cpos]->import_group_file = group_file; + ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(file); + if (uid != ResourceUID::INVALID_ID && ResourceUID::get_singleton()->has_id(uid)) { + file = ResourceUID::get_singleton()->get_id_path(uid); + } + + String group_file; + if (p_import_group_files) { + if (p_import_group_files->has(file)) { + group_file = (*p_import_group_files)[file]; + } + } else { + group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(file); + } + + if (group_file_cache.has(file)) { + // Maybe the file itself is a group! + groups_to_reimport.insert(file); + // Groups do not belong to groups. + group_file = String(); + } else if (groups_to_reimport.has(file)) { + // Groups do not belong to groups. + group_file = String(); + } else if (!group_file.is_empty()) { + // It's a group file, add group to import and skip this file. + groups_to_reimport.insert(group_file); + } else { + // It's a regular file. + ImportFile ifile; + ifile.path = file; + ResourceFormatImporter::get_singleton()->get_import_order_threads_and_importer(file, ifile.order, ifile.threaded, ifile.importer); + reloads.push_back(file); + reimport_files.push_back(ifile); + } + + // Group may have changed, so also update group reference. + if (!p_import_group_files) { + EditorFileSystemDirectory *fs = nullptr; + int cpos = -1; + if (_find_file(file, &fs, cpos)) { + fs->files.write[cpos]->import_group_file = group_file; + } + } + + pr.step(TTR("Preparing files to reimport..."), i, false); } - } - reimport_files.sort(); + reimport_files.sort(); #ifdef THREADS_ENABLED - bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads"); + bool use_multiple_threads = GLOBAL_GET("editor/import/use_multiple_threads"); #else - bool use_multiple_threads = false; + bool use_multiple_threads = false; #endif - int from = 0; - for (int i = 0; i < reimport_files.size(); i++) { - if (groups_to_reimport.has(reimport_files[i].path)) { - continue; - } - - if (use_multiple_threads && reimport_files[i].threaded) { - if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer) { - if (from - i == 0) { - // Single file, do not use threads. - pr.step(reimport_files[i].path.get_file(), i); - _reimport_file(reimport_files[i].path); - } else { - Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(reimport_files[from].importer); - ERR_CONTINUE(!importer.is_valid()); - - importer->import_threaded_begin(); - - ImportThreadData tdata; - tdata.max_index.set(from); - tdata.reimport_from = from; - tdata.reimport_files = reimport_files.ptr(); - - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &EditorFileSystem::_reimport_thread, &tdata, i - from + 1, -1, false, vformat(TTR("Import resources of type: %s"), reimport_files[from].importer)); - int current_index = from - 1; - do { - if (current_index < tdata.max_index.get()) { - current_index = tdata.max_index.get(); - pr.step(reimport_files[current_index].path.get_file(), current_index); - } - OS::get_singleton()->delay_usec(1); - } while (!WorkerThreadPool::get_singleton()->is_group_task_completed(group_task)); + int from = 0; + for (int i = 0; i < reimport_files.size(); i++) { + if (groups_to_reimport.has(reimport_files[i].path)) { + continue; + } - WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + if (use_multiple_threads && reimport_files[i].threaded) { + if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer) { + if (from - i == 0) { + // Single file, do not use threads. + pr.step(reimport_files[i].path.get_file(), i, false); + _reimport_file(reimport_files[i].path); + } else { + Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(reimport_files[from].importer); + ERR_CONTINUE(importer.is_null()); + + importer->import_threaded_begin(); + + ImportThreadData tdata; + tdata.max_index.set(from); + tdata.reimport_from = from; + tdata.reimport_files = reimport_files.ptr(); + + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &EditorFileSystem::_reimport_thread, &tdata, i - from + 1, -1, false, vformat(TTR("Import resources of type: %s"), reimport_files[from].importer)); + int current_index = from - 1; + do { + if (current_index < tdata.max_index.get()) { + current_index = tdata.max_index.get(); + pr.step(reimport_files[current_index].path.get_file(), current_index, false); + } + OS::get_singleton()->delay_usec(1); + } while (!WorkerThreadPool::get_singleton()->is_group_task_completed(group_task)); + + WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); + + importer->import_threaded_end(); + } - importer->import_threaded_end(); + from = i + 1; } - from = i + 1; + } else { + pr.step(reimport_files[i].path.get_file(), i, false); + _reimport_file(reimport_files[i].path); } - - } else { - pr.step(reimport_files[i].path.get_file(), i); - _reimport_file(reimport_files[i].path); } - } - // Reimport groups. + // Reimport groups. - from = reimport_files.size(); + from = reimport_files.size(); - if (groups_to_reimport.size()) { - HashMap<String, Vector<String>> group_files; - _find_group_files(filesystem, group_files, groups_to_reimport); - for (const KeyValue<String, Vector<String>> &E : group_files) { - pr.step(E.key.get_file(), from++); - Error err = _reimport_group(E.key, E.value); - reloads.push_back(E.key); - reloads.append_array(E.value); - if (err == OK) { - _reimport_file(E.key); + if (groups_to_reimport.size()) { + HashMap<String, Vector<String>> group_files; + _find_group_files(filesystem, group_files, groups_to_reimport); + for (const KeyValue<String, Vector<String>> &E : group_files) { + pr.step(E.key.get_file(), from++, false); + Error err = _reimport_group(E.key, E.value); + reloads.push_back(E.key); + reloads.append_array(E.value); + if (err == OK) { + _reimport_file(E.key); + } } } - } - ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache. + ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache. + + _save_filesystem_cache(); + } - _save_filesystem_cache(); - _update_pending_script_classes(); + _update_pending_script_classes(true); _update_pending_scene_groups(); importing = false; if (!is_scanning()) { @@ -2681,6 +2767,8 @@ void EditorFileSystem::_bind_methods() { ADD_SIGNAL(MethodInfo("sources_changed", PropertyInfo(Variant::BOOL, "exist"))); ADD_SIGNAL(MethodInfo("resources_reimported", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources"))); ADD_SIGNAL(MethodInfo("resources_reload", PropertyInfo(Variant::PACKED_STRING_ARRAY, "resources"))); + ADD_SIGNAL(MethodInfo("scan_started", PropertyInfo(Variant::BOOL, "complete_scan"))); + ADD_SIGNAL(MethodInfo("scan_stopped", PropertyInfo(Variant::BOOL, "complete_scan"))); } void EditorFileSystem::_update_extensions() { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 84ae1e182c..ea839ec823 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -153,6 +153,7 @@ class EditorFileSystem : public Node { Action action = ACTION_NONE; EditorFileSystemDirectory *dir = nullptr; String file; + String import_group_file; EditorFileSystemDirectory *new_dir = nullptr; EditorFileSystemDirectory::FileInfo *new_file = nullptr; }; @@ -200,11 +201,15 @@ class EditorFileSystem : public Node { HashSet<String> dep_update_list; struct ScanProgress { - float low = 0; - float hi = 0; - mutable EditorProgressBG *progress = nullptr; - void update(int p_current, int p_total) const; - ScanProgress get_sub(int p_current, int p_total) const; + int directories_processed = 0; + int nb_dir_to_scan = 0; + int nb_file_to_scan = 0; + float estimated = 0; + float real = 0; + float current = 0; + EditorProgressBG *progress = nullptr; + void increment(); + void update_estimated(int p_depth, int p_nb_dirs, int p_nb_files); }; void _save_filesystem_cache(); @@ -212,7 +217,7 @@ class EditorFileSystem : public Node { bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const; - void _scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress); + void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress *p_progress); void _delete_internal_files(const String &p_file); @@ -220,7 +225,7 @@ class EditorFileSystem : public Node { HashSet<String> valid_extensions; HashSet<String> import_extensions; - void _scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress); + void _scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, ScanProgress *p_progress, int p_depth); Thread thread_sources; bool scanning_changes = false; @@ -231,7 +236,7 @@ class EditorFileSystem : public Node { List<String> sources_changed; List<ItemAction> scan_actions; - bool _update_scan_actions(); + bool _update_scan_actions(bool p_show_progress = false); void _update_extensions(); @@ -257,8 +262,8 @@ class EditorFileSystem : public Node { Mutex update_script_mutex; HashSet<String> update_script_paths; void _queue_update_script_class(const String &p_path); - void _update_script_classes(); - void _update_pending_script_classes(); + void _update_script_classes(bool p_show_progress = false); + void _update_pending_script_classes(bool p_show_progress = false); Mutex update_scene_mutex; HashSet<String> update_scene_paths; @@ -286,6 +291,7 @@ class EditorFileSystem : public Node { SafeNumeric<int> max_index; }; + void _reimport_files_internal(const Vector<String> &p_files, const HashMap<String, String> *p_import_group_files); void _reimport_thread(uint32_t p_index, ImportThreadData *p_import_data); static ResourceUID::ID _resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate); @@ -310,6 +316,7 @@ public: void scan(); void scan_changes(); void update_file(const String &p_file); + void update_files(const Vector<String> &p_script_paths); HashSet<String> get_valid_extensions() const; void register_global_class_script(const String &p_search_path, const String &p_target_path); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 45d276dd79..b58b378414 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -5103,6 +5103,8 @@ void EditorNode::save_editor_layout_delayed() { } void EditorNode::_load_editor_layout() { + EditorProgress ep("loading_editor_layout", TTR("Loading editor"), 5); + ep.step("Loading editor layout...", 0, true); Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); @@ -5124,11 +5126,19 @@ void EditorNode::_load_editor_layout() { return; } + ep.step("Loading docks...", 1, true); editor_dock_manager->load_docks_from_config(config, "docks"); + + ep.step("Reopening scenes...", 2, true); _load_open_scenes_from_config(config); + + ep.step("Loading central editor layout...", 3, true); _load_central_editor_layout_from_config(config); + ep.step("Loading plugin window layout...", 4, true); editor_data.set_plugin_window_layout(config); + + ep.step("Editor layout ready.", 5, true); } void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) { @@ -7037,7 +7047,7 @@ EditorNode::EditorNode() { ED_SHORTCUT_OVERRIDE("editor/take_screenshot", "macos", KeyModifierMask::META | Key::F12); settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT); - settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder.")); + settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\").")); #ifndef ANDROID_ENABLED ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index f8a82187b9..7d90da05c2 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -550,6 +550,8 @@ void FileSystemDock::_notification(int p_what) { case NOTIFICATION_READY: { EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &FileSystemDock::_feature_profile_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &FileSystemDock::_fs_changed)); + EditorFileSystem::get_singleton()->connect("scan_started", callable_mp(this, &FileSystemDock::_scan_started)); + EditorFileSystem::get_singleton()->connect("scan_stopped", callable_mp(this, &FileSystemDock::_scan_stopped)); EditorResourcePreview::get_singleton()->connect("preview_invalidated", callable_mp(this, &FileSystemDock::_preview_invalidated)); button_file_list_display_mode->connect(SceneStringName(pressed), callable_mp(this, &FileSystemDock::_toggle_file_display)); @@ -566,9 +568,7 @@ void FileSystemDock::_notification(int p_what) { _update_display_mode(); - if (EditorFileSystem::get_singleton()->is_scanning()) { - _set_scanning_mode(); - } else { + if (!EditorFileSystem::get_singleton()->is_scanning()) { _update_tree(Vector<String>(), true); } } break; @@ -1326,6 +1326,18 @@ void FileSystemDock::_fs_changed() { set_process(false); } +void FileSystemDock::_scan_started(bool p_complete_scan) { + if (p_complete_scan) { + _set_scanning_mode(); + } +} + +void FileSystemDock::_scan_stopped(bool p_complete_scan) { + if (p_complete_scan) { + _fs_changed(); + } +} + void FileSystemDock::_set_scanning_mode() { button_hist_prev->set_disabled(true); button_hist_next->set_disabled(true); @@ -2649,7 +2661,6 @@ bool FileSystemDock::_matches_all_search_tokens(const String &p_text) { } void FileSystemDock::_rescan() { - _set_scanning_mode(); EditorFileSystem::get_singleton()->scan(); } diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 3fbff3ef19..2e9e52fcf6 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -308,6 +308,8 @@ private: void _set_scanning_mode(); void _rescan(); + void _scan_started(bool p_complete_scan); + void _scan_stopped(bool p_complete_scan); void _change_split_mode(); void _split_dragged(int p_offset); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 7e2d6c9d1e..fc52d7a0ae 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -232,23 +232,27 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { item->set_icon(0, icon); item->set_metadata(0, p_node->get_path()); + if (connecting_signal) { + // Add script icons for all scripted nodes. + Ref<Script> scr = p_node->get_script(); + if (scr.is_valid()) { + item->add_button(0, get_editor_theme_icon(SNAME("Script")), BUTTON_SCRIPT); + if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == scr) { + // Disable button on custom scripts (pure visual cue). + item->set_button_disabled(0, item->get_button_count(0) - 1, true); + } + } + } + if (connect_to_script_mode) { Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); Ref<Script> scr = p_node->get_script(); - if (!scr.is_null() && EditorNode::get_singleton()->get_object_custom_type_base(p_node) != scr) { - //has script - item->add_button(0, get_editor_theme_icon(SNAME("Script")), BUTTON_SCRIPT); - } else { - //has no script (or script is a custom type) + bool has_custom_script = scr.is_valid() && EditorNode::get_singleton()->get_object_custom_type_base(p_node) == scr; + if (scr.is_null() || has_custom_script) { _set_item_custom_color(item, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))); item->set_selectable(0, false); - if (!scr.is_null()) { // make sure to mark the script if a custom type - item->add_button(0, get_editor_theme_icon(SNAME("Script")), BUTTON_SCRIPT); - item->set_button_disabled(0, item->get_button_count(0) - 1, true); - } - accent.a *= 0.7; } diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h index 9ae1e99a27..b4d9644f16 100644 --- a/editor/gui/scene_tree_editor.h +++ b/editor/gui/scene_tree_editor.h @@ -160,7 +160,6 @@ public: void set_marked(const HashSet<Node *> &p_marked, bool p_selectable = true, bool p_children_selectable = true); void set_marked(Node *p_marked, bool p_selectable = true, bool p_children_selectable = true); - bool has_marked() const { return !marked.is_empty(); } void set_selected(Node *p_node, bool p_emit_selected = true); Node *get_selected(); void set_can_rename(bool p_can_rename) { can_rename = p_can_rename; } diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index 3c06c68414..2347c715a8 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -189,13 +189,15 @@ void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_ params->base_options_path = p_path; HashMap<StringName, Variant> import_options; - List<String> section_keys; - p_config->get_section_keys("params", §ion_keys); - for (const String §ion_key : section_keys) { - import_options[section_key] = p_config->get_value("params", section_key); - } - if (params->importer.is_valid()) { - params->importer->handle_compatibility_options(import_options); + if (p_config.is_valid() && p_config->has_section("params")) { + List<String> section_keys; + p_config->get_section_keys("params", §ion_keys); + for (const String §ion_key : section_keys) { + import_options[section_key] = p_config->get_value("params", section_key); + } + if (params->importer.is_valid()) { + params->importer->handle_compatibility_options(import_options); + } } for (const ResourceImporter::ImportOption &E : options) { diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index fe56f48889..0a2c192ea4 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -243,12 +243,12 @@ void AnimationPlayerEditor::_play_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); + double time = player->get_current_animation_position(); if (current == player->get_assigned_animation() && player->is_playing()) { - player->stop(); //so it won't blend with itself + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play(current); } @@ -281,12 +281,12 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); - if (current == player->get_assigned_animation()) { - player->stop(); //so it won't blend with itself + double time = player->get_current_animation_position(); + if (current == player->get_assigned_animation() && player->is_playing()) { + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play_backwards(current); } diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp index c96bec6e7f..fbb389ccb4 100644 --- a/editor/plugins/editor_debugger_plugin.cpp +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -56,6 +56,7 @@ void EditorDebuggerSession::_bind_methods() { ClassDB::bind_method(D_METHOD("is_active"), &EditorDebuggerSession::is_active); ClassDB::bind_method(D_METHOD("add_session_tab", "control"), &EditorDebuggerSession::add_session_tab); ClassDB::bind_method(D_METHOD("remove_session_tab", "control"), &EditorDebuggerSession::remove_session_tab); + ClassDB::bind_method(D_METHOD("set_breakpoint", "path", "line", "enabled"), &EditorDebuggerSession::set_breakpoint); ADD_SIGNAL(MethodInfo("started")); ADD_SIGNAL(MethodInfo("stopped")); @@ -100,6 +101,11 @@ bool EditorDebuggerSession::is_active() { return debugger->is_session_active(); } +void EditorDebuggerSession::set_breakpoint(const String &p_path, int p_line, bool p_enabled) { + ERR_FAIL_NULL_MSG(debugger, "Plugin is not attached to debugger."); + debugger->set_breakpoint(p_path, p_line, p_enabled); +} + void EditorDebuggerSession::detach_debugger() { if (!debugger) { return; @@ -184,10 +190,31 @@ bool EditorDebuggerPlugin::capture(const String &p_message, const Array &p_data, return false; } +void EditorDebuggerPlugin::goto_script_line(const Ref<Script> &p_script, int p_line) { + GDVIRTUAL_CALL(_goto_script_line, p_script, p_line); +} + +void EditorDebuggerPlugin::breakpoints_cleared_in_tree() { + GDVIRTUAL_CALL(_breakpoints_cleared_in_tree); +} + +void EditorDebuggerPlugin::breakpoint_set_in_tree(const Ref<Script> &p_script, int p_line, bool p_enabled) { + GDVIRTUAL_CALL(_breakpoint_set_in_tree, p_script, p_line, p_enabled); +} + void EditorDebuggerPlugin::_bind_methods() { GDVIRTUAL_BIND(_setup_session, "session_id"); GDVIRTUAL_BIND(_has_capture, "capture"); GDVIRTUAL_BIND(_capture, "message", "data", "session_id"); + GDVIRTUAL_BIND(_goto_script_line, "script", "line"); + GDVIRTUAL_BIND(_breakpoints_cleared_in_tree); + GDVIRTUAL_BIND(_breakpoint_set_in_tree, "script", "line", "enabled"); ClassDB::bind_method(D_METHOD("get_session", "id"), &EditorDebuggerPlugin::get_session); ClassDB::bind_method(D_METHOD("get_sessions"), &EditorDebuggerPlugin::get_sessions); } + +EditorDebuggerPlugin::EditorDebuggerPlugin() { + EditorDebuggerNode::get_singleton()->connect("goto_script_line", callable_mp(this, &EditorDebuggerPlugin::goto_script_line)); + EditorDebuggerNode::get_singleton()->connect("breakpoints_cleared_in_tree", callable_mp(this, &EditorDebuggerPlugin::breakpoints_cleared_in_tree)); + EditorDebuggerNode::get_singleton()->connect("breakpoint_set_in_tree", callable_mp(this, &EditorDebuggerPlugin::breakpoint_set_in_tree)); +} diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h index 41f34f67cf..4f09824d03 100644 --- a/editor/plugins/editor_debugger_plugin.h +++ b/editor/plugins/editor_debugger_plugin.h @@ -62,6 +62,8 @@ public: bool is_debuggable(); bool is_active(); + void set_breakpoint(const String &p_path, int p_line, bool p_enabled); + EditorDebuggerSession(ScriptEditorDebugger *p_debugger); ~EditorDebuggerSession(); }; @@ -90,7 +92,15 @@ public: GDVIRTUAL1RC(bool, _has_capture, const String &); GDVIRTUAL1(_setup_session, int); - EditorDebuggerPlugin() {} + virtual void goto_script_line(const Ref<Script> &p_script, int p_line); + virtual void breakpoints_cleared_in_tree(); + virtual void breakpoint_set_in_tree(const Ref<Script> &p_script, int p_line, bool p_enabled); + + GDVIRTUAL2(_goto_script_line, const Ref<Script> &, int); + GDVIRTUAL0(_breakpoints_cleared_in_tree); + GDVIRTUAL3(_breakpoint_set_in_tree, const Ref<Script> &, int, bool); + + EditorDebuggerPlugin(); ~EditorDebuggerPlugin(); }; diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index 406425e9fd..2623079cfe 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -126,6 +126,16 @@ void BackgroundProgress::end_task(const String &p_task) { ProgressDialog *ProgressDialog::singleton = nullptr; +void ProgressDialog::_update_ui() { + // Run main loop for two frames. + if (is_inside_tree()) { + DisplayServer::get_singleton()->process_events(); +#ifndef ANDROID_ENABLED + Main::iteration(); +#endif + } +} + void ProgressDialog::_popup() { Size2 ms = main->get_combined_minimum_size(); ms.width = MAX(500 * EDSCALE, ms.width); @@ -138,7 +148,13 @@ void ProgressDialog::_popup() { main->set_offset(SIDE_TOP, style->get_margin(SIDE_TOP)); main->set_offset(SIDE_BOTTOM, -style->get_margin(SIDE_BOTTOM)); - if (!is_inside_tree()) { + if (is_inside_tree()) { + Rect2i adjust = _popup_adjust_rect(); + if (adjust != Rect2i()) { + set_position(adjust.position); + set_size(adjust.size); + } + } else { for (Window *window : host_windows) { if (window->has_focus()) { popup_exclusive_centered(window, ms); @@ -182,6 +198,7 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p if (p_can_cancel) { cancel->grab_focus(); } + _update_ui(); } bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) { @@ -203,11 +220,8 @@ bool ProgressDialog::task_step(const String &p_task, const String &p_state, int t.state->set_text(p_state); last_progress_tick = OS::get_singleton()->get_ticks_usec(); - DisplayServer::get_singleton()->process_events(); + _update_ui(); -#ifndef ANDROID_ENABLED - Main::iteration(); // this will not work on a lot of platforms, so it's only meant for the editor -#endif return canceled; } diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h index 74196a28df..82d59219da 100644 --- a/editor/progress_dialog.h +++ b/editor/progress_dialog.h @@ -85,6 +85,8 @@ class ProgressDialog : public PopupPanel { void _popup(); void _cancel_pressed(); + + void _update_ui(); bool canceled = false; public: diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index c71cb9d4ac..4448f165d3 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -76,12 +76,15 @@ void SceneTreeDock::_quick_open() { } void SceneTreeDock::_inspect_hovered_node() { - scene_tree->set_selected(node_hovered_now); - scene_tree->set_marked(node_hovered_now); + select_node_hovered_at_end_of_drag = true; + if (tree_item_inspected != nullptr) { + tree_item_inspected->clear_custom_color(0); + } Tree *tree = scene_tree->get_scene_tree(); - TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + TreeItem *item = tree->get_item_with_metadata(node_hovered_now->get_path()); if (item) { - item->set_as_cursor(0); + tree_item_inspected = item; + tree_item_inspected->set_custom_color(0, get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); } InspectorDock::get_inspector_singleton()->edit(node_hovered_now); InspectorDock::get_inspector_singleton()->propagate_notification(NOTIFICATION_DRAG_BEGIN); // Enable inspector drag preview after it updated. @@ -130,8 +133,8 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { } if (mb->is_released()) { - if (scene_tree->has_marked()) { - scene_tree->set_marked(nullptr); + if (tree_item_inspected != nullptr) { + tree_item_inspected->clear_custom_color(0); } _reset_hovering_timer(); } @@ -1658,6 +1661,16 @@ void SceneTreeDock::_notification(int p_what) { case NOTIFICATION_DRAG_END: { _reset_hovering_timer(); + if (select_node_hovered_at_end_of_drag && !hovered_but_reparenting) { + Node *node_inspected = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object()); + if (node_inspected) { + editor_selection->clear(); + editor_selection->add_node(node_inspected); + scene_tree->set_selected(node_inspected); + select_node_hovered_at_end_of_drag = false; + } + } + hovered_but_reparenting = false; } break; } } @@ -2185,6 +2198,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { ERR_FAIL_NULL(new_parent); List<Node *> selection = editor_selection->get_selected_node_list(); + List<Node *> full_selection = editor_selection->get_full_selected_node_list(); if (selection.is_empty()) { return; // Nothing to reparent. @@ -2197,6 +2211,10 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { } _do_reparent(new_parent, -1, nodes, p_keep_global_xform); + + for (Node *E : full_selection) { + editor_selection->add_node(E); + } } void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) { @@ -2238,6 +2256,9 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V return; // Position and parent didn't change. } + // Prevent selecting the hovered node and keep the reparented node(s) selected instead. + hovered_but_reparenting = true; + Node *validate = new_parent; while (validate) { ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent."); @@ -3400,6 +3421,7 @@ void SceneTreeDock::_nodes_dragged(const Array &p_nodes, NodePath p_to, int p_ty } List<Node *> selection = editor_selection->get_selected_node_list(); + List<Node *> full_selection = editor_selection->get_full_selected_node_list(); if (selection.is_empty()) { return; //nothing to reparent @@ -3419,7 +3441,8 @@ void SceneTreeDock::_nodes_dragged(const Array &p_nodes, NodePath p_to, int p_ty _normalize_drop(to_node, to_pos, p_type); _do_reparent(to_node, to_pos, nodes, !Input::get_singleton()->is_key_pressed(Key::SHIFT)); - for (Node *E : nodes) { + + for (Node *E : full_selection) { editor_selection->add_node(E); } } diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index abef990995..5028cd5cc9 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -239,8 +239,11 @@ class SceneTreeDock : public VBoxContainer { void _inspect_hovered_node(); void _reset_hovering_timer(); Timer *inspect_hovered_node_delay = nullptr; + TreeItem *tree_item_inspected = nullptr; Node *node_hovered_now = nullptr; Node *node_hovered_previously = nullptr; + bool select_node_hovered_at_end_of_drag = false; + bool hovered_but_reparenting = false; virtual void input(const Ref<InputEvent> &p_event) override; virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 9a0941c75e..10b7c64999 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -451,6 +451,9 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme, p_theme->set_color("success_color", EditorStringName(Editor), p_config.success_color); p_theme->set_color("warning_color", EditorStringName(Editor), p_config.warning_color); p_theme->set_color("error_color", EditorStringName(Editor), p_config.error_color); +#ifndef DISABLE_DEPRECATED // Used before 4.3. + p_theme->set_color("disabled_highlight_color", EditorStringName(Editor), p_config.highlight_disabled_color); +#endif // Only used when the Draw Extra Borders editor setting is enabled. p_config.extra_border_color_1 = Color(0.5, 0.5, 0.5); @@ -480,6 +483,12 @@ void EditorThemeManager::_create_shared_styles(const Ref<EditorTheme> &p_theme, p_theme->set_color("font_readonly_color", EditorStringName(Editor), p_config.font_readonly_color); p_theme->set_color("font_placeholder_color", EditorStringName(Editor), p_config.font_placeholder_color); p_theme->set_color("font_outline_color", EditorStringName(Editor), p_config.font_outline_color); +#ifndef DISABLE_DEPRECATED // Used before 4.3. + p_theme->set_color("readonly_font_color", EditorStringName(Editor), p_config.font_readonly_color); + p_theme->set_color("disabled_font_color", EditorStringName(Editor), p_config.font_disabled_color); + p_theme->set_color("readonly_color", EditorStringName(Editor), p_config.font_readonly_color); + p_theme->set_color("highlighted_font_color", EditorStringName(Editor), p_config.font_hover_color); // Closest equivalent. +#endif // Icon colors. @@ -728,6 +737,8 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_constant("h_separation", "Button", 4 * EDSCALE); p_theme->set_constant("outline_size", "Button", 0); + p_theme->set_constant("align_to_largest_stylebox", "Button", 1); // Enabled. + // MenuButton. p_theme->set_stylebox(CoreStringName(normal), "MenuButton", p_config.panel_container_style); @@ -2106,6 +2117,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme p_theme->set_color("prop_category", EditorStringName(Editor), prop_category_color); p_theme->set_color("prop_section", EditorStringName(Editor), prop_section_color); p_theme->set_color("prop_subsection", EditorStringName(Editor), prop_subsection_color); +#ifndef DISABLE_DEPRECATED // Used before 4.3. + p_theme->set_color("property_color", EditorStringName(Editor), prop_category_color); +#endif // EditorInspectorCategory. diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index b27f80ee29..03c7a8291a 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -364,3 +364,9 @@ GH-92322 Validate extension JSON: Error: Field 'classes/EditorInspectorPlugin/methods/add_property_editor/arguments': size changed value in new API, from 3 to 4. Optional arguments added. Compatibility methods registered. + +GH-86661 +-------- +Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/arguments': size changed value in new API, from 3 to 5. + +Added optional argument to track_find_key to handle backward seeking. Compatibility method registered. diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 771ccf47b7..f1a35c84b7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -166,7 +166,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { panic_mode = true; // TODO: Improve positional information. if (p_origin == nullptr) { - errors.push_back({ p_message, current.start_line, current.start_column }); + errors.push_back({ p_message, previous.start_line, previous.start_column }); } else { errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index eef26cdd4e..5cc2a8026e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -427,10 +427,11 @@ namespace Godot.Bridge // This method may be called before initialization. if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint()) { - foreach (var scriptPath in _pathTypeBiMap.Paths) + if (_pathTypeBiMap.Paths.Count > 0) { - using godot_string nativeScriptPath = Marshaling.ConvertStringToNative(scriptPath); - NativeFuncs.godotsharp_internal_editor_file_system_update_file(nativeScriptPath); + string[] scriptPaths = _pathTypeBiMap.Paths.ToArray(); + using godot_packed_string_array scriptPathsNative = Marshaling.ConvertSystemArrayToNativePackedStringArray(scriptPaths); + NativeFuncs.godotsharp_internal_editor_file_system_update_files(scriptPathsNative); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs index 1ec1a75516..29fa13d625 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -64,7 +65,7 @@ public static partial class ScriptManagerBridge private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new(); private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new(); - public System.Collections.Generic.IEnumerable<string> Paths => _pathTypeMap.Keys; + public IReadOnlyCollection<string> Paths => _pathTypeMap.Keys; public void Add(string scriptPath, Type scriptType) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index c4fd639cce..cfd9ed7acc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -59,7 +59,7 @@ namespace Godot.NativeInterop internal static partial void godotsharp_stack_info_vector_destroy( ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); - internal static partial void godotsharp_internal_editor_file_system_update_file(in godot_string p_script_path); + internal static partial void godotsharp_internal_editor_file_system_update_files(in godot_packed_string_array p_script_paths); internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 1af462dafd..80e9fdf77f 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -315,13 +315,13 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript))); } -void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) { +void godotsharp_internal_editor_file_system_update_files(const PackedStringArray &p_script_paths) { #ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); if (efs) { - efs->update_file(*p_script_path); + efs->update_files(p_script_paths); } #else // EditorFileSystem is only available when running in the Godot editor. @@ -1450,7 +1450,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, (void *)godotsharp_stack_info_vector_destroy, - (void *)godotsharp_internal_editor_file_system_update_file, + (void *)godotsharp_internal_editor_file_system_update_files, (void *)godotsharp_internal_script_debugger_send_error, (void *)godotsharp_internal_script_debugger_is_active, (void *)godotsharp_internal_object_get_associated_gchandle, diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 4ca1b11094..0e4c723a87 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4184,8 +4184,11 @@ void DisplayServerX11::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 1032766480..a1a91345ac 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3366,8 +3366,11 @@ void DisplayServerMacOS::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { bool was_empty = popup_list.is_empty(); // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 73987c44db..10cec9f5ed 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3575,8 +3575,11 @@ void DisplayServerWindows::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 0ac236eaa7..cdd7b15110 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -205,11 +205,12 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { // Check if anything changed that might change the quadrant shape. // If so, recreate everything. - bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] || - (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_TILE_SET])); + bool quadrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_TILE_SET] || + (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM])) || + (!is_y_sort_enabled() && dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE]); // Free all quadrants. - if (forced_cleanup || quandrant_shape_changed) { + if (forced_cleanup || quadrant_shape_changed) { for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { for (const RID &ci : kv.value->canvas_items) { if (ci.is_valid()) { @@ -264,9 +265,8 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { rendering_quadrant->canvas_items.clear(); // Sort the quadrant cells. - if (is_y_sort_enabled()) { - // For compatibility reasons, we use another comparator for Y-sorted layers. - rendering_quadrant->cells.sort_custom<CellDataYSortedComparator>(); + if (is_y_sort_enabled() && x_draw_order_reversed) { + rendering_quadrant->cells.sort_custom<CellDataYSortedXReversedComparator>(); } else { rendering_quadrant->cells.sort(); } @@ -1770,6 +1770,8 @@ void TileMapLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileMapLayer::set_y_sort_origin); ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileMapLayer::get_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_x_draw_order_reversed", "x_draw_order_reversed"), &TileMapLayer::set_x_draw_order_reversed); + ClassDB::bind_method(D_METHOD("is_x_draw_order_reversed"), &TileMapLayer::is_x_draw_order_reversed); ClassDB::bind_method(D_METHOD("set_rendering_quadrant_size", "size"), &TileMapLayer::set_rendering_quadrant_size); ClassDB::bind_method(D_METHOD("get_rendering_quadrant_size"), &TileMapLayer::get_rendering_quadrant_size); @@ -1796,6 +1798,7 @@ void TileMapLayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tile_set", "get_tile_set"); ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "x_draw_order_reversed"), "set_x_draw_order_reversed", "is_x_draw_order_reversed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size"); ADD_GROUP("Physics", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled"); @@ -1814,6 +1817,18 @@ void TileMapLayer::_bind_methods() { BIND_ENUM_CONSTANT(DEBUG_VISIBILITY_MODE_FORCE_SHOW); } +void TileMapLayer::_validate_property(PropertyInfo &p_property) const { + if (is_y_sort_enabled()) { + if (p_property.name == "rendering_quadrant_size") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } else { + if (p_property.name == "x_draw_order_reversed") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} + void TileMapLayer::_update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) { // Set a default texture filter for the whole tilemap. CanvasItem::_update_self_texture_filter(p_texture_filter); @@ -2772,6 +2787,7 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { _queue_internal_update(); emit_signal(CoreStringName(changed)); + notify_property_list_changed(); _update_notify_local_transform(); } @@ -2789,6 +2805,20 @@ int TileMapLayer::get_y_sort_origin() const { return y_sort_origin; } +void TileMapLayer::set_x_draw_order_reversed(bool p_x_draw_order_reversed) { + if (x_draw_order_reversed == p_x_draw_order_reversed) { + return; + } + x_draw_order_reversed = p_x_draw_order_reversed; + dirty.flags[DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +bool TileMapLayer::is_x_draw_order_reversed() const { + return x_draw_order_reversed; +} + void TileMapLayer::set_z_index(int p_z_index) { if (get_z_index() == p_z_index) { return; @@ -2813,10 +2843,9 @@ void TileMapLayer::set_rendering_quadrant_size(int p_size) { if (rendering_quadrant_size == p_size) { return; } - dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] = true; ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - rendering_quadrant_size = p_size; + dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] = true; _queue_internal_update(); emit_signal(CoreStringName(changed)); } @@ -2840,6 +2869,9 @@ bool TileMapLayer::is_collision_enabled() const { } void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) { + if (use_kinematic_bodies == p_use_kinematic_bodies) { + return; + } use_kinematic_bodies = p_use_kinematic_bodies; dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies; _queue_internal_update(); diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 57c83d7c4c..19c6fc128b 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -160,8 +160,8 @@ struct CellData { } }; -// For compatibility reasons, we use another comparator for Y-sorted layers. -struct CellDataYSortedComparator { +// We use another comparator for Y-sorted layers with reversed X drawing order. +struct CellDataYSortedXReversedComparator { _FORCE_INLINE_ bool operator()(const CellData &p_a, const CellData &p_b) const { return p_a.coords.x == p_b.coords.x ? (p_a.coords.y < p_b.coords.y) : (p_a.coords.x > p_b.coords.x); } @@ -245,6 +245,7 @@ public: DIRTY_FLAGS_LAYER_SELF_MODULATE, DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, + DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED, DIRTY_FLAGS_LAYER_Z_INDEX, DIRTY_FLAGS_LAYER_LIGHT_MASK, DIRTY_FLAGS_LAYER_TEXTURE_FILTER, @@ -280,6 +281,7 @@ private: HighlightMode highlight_mode = HIGHLIGHT_MODE_DEFAULT; int y_sort_origin = 0; + bool x_draw_order_reversed = false; int rendering_quadrant_size = 16; bool collision_enabled = true; @@ -383,6 +385,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) override; virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) override; @@ -470,6 +473,8 @@ public: virtual void set_y_sort_enabled(bool p_y_sort_enabled) override; void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; + void set_x_draw_order_reversed(bool p_x_draw_order_reversed); + bool is_x_draw_order_reversed() const; virtual void set_z_index(int p_z_index) override; virtual void set_light_mask(int p_light_mask) override; void set_rendering_quadrant_size(int p_size); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 2716738684..6aade24e4e 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -44,16 +44,8 @@ void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const { } if (parent) { - String names; - for (int i = 0; i < parent->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += parent->get_bone_name(i); - } - p_property.hint = PROPERTY_HINT_ENUM; - p_property.hint_string = names; + p_property.hint_string = parent->get_concatenated_bone_names(); } else { p_property.hint = PROPERTY_HINT_NONE; p_property.hint_string = ""; diff --git a/scene/3d/physics/physical_bone_3d.cpp b/scene/3d/physics/physical_bone_3d.cpp index c6be2a9da8..c290f16c0d 100644 --- a/scene/3d/physics/physical_bone_3d.cpp +++ b/scene/3d/physics/physical_bone_3d.cpp @@ -738,15 +738,7 @@ bool PhysicalBone3D::_get(const StringName &p_name, Variant &r_ret) const { void PhysicalBone3D::_get_property_list(List<PropertyInfo> *p_list) const { Skeleton3D *skeleton = get_skeleton(); if (skeleton) { - String names; - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += skeleton->get_bone_name(i); - } - - p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, names)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, skeleton->get_concatenated_bone_names())); } else { p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"))); } diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index a4804e928a..21e82adf47 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -265,11 +265,31 @@ void Skeleton3D::_update_process_order() { bones_backup.resize(bones.size()); + concatenated_bone_names = StringName(); + process_order_dirty = false; emit_signal("bone_list_changed"); } +void Skeleton3D::_update_bone_names() const { + String names; + for (int i = 0; i < bones.size(); i++) { + if (i > 0) { + names += ","; + } + names += bones[i].name; + } + concatenated_bone_names = StringName(names); +} + +StringName Skeleton3D::get_concatenated_bone_names() const { + if (concatenated_bone_names == StringName()) { + _update_bone_names(); + } + return concatenated_bone_names; +} + #ifndef DISABLE_DEPRECATED void Skeleton3D::setup_simulator() { if (simulator && simulator->get_parent() == this) { @@ -983,6 +1003,8 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names); + ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent); ClassDB::bind_method(D_METHOD("set_bone_parent", "bone_idx", "parent_idx"), &Skeleton3D::set_bone_parent); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 23b9423993..b8e38242b9 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -150,6 +150,9 @@ private: Vector<int> parentless_bones; HashMap<String, int> name_to_bone_index; + mutable StringName concatenated_bone_names = StringName(); + void _update_bone_names() const; + void _make_dirty(); bool dirty = false; bool rest_dirty = false; @@ -200,6 +203,7 @@ public: int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; void set_bone_name(int p_bone, const String &p_name); + StringName get_concatenated_bone_names() const; bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 78a21ba9e1..0d6316ee35 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -306,16 +306,8 @@ void SkeletonIK3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "root_bone" || p_property.name == "tip_bone") { Skeleton3D *skeleton = get_skeleton(); if (skeleton) { - String names("--,"); - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += skeleton->get_bone_name(i); - } - p_property.hint = PROPERTY_HINT_ENUM; - p_property.hint_string = names; + p_property.hint_string = skeleton->get_concatenated_bone_names(); } else { p_property.hint = PROPERTY_HINT_NONE; p_property.hint_string = ""; diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp index 8d806ef5fc..9851214194 100644 --- a/scene/3d/skeleton_modifier_3d.cpp +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -123,6 +123,8 @@ void SkeletonModifier3D::_notification(int p_what) { } void SkeletonModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModifier3D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_active", "active"), &SkeletonModifier3D::set_active); ClassDB::bind_method(D_METHOD("is_active"), &SkeletonModifier3D::is_active); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index e600de6b8b..5074145168 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1109,6 +1109,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { real_t weight = ai.playback_info.weight; Vector<real_t> track_weights = ai.playback_info.track_weights; bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. + bool seeked_backward = signbit(p_delta); #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED @@ -1463,7 +1464,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } else { if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, false, seeked_backward); if (idx < 0) { continue; } @@ -1744,8 +1745,10 @@ void AnimationMixer::_blend_apply() { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - if (t->use_discrete && !t->use_continuous) { - t->is_init = true; // If only disctere value is applied, no more RESET. + if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { + t->is_init = false; // Always update in Force Continuous. + } else if (!t->use_continuous && (t->use_discrete || !deterministic)) { + t->is_init = true; // If there is no continuous value and only disctere value is applied or just started, don't RESET. } if ((t->is_init && (is_zero_amount || !t->use_continuous)) || @@ -1756,9 +1759,7 @@ void AnimationMixer::_blend_apply() { break; // Don't overwrite the value set by UPDATE_DISCRETE. } - if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { - t->is_init = false; // Always update in Force Continuous. - } else { + if (callback_mode_discrete != ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { t->is_init = !t->use_continuous; // If there is no Continuous in non-Force Continuous type, it means RESET. } diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 0c24d79ad7..5756edaa48 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -234,6 +234,9 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f pi.delta = delta; pi.seeked = p_seeked; } + if (Math::is_zero_approx(pi.delta) && backwards) { + pi.delta = -0.0; // Sign is needed to handle converted Continuous track from Discrete track correctly. + } // AnimationPlayer doesn't have internal seeking. // However, immediately after playback, discrete keys should be retrieved with EXACT mode since behind keys must be ignored at that time. pi.is_external_seeking = !p_started; @@ -257,7 +260,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) { bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process. - if (p_delta != 0) { + if (!Math::is_zero_approx(p_delta)) { c.seeked = false; } @@ -581,6 +584,8 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) { return; } + bool is_backward = p_time < playback.current.pos; + _check_immediately_after_start(); playback.current.pos = p_time; @@ -596,7 +601,7 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) { playback.seeked = true; if (p_update) { - _process_animation(0, p_update_only); + _process_animation(is_backward ? -0.0 : 0.0, p_update_only); playback.seeked = false; // If animation was proceeded here, no more seek in internal process. } } diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 3bf9d79c7f..55ca5fc3ee 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -121,6 +121,7 @@ void Button::_update_theme_item_cache() { theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled->get_margin(SIDE_TOP)); theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled->get_margin(SIDE_BOTTOM)); } + theme_cache.max_style_size = theme_cache.max_style_size.max(Vector2(theme_cache.style_margin_left + theme_cache.style_margin_right, theme_cache.style_margin_top + theme_cache.style_margin_bottom)); } Size2 Button::_get_largest_stylebox_size() const { @@ -214,9 +215,10 @@ void Button::_notification(int p_what) { const RID ci = get_canvas_item(); const Size2 size = get_size(); + Ref<StyleBox> style = _get_current_stylebox(); // Draws the stylebox in the current state. if (!flat) { - _get_current_stylebox()->draw(ci, Rect2(Point2(), size)); + style->draw(ci, Rect2(Point2(), size)); } if (has_focus()) { @@ -232,18 +234,23 @@ void Button::_notification(int p_what) { break; } + const float style_margin_left = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_left : style->get_margin(SIDE_LEFT); + const float style_margin_right = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_right : style->get_margin(SIDE_RIGHT); + const float style_margin_top = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_top : style->get_margin(SIDE_TOP); + const float style_margin_bottom = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_bottom : style->get_margin(SIDE_BOTTOM); + Size2 drawable_size_remained = size; { // The size after the stelybox is stripped. - drawable_size_remained.width -= theme_cache.style_margin_left + theme_cache.style_margin_right; - drawable_size_remained.height -= theme_cache.style_margin_top + theme_cache.style_margin_bottom; + drawable_size_remained.width -= style_margin_left + style_margin_right; + drawable_size_remained.height -= style_margin_top + style_margin_bottom; } const int h_separation = MAX(0, theme_cache.h_separation); float left_internal_margin_with_h_separation = _internal_margin[SIDE_LEFT]; float right_internal_margin_with_h_separation = _internal_margin[SIDE_RIGHT]; - { // The width reserved for internal element in derived classes (and h_separation if need). + { // The width reserved for internal element in derived classes (and h_separation if needed). if (_internal_margin[SIDE_LEFT] > 0.0f) { left_internal_margin_with_h_separation += h_separation; @@ -381,12 +388,12 @@ void Button::_notification(int p_what) { [[fallthrough]]; case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: { - icon_ofs.x += theme_cache.style_margin_left; + icon_ofs.x += style_margin_left; icon_ofs.x += left_internal_margin_with_h_separation; } break; case HORIZONTAL_ALIGNMENT_RIGHT: { - icon_ofs.x = size.x - theme_cache.style_margin_right; + icon_ofs.x = size.x - style_margin_right; icon_ofs.x -= right_internal_margin_with_h_separation; icon_ofs.x -= icon_size.width; } break; @@ -399,11 +406,11 @@ void Button::_notification(int p_what) { [[fallthrough]]; case VERTICAL_ALIGNMENT_FILL: case VERTICAL_ALIGNMENT_TOP: { - icon_ofs.y += theme_cache.style_margin_top; + icon_ofs.y += style_margin_top; } break; case VERTICAL_ALIGNMENT_BOTTOM: { - icon_ofs.y = size.y - theme_cache.style_margin_bottom - icon_size.height; + icon_ofs.y = size.y - style_margin_bottom - icon_size.height; } break; } icon_ofs = icon_ofs.floor(); @@ -442,7 +449,7 @@ void Button::_notification(int p_what) { case HORIZONTAL_ALIGNMENT_FILL: case HORIZONTAL_ALIGNMENT_LEFT: case HORIZONTAL_ALIGNMENT_RIGHT: { - text_ofs.x += theme_cache.style_margin_left; + text_ofs.x += style_margin_left; text_ofs.x += left_internal_margin_with_h_separation; if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { // Offset by the space's width that occupied by icon and h_separation together. @@ -451,7 +458,7 @@ void Button::_notification(int p_what) { } break; } - text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + theme_cache.style_margin_top; + text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + style_margin_top; if (vertical_icon_alignment == VERTICAL_ALIGNMENT_TOP) { text_ofs.y += custom_element_size.height - drawable_size_remained.height; // Offset by the icon's height. } @@ -493,6 +500,21 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu minsize.width = 0; } + float left_internal_margin_with_h_separation = _internal_margin[SIDE_LEFT]; + float right_internal_margin_with_h_separation = _internal_margin[SIDE_RIGHT]; + { // The width reserved for internal element in derived classes (and h_separation if needed). + + if (_internal_margin[SIDE_LEFT] > 0.0f) { + left_internal_margin_with_h_separation += theme_cache.h_separation; + } + + if (_internal_margin[SIDE_RIGHT] > 0.0f) { + right_internal_margin_with_h_separation += theme_cache.h_separation; + } + + minsize.width += left_internal_margin_with_h_separation + right_internal_margin_with_h_separation; // The size after the internal element is stripped. + } + if (!expand_icon && p_icon.is_valid()) { Size2 icon_size = _fit_icon_size(p_icon->get_size()); if (vertical_icon_alignment == VERTICAL_ALIGNMENT_CENTER) { @@ -828,6 +850,8 @@ void Button::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, h_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, icon_max_width); + + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, align_to_largest_stylebox); } Button::Button(const String &p_text) { diff --git a/scene/gui/button.h b/scene/gui/button.h index eefb690913..5f4429bc1d 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -75,6 +75,8 @@ private: float style_margin_top = 0; float style_margin_bottom = 0; + bool align_to_largest_stylebox = false; + Color font_color; Color font_focus_color; Color font_pressed_color; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 0c146ce173..a23ee6db71 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -99,7 +99,9 @@ void FileDialog::set_visible(bool p_visible) { #endif if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - _native_popup(); + if (p_visible) { + _native_popup(); + } } else { ConfirmationDialog::set_visible(p_visible); } diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 38bd7141c2..468d4e3c0f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1430,7 +1430,7 @@ void Viewport::_gui_show_tooltip() { Control *tooltip_owner = nullptr; gui.tooltip_text = _gui_get_tooltip( gui.tooltip_control, - gui.tooltip_control->get_global_transform().xform_inv(gui.last_mouse_pos), + gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos), &tooltip_owner); gui.tooltip_text = gui.tooltip_text.strip_edges(); @@ -1910,7 +1910,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.tooltip_popup) { if (gui.tooltip_control) { - String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform().xform_inv(mpos)); + String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(mpos)); tooltip = tooltip.strip_edges(); if (tooltip.is_empty() || tooltip != gui.tooltip_text) { diff --git a/scene/resources/animation.compat.inc b/scene/resources/animation.compat.inc index d7d8867748..fc2672bb25 100644 --- a/scene/resources/animation.compat.inc +++ b/scene/resources/animation.compat.inc @@ -50,8 +50,8 @@ Variant Animation::_value_track_interpolate_bind_compat_86629(int p_track, doubl return value_track_interpolate(p_track, p_time, false); } -int Animation::_track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode) const { - return track_find_key(p_track, p_time, p_find_mode, false); +int Animation::_track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode) const { + return track_find_key(p_track, p_time, p_find_mode, false, false); } void Animation::_bind_compatibility_methods() { @@ -60,7 +60,7 @@ void Animation::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("scale_track_interpolate", "track_idx", "time_sec"), &Animation::_scale_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("blend_shape_track_interpolate", "track_idx", "time_sec"), &Animation::_blend_shape_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::_value_track_interpolate_bind_compat_86629); - ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_86661, DEFVAL(FIND_MODE_NEAREST)); + ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_92861, DEFVAL(FIND_MODE_NEAREST)); } #endif // DISABLE_DEPRECATED diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index a3bfa987c6..254bd38be7 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1496,7 +1496,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { emit_changed(); } -int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit) const { +int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit, bool p_backward) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1518,7 +1518,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(tt->positions, p_time, false, p_limit); + int k = _find(tt->positions, p_time, p_backward, p_limit); if (k < 0 || k >= tt->positions.size()) { return -1; } @@ -1545,7 +1545,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(rt->rotations, p_time, false, p_limit); + int k = _find(rt->rotations, p_time, p_backward, p_limit); if (k < 0 || k >= rt->rotations.size()) { return -1; } @@ -1572,7 +1572,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(st->scales, p_time, false, p_limit); + int k = _find(st->scales, p_time, p_backward, p_limit); if (k < 0 || k >= st->scales.size()) { return -1; } @@ -1599,7 +1599,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(bst->blend_shapes, p_time, false, p_limit); + int k = _find(bst->blend_shapes, p_time, p_backward, p_limit); if (k < 0 || k >= bst->blend_shapes.size()) { return -1; } @@ -1611,7 +1611,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_VALUE: { ValueTrack *vt = static_cast<ValueTrack *>(t); - int k = _find(vt->values, p_time, false, p_limit); + int k = _find(vt->values, p_time, p_backward, p_limit); if (k < 0 || k >= vt->values.size()) { return -1; } @@ -1623,7 +1623,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_METHOD: { MethodTrack *mt = static_cast<MethodTrack *>(t); - int k = _find(mt->methods, p_time, false, p_limit); + int k = _find(mt->methods, p_time, p_backward, p_limit); if (k < 0 || k >= mt->methods.size()) { return -1; } @@ -1635,7 +1635,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_BEZIER: { BezierTrack *bt = static_cast<BezierTrack *>(t); - int k = _find(bt->values, p_time, false, p_limit); + int k = _find(bt->values, p_time, p_backward, p_limit); if (k < 0 || k >= bt->values.size()) { return -1; } @@ -1647,7 +1647,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_AUDIO: { AudioTrack *at = static_cast<AudioTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -1659,7 +1659,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_ANIMATION: { AnimationTrack *at = static_cast<AnimationTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -3836,7 +3836,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_get_key_count", "track_idx"), &Animation::track_get_key_count); ClassDB::bind_method(D_METHOD("track_get_key_value", "track_idx", "key_idx"), &Animation::track_get_key_value); ClassDB::bind_method(D_METHOD("track_get_key_time", "track_idx", "key_idx"), &Animation::track_get_key_time); - ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit", "backward"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("track_set_interpolation_type", "track_idx", "interpolation"), &Animation::track_set_interpolation_type); ClassDB::bind_method(D_METHOD("track_get_interpolation_type", "track_idx"), &Animation::track_get_interpolation_type); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 604bce497a..e9bfc298a5 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -388,7 +388,7 @@ protected: Vector3 _scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const; float _blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const; Variant _value_track_interpolate_bind_compat_86629(int p_track, double p_time) const; - int _track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; + int _track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -423,7 +423,7 @@ public: void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); void track_set_key_time(int p_track, int p_key_idx, double p_time); - int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false) const; + int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false, bool p_backward = false) const; void track_remove_key(int p_track, int p_idx); void track_remove_key_at_time(int p_track, double p_time); int track_get_key_count(int p_track) const; diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 6d187bbd64..5315b0acc9 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -187,6 +187,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_separation", "Button", Math::round(4 * scale)); theme->set_constant("icon_max_width", "Button", 0); + theme->set_constant("align_to_largest_stylebox", "Button", 0); // Disabled. + // MenuBar theme->set_stylebox(CoreStringName(normal), "MenuBar", button_normal); theme->set_stylebox("hover", "MenuBar", button_hover); diff --git a/thirdparty/README.md b/thirdparty/README.md index ab62ad80d8..2b48e19143 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.13.3 (6235068cad8cad176ccd0cbcf82f25e985fbc258, 2024) +- Version: 0.13.7 (d2c0428a99f7305c086caffe0c730add601ebd6e, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 9c83f25c79..699076fdf1 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -13,5 +13,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.13.5" +#define THORVG_VERSION_STRING "0.13.7" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index f7396050d7..8285aa1c4c 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -80,7 +80,7 @@ enum class Result InsufficientCondition, ///< The value returned in case the request cannot be processed - e.g. asking for properties of an object, which does not exist. FailedAllocation, ///< The value returned in case of unsuccessful memory allocation. MemoryCorruption, ///< The value returned in the event of bad memory handling - e.g. failing in pointer releasing or casting - NonSupport, ///< The value returned in case of choosing unsupported options. + NonSupport, ///< The value returned in case of choosing unsupported engine features(options). Unknown ///< The value returned in all other cases. }; @@ -982,7 +982,7 @@ public: * * @param[in] width The width of the stroke. The default value is 0. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(float width) noexcept; @@ -994,7 +994,7 @@ public: * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; @@ -1004,8 +1004,7 @@ public: * @param[in] f The gradient fill. * * @retval Result::Success When succeed. - * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be filled. - * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument or an error with accessing it. */ Result stroke(std::unique_ptr<Fill> f) noexcept; @@ -1029,7 +1028,7 @@ public: * * @param[in] cap The cap style value. The default value is @c StrokeCap::Square. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(StrokeCap cap) noexcept; @@ -1040,23 +1039,38 @@ public: * * @param[in] join The join style value. The default value is @c StrokeJoin::Bevel. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(StrokeJoin join) noexcept; - /** * @brief Sets the stroke miterlimit. * * @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4. * - * @retval Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed or Result::InvalidArgument for @p miterlimit values less than zero. * * @since 0.11 */ Result strokeMiterlimit(float miterlimit) noexcept; /** + * @brief Sets the trim of the stroke along the defined path segment, allowing control over which part of the stroke is visible. + * + * The values of the arguments @p begin, @p end, and @p offset are in the range of 0.0 to 1.0, representing the beginning of the path and the end, respectively. + * + * @param[in] begin Specifies the start of the segment to display along the path. + * @param[in] end Specifies the end of the segment to display along the path. + * @param[in] simultaneous Determines how to trim multiple paths within a single shape. If set to @c true (default), trimming is applied simultaneously to all paths; + * Otherwise, all paths are treated as a single entity with a combined length equal to the sum of their individual lengths and are trimmed as such. + * + * @retval Result::Success when succeed. + * + * @note Experimental API + */ + Result strokeTrim(float begin, float end, bool simultaneous = true) noexcept; + + /** * @brief Sets the solid color for all of the figures from the path. * * The parts of the shape defined as inner are colored. @@ -1095,19 +1109,17 @@ public: */ Result fill(FillRule r) noexcept; - /** * @brief Sets the rendering order of the stroke and the fill. * * @param[in] strokeFirst If @c true the stroke is rendered before the fill, otherwise the stroke is rendered as the second one (the default option). * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. * * @since 0.10 */ Result order(bool strokeFirst) noexcept; - /** * @brief Gets the commands data of the path. * @@ -1211,6 +1223,18 @@ public: float strokeMiterlimit() const noexcept; /** + * @brief Gets the trim of the stroke along the defined path segment. + * + * @param[out] begin The starting point of the segment to display along the path. + * @param[out] end Specifies the end of the segment to display along the path. + * + * @retval @c true if trimming is applied simultaneously to all paths of the shape, @c false otherwise. + * + * @note Experimental API + */ + bool strokeTrim(float* begin, float* end) const noexcept; + + /** * @brief Creates a new Shape object. * * @return A new Shape object. diff --git a/thirdparty/thorvg/src/common/tvgCompressor.cpp b/thirdparty/thorvg/src/common/tvgCompressor.cpp index 9a1dc54632..b61718f9a7 100644 --- a/thirdparty/thorvg/src/common/tvgCompressor.cpp +++ b/thirdparty/thorvg/src/common/tvgCompressor.cpp @@ -472,4 +472,19 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded) } +/************************************************************************/ +/* DJB2 Implementation */ +/************************************************************************/ + +unsigned long djb2Encode(const char* str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + c; // hash * 33 + c + } + return hash; +} + } diff --git a/thirdparty/thorvg/src/common/tvgCompressor.h b/thirdparty/thorvg/src/common/tvgCompressor.h index 0756127ec6..b043cc77de 100644 --- a/thirdparty/thorvg/src/common/tvgCompressor.h +++ b/thirdparty/thorvg/src/common/tvgCompressor.h @@ -30,6 +30,7 @@ namespace tvg uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits); uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes); size_t b64Decode(const char* encoded, const size_t len, char** decoded); + unsigned long djb2Encode(const char* str); } #endif //_TVG_COMPRESSOR_H_ diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index 37a8879cb5..e6b5d47050 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -47,23 +47,14 @@ bool mathInverse(const Matrix* m, Matrix* out) } -Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) +bool mathIdentity(const Matrix* m) { - Matrix m; - - m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31; - m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32; - m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33; - - m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31; - m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32; - m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33; - - m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31; - m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32; - m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33; - - return m; + if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || + m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || + m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { + return false; + } + return true; } @@ -82,21 +73,41 @@ void mathRotate(Matrix* m, float degree) } -bool mathIdentity(const Matrix* m) +Matrix operator*(const Matrix& lhs, const Matrix& rhs) { - if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || - m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || - m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { - return false; + Matrix m; + + m.e11 = lhs.e11 * rhs.e11 + lhs.e12 * rhs.e21 + lhs.e13 * rhs.e31; + m.e12 = lhs.e11 * rhs.e12 + lhs.e12 * rhs.e22 + lhs.e13 * rhs.e32; + m.e13 = lhs.e11 * rhs.e13 + lhs.e12 * rhs.e23 + lhs.e13 * rhs.e33; + + m.e21 = lhs.e21 * rhs.e11 + lhs.e22 * rhs.e21 + lhs.e23 * rhs.e31; + m.e22 = lhs.e21 * rhs.e12 + lhs.e22 * rhs.e22 + lhs.e23 * rhs.e32; + m.e23 = lhs.e21 * rhs.e13 + lhs.e22 * rhs.e23 + lhs.e23 * rhs.e33; + + m.e31 = lhs.e31 * rhs.e11 + lhs.e32 * rhs.e21 + lhs.e33 * rhs.e31; + m.e32 = lhs.e31 * rhs.e12 + lhs.e32 * rhs.e22 + lhs.e33 * rhs.e32; + m.e33 = lhs.e31 * rhs.e13 + lhs.e32 * rhs.e23 + lhs.e33 * rhs.e33; + + return m; +} + + +bool operator==(const Matrix& lhs, const Matrix& rhs) +{ + if (!mathEqual(lhs.e11, rhs.e11) || !mathEqual(lhs.e12, rhs.e12) || !mathEqual(lhs.e13, rhs.e13) || + !mathEqual(lhs.e21, rhs.e21) || !mathEqual(lhs.e22, rhs.e22) || !mathEqual(lhs.e23, rhs.e23) || + !mathEqual(lhs.e31, rhs.e31) || !mathEqual(lhs.e32, rhs.e32) || !mathEqual(lhs.e33, rhs.e33)) { + return false; } return true; } -void mathMultiply(Point* pt, const Matrix* transform) +void operator*=(Point& pt, const Matrix& m) { - auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13; - auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23; - pt->x = tx; - pt->y = ty; + auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13; + auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23; + pt.x = tx; + pt.y = ty; } diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 5c2966956a..0f877d919e 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -38,11 +38,9 @@ #define mathMax(x, y) (((x) > (y)) ? (x) : (y)) -bool mathInverse(const Matrix* m, Matrix* out); -Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs); -void mathRotate(Matrix* m, float degree); -bool mathIdentity(const Matrix* m); -void mathMultiply(Point* pt, const Matrix* transform); +/************************************************************************/ +/* General functions */ +/************************************************************************/ static inline float mathDeg2Rad(float degree) @@ -63,28 +61,21 @@ static inline bool mathZero(float a) } -static inline bool mathZero(const Point& p) -{ - return mathZero(p.x) && mathZero(p.y); -} - - static inline bool mathEqual(float a, float b) { return mathZero(a - b); } -static inline bool mathEqual(const Matrix& a, const Matrix& b) -{ - if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) || - !mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) || - !mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) { - return false; - } - return true; -} +/************************************************************************/ +/* Matrix functions */ +/************************************************************************/ +void mathRotate(Matrix* m, float degree); +bool mathInverse(const Matrix* m, Matrix* out); +bool mathIdentity(const Matrix* m); +Matrix operator*(const Matrix& lhs, const Matrix& rhs); +bool operator==(const Matrix& lhs, const Matrix& rhs); static inline bool mathRightAngle(const Matrix* m) { @@ -114,15 +105,6 @@ static inline void mathIdentity(Matrix* m) } -static inline void mathTransform(Matrix* transform, Point* coord) -{ - auto x = coord->x; - auto y = coord->y; - coord->x = x * transform->e11 + y * transform->e12 + transform->e13; - coord->y = x * transform->e21 + y * transform->e22 + transform->e23; -} - - static inline void mathScale(Matrix* m, float sx, float sy) { m->e11 *= sx; @@ -158,12 +140,37 @@ static inline void mathTranslateR(Matrix* m, float x, float y) } +static inline bool operator!=(const Matrix& lhs, const Matrix& rhs) +{ + return !(lhs == rhs); +} + + +static inline void operator*=(Matrix& lhs, const Matrix& rhs) +{ + lhs = lhs * rhs; +} + + static inline void mathLog(Matrix* m) { TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33); } +/************************************************************************/ +/* Point functions */ +/************************************************************************/ + +void operator*=(Point& pt, const Matrix& m); + + +static inline bool mathZero(const Point& p) +{ + return mathZero(p.x) && mathZero(p.y); +} + + static inline float mathLength(const Point* a, const Point* b) { auto x = b->x - a->x; @@ -182,6 +189,18 @@ static inline float mathLength(const Point& a) } +static inline bool operator==(const Point& lhs, const Point& rhs) +{ + return mathEqual(lhs.x, rhs.x) && mathEqual(lhs.y, rhs.y); +} + + +static inline bool operator!=(const Point& lhs, const Point& rhs) +{ + return !(lhs == rhs); +} + + static inline Point operator-(const Point& lhs, const Point& rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y}; @@ -212,6 +231,10 @@ static inline Point operator/(const Point& lhs, const float rhs) } +/************************************************************************/ +/* Interpolation functions */ +/************************************************************************/ + template <typename T> static inline T mathLerp(const T &start, const T &end, float t) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 0a47112084..f59994aae6 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -709,7 +709,7 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** *ref = _idFromUrl((const char*)(str + 3)); return true; } else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') { - float th, ts, tb; + float_t th, ts, tb; const char *content, *hue, *satuation, *brightness; content = str + 4; content = _skipSpace(content, nullptr); @@ -840,14 +840,14 @@ static Matrix* _parseTransformationMatrix(const char* value) if (state == MatrixState::Matrix) { if (ptCount != 6) goto error; Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::Translate) { if (ptCount == 1) { Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (ptCount == 2) { Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else goto error; } else if (state == MatrixState::Rotate) { //Transform to signed. @@ -857,14 +857,14 @@ static Matrix* _parseTransformationMatrix(const char* value) auto s = sinf(mathDeg2Rad(points[0])); if (ptCount == 1) { Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (ptCount == 3) { Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else { goto error; } @@ -874,17 +874,17 @@ static Matrix* _parseTransformationMatrix(const char* value) auto sy = sx; if (ptCount == 2) sy = points[1]; Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::SkewX) { if (ptCount != 1) goto error; auto deg = tanf(mathDeg2Rad(points[0])); Matrix tmp = { 1, deg, 0, 0, 1, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::SkewY) { if (ptCount != 1) goto error; auto deg = tanf(mathDeg2Rad(points[0])); Matrix tmp = { 1, 0, 0, deg, 1, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } } return matrix; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index 0b2113dd07..7e7efed3fc 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -203,9 +203,9 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* if (node->transform) finalTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - finalTransform = mathMultiply(&finalTransform, &m); + finalTransform *= m; } - if (child->transform) finalTransform = mathMultiply(child->transform, &finalTransform); + if (child->transform) finalTransform = *child->transform * finalTransform; return _appendClipShape(loaderData, child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); } @@ -228,13 +228,13 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg m = *node->transform; } if (compNode->transform) { - m = mathMultiply(&m, compNode->transform); + m *= *compNode->transform; } if (!compNode->node.clip.userSpace) { float x, y, w, h; P(paint)->bounds(&x, &y, &w, &h, false, false); Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1}; - m = mathMultiply(&m, &mBBox); + m *= mBBox; } return m; } @@ -474,7 +474,10 @@ static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* sh auto ptsCnt = shape->pathCoords(&pts); auto p = const_cast<Point*>(pts) + currentPtsCnt; - while (currentPtsCnt++ < ptsCnt) mathMultiply(p++, m); + while (currentPtsCnt++ < ptsCnt) { + *p *= *m; + ++p; + } } _applyProperty(loaderData, node, shape, vBox, svgPath, true); @@ -505,6 +508,7 @@ static constexpr struct } imageMimeTypes[] = { {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64}, {"png", sizeof("png"), imageMimeTypeEncoding::base64}, + {"webp", sizeof("webp"), imageMimeTypeEncoding::base64}, {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8}, }; @@ -615,7 +619,7 @@ static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* auto sy = node->node.image.h / h; m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; } - if (node->transform) m = mathMultiply(node->transform, &m); + if (node->transform) m = *node->transform * m; picture->transform(m); _applyComposition(loaderData, picture.get(), node, vBox, svgPath); @@ -708,7 +712,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod if (node->transform) mUseTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - mUseTransform = mathMultiply(&mUseTransform, &mTranslate); + mUseTransform *= mTranslate; } if (node->node.use.symbol) { @@ -732,9 +736,9 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod // mSceneTransform = mUseTransform * mSymbolTransform * mViewBox Matrix mSceneTransform = mViewBox; if (node->node.use.symbol->transform) { - mSceneTransform = mathMultiply(node->node.use.symbol->transform, &mViewBox); + mSceneTransform = *node->node.use.symbol->transform * mViewBox; } - mSceneTransform = mathMultiply(&mUseTransform, &mSceneTransform); + mSceneTransform = mUseTransform * mSceneTransform; scene->transform(mSceneTransform); if (node->node.use.symbol->node.symbol.overflowVisible) { @@ -746,7 +750,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod // mClipTransform = mUseTransform * mSymbolTransform Matrix mClipTransform = mUseTransform; if (node->node.use.symbol->transform) { - mClipTransform = mathMultiply(&mUseTransform, node->node.use.symbol->transform); + mClipTransform = mUseTransform * *node->node.use.symbol->transform; } viewBoxClip->transform(mClipTransform); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index 3d582d291c..be1662daeb 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -150,7 +150,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); if (isTransformation) { - if (transform) gradTransform = mathMultiply(transform, &gradTransform); + if (transform) gradTransform = *transform * gradTransform; } else if (transform) { gradTransform = *transform; isTransformation = true; @@ -216,7 +216,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); if (transform) { - if (isTransformation) gradTransform = mathMultiply(transform, &gradTransform); + if (isTransformation) gradTransform = *transform * gradTransform; else { gradTransform = *transform; isTransformation = true; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp index b85d943873..3431f03411 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp @@ -81,7 +81,7 @@ SwMpool* mpoolInit(uint32_t threads) { auto allocSize = threads + 1; - auto mpool = static_cast<SwMpool*>(calloc(sizeof(SwMpool), 1)); + auto mpool = static_cast<SwMpool*>(calloc(1, sizeof(SwMpool))); mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); mpool->dashOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h index 731f6984e3..8ec2bc0c47 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h @@ -1108,7 +1108,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const float ys = FLT_MAX, ye = -1.0f; for (int i = 0; i < 4; i++) { - if (transform) mathMultiply(&vertices[i].pt, transform); + if (transform) vertices[i].pt *= *transform; if (vertices[i].pt.y < ys) ys = vertices[i].pt.y; if (vertices[i].pt.y > ye) ye = vertices[i].pt.y; } @@ -1169,9 +1169,9 @@ static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, c float ys = FLT_MAX, ye = -1.0f; for (uint32_t i = 0; i < mesh->triangleCnt; i++) { transformedTris[i] = mesh->triangles[i]; - mathMultiply(&transformedTris[i].vertex[0].pt, transform); - mathMultiply(&transformedTris[i].vertex[1].pt, transform); - mathMultiply(&transformedTris[i].vertex[2].pt, transform); + transformedTris[i].vertex[0].pt *= *transform; + transformedTris[i].vertex[1].pt *= *transform; + transformedTris[i].vertex[2].pt *= *transform; if (transformedTris[i].vertex[0].pt.y < ys) ys = transformedTris[i].vertex[0].pt.y; else if (transformedTris[i].vertex[0].pt.y > ye) ye = transformedTris[i].vertex[0].pt.y; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 0b2940c32d..f689179928 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -78,7 +78,6 @@ struct SwShapeTask : SwTask { SwShape shape; const RenderShape* rshape = nullptr; - bool cmpStroking = false; bool clipper = false; /* We assume that if the stroke width is greater than 2, diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 386cc594b4..1e5c4ef409 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -928,7 +928,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; rw.ySpan = 0; rw.outline = const_cast<SwOutline*>(outline); - rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 + rw.bandSize = rw.bufferSize / (sizeof(Cell) * 2); //bandSize: 256 rw.bandShoot = 0; rw.antiAlias = antiAlias; @@ -966,10 +966,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren if (cellMod > 0) cellStart += sizeof(Cell) - cellMod; - auto cellEnd = rw.bufferSize; - cellEnd -= cellEnd % sizeof(Cell); - - auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + cellEnd); + auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + rw.bufferSize); rw.cells = reinterpret_cast<Cell*>((char*)rw.buffer + cellStart); if (rw.cells >= cellsMax) goto reduce_bands; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index b9327374b6..d8dd40d45b 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -107,7 +107,7 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans if (mathZero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); //draw the current line fully - } else if (len < dash.curLen) { + } else if (len <= dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { @@ -168,7 +168,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct //draw the current line fully if (mathZero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); - } else if (len < dash.curLen) { + } else if (len <= dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { @@ -245,7 +245,86 @@ static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const } -static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length, SwMpool* mpool, unsigned tid) +static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length) +{ + auto begin = length * rshape->stroke->trim.begin; + auto end = length * rshape->stroke->trim.end; + + //default + if (end > begin) { + if (begin > 0.0f) dash->cnt = 4; + else dash->cnt = 2; + //looping + } else dash->cnt = 3; + + if (dash->cnt == 2) { + dash->pattern[0] = end - begin; + dash->pattern[1] = length - (end - begin); + } else if (dash->cnt == 3) { + dash->pattern[0] = end; + dash->pattern[1] = (begin - end); + dash->pattern[2] = length - begin; + } else { + dash->pattern[0] = 0; //zero dash to start with a space. + dash->pattern[1] = begin; + dash->pattern[2] = end - begin; + dash->pattern[3] = length - end; + } +} + + +static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32_t shiftCmds, bool subpath) +{ + const PathCommand* cmds = rshape->path.cmds.data + shiftCmds; + auto cmdCnt = rshape->path.cmds.count - shiftCmds; + const Point* pts = rshape->path.pts.data + shiftPts; + auto ptsCnt = rshape->path.pts.count - shiftPts; + + //No actual shape data + if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f; + + const Point* close = nullptr; + auto length = 0.0f; + + //must begin with moveTo + if (cmds[0] == PathCommand::MoveTo) { + close = pts; + cmds++; + pts++; + cmdCnt--; + } + + while (cmdCnt-- > 0) { + switch (*cmds) { + case PathCommand::Close: { + length += mathLength(pts - 1, close); + if (subpath) return length; + break; + } + case PathCommand::MoveTo: { + if (subpath) return length; + close = pts; + ++pts; + break; + } + case PathCommand::LineTo: { + length += mathLength(pts - 1, pts); + ++pts; + break; + } + case PathCommand::CubicTo: { + length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); + pts += 3; + break; + } + } + ++cmds; + } + return length; +} + + +static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, bool trimmed, SwMpool* mpool, unsigned tid) { const PathCommand* cmds = rshape->path.cmds.data; auto cmdCnt = rshape->path.cmds.count; @@ -255,49 +334,23 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans //No actual shape data if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + auto startPts = pts; + auto startCmds = cmds; + SwDashStroke dash; auto offset = 0.0f; - auto trimmed = false; - dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset); + auto simultaneous = rshape->stroke->trim.simultaneous; - //dash by trimming. - if (length > 0.0f && dash.cnt == 0) { - auto begin = length * rshape->stroke->trim.begin; - auto end = length * rshape->stroke->trim.end; - - //TODO: mix trimming + dash style - - //default - if (end > begin) { - if (begin > 0.0f) dash.cnt += 4; - else dash.cnt += 2; - //looping - } else dash.cnt += 3; - - dash.pattern = (float*)malloc(sizeof(float) * dash.cnt); - - if (dash.cnt == 2) { - dash.pattern[0] = end - begin; - dash.pattern[1] = length - (end - begin); - } else if (dash.cnt == 3) { - dash.pattern[0] = end; - dash.pattern[1] = (begin - end); - dash.pattern[2] = length - begin; - } else { - dash.pattern[0] = 0; //zero dash to start with a space. - dash.pattern[1] = begin; - dash.pattern[2] = end - begin; - dash.pattern[3] = length - end; - } - - trimmed = true; - //just a dasy style. + if (dash.cnt == 0) { + if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4); + else return nullptr; } else { - if (dash.cnt == 0) return nullptr; + //TODO: handle dash + trim - for now trimming ignoring is forced + trimmed = false; } - //offset? + //offset auto patternLength = 0.0f; uint32_t offIdx = 0; if (!mathZero(offset)) { @@ -319,6 +372,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans //must begin with moveTo if (cmds[0] == PathCommand::MoveTo) { + if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous)); _dashMoveTo(dash, offIdx, offset, pts); cmds++; pts++; @@ -331,8 +385,12 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans break; } case PathCommand::MoveTo: { - if (rshape->stroke->trim.individual) _dashMoveTo(dash, pts); - else _dashMoveTo(dash, offIdx, offset, pts); + if (trimmed) { + if (simultaneous) { + _trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true)); + _dashMoveTo(dash, offIdx, offset, pts); + } else _dashMoveTo(dash, pts); + } else _dashMoveTo(dash, offIdx, offset, pts); ++pts; break; } @@ -358,56 +416,6 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans } -static float _outlineLength(const RenderShape* rshape) -{ - const PathCommand* cmds = rshape->path.cmds.data; - auto cmdCnt = rshape->path.cmds.count; - const Point* pts = rshape->path.pts.data; - auto ptsCnt = rshape->path.pts.count; - - //No actual shape data - if (cmdCnt == 0 || ptsCnt == 0) return 0.0f; - - const Point* close = nullptr; - auto length = 0.0f; - auto slength = -1.0f; - auto simultaneous = !rshape->stroke->trim.individual; - - //Compute the whole length - while (cmdCnt-- > 0) { - switch (*cmds) { - case PathCommand::Close: { - length += mathLength(pts - 1, close); - //retrieve the max length of the shape if the simultaneous mode. - if (simultaneous) { - if (slength < length) slength = length; - length = 0.0f; - } - break; - } - case PathCommand::MoveTo: { - close = pts; - ++pts; - break; - } - case PathCommand::LineTo: { - length += mathLength(pts - 1, pts); - ++pts; - break; - } - case PathCommand::CubicTo: { - length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); - pts += 3; - break; - } - } - ++cmds; - } - if (simultaneous && slength > length) return slength; - else return length; -} - - static bool _axisAlignedRect(const SwOutline* outline) { //Fast Track: axis-aligned rectangle? @@ -584,11 +592,10 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* auto dashStroking = false; auto ret = true; - auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f; - //Dash style (+trimming) - if (rshape->stroke->dashCnt > 0 || length > 0) { - shapeOutline = _genDashOutline(rshape, transform, length, mpool, tid); + auto trimmed = rshape->strokeTrim(); + if (rshape->stroke->dashCnt > 0 || trimmed) { + shapeOutline = _genDashOutline(rshape, transform, trimmed, mpool, tid); if (!shapeOutline) return false; dashStroking = true; //Normal style diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 227ce10a0d..fcb632e2b1 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -75,13 +75,13 @@ static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform auto v2 = *pt3; if (rTransform) { - mathMultiply(&v1, &rTransform->m); - mathMultiply(&v2, &rTransform->m); + v1 *= rTransform->m; + v2 *= rTransform->m; } if (pTransform) { - mathMultiply(&v1, &pTransform->m); - mathMultiply(&v2, &pTransform->m); + v1 *= pTransform->m; + v2 *= pTransform->m; } //sorting @@ -327,7 +327,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme //Compute the AABB after transformation for (int i = 0; i < 4; i++) { - mathMultiply(&pt[i], m); + pt[i] *= *m; if (pt[i].x < x1) x1 = pt[i].x; if (pt[i].x > x2) x2 = pt[i].x; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.cpp b/thirdparty/thorvg/src/renderer/tvgRender.cpp index 14f77571fb..9c779f7c00 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.cpp +++ b/thirdparty/thorvg/src/renderer/tvgRender.cpp @@ -47,7 +47,7 @@ void RenderTransform::update() mathScale(&m, scale, scale); - if (!mathZero(degree)) mathRotate(&m, degree); + mathRotate(&m, degree); mathTranslate(&m, x, y); } @@ -55,7 +55,7 @@ void RenderTransform::update() RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs) { - if (lhs && rhs) m = mathMultiply(&lhs->m, &rhs->m); + if (lhs && rhs) m = lhs->m * rhs->m; else if (lhs) m = lhs->m; else if (rhs) m = rhs->m; else mathIdentity(&m); diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 6ea516c2f9..8f28d37dbc 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -142,7 +142,7 @@ struct RenderStroke struct { float begin = 0.0f; float end = 1.0f; - bool individual = false; + bool simultaneous = true; } trim; ~RenderStroke() diff --git a/thirdparty/thorvg/src/renderer/tvgShape.cpp b/thirdparty/thorvg/src/renderer/tvgShape.cpp index 4cc8f64900..c010aa7bbf 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.cpp +++ b/thirdparty/thorvg/src/renderer/tvgShape.cpp @@ -287,16 +287,14 @@ const Fill* Shape::fill() const noexcept Result Shape::order(bool strokeFirst) noexcept { - if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation; - + pImpl->strokeFirst(strokeFirst); return Result::Success; } Result Shape::stroke(float width) noexcept { - if (!pImpl->strokeWidth(width)) return Result::FailedAllocation; - + pImpl->strokeWidth(width); return Result::Success; } @@ -309,8 +307,7 @@ float Shape::strokeWidth() const noexcept Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept { - if (!pImpl->strokeColor(r, g, b, a)) return Result::FailedAllocation; - + pImpl->strokeColor(r, g, b, a); return Result::Success; } @@ -349,27 +346,25 @@ uint32_t Shape::strokeDash(const float** dashPattern) const noexcept Result Shape::stroke(StrokeCap cap) noexcept { - if (!pImpl->strokeCap(cap)) return Result::FailedAllocation; - + pImpl->strokeCap(cap); return Result::Success; } Result Shape::stroke(StrokeJoin join) noexcept { - if (!pImpl->strokeJoin(join)) return Result::FailedAllocation; - + pImpl->strokeJoin(join); return Result::Success; } + Result Shape::strokeMiterlimit(float miterlimit) noexcept { // https://www.w3.org/TR/SVG2/painting.html#LineJoin // - A negative value for stroke-miterlimit must be treated as an illegal value. - if (miterlimit < 0.0f) return Result::NonSupport; + if (miterlimit < 0.0f) return Result::InvalidArguments; // TODO Find out a reasonable max value. - if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation; - + pImpl->strokeMiterlimit(miterlimit); return Result::Success; } @@ -385,12 +380,26 @@ StrokeJoin Shape::strokeJoin() const noexcept return pImpl->rs.strokeJoin(); } + float Shape::strokeMiterlimit() const noexcept { return pImpl->rs.strokeMiterlimit(); } +Result Shape::strokeTrim(float begin, float end, bool simultaneous) noexcept +{ + pImpl->strokeTrim(begin, end, simultaneous); + return Result::Success; +} + + +bool Shape::strokeTrim(float* begin, float* end) const noexcept +{ + return pImpl->strokeTrim(begin, end); +} + + Result Shape::fill(FillRule r) noexcept { pImpl->rs.rule = r; diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index 55335214be..4e85db37d0 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -207,60 +207,81 @@ struct Shape::Impl flag |= RenderUpdateFlag::Path; } - bool strokeWidth(float width) + void strokeWidth(float width) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->width = width; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeTrim(float begin, float end, bool individual) + void strokeTrim(float begin, float end, bool simultaneous) { if (!rs.stroke) { - if (begin == 0.0f && end == 1.0f) return true; + if (begin == 0.0f && end == 1.0f) return; rs.stroke = new RenderStroke(); } - if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true; + if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end) && + rs.stroke->trim.simultaneous == simultaneous) return; + + auto loop = true; + + if (begin > 1.0f && end > 1.0f) loop = false; + if (begin < 0.0f && end < 0.0f) loop = false; + if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false; + + if (begin > 1.0f) begin -= 1.0f; + if (begin < 0.0f) begin += 1.0f; + if (end > 1.0f) end -= 1.0f; + if (end < 0.0f) end += 1.0f; + + if ((loop && begin < end) || (!loop && begin > end)) { + auto tmp = begin; + begin = end; + end = tmp; + } rs.stroke->trim.begin = begin; rs.stroke->trim.end = end; - rs.stroke->trim.individual = individual; + rs.stroke->trim.simultaneous = simultaneous; flag |= RenderUpdateFlag::Stroke; + } - return true; + bool strokeTrim(float* begin, float* end) + { + if (rs.stroke) { + if (begin) *begin = rs.stroke->trim.begin; + if (end) *end = rs.stroke->trim.end; + return rs.stroke->trim.simultaneous; + } else { + if (begin) *begin = 0.0f; + if (end) *end = 1.0f; + return false; + } } - bool strokeCap(StrokeCap cap) + void strokeCap(StrokeCap cap) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->cap = cap; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeJoin(StrokeJoin join) + void strokeJoin(StrokeJoin join) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->join = join; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeMiterlimit(float miterlimit) + void strokeMiterlimit(float miterlimit) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->miterlimit = miterlimit; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + void strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!rs.stroke) rs.stroke = new RenderStroke(); if (rs.stroke->fill) { @@ -275,8 +296,6 @@ struct Shape::Impl rs.stroke->color[3] = a; flag |= RenderUpdateFlag::Stroke; - - return true; } Result strokeFill(unique_ptr<Fill> f) @@ -335,13 +354,11 @@ struct Shape::Impl return rs.stroke->strokeFirst; } - bool strokeFirst(bool strokeFirst) + void strokeFirst(bool strokeFirst) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->strokeFirst = strokeFirst; flag |= RenderUpdateFlag::Stroke; - - return true; } void update(RenderUpdateFlag flag) diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index cd1aeadec0..c980b89c4b 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.13.5 +VERSION=0.13.7 cd thirdparty/thorvg/ || true rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ |
