summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/actions/godot-deps/action.yml2
-rw-r--r--.pre-commit-config.yaml31
-rw-r--r--core/core_bind.cpp77
-rw-r--r--core/core_bind.h16
-rw-r--r--core/io/resource_importer.cpp1
-rw-r--r--core/object/script_language_extension.cpp1
-rw-r--r--core/object/undo_redo.cpp14
-rw-r--r--doc/classes/Animation.xml4
-rw-r--r--doc/classes/Basis.xml10
-rw-r--r--doc/classes/Button.xml3
-rw-r--r--doc/classes/EditorDebuggerPlugin.xml23
-rw-r--r--doc/classes/EditorDebuggerSession.xml9
-rw-r--r--doc/classes/EngineDebugger.xml85
-rw-r--r--doc/classes/ScriptLanguageExtension.xml7
-rw-r--r--doc/classes/Skeleton3D.xml7
-rw-r--r--doc/classes/SkeletonModifier3D.xml6
-rw-r--r--doc/classes/TileMap.xml4
-rw-r--r--doc/classes/TileMapLayer.xml7
-rw-r--r--editor/editor_file_system.cpp168
-rw-r--r--editor/editor_file_system.h1
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/gui/scene_tree_editor.cpp24
-rw-r--r--editor/gui/scene_tree_editor.h1
-rw-r--r--editor/import_dock.cpp16
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp14
-rw-r--r--editor/plugins/editor_debugger_plugin.cpp27
-rw-r--r--editor/plugins/editor_debugger_plugin.h12
-rw-r--r--editor/progress_dialog.cpp24
-rw-r--r--editor/progress_dialog.h2
-rw-r--r--editor/scene_tree_dock.cpp38
-rw-r--r--editor/scene_tree_dock.h3
-rw-r--r--editor/themes/editor_theme_manager.cpp14
-rw-r--r--misc/extension_api_validation/4.2-stable.expected15
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs2
-rw-r--r--modules/mono/glue/runtime_interop.cpp6
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp5
-rw-r--r--platform/macos/display_server_macos.mm5
-rw-r--r--platform/web/detect.py2
-rw-r--r--platform/windows/detect.py49
-rw-r--r--platform/windows/display_server_windows.cpp5
-rw-r--r--pyproject.toml48
-rw-r--r--scene/2d/navigation_region_2d.cpp1
-rw-r--r--scene/2d/tile_map_layer.cpp48
-rw-r--r--scene/2d/tile_map_layer.h9
-rw-r--r--scene/3d/bone_attachment_3d.cpp10
-rw-r--r--scene/3d/physics/physical_bone_3d.cpp10
-rw-r--r--scene/3d/skeleton_3d.cpp22
-rw-r--r--scene/3d/skeleton_3d.h4
-rw-r--r--scene/3d/skeleton_ik_3d.cpp10
-rw-r--r--scene/3d/skeleton_modifier_3d.cpp2
-rw-r--r--scene/animation/animation_mixer.cpp13
-rw-r--r--scene/animation/animation_player.cpp9
-rw-r--r--scene/gui/button.cpp44
-rw-r--r--scene/gui/button.h2
-rw-r--r--scene/gui/container.cpp1
-rw-r--r--scene/gui/file_dialog.cpp4
-rw-r--r--scene/gui/rich_text_label.cpp210
-rw-r--r--scene/gui/rich_text_label.h7
-rw-r--r--scene/gui/texture_progress_bar.cpp23
-rw-r--r--scene/main/viewport.cpp4
-rw-r--r--scene/main/window.cpp4
-rw-r--r--scene/resources/animation.compat.inc6
-rw-r--r--scene/resources/animation.cpp22
-rw-r--r--scene/resources/animation.h4
-rw-r--r--scene/resources/visual_shader.cpp6
-rw-r--r--scene/theme/default_theme.cpp2
-rw-r--r--thirdparty/README.md6
-rw-r--r--thirdparty/embree/kernels/geometry/pointi.h4
-rw-r--r--thirdparty/embree/kernels/subdiv/bezier_curve.h2
-rw-r--r--thirdparty/misc/patches/qoa-min-fix.patch114
-rw-r--r--thirdparty/misc/qoa.h16
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/inc/thorvg.h48
-rw-r--r--thirdparty/thorvg/src/common/tvgCompressor.cpp15
-rw-r--r--thirdparty/thorvg/src/common/tvgCompressor.h1
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp63
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h81
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp22
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp24
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp4
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h8
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp1
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp7
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp197
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.cpp10
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.cpp4
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.cpp35
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h63
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh2
94 files changed, 1310 insertions, 702 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/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 12240aef93..d09ecabe70 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -35,37 +35,10 @@ repos:
types_or: [text]
- repo: https://github.com/codespell-project/codespell
- rev: v2.2.6
+ rev: v2.3.0
hooks:
- id: codespell
- types_or: [text]
- exclude: |
- (?x)^(
- .*\.desktop$|
- .*\.gitignore$|
- .*\.po$|
- .*\.pot$|
- .*\.rc$|
- \.mailmap$|
- AUTHORS.md$|
- COPYRIGHT.txt$|
- DONORS.md$|
- core/input/gamecontrollerdb.txt$|
- core/string/locales.h$|
- editor/project_converter_3_to_4.cpp$|
- platform/android/java/lib/src/com/.*|
- platform/web/package-lock.json$
- )
- args:
- - --enable-colors
- - --write-changes
- - --check-hidden
- - --quiet-level
- - '3'
- - --ignore-words-list
- - aesthetic,aesthetics,breaked,cancelled,colour,curvelinear,doubleclick,expct,findn,gird,hel,inout,lod,mis,nd,numer,ot,requestor,te,thirdparty,vai
- - --builtin
- - clear,rare,en-GB_to_en-US
+ additional_dependencies: [tomli]
### Requires Docker; look into alternative implementation.
# - repo: https://github.com/comkieffer/pre-commit-xmllint.git
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/resource_importer.cpp b/core/io/resource_importer.cpp
index fcf4a727ca..4c16650439 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -410,6 +410,7 @@ void ResourceFormatImporter::get_importers_for_extension(const String &p_extensi
for (const String &F : local_exts) {
if (p_extension.to_lower() == F) {
r_importers->push_back(importers[i]);
+ 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/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 0f7884305a..4d67cd930e 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -159,11 +159,10 @@ void UndoRedo::add_do_method(const Callable &p_callable) {
do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object));
}
do_op.type = Operation::TYPE_METHOD;
- // There's no `get_method()` for custom callables, so use `operator String()` instead.
- if (p_callable.is_custom()) {
+ do_op.name = p_callable.get_method();
+ if (do_op.name == StringName()) {
+ // There's no `get_method()` for custom callables, so use `operator String()` instead.
do_op.name = static_cast<String>(p_callable);
- } else {
- do_op.name = p_callable.get_method();
}
actions.write[current_action + 1].do_ops.push_back(do_op);
@@ -191,11 +190,10 @@ void UndoRedo::add_undo_method(const Callable &p_callable) {
}
undo_op.type = Operation::TYPE_METHOD;
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
- // There's no `get_method()` for custom callables, so use `operator String()` instead.
- if (p_callable.is_custom()) {
+ undo_op.name = p_callable.get_method();
+ if (undo_op.name == StringName()) {
+ // There's no `get_method()` for custom callables, so use `operator String()` instead.
undo_op.name = static_cast<String>(p_callable);
- } else {
- undo_op.name = p_callable.get_method();
}
actions.write[current_action + 1].undo_ops.push_back(undo_op);
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/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..cdad1f0fab 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -1714,105 +1714,107 @@ 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) {
+ continue;
+ }
}
- }
- 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(file);
+ }
+ if (fs->files[cpos]->type == SNAME("PackedScene")) {
+ _queue_update_scene_groups(file);
+ }
+
+ memdelete(fs->files[cpos]);
+ fs->files.remove_at(cpos);
}
- 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);
+ } else {
+ 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);
- memdelete(fs->files[cpos]);
- fs->files.remove_at(cpos);
- }
+ ResourceUID::ID uid = ResourceLoader::get_resource_uid(file);
- _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);
+ if (cpos == -1) {
+ // The file did not exist, it was added.
+ int idx = 0;
+ String file_name = file.get_file();
- ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file);
+ for (int i = 0; i < fs->files.size(); i++) {
+ if (file.filenocasecmp_to(fs->files[i]->file) < 0) {
+ break;
+ }
+ idx++;
+ }
- if (cpos == -1) {
- // The file did not exist, it was added.
- int idx = 0;
- String file_name = p_file.get_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);
- for (int i = 0; i < fs->files.size(); i++) {
- if (p_file.filenocasecmp_to(fs->files[i]->file) < 0) {
- break;
+ if (idx == fs->files.size()) {
+ fs->files.push_back(fi);
+ } else {
+ fs->files.insert(idx, fi);
+ }
+ 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(file);
+ _save_late_updated_files(); //files need to be updated in the re-scan
}
- 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);
- if (idx == fs->files.size()) {
- fs->files.push_back(fi);
- } else {
- fs->files.insert(idx, fi);
- }
- 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, 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, file);
+ } else {
+ ResourceUID::get_singleton()->add_id(uid, 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, 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);
+ ResourceUID::get_singleton()->update_cache();
+ }
+ // Update preview
+ EditorResourcePreview::get_singleton()->check_for_invalidation(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 (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);
+ }
}
-
- ResourceUID::get_singleton()->update_cache();
- }
- // 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);
}
_update_pending_script_classes();
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 84ae1e182c..cd95d5fb95 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -310,6 +310,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..94bd590fc1 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -7037,7 +7037,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/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", &section_keys);
- for (const String &section_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", &section_keys);
+ for (const String &section_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..11e477e044 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.");
@@ -2674,6 +2695,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
editor_history->cleanup_history();
InspectorDock::get_singleton()->call("_prepare_history");
+ InspectorDock::get_singleton()->update(nullptr);
}
void SceneTreeDock::_update_script_button() {
@@ -3400,6 +3422,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 +3442,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..b8f244ca02 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -126,13 +126,6 @@ Validate extension JSON: Error: Field 'classes/Animation/methods/value_track_int
Added optional argument to track_interpolate to treat playing backward correctly. Compatibility method registered.
-GH-86661
---------
-Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/arguments': size changed value in new API, from 3 to 4.
-
-Added optional argument to track_find_key to avoid finding keys out of the animation range. Compatibility method registered.
-
-
GH-84792
--------
Validate extension JSON: Error: Field 'classes/RenderingServer/methods/environment_set_fog/arguments': size changed value in new API, from 10 to 11.
@@ -364,3 +357,11 @@ 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-92861
+--------
+Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/arguments': size changed value in new API, from 3 to 5.
+
+Added optional arguments to avoid finding keys out of the animation range (GH-86661), and 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/web/detect.py b/platform/web/detect.py
index c6568625c1..a9be1471ae 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -204,7 +204,7 @@ def configure(env: "SConsEnvironment"):
if env["opengl3"]:
env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"])
# This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
- env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
+ env.Append(LINKFLAGS=["-s", "MAX_WEBGL_VERSION=2"])
# Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
# Breaking change since emscripten 3.1.51
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 0d3e9c606e..5092c6fadf 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -1,4 +1,5 @@
import os
+import re
import subprocess
import sys
from typing import TYPE_CHECKING
@@ -304,6 +305,7 @@ def setup_msvc_manual(env: "SConsEnvironment"):
print("Using VCVARS-determined MSVC, arch %s" % (env_arch))
+# FIXME: Likely overwrites command-line options for the msvc compiler. See #91883.
def setup_msvc_auto(env: "SConsEnvironment"):
"""Set up MSVC using SCons's auto-detection logic"""
@@ -386,33 +388,64 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable.
- if env["silence_msvc"]:
+ if env["silence_msvc"] and not env.GetOption("clean"):
from tempfile import mkstemp
+ # Ensure we have a location to write captured output to, in case of false positives.
+ capture_path = methods.base_folder_path + "platform/windows/msvc_capture.log"
+ with open(capture_path, "wt"):
+ pass
+
old_spawn = env["SPAWN"]
+ re_redirect_stream = re.compile(r"^[12]?>")
+ re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE)
+ re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)')
def spawn_capture(sh, escape, cmd, args, env):
# We only care about cl/link, process everything else as normal.
if args[0] not in ["cl", "link"]:
return old_spawn(sh, escape, cmd, args, env)
+ # Process as normal if the user is manually rerouting output.
+ for arg in args:
+ if re_redirect_stream.match(arg):
+ return old_spawn(sh, escape, cmd, args, env)
+
tmp_stdout, tmp_stdout_name = mkstemp()
os.close(tmp_stdout)
args.append(f">{tmp_stdout_name}")
ret = old_spawn(sh, escape, cmd, args, env)
try:
- with open(tmp_stdout_name, "rb") as tmp_stdout:
- # First line is always bloat, subsequent lines are always errors. If content
- # exists after discarding the first line, safely decode & send to stderr.
- tmp_stdout.readline()
- content = tmp_stdout.read()
- if content:
- sys.stderr.write(content.decode(sys.stdout.encoding, "replace"))
+ with open(tmp_stdout_name, encoding="oem", errors="replace") as tmp_stdout:
+ lines = tmp_stdout.read().splitlines()
os.remove(tmp_stdout_name)
except OSError:
pass
+ # Early process no lines (OSError)
+ if not lines:
+ return ret
+
+ is_cl = args[0] == "cl"
+ content = ""
+ caught = False
+ for line in lines:
+ # These conditions are far from all-encompassing, but are specialized
+ # for what can be reasonably expected to show up in the repository.
+ if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)):
+ caught = True
+ try:
+ with open(capture_path, "a") as log:
+ log.write(line + "\n")
+ except OSError:
+ print_warning(f'Failed to log captured line: "{line}".')
+ continue
+ content += line + "\n"
+ # Content remaining assumed to be an error/warning.
+ if content:
+ sys.stderr.write(content)
+
return ret
env["SPAWN"] = spawn_capture
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/pyproject.toml b/pyproject.toml
index 34ae075f2b..59b6d09a03 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,3 +27,51 @@ extend-select = [
"E402", # Module level import not at top of file
"F821", # Undefined name
]
+
+[tool.codespell]
+enable-colors = ""
+write-changes = ""
+check-hidden = ""
+quiet-level = 3
+builtin = "clear,rare,en-GB_to_en-US"
+skip = """\
+ .mailmap,
+ *.desktop,
+ *.gitignore,
+ *.po,
+ *.pot,
+ *.rc,
+ AUTHORS.md,
+ COPYRIGHT.txt,
+ core/input/gamecontrollerdb.txt,
+ core/string/locales.h,
+ DONORS.md,
+ editor/project_converter_3_to_4.cpp,
+ platform/android/java/lib/src/com/*,
+ platform/web/package-lock.json
+"""
+ignore-words-list = """\
+ breaked,
+ cancelled,
+ checkin,
+ colour,
+ curvelinear,
+ doubleclick,
+ expct,
+ findn,
+ gird,
+ hel,
+ inout,
+ labelin,
+ lod,
+ mis,
+ nd,
+ numer,
+ ot,
+ outin,
+ requestor,
+ te,
+ textin,
+ thirdparty,
+ vai
+"""
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index bad9de5daa..9b3c7bb9ea 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -458,6 +458,7 @@ void NavigationRegion2D::_update_debug_mesh() {
return;
}
+ rs->canvas_item_clear(debug_instance_rid);
rs->mesh_clear(debug_mesh_rid);
debug_mesh_dirty = false;
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/container.cpp b/scene/gui/container.cpp
index c328022d4f..e867fceaf2 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -80,6 +80,7 @@ void Container::remove_child_notify(Node *p_child) {
void Container::_sort_children() {
if (!is_inside_tree()) {
+ pending_sort = false;
return;
}
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/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 7590cf2ad3..d69fa78d9d 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -228,6 +228,36 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
return s;
}
+String RichTextLabel::_get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items) {
+ String prefix;
+ int segments = 0;
+ for (int i = 0; i < p_list_index.size(); i++) {
+ String segment;
+ if (p_list_items[i]->list_type == LIST_DOTS) {
+ if (segments == 0) {
+ prefix = p_list_items[i]->bullet;
+ }
+ break;
+ }
+ prefix = "." + prefix;
+ if (p_list_items[i]->list_type == LIST_NUMBERS) {
+ segment = itos(p_list_index[i]);
+ if (is_localizing_numeral_system()) {
+ segment = TS->format_number(segment, _find_language(p_item));
+ }
+ segments++;
+ } else if (p_list_items[i]->list_type == LIST_LETTERS) {
+ segment = _letters(p_list_index[i], p_list_items[i]->capitalize);
+ segments++;
+ } else if (p_list_items[i]->list_type == LIST_ROMAN) {
+ segment = _roman(p_list_index[i], p_list_items[i]->capitalize);
+ segments++;
+ }
+ prefix = segment + prefix;
+ }
+ return prefix;
+}
+
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
ERR_FAIL_NULL(p_frame);
ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
@@ -235,6 +265,51 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
Line &l = p_frame->lines[p_line];
MutexLock lock(l.text_buf->get_mutex());
+ // Prefix.
+ Vector<int> list_index;
+ Vector<int> list_count;
+ Vector<ItemList *> list_items;
+ _find_list(l.from, list_index, list_count, list_items);
+
+ if (list_items.size() > 0) {
+ if (list_index[0] == 1) {
+ // List level start, shape all prefixes for this level and compute max. prefix width.
+ list_items[0]->max_width = 0;
+ int index = 0;
+ for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
+ Line &list_l = p_frame->lines[i];
+ if (_find_list_item(list_l.from) == list_items[0]) {
+ index++;
+
+ Ref<Font> font = theme_cache.normal_font;
+ int font_size = theme_cache.normal_font_size;
+
+ ItemFont *font_it = _find_font(list_l.from);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ }
+ if (font_it->font_size > 0) {
+ font_size = font_it->font_size;
+ }
+ }
+ ItemFontSize *font_size_it = _find_font_size(list_l.from);
+ if (font_size_it && font_size_it->font_size > 0) {
+ font_size = font_size_it->font_size;
+ }
+
+ list_index.write[0] = index;
+ String prefix = _get_prefix(list_l.from, list_index, list_items);
+ list_l.text_prefix.instantiate();
+ list_l.text_prefix->set_direction(_find_direction(list_l.from));
+ list_l.text_prefix->add_string(prefix, font, font_size);
+ list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
+ }
+ }
+ }
+ l.prefix_width = list_items[0]->max_width;
+ }
+
RID t = l.text_buf->get_rid();
int spans = TS->shaped_get_span_count(t);
for (int i = 0; i < spans; i++) {
@@ -287,7 +362,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
Line &l = p_frame->lines[p_line];
MutexLock lock(l.text_buf->get_mutex());
- l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
+ l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width;
l.text_buf->set_width(p_width - l.offset.x);
PackedFloat32Array tab_stops = _find_tab_stops(l.from);
@@ -378,8 +453,53 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
l.char_offset = *r_char_offset;
l.char_count = 0;
+ // List prefix.
+ Vector<int> list_index;
+ Vector<int> list_count;
+ Vector<ItemList *> list_items;
+ _find_list(l.from, list_index, list_count, list_items);
+
+ if (list_items.size() > 0) {
+ if (list_index[0] == 1) {
+ // List level start, shape all prefixes for this level and compute max. prefix width.
+ list_items[0]->max_width = 0;
+ int index = 0;
+ for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
+ Line &list_l = p_frame->lines[i];
+ if (_find_list_item(list_l.from) == list_items[0]) {
+ index++;
+
+ Ref<Font> font = theme_cache.normal_font;
+ int font_size = theme_cache.normal_font_size;
+
+ ItemFont *font_it = _find_font(list_l.from);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ }
+ if (font_it->font_size > 0) {
+ font_size = font_it->font_size;
+ }
+ }
+ ItemFontSize *font_size_it = _find_font_size(list_l.from);
+ if (font_size_it && font_size_it->font_size > 0) {
+ font_size = font_size_it->font_size;
+ }
+
+ list_index.write[0] = index;
+ String prefix = _get_prefix(list_l.from, list_index, list_items);
+ list_l.text_prefix.instantiate();
+ list_l.text_prefix->set_direction(_find_direction(list_l.from));
+ list_l.text_prefix->add_string(prefix, font, font_size);
+ list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
+ }
+ }
+ }
+ l.prefix_width = list_items[0]->max_width;
+ }
+
// Add indent.
- l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
+ l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width;
l.text_buf->set_width(p_width - l.offset.x);
l.text_buf->set_alignment(_find_alignment(l.from));
l.text_buf->set_direction(_find_direction(l.from));
@@ -685,45 +805,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
int total_glyphs = (trim_glyphs_ltr || trim_glyphs_rtl) ? get_total_glyph_count() : 0;
int visible_glyphs = total_glyphs * visible_ratio;
- Vector<int> list_index;
- Vector<ItemList *> list_items;
- _find_list(l.from, list_index, list_items);
-
- String prefix;
- int segments = 0;
- for (int i = 0; i < list_index.size(); i++) {
- String segment;
- if (list_items[i]->list_type == LIST_DOTS) {
- if (segments == 0) {
- prefix = list_items[i]->bullet;
- }
- break;
- }
- if (rtl) {
- prefix = prefix + ".";
- } else {
- prefix = "." + prefix;
- }
- if (list_items[i]->list_type == LIST_NUMBERS) {
- segment = itos(list_index[i]);
- if (is_localizing_numeral_system()) {
- segment = TS->format_number(segment, _find_language(l.from));
- }
- segments++;
- } else if (list_items[i]->list_type == LIST_LETTERS) {
- segment = _letters(list_index[i], list_items[i]->capitalize);
- segments++;
- } else if (list_items[i]->list_type == LIST_ROMAN) {
- segment = _roman(list_index[i], list_items[i]->capitalize);
- segments++;
- }
- if (rtl) {
- prefix = prefix + segment;
- } else {
- prefix = segment + prefix;
- }
- }
-
// Draw dropcap.
int dc_lines = l.text_buf->get_dropcap_lines();
float h_off = l.text_buf->get_dropcap_size().x;
@@ -790,27 +871,27 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
bool skip_prefix = (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && l.char_offset == visible_characters) || (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs));
- if (!prefix.is_empty() && line == 0 && !skip_prefix) {
- Ref<Font> font = theme_cache.normal_font;
- int font_size = theme_cache.normal_font_size;
-
- ItemFont *font_it = _find_font(l.from);
- if (font_it) {
- if (font_it->font.is_valid()) {
- font = font_it->font;
+ if (l.text_prefix.is_valid() && line == 0 && !skip_prefix) {
+ Color font_color = _find_color(l.from, p_base_color);
+ int outline_size = _find_outline_size(l.from, p_outline_size);
+ Color font_outline_color = _find_outline_color(l.from, p_outline_color);
+ Color font_shadow_color = p_font_shadow_color * Color(1, 1, 1, font_color.a);
+ if (rtl) {
+ if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
+ l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x + length, 0) + p_shadow_ofs, p_shadow_outline_size, font_shadow_color);
}
- if (font_it->font_size > 0) {
- font_size = font_it->font_size;
+ if (outline_size > 0 && font_outline_color.a != 0.0) {
+ l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x + length, 0), outline_size, font_outline_color);
}
- }
- ItemFontSize *font_size_it = _find_font_size(l.from);
- if (font_size_it && font_size_it->font_size > 0) {
- font_size = font_size_it->font_size;
- }
- if (rtl) {
- font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ l.text_prefix->draw(ci, p_ofs + Vector2(off.x + length, 0), font_color);
} else {
- font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
+ l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0) + p_shadow_ofs, p_shadow_outline_size, font_shadow_color);
+ }
+ if (outline_size > 0 && font_outline_color.a != 0.0) {
+ l.text_prefix->draw_outline(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0), outline_size, font_outline_color);
+ }
+ l.text_prefix->draw(ci, p_ofs + Vector2(off.x - l.text_prefix->get_size().x, 0), font_color);
}
}
@@ -2320,7 +2401,7 @@ RichTextLabel::ItemList *RichTextLabel::_find_list_item(Item *p_item) {
return nullptr;
}
-int RichTextLabel::_find_list(Item *p_item, Vector<int> &r_index, Vector<ItemList *> &r_list) {
+int RichTextLabel::_find_list(Item *p_item, Vector<int> &r_index, Vector<int> &r_count, Vector<ItemList *> &r_list) {
Item *item = p_item;
Item *prev_item = p_item;
@@ -2335,15 +2416,20 @@ int RichTextLabel::_find_list(Item *p_item, Vector<int> &r_index, Vector<ItemLis
_find_frame(list, &frame, &line);
int index = 1;
+ int count = 1;
if (frame != nullptr) {
- for (int i = list->line + 1; i <= prev_item->line && i < (int)frame->lines.size(); i++) {
+ for (int i = list->line + 1; i < (int)frame->lines.size(); i++) {
if (_find_list_item(frame->lines[i].from) == list) {
- index++;
+ if (i <= prev_item->line) {
+ index++;
+ }
+ count++;
}
}
}
r_index.push_back(index);
+ r_count.push_back(count);
r_list.push_back(list);
prev_item = item;
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 45dfb88c55..83285bd7cd 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -144,6 +144,8 @@ private:
struct Line {
Item *from = nullptr;
+ Ref<TextLine> text_prefix;
+ float prefix_width = 0;
Ref<TextParagraph> text_buf;
Color dc_color;
int dc_ol_size = 0;
@@ -322,6 +324,7 @@ private:
bool capitalize = false;
int level = 0;
String bullet = U"•";
+ float max_width = 0;
ItemList() { type = ITEM_LIST; }
};
@@ -571,7 +574,7 @@ private:
int _find_outline_size(Item *p_item, int p_default);
ItemList *_find_list_item(Item *p_item);
ItemDropcap *_find_dc_item(Item *p_item);
- int _find_list(Item *p_item, Vector<int> &r_index, Vector<ItemList *> &r_list);
+ int _find_list(Item *p_item, Vector<int> &r_index, Vector<int> &r_count, Vector<ItemList *> &r_list);
int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size);
PackedFloat32Array _find_tab_stops(Item *p_item);
HorizontalAlignment _find_alignment(Item *p_item);
@@ -608,6 +611,8 @@ private:
Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2());
+ String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items);
+
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index dc48945d9b..3c3e6cd30c 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -537,17 +537,20 @@ void TextureProgressBar::_notification(int p_what) {
uvs.push_back(uv);
}
- Point2 center_point = get_relative_center();
- points.push_back(progress_offset + s * center_point);
- if (valid_atlas_progress) {
- center_point.x = Math::remap(center_point.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x);
- center_point.y = Math::remap(center_point.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y);
- }
- uvs.push_back(center_point);
+ // Filter out an edge case where almost equal `from`, `to` were mapped to the same UV.
+ if (points.size() >= 2) {
+ Point2 center_point = get_relative_center();
+ points.push_back(progress_offset + s * center_point);
+ if (valid_atlas_progress) {
+ center_point.x = Math::remap(center_point.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x);
+ center_point.y = Math::remap(center_point.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y);
+ }
+ uvs.push_back(center_point);
- Vector<Color> colors;
- colors.push_back(tint_progress);
- draw_polygon(points, colors, uvs, progress);
+ Vector<Color> colors;
+ colors.push_back(tint_progress);
+ draw_polygon(points, colors, uvs, progress);
+ }
}
// Draw a reference cross.
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/main/window.cpp b/scene/main/window.cpp
index 39e5b6de33..7502f97e89 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1354,6 +1354,10 @@ void Window::_notification(int p_what) {
_update_window_size(); // Inform DisplayServer of minimum and maximum size.
_update_viewport_size(); // Then feed back to the viewport.
_update_window_callbacks();
+ // Simulate mouse-enter event when mouse is over the window, since OS event might arrive before setting callbacks.
+ if (!mouse_in_window && Rect2(position, size).has_point(DisplayServer::get_singleton()->mouse_get_position())) {
+ _event_callback(DisplayServer::WINDOW_EVENT_MOUSE_ENTER);
+ }
RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
if (DisplayServer::get_singleton()->window_get_flag(DisplayServer::WindowFlags(FLAG_TRANSPARENT), window_id)) {
set_transparent_background(true);
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/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 277d568aaf..222f5c8a25 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -5166,11 +5166,11 @@ VisualShaderNodeVarying::PortType VisualShaderNodeVarying::get_port_type(VisualS
case VisualShader::VARYING_TYPE_UINT:
return PORT_TYPE_SCALAR_UINT;
case VisualShader::VARYING_TYPE_VECTOR_2D:
- return PORT_TYPE_VECTOR_2D;
+ return p_port == 0 ? PORT_TYPE_VECTOR_2D : PORT_TYPE_SCALAR;
case VisualShader::VARYING_TYPE_VECTOR_3D:
- return PORT_TYPE_VECTOR_3D;
+ return p_port == 0 ? PORT_TYPE_VECTOR_3D : PORT_TYPE_SCALAR;
case VisualShader::VARYING_TYPE_VECTOR_4D:
- return PORT_TYPE_VECTOR_4D;
+ return p_port == 0 ? PORT_TYPE_VECTOR_4D : PORT_TYPE_SCALAR;
case VisualShader::VARYING_TYPE_BOOLEAN:
return PORT_TYPE_BOOLEAN;
case VisualShader::VARYING_TYPE_TRANSFORM:
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 137e3a0cbc..2b48e19143 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -681,8 +681,8 @@ Collection of single-file libraries used in Godot components.
* License: MIT
- `qoa.h`
* Upstream: https://github.com/phoboslab/qoa
- * Version: git (e4c751d61af2c395ea828c5888e728c1953bf09f, 2024)
- * Modifications: Inlined functions and patched compiler warnings.
+ * Version: git (5c2a86d615661f34636cf179abf4fa278d3257e0, 2024)
+ * Modifications: Inlined functions, patched uninitialized variables and untyped mallocs.
* License: MIT
- `r128.{c,h}`
* Upstream: https://github.com/fahickman/r128
@@ -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/embree/kernels/geometry/pointi.h b/thirdparty/embree/kernels/geometry/pointi.h
index f81edb9035..aba8ec4ab3 100644
--- a/thirdparty/embree/kernels/geometry/pointi.h
+++ b/thirdparty/embree/kernels/geometry/pointi.h
@@ -210,9 +210,9 @@ namespace embree
};
/*! output operator */
- friend __forceinline embree_ostream operator<<(embree_ostream cout, const PointMi& line)
+ friend __forceinline embree_ostream operator<<(embree_ostream cout, const PointMi& point)
{
- return cout << "Line" << M << "i {" << line.v0 << ", " << line.geomID() << ", " << line.primID() << "}";
+ return cout << "Point" << M << "i {" << point.geomID() << ", " << point.primID() << "}";
}
public:
diff --git a/thirdparty/embree/kernels/subdiv/bezier_curve.h b/thirdparty/embree/kernels/subdiv/bezier_curve.h
index 257e0afd40..5e3b5c83b3 100644
--- a/thirdparty/embree/kernels/subdiv/bezier_curve.h
+++ b/thirdparty/embree/kernels/subdiv/bezier_curve.h
@@ -135,7 +135,7 @@ namespace embree
}
friend embree_ostream operator<<(embree_ostream cout, const QuadraticBezierCurve& a) {
- return cout << "QuadraticBezierCurve ( (" << a.u.lower << ", " << a.u.upper << "), " << a.v0 << ", " << a.v1 << ", " << a.v2 << ")";
+ return cout << "QuadraticBezierCurve (" << a.v0 << ", " << a.v1 << ", " << a.v2 << ")";
}
};
diff --git a/thirdparty/misc/patches/qoa-min-fix.patch b/thirdparty/misc/patches/qoa-min-fix.patch
index 1043d8bbe7..38303a1521 100644
--- a/thirdparty/misc/patches/qoa-min-fix.patch
+++ b/thirdparty/misc/patches/qoa-min-fix.patch
@@ -1,5 +1,5 @@
diff --git a/qoa.h b/qoa.h
-index aa8fb59434..2dde8df098 100644
+index 592082933a..c890b88bd6 100644
--- a/qoa.h
+++ b/qoa.h
@@ -140,14 +140,14 @@ typedef struct {
@@ -24,32 +24,10 @@ index aa8fb59434..2dde8df098 100644
#ifndef QOA_NO_STDIO
-@@ -366,7 +366,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- ), bytes, &p);
-
-
-- for (int c = 0; c < channels; c++) {
-+ for (unsigned int c = 0; c < channels; c++) {
- /* Write the current LMS state */
- qoa_uint64_t weights = 0;
- qoa_uint64_t history = 0;
-@@ -380,9 +380,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
-
- /* We encode all samples with the channels interleaved on a slice level.
- E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/
-- for (int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) {
-+ for (unsigned int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) {
-
-- for (int c = 0; c < channels; c++) {
-+ for (unsigned int c = 0; c < channels; c++) {
- int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index);
- int slice_start = sample_index * channels + c;
- int slice_end = (sample_index + slice_len) * channels + c;
-@@ -391,10 +391,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- 16 scalefactors, encode all samples for the current slice and
- meassure the total squared error. */
- qoa_uint64_t best_rank = -1;
-- qoa_uint64_t best_error = -1;
+@@ -394,9 +394,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
+ #ifdef QOA_RECORD_TOTAL_ERROR
+ qoa_uint64_t best_error = -1;
+ #endif
- qoa_uint64_t best_slice;
- qoa_lms_t best_lms;
- int best_scalefactor;
@@ -59,92 +37,16 @@ index aa8fb59434..2dde8df098 100644
for (int sfi = 0; sfi < 16; sfi++) {
/* There is a strong correlation between the scalefactors of
-@@ -408,7 +407,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- qoa_lms_t lms = qoa->lms[c];
- qoa_uint64_t slice = scalefactor;
- qoa_uint64_t current_rank = 0;
-- qoa_uint64_t current_error = 0;
-
- for (int si = slice_start; si < slice_end; si += channels) {
- int sample = sample_data[si];
-@@ -438,7 +436,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- qoa_uint64_t error_sq = error * error;
-
- current_rank += error_sq + weights_penalty * weights_penalty;
-- current_error += error_sq;
- if (current_rank > best_rank) {
- break;
- }
-@@ -449,7 +446,6 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
-
- if (current_rank < best_rank) {
- best_rank = current_rank;
-- best_error = current_error;
- best_slice = slice;
- best_lms = lms;
- best_scalefactor = scalefactor;
-@@ -492,9 +488,9 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len)
+@@ -500,7 +500,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len)
num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */
num_slices * 8 * qoa->channels; /* 8 byte slices */
- unsigned char *bytes = QOA_MALLOC(encoded_size);
+ unsigned char *bytes = (unsigned char *)QOA_MALLOC(encoded_size);
-- for (int c = 0; c < qoa->channels; c++) {
-+ for (unsigned int c = 0; c < qoa->channels; c++) {
+ for (unsigned int c = 0; c < qoa->channels; c++) {
/* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the
- prediction of the first few ms of a file. */
- qoa->lms[c].weights[0] = 0;
-@@ -517,7 +513,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len)
- #endif
-
- int frame_len = QOA_FRAME_LEN;
-- for (int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) {
-+ for (unsigned int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) {
- frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index);
- const short *frame_samples = sample_data + sample_index * qoa->channels;
- unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p);
-@@ -580,14 +576,14 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
-
- /* Read and verify the frame header */
- qoa_uint64_t frame_header = qoa_read_u64(bytes, &p);
-- int channels = (frame_header >> 56) & 0x0000ff;
-- int samplerate = (frame_header >> 32) & 0xffffff;
-- int samples = (frame_header >> 16) & 0x00ffff;
-- int frame_size = (frame_header ) & 0x00ffff;
-+ unsigned int channels = (frame_header >> 56) & 0x0000ff;
-+ unsigned int samplerate = (frame_header >> 32) & 0xffffff;
-+ unsigned int samples = (frame_header >> 16) & 0x00ffff;
-+ unsigned int frame_size = (frame_header ) & 0x00ffff;
-
- int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels;
- int num_slices = data_size / 8;
-- int max_total_samples = num_slices * QOA_SLICE_LEN;
-+ unsigned int max_total_samples = num_slices * QOA_SLICE_LEN;
-
- if (
- channels != qoa->channels ||
-@@ -600,7 +596,7 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
-
-
- /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */
-- for (int c = 0; c < channels; c++) {
-+ for (unsigned int c = 0; c < channels; c++) {
- qoa_uint64_t history = qoa_read_u64(bytes, &p);
- qoa_uint64_t weights = qoa_read_u64(bytes, &p);
- for (int i = 0; i < QOA_LMS_LEN; i++) {
-@@ -613,8 +609,8 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
-
-
- /* Decode all slices for all channels in this frame */
-- for (int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) {
-- for (int c = 0; c < channels; c++) {
-+ for (unsigned int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) {
-+ for (unsigned int c = 0; c < channels; c++) {
- qoa_uint64_t slice = qoa_read_u64(bytes, &p);
-
- int scalefactor = (slice >> 60) & 0xf;
-@@ -647,7 +643,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) {
+@@ -655,7 +655,7 @@ short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) {
/* Calculate the required size of the sample buffer and allocate */
int total_samples = qoa->samples * qoa->channels;
diff --git a/thirdparty/misc/qoa.h b/thirdparty/misc/qoa.h
index 2dde8df098..c890b88bd6 100644
--- a/thirdparty/misc/qoa.h
+++ b/thirdparty/misc/qoa.h
@@ -391,6 +391,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
16 scalefactors, encode all samples for the current slice and
meassure the total squared error. */
qoa_uint64_t best_rank = -1;
+ #ifdef QOA_RECORD_TOTAL_ERROR
+ qoa_uint64_t best_error = -1;
+ #endif
qoa_uint64_t best_slice = -1;
qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}};
int best_scalefactor = -1;
@@ -407,6 +410,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
qoa_lms_t lms = qoa->lms[c];
qoa_uint64_t slice = scalefactor;
qoa_uint64_t current_rank = 0;
+ #ifdef QOA_RECORD_TOTAL_ERROR
+ qoa_uint64_t current_error = 0;
+ #endif
for (int si = slice_start; si < slice_end; si += channels) {
int sample = sample_data[si];
@@ -436,6 +442,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
qoa_uint64_t error_sq = error * error;
current_rank += error_sq + weights_penalty * weights_penalty;
+ #ifdef QOA_RECORD_TOTAL_ERROR
+ current_error += error_sq;
+ #endif
if (current_rank > best_rank) {
break;
}
@@ -446,6 +455,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
if (current_rank < best_rank) {
best_rank = current_rank;
+ #ifdef QOA_RECORD_TOTAL_ERROR
+ best_error = current_error;
+ #endif
best_slice = slice;
best_lms = lms;
best_scalefactor = scalefactor;
@@ -581,8 +593,8 @@ unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa
unsigned int samples = (frame_header >> 16) & 0x00ffff;
unsigned int frame_size = (frame_header ) & 0x00ffff;
- int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels;
- int num_slices = data_size / 8;
+ unsigned int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels;
+ unsigned int num_slices = data_size / 8;
unsigned int max_total_samples = num_slices * QOA_SLICE_LEN;
if (
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/