diff options
61 files changed, 674 insertions, 440 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/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/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..37e00bf042 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1714,105 +1714,112 @@ HashSet<StringName> EditorFileSystem::_get_scene_groups(const String &p_path) { void EditorFileSystem::update_file(const String &p_file) { ERR_FAIL_COND(p_file.is_empty()); - EditorFileSystemDirectory *fs = nullptr; - int cpos = -1; + update_files({ p_file }); +} - if (!_find_file(p_file, &fs, cpos)) { - if (!fs) { - return; +void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { + for (const String &file : p_script_paths) { + ERR_CONTINUE(file.is_empty()); + EditorFileSystemDirectory *fs = nullptr; + int cpos = -1; + + if (!_find_file(file, &fs, cpos)) { + if (!fs) { + return; + } } - } - if (!FileAccess::exists(p_file)) { - //was removed - _delete_internal_files(p_file); - if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog). - if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) { - if (ResourceUID::get_singleton()->has_id(fs->files[cpos]->uid)) { - ResourceUID::get_singleton()->remove_id(fs->files[cpos]->uid); + if (!FileAccess::exists(file)) { + //was removed + _delete_internal_files(file); + if (cpos != -1) { // Might've never been part of the editor file system (*.* files deleted in Open dialog). + if (fs->files[cpos]->uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(fs->files[cpos]->uid)) { + ResourceUID::get_singleton()->remove_id(fs->files[cpos]->uid); + } } - } - if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(p_file); - } - if (fs->files[cpos]->type == SNAME("PackedScene")) { - _queue_update_scene_groups(p_file); + if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { + _queue_update_script_class(file); + } + if (fs->files[cpos]->type == SNAME("PackedScene")) { + _queue_update_scene_groups(file); + } + + memdelete(fs->files[cpos]); + fs->files.remove_at(cpos); } - memdelete(fs->files[cpos]); - fs->files.remove_at(cpos); + _update_pending_script_classes(); + _update_pending_scene_groups(); + call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later + return; } - _update_pending_script_classes(); - _update_pending_scene_groups(); - call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later - return; - } - - String type = ResourceLoader::get_resource_type(p_file); - if (type.is_empty() && textfile_extensions.has(p_file.get_extension())) { - type = "TextFile"; - } - String script_class = ResourceLoader::get_resource_script_class(p_file); + String type = ResourceLoader::get_resource_type(file); + if (type.is_empty() && textfile_extensions.has(file.get_extension())) { + type = "TextFile"; + } + String script_class = ResourceLoader::get_resource_script_class(file); - ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file); + ResourceUID::ID uid = ResourceLoader::get_resource_uid(file); - if (cpos == -1) { - // The file did not exist, it was added. - int idx = 0; - String file_name = p_file.get_file(); + if (cpos == -1) { + // The file did not exist, it was added. + int idx = 0; + String file_name = file.get_file(); - for (int i = 0; i < fs->files.size(); i++) { - if (p_file.filenocasecmp_to(fs->files[i]->file) < 0) { - break; + for (int i = 0; i < fs->files.size(); i++) { + if (file.filenocasecmp_to(fs->files[i]->file) < 0) { + break; + } + idx++; } - idx++; - } - EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); - fi->file = file_name; - fi->import_modified_time = 0; - fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); + fi->file = file_name; + fi->import_modified_time = 0; + fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); - if (idx == fs->files.size()) { - fs->files.push_back(fi); + if (idx == fs->files.size()) { + fs->files.push_back(fi); + } else { + fs->files.insert(idx, fi); + } + cpos = idx; } else { - fs->files.insert(idx, fi); + //the file exists and it was updated, and was not added in this step. + //this means we must force upon next restart to scan it again, to get proper type and dependencies + late_update_files.insert(file); + _save_late_updated_files(); //files need to be updated in the re-scan } - cpos = idx; - } else { - //the file exists and it was updated, and was not added in this step. - //this means we must force upon next restart to scan it again, to get proper type and dependencies - late_update_files.insert(p_file); - _save_late_updated_files(); //files need to be updated in the re-scan - } - fs->files[cpos]->type = type; - fs->files[cpos]->resource_script_class = script_class; - fs->files[cpos]->uid = uid; - fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path); - fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(p_file); - fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); - fs->files[cpos]->deps = _get_dependencies(p_file); - fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + fs->files[cpos]->type = type; + fs->files[cpos]->resource_script_class = script_class; + fs->files[cpos]->uid = uid; + fs->files[cpos]->script_class_name = _get_global_script_class(type, file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path); + fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file); + fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); + fs->files[cpos]->deps = _get_dependencies(file); + fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); - if (uid != ResourceUID::INVALID_ID) { - if (ResourceUID::get_singleton()->has_id(uid)) { - ResourceUID::get_singleton()->set_id(uid, p_file); - } else { - ResourceUID::get_singleton()->add_id(uid, p_file); - } + if (uid != ResourceUID::INVALID_ID) { + if (ResourceUID::get_singleton()->has_id(uid)) { + ResourceUID::get_singleton()->set_id(uid, file); + } else { + ResourceUID::get_singleton()->add_id(uid, file); + } - ResourceUID::get_singleton()->update_cache(); - } - // Update preview - EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); + ResourceUID::get_singleton()->update_cache(); + } + // Update preview + EditorResourcePreview::get_singleton()->check_for_invalidation(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); + if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { + _queue_update_script_class(file); + } + if (fs->files[cpos]->type == SNAME("PackedScene")) { + _queue_update_scene_groups(file); + } } _update_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", §ion_keys); - for (const String §ion_key : section_keys) { - import_options[section_key] = p_config->get_value("params", section_key); - } - if (params->importer.is_valid()) { - params->importer->handle_compatibility_options(import_options); + if (p_config.is_valid() && p_config->has_section("params")) { + List<String> section_keys; + p_config->get_section_keys("params", §ion_keys); + for (const String §ion_key : section_keys) { + import_options[section_key] = p_config->get_value("params", section_key); + } + if (params->importer.is_valid()) { + params->importer->handle_compatibility_options(import_options); + } } for (const ResourceImporter::ImportOption &E : options) { diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index fe56f48889..0a2c192ea4 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -243,12 +243,12 @@ void AnimationPlayerEditor::_play_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); + double time = player->get_current_animation_position(); if (current == player->get_assigned_animation() && player->is_playing()) { - player->stop(); //so it won't blend with itself + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play(current); } @@ -281,12 +281,12 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); - if (current == player->get_assigned_animation()) { - player->stop(); //so it won't blend with itself + double time = player->get_current_animation_position(); + if (current == player->get_assigned_animation() && player->is_playing()) { + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play_backwards(current); } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index c71cb9d4ac..4448f165d3 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -76,12 +76,15 @@ void SceneTreeDock::_quick_open() { } void SceneTreeDock::_inspect_hovered_node() { - scene_tree->set_selected(node_hovered_now); - scene_tree->set_marked(node_hovered_now); + select_node_hovered_at_end_of_drag = true; + if (tree_item_inspected != nullptr) { + tree_item_inspected->clear_custom_color(0); + } Tree *tree = scene_tree->get_scene_tree(); - TreeItem *item = tree->get_item_at_position(tree->get_local_mouse_position()); + TreeItem *item = tree->get_item_with_metadata(node_hovered_now->get_path()); if (item) { - item->set_as_cursor(0); + tree_item_inspected = item; + tree_item_inspected->set_custom_color(0, get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); } InspectorDock::get_inspector_singleton()->edit(node_hovered_now); InspectorDock::get_inspector_singleton()->propagate_notification(NOTIFICATION_DRAG_BEGIN); // Enable inspector drag preview after it updated. @@ -130,8 +133,8 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { } if (mb->is_released()) { - if (scene_tree->has_marked()) { - scene_tree->set_marked(nullptr); + if (tree_item_inspected != nullptr) { + tree_item_inspected->clear_custom_color(0); } _reset_hovering_timer(); } @@ -1658,6 +1661,16 @@ void SceneTreeDock::_notification(int p_what) { case NOTIFICATION_DRAG_END: { _reset_hovering_timer(); + if (select_node_hovered_at_end_of_drag && !hovered_but_reparenting) { + Node *node_inspected = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object()); + if (node_inspected) { + editor_selection->clear(); + editor_selection->add_node(node_inspected); + scene_tree->set_selected(node_inspected); + select_node_hovered_at_end_of_drag = false; + } + } + hovered_but_reparenting = false; } break; } } @@ -2185,6 +2198,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { ERR_FAIL_NULL(new_parent); List<Node *> selection = editor_selection->get_selected_node_list(); + List<Node *> full_selection = editor_selection->get_full_selected_node_list(); if (selection.is_empty()) { return; // Nothing to reparent. @@ -2197,6 +2211,10 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { } _do_reparent(new_parent, -1, nodes, p_keep_global_xform); + + for (Node *E : full_selection) { + editor_selection->add_node(E); + } } void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) { @@ -2238,6 +2256,9 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V return; // Position and parent didn't change. } + // Prevent selecting the hovered node and keep the reparented node(s) selected instead. + hovered_but_reparenting = true; + Node *validate = new_parent; while (validate) { ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent."); @@ -3400,6 +3421,7 @@ void SceneTreeDock::_nodes_dragged(const Array &p_nodes, NodePath p_to, int p_ty } List<Node *> selection = editor_selection->get_selected_node_list(); + List<Node *> full_selection = editor_selection->get_full_selected_node_list(); if (selection.is_empty()) { return; //nothing to reparent @@ -3419,7 +3441,8 @@ void SceneTreeDock::_nodes_dragged(const Array &p_nodes, NodePath p_to, int p_ty _normalize_drop(to_node, to_pos, p_type); _do_reparent(to_node, to_pos, nodes, !Input::get_singleton()->is_key_pressed(Key::SHIFT)); - for (Node *E : nodes) { + + for (Node *E : full_selection) { editor_selection->add_node(E); } } diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index abef990995..5028cd5cc9 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -239,8 +239,11 @@ class SceneTreeDock : public VBoxContainer { void _inspect_hovered_node(); void _reset_hovering_timer(); Timer *inspect_hovered_node_delay = nullptr; + TreeItem *tree_item_inspected = nullptr; Node *node_hovered_now = nullptr; Node *node_hovered_previously = nullptr; + bool select_node_hovered_at_end_of_drag = false; + bool hovered_but_reparenting = false; virtual void input(const Ref<InputEvent> &p_event) override; virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index b27f80ee29..03c7a8291a 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -364,3 +364,9 @@ GH-92322 Validate extension JSON: Error: Field 'classes/EditorInspectorPlugin/methods/add_property_editor/arguments': size changed value in new API, from 3 to 4. Optional arguments added. Compatibility methods registered. + +GH-86661 +-------- +Validate extension JSON: Error: Field 'classes/Animation/methods/track_find_key/arguments': size changed value in new API, from 3 to 5. + +Added optional argument to track_find_key to handle backward seeking. Compatibility method registered. diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 771ccf47b7..f1a35c84b7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -166,7 +166,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { panic_mode = true; // TODO: Improve positional information. if (p_origin == nullptr) { - errors.push_back({ p_message, current.start_line, current.start_column }); + errors.push_back({ p_message, previous.start_line, previous.start_column }); } else { errors.push_back({ p_message, p_origin->start_line, p_origin->leftmost_column }); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index eef26cdd4e..5cc2a8026e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -427,10 +427,11 @@ namespace Godot.Bridge // This method may be called before initialization. if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint()) { - foreach (var scriptPath in _pathTypeBiMap.Paths) + if (_pathTypeBiMap.Paths.Count > 0) { - using godot_string nativeScriptPath = Marshaling.ConvertStringToNative(scriptPath); - NativeFuncs.godotsharp_internal_editor_file_system_update_file(nativeScriptPath); + string[] scriptPaths = _pathTypeBiMap.Paths.ToArray(); + using godot_packed_string_array scriptPathsNative = Marshaling.ConvertSystemArrayToNativePackedStringArray(scriptPaths); + NativeFuncs.godotsharp_internal_editor_file_system_update_files(scriptPathsNative); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs index 1ec1a75516..29fa13d625 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -64,7 +65,7 @@ public static partial class ScriptManagerBridge private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new(); private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new(); - public System.Collections.Generic.IEnumerable<string> Paths => _pathTypeMap.Keys; + public IReadOnlyCollection<string> Paths => _pathTypeMap.Keys; public void Add(string scriptPath, Type scriptType) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index c4fd639cce..cfd9ed7acc 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -59,7 +59,7 @@ namespace Godot.NativeInterop internal static partial void godotsharp_stack_info_vector_destroy( ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); - internal static partial void godotsharp_internal_editor_file_system_update_file(in godot_string p_script_path); + internal static partial void godotsharp_internal_editor_file_system_update_files(in godot_packed_string_array p_script_paths); internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 1af462dafd..80e9fdf77f 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -315,13 +315,13 @@ void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript))); } -void godotsharp_internal_editor_file_system_update_file(const String *p_script_path) { +void godotsharp_internal_editor_file_system_update_files(const PackedStringArray &p_script_paths) { #ifdef TOOLS_ENABLED // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); if (efs) { - efs->update_file(*p_script_path); + efs->update_files(p_script_paths); } #else // EditorFileSystem is only available when running in the Godot editor. @@ -1450,7 +1450,7 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, (void *)godotsharp_stack_info_vector_destroy, - (void *)godotsharp_internal_editor_file_system_update_file, + (void *)godotsharp_internal_editor_file_system_update_files, (void *)godotsharp_internal_script_debugger_send_error, (void *)godotsharp_internal_script_debugger_is_active, (void *)godotsharp_internal_object_get_associated_gchandle, diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 4ca1b11094..0e4c723a87 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -4184,8 +4184,11 @@ void DisplayServerX11::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 1032766480..a1a91345ac 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3366,8 +3366,11 @@ void DisplayServerMacOS::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { bool was_empty = popup_list.is_empty(); // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 73987c44db..10cec9f5ed 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -3575,8 +3575,11 @@ void DisplayServerWindows::popup_open(WindowID p_window) { } } + // Detect tooltips and other similar popups that shouldn't block input to their parent. + bool ignores_input = window_get_flag(WINDOW_FLAG_NO_FOCUS, p_window) && window_get_flag(WINDOW_FLAG_MOUSE_PASSTHROUGH, p_window); + WindowData &wd = windows[p_window]; - if (wd.is_popup || has_popup_ancestor) { + if (wd.is_popup || (has_popup_ancestor && !ignores_input)) { // Find current popup parent, or root popup if new window is not transient. List<WindowID>::Element *C = nullptr; List<WindowID>::Element *E = popup_list.back(); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 0ac236eaa7..cdd7b15110 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -205,11 +205,12 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { // Check if anything changed that might change the quadrant shape. // If so, recreate everything. - bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] || - (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM] || dirty.flags[DIRTY_FLAGS_TILE_SET])); + bool quadrant_shape_changed = dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_TILE_SET] || + (is_y_sort_enabled() && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED] || dirty.flags[DIRTY_FLAGS_LAYER_LOCAL_TRANSFORM])) || + (!is_y_sort_enabled() && dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE]); // Free all quadrants. - if (forced_cleanup || quandrant_shape_changed) { + if (forced_cleanup || quadrant_shape_changed) { for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { for (const RID &ci : kv.value->canvas_items) { if (ci.is_valid()) { @@ -264,9 +265,8 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) { rendering_quadrant->canvas_items.clear(); // Sort the quadrant cells. - if (is_y_sort_enabled()) { - // For compatibility reasons, we use another comparator for Y-sorted layers. - rendering_quadrant->cells.sort_custom<CellDataYSortedComparator>(); + if (is_y_sort_enabled() && x_draw_order_reversed) { + rendering_quadrant->cells.sort_custom<CellDataYSortedXReversedComparator>(); } else { rendering_quadrant->cells.sort(); } @@ -1770,6 +1770,8 @@ void TileMapLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileMapLayer::set_y_sort_origin); ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileMapLayer::get_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_x_draw_order_reversed", "x_draw_order_reversed"), &TileMapLayer::set_x_draw_order_reversed); + ClassDB::bind_method(D_METHOD("is_x_draw_order_reversed"), &TileMapLayer::is_x_draw_order_reversed); ClassDB::bind_method(D_METHOD("set_rendering_quadrant_size", "size"), &TileMapLayer::set_rendering_quadrant_size); ClassDB::bind_method(D_METHOD("get_rendering_quadrant_size"), &TileMapLayer::get_rendering_quadrant_size); @@ -1796,6 +1798,7 @@ void TileMapLayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tile_set", "get_tile_set"); ADD_GROUP("Rendering", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "x_draw_order_reversed"), "set_x_draw_order_reversed", "is_x_draw_order_reversed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size"); ADD_GROUP("Physics", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled"); @@ -1814,6 +1817,18 @@ void TileMapLayer::_bind_methods() { BIND_ENUM_CONSTANT(DEBUG_VISIBILITY_MODE_FORCE_SHOW); } +void TileMapLayer::_validate_property(PropertyInfo &p_property) const { + if (is_y_sort_enabled()) { + if (p_property.name == "rendering_quadrant_size") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } else { + if (p_property.name == "x_draw_order_reversed") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} + void TileMapLayer::_update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) { // Set a default texture filter for the whole tilemap. CanvasItem::_update_self_texture_filter(p_texture_filter); @@ -2772,6 +2787,7 @@ void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { _queue_internal_update(); emit_signal(CoreStringName(changed)); + notify_property_list_changed(); _update_notify_local_transform(); } @@ -2789,6 +2805,20 @@ int TileMapLayer::get_y_sort_origin() const { return y_sort_origin; } +void TileMapLayer::set_x_draw_order_reversed(bool p_x_draw_order_reversed) { + if (x_draw_order_reversed == p_x_draw_order_reversed) { + return; + } + x_draw_order_reversed = p_x_draw_order_reversed; + dirty.flags[DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +bool TileMapLayer::is_x_draw_order_reversed() const { + return x_draw_order_reversed; +} + void TileMapLayer::set_z_index(int p_z_index) { if (get_z_index() == p_z_index) { return; @@ -2813,10 +2843,9 @@ void TileMapLayer::set_rendering_quadrant_size(int p_size) { if (rendering_quadrant_size == p_size) { return; } - dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] = true; ERR_FAIL_COND_MSG(p_size < 1, "TileMapQuadrant size cannot be smaller than 1."); - rendering_quadrant_size = p_size; + dirty.flags[DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE] = true; _queue_internal_update(); emit_signal(CoreStringName(changed)); } @@ -2840,6 +2869,9 @@ bool TileMapLayer::is_collision_enabled() const { } void TileMapLayer::set_use_kinematic_bodies(bool p_use_kinematic_bodies) { + if (use_kinematic_bodies == p_use_kinematic_bodies) { + return; + } use_kinematic_bodies = p_use_kinematic_bodies; dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] = p_use_kinematic_bodies; _queue_internal_update(); diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 57c83d7c4c..19c6fc128b 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -160,8 +160,8 @@ struct CellData { } }; -// For compatibility reasons, we use another comparator for Y-sorted layers. -struct CellDataYSortedComparator { +// We use another comparator for Y-sorted layers with reversed X drawing order. +struct CellDataYSortedXReversedComparator { _FORCE_INLINE_ bool operator()(const CellData &p_a, const CellData &p_b) const { return p_a.coords.x == p_b.coords.x ? (p_a.coords.y < p_b.coords.y) : (p_a.coords.x > p_b.coords.x); } @@ -245,6 +245,7 @@ public: DIRTY_FLAGS_LAYER_SELF_MODULATE, DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, + DIRTY_FLAGS_LAYER_X_DRAW_ORDER_REVERSED, DIRTY_FLAGS_LAYER_Z_INDEX, DIRTY_FLAGS_LAYER_LIGHT_MASK, DIRTY_FLAGS_LAYER_TEXTURE_FILTER, @@ -280,6 +281,7 @@ private: HighlightMode highlight_mode = HIGHLIGHT_MODE_DEFAULT; int y_sort_origin = 0; + bool x_draw_order_reversed = false; int rendering_quadrant_size = 16; bool collision_enabled = true; @@ -383,6 +385,7 @@ protected: void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; virtual void _update_self_texture_filter(RS::CanvasItemTextureFilter p_texture_filter) override; virtual void _update_self_texture_repeat(RS::CanvasItemTextureRepeat p_texture_repeat) override; @@ -470,6 +473,8 @@ public: virtual void set_y_sort_enabled(bool p_y_sort_enabled) override; void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; + void set_x_draw_order_reversed(bool p_x_draw_order_reversed); + bool is_x_draw_order_reversed() const; virtual void set_z_index(int p_z_index) override; virtual void set_light_mask(int p_light_mask) override; void set_rendering_quadrant_size(int p_size); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index 2716738684..6aade24e4e 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -44,16 +44,8 @@ void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const { } if (parent) { - String names; - for (int i = 0; i < parent->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += parent->get_bone_name(i); - } - p_property.hint = PROPERTY_HINT_ENUM; - p_property.hint_string = names; + p_property.hint_string = parent->get_concatenated_bone_names(); } else { p_property.hint = PROPERTY_HINT_NONE; p_property.hint_string = ""; diff --git a/scene/3d/physics/physical_bone_3d.cpp b/scene/3d/physics/physical_bone_3d.cpp index c6be2a9da8..c290f16c0d 100644 --- a/scene/3d/physics/physical_bone_3d.cpp +++ b/scene/3d/physics/physical_bone_3d.cpp @@ -738,15 +738,7 @@ bool PhysicalBone3D::_get(const StringName &p_name, Variant &r_ret) const { void PhysicalBone3D::_get_property_list(List<PropertyInfo> *p_list) const { Skeleton3D *skeleton = get_skeleton(); if (skeleton) { - String names; - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += skeleton->get_bone_name(i); - } - - p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, names)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"), PROPERTY_HINT_ENUM, skeleton->get_concatenated_bone_names())); } else { p_list->push_back(PropertyInfo(Variant::STRING_NAME, PNAME("bone_name"))); } diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index a4804e928a..21e82adf47 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -265,11 +265,31 @@ void Skeleton3D::_update_process_order() { bones_backup.resize(bones.size()); + concatenated_bone_names = StringName(); + process_order_dirty = false; emit_signal("bone_list_changed"); } +void Skeleton3D::_update_bone_names() const { + String names; + for (int i = 0; i < bones.size(); i++) { + if (i > 0) { + names += ","; + } + names += bones[i].name; + } + concatenated_bone_names = StringName(names); +} + +StringName Skeleton3D::get_concatenated_bone_names() const { + if (concatenated_bone_names == StringName()) { + _update_bone_names(); + } + return concatenated_bone_names; +} + #ifndef DISABLE_DEPRECATED void Skeleton3D::setup_simulator() { if (simulator && simulator->get_parent() == this) { @@ -983,6 +1003,8 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names); + ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent); ClassDB::bind_method(D_METHOD("set_bone_parent", "bone_idx", "parent_idx"), &Skeleton3D::set_bone_parent); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 23b9423993..b8e38242b9 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -150,6 +150,9 @@ private: Vector<int> parentless_bones; HashMap<String, int> name_to_bone_index; + mutable StringName concatenated_bone_names = StringName(); + void _update_bone_names() const; + void _make_dirty(); bool dirty = false; bool rest_dirty = false; @@ -200,6 +203,7 @@ public: int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; void set_bone_name(int p_bone, const String &p_name); + StringName get_concatenated_bone_names() const; bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const; diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 78a21ba9e1..0d6316ee35 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -306,16 +306,8 @@ void SkeletonIK3D::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "root_bone" || p_property.name == "tip_bone") { Skeleton3D *skeleton = get_skeleton(); if (skeleton) { - String names("--,"); - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (i > 0) { - names += ","; - } - names += skeleton->get_bone_name(i); - } - p_property.hint = PROPERTY_HINT_ENUM; - p_property.hint_string = names; + p_property.hint_string = skeleton->get_concatenated_bone_names(); } else { p_property.hint = PROPERTY_HINT_NONE; p_property.hint_string = ""; diff --git a/scene/3d/skeleton_modifier_3d.cpp b/scene/3d/skeleton_modifier_3d.cpp index 8d806ef5fc..9851214194 100644 --- a/scene/3d/skeleton_modifier_3d.cpp +++ b/scene/3d/skeleton_modifier_3d.cpp @@ -123,6 +123,8 @@ void SkeletonModifier3D::_notification(int p_what) { } void SkeletonModifier3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModifier3D::get_skeleton); + ClassDB::bind_method(D_METHOD("set_active", "active"), &SkeletonModifier3D::set_active); ClassDB::bind_method(D_METHOD("is_active"), &SkeletonModifier3D::is_active); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index e600de6b8b..5074145168 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -1109,6 +1109,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { real_t weight = ai.playback_info.weight; Vector<real_t> track_weights = ai.playback_info.track_weights; bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. + bool seeked_backward = signbit(p_delta); #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED @@ -1463,7 +1464,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } else { if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, true); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT, false, seeked_backward); if (idx < 0) { continue; } @@ -1744,8 +1745,10 @@ void AnimationMixer::_blend_apply() { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); - if (t->use_discrete && !t->use_continuous) { - t->is_init = true; // If only disctere value is applied, no more RESET. + if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { + t->is_init = false; // Always update in Force Continuous. + } else if (!t->use_continuous && (t->use_discrete || !deterministic)) { + t->is_init = true; // If there is no continuous value and only disctere value is applied or just started, don't RESET. } if ((t->is_init && (is_zero_amount || !t->use_continuous)) || @@ -1756,9 +1759,7 @@ void AnimationMixer::_blend_apply() { break; // Don't overwrite the value set by UPDATE_DISCRETE. } - if (callback_mode_discrete == ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { - t->is_init = false; // Always update in Force Continuous. - } else { + if (callback_mode_discrete != ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS) { t->is_init = !t->use_continuous; // If there is no Continuous in non-Force Continuous type, it means RESET. } diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 0c24d79ad7..5756edaa48 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -234,6 +234,9 @@ void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, f pi.delta = delta; pi.seeked = p_seeked; } + if (Math::is_zero_approx(pi.delta) && backwards) { + pi.delta = -0.0; // Sign is needed to handle converted Continuous track from Discrete track correctly. + } // AnimationPlayer doesn't have internal seeking. // However, immediately after playback, discrete keys should be retrieved with EXACT mode since behind keys must be ignored at that time. pi.is_external_seeking = !p_started; @@ -257,7 +260,7 @@ void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) { bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process. - if (p_delta != 0) { + if (!Math::is_zero_approx(p_delta)) { c.seeked = false; } @@ -581,6 +584,8 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) { return; } + bool is_backward = p_time < playback.current.pos; + _check_immediately_after_start(); playback.current.pos = p_time; @@ -596,7 +601,7 @@ void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) { playback.seeked = true; if (p_update) { - _process_animation(0, p_update_only); + _process_animation(is_backward ? -0.0 : 0.0, p_update_only); playback.seeked = false; // If animation was proceeded here, no more seek in internal process. } } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 0c146ce173..a23ee6db71 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -99,7 +99,9 @@ void FileDialog::set_visible(bool p_visible) { #endif if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - _native_popup(); + if (p_visible) { + _native_popup(); + } } else { ConfirmationDialog::set_visible(p_visible); } diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 38bd7141c2..468d4e3c0f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1430,7 +1430,7 @@ void Viewport::_gui_show_tooltip() { Control *tooltip_owner = nullptr; gui.tooltip_text = _gui_get_tooltip( gui.tooltip_control, - gui.tooltip_control->get_global_transform().xform_inv(gui.last_mouse_pos), + gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos), &tooltip_owner); gui.tooltip_text = gui.tooltip_text.strip_edges(); @@ -1910,7 +1910,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.tooltip_popup) { if (gui.tooltip_control) { - String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform().xform_inv(mpos)); + String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(mpos)); tooltip = tooltip.strip_edges(); if (tooltip.is_empty() || tooltip != gui.tooltip_text) { diff --git a/scene/resources/animation.compat.inc b/scene/resources/animation.compat.inc index d7d8867748..fc2672bb25 100644 --- a/scene/resources/animation.compat.inc +++ b/scene/resources/animation.compat.inc @@ -50,8 +50,8 @@ Variant Animation::_value_track_interpolate_bind_compat_86629(int p_track, doubl return value_track_interpolate(p_track, p_time, false); } -int Animation::_track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode) const { - return track_find_key(p_track, p_time, p_find_mode, false); +int Animation::_track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode) const { + return track_find_key(p_track, p_time, p_find_mode, false, false); } void Animation::_bind_compatibility_methods() { @@ -60,7 +60,7 @@ void Animation::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("scale_track_interpolate", "track_idx", "time_sec"), &Animation::_scale_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("blend_shape_track_interpolate", "track_idx", "time_sec"), &Animation::_blend_shape_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::_value_track_interpolate_bind_compat_86629); - ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_86661, DEFVAL(FIND_MODE_NEAREST)); + ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_92861, DEFVAL(FIND_MODE_NEAREST)); } #endif // DISABLE_DEPRECATED diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index a3bfa987c6..254bd38be7 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1496,7 +1496,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { emit_changed(); } -int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit) const { +int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit, bool p_backward) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1518,7 +1518,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(tt->positions, p_time, false, p_limit); + int k = _find(tt->positions, p_time, p_backward, p_limit); if (k < 0 || k >= tt->positions.size()) { return -1; } @@ -1545,7 +1545,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(rt->rotations, p_time, false, p_limit); + int k = _find(rt->rotations, p_time, p_backward, p_limit); if (k < 0 || k >= rt->rotations.size()) { return -1; } @@ -1572,7 +1572,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(st->scales, p_time, false, p_limit); + int k = _find(st->scales, p_time, p_backward, p_limit); if (k < 0 || k >= st->scales.size()) { return -1; } @@ -1599,7 +1599,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(bst->blend_shapes, p_time, false, p_limit); + int k = _find(bst->blend_shapes, p_time, p_backward, p_limit); if (k < 0 || k >= bst->blend_shapes.size()) { return -1; } @@ -1611,7 +1611,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_VALUE: { ValueTrack *vt = static_cast<ValueTrack *>(t); - int k = _find(vt->values, p_time, false, p_limit); + int k = _find(vt->values, p_time, p_backward, p_limit); if (k < 0 || k >= vt->values.size()) { return -1; } @@ -1623,7 +1623,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_METHOD: { MethodTrack *mt = static_cast<MethodTrack *>(t); - int k = _find(mt->methods, p_time, false, p_limit); + int k = _find(mt->methods, p_time, p_backward, p_limit); if (k < 0 || k >= mt->methods.size()) { return -1; } @@ -1635,7 +1635,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_BEZIER: { BezierTrack *bt = static_cast<BezierTrack *>(t); - int k = _find(bt->values, p_time, false, p_limit); + int k = _find(bt->values, p_time, p_backward, p_limit); if (k < 0 || k >= bt->values.size()) { return -1; } @@ -1647,7 +1647,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_AUDIO: { AudioTrack *at = static_cast<AudioTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -1659,7 +1659,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_ANIMATION: { AnimationTrack *at = static_cast<AnimationTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -3836,7 +3836,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_get_key_count", "track_idx"), &Animation::track_get_key_count); ClassDB::bind_method(D_METHOD("track_get_key_value", "track_idx", "key_idx"), &Animation::track_get_key_value); ClassDB::bind_method(D_METHOD("track_get_key_time", "track_idx", "key_idx"), &Animation::track_get_key_time); - ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit", "backward"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("track_set_interpolation_type", "track_idx", "interpolation"), &Animation::track_set_interpolation_type); ClassDB::bind_method(D_METHOD("track_get_interpolation_type", "track_idx"), &Animation::track_get_interpolation_type); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 604bce497a..e9bfc298a5 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -388,7 +388,7 @@ protected: Vector3 _scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const; float _blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const; Variant _value_track_interpolate_bind_compat_86629(int p_track, double p_time) const; - int _track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; + int _track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -423,7 +423,7 @@ public: void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); void track_set_key_time(int p_track, int p_key_idx, double p_time); - int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false) const; + int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false, bool p_backward = false) const; void track_remove_key(int p_track, int p_idx); void track_remove_key_at_time(int p_track, double p_time); int track_get_key_count(int p_track) const; diff --git a/thirdparty/README.md b/thirdparty/README.md index 137e3a0cbc..6878af863f 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.13.3 (6235068cad8cad176ccd0cbcf82f25e985fbc258, 2024) +- Version: 0.13.7 (d2c0428a99f7305c086caffe0c730add601ebd6e, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 9c83f25c79..699076fdf1 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -13,5 +13,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.13.5" +#define THORVG_VERSION_STRING "0.13.7" #endif diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h index f7396050d7..8285aa1c4c 100644 --- a/thirdparty/thorvg/inc/thorvg.h +++ b/thirdparty/thorvg/inc/thorvg.h @@ -80,7 +80,7 @@ enum class Result InsufficientCondition, ///< The value returned in case the request cannot be processed - e.g. asking for properties of an object, which does not exist. FailedAllocation, ///< The value returned in case of unsuccessful memory allocation. MemoryCorruption, ///< The value returned in the event of bad memory handling - e.g. failing in pointer releasing or casting - NonSupport, ///< The value returned in case of choosing unsupported options. + NonSupport, ///< The value returned in case of choosing unsupported engine features(options). Unknown ///< The value returned in all other cases. }; @@ -982,7 +982,7 @@ public: * * @param[in] width The width of the stroke. The default value is 0. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(float width) noexcept; @@ -994,7 +994,7 @@ public: * @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0. * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; @@ -1004,8 +1004,7 @@ public: * @param[in] f The gradient fill. * * @retval Result::Success When succeed. - * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be filled. - * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument. + * @retval Result::MemoryCorruption In case a @c nullptr is passed as the argument or an error with accessing it. */ Result stroke(std::unique_ptr<Fill> f) noexcept; @@ -1029,7 +1028,7 @@ public: * * @param[in] cap The cap style value. The default value is @c StrokeCap::Square. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(StrokeCap cap) noexcept; @@ -1040,23 +1039,38 @@ public: * * @param[in] join The join style value. The default value is @c StrokeJoin::Bevel. * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. */ Result stroke(StrokeJoin join) noexcept; - /** * @brief Sets the stroke miterlimit. * * @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4. * - * @retval Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed or Result::InvalidArgument for @p miterlimit values less than zero. * * @since 0.11 */ Result strokeMiterlimit(float miterlimit) noexcept; /** + * @brief Sets the trim of the stroke along the defined path segment, allowing control over which part of the stroke is visible. + * + * The values of the arguments @p begin, @p end, and @p offset are in the range of 0.0 to 1.0, representing the beginning of the path and the end, respectively. + * + * @param[in] begin Specifies the start of the segment to display along the path. + * @param[in] end Specifies the end of the segment to display along the path. + * @param[in] simultaneous Determines how to trim multiple paths within a single shape. If set to @c true (default), trimming is applied simultaneously to all paths; + * Otherwise, all paths are treated as a single entity with a combined length equal to the sum of their individual lengths and are trimmed as such. + * + * @retval Result::Success when succeed. + * + * @note Experimental API + */ + Result strokeTrim(float begin, float end, bool simultaneous = true) noexcept; + + /** * @brief Sets the solid color for all of the figures from the path. * * The parts of the shape defined as inner are colored. @@ -1095,19 +1109,17 @@ public: */ Result fill(FillRule r) noexcept; - /** * @brief Sets the rendering order of the stroke and the fill. * * @param[in] strokeFirst If @c true the stroke is rendered before the fill, otherwise the stroke is rendered as the second one (the default option). * - * @retval Result::Success when succeed, Result::FailedAllocation otherwise. + * @retval Result::Success when succeed. * * @since 0.10 */ Result order(bool strokeFirst) noexcept; - /** * @brief Gets the commands data of the path. * @@ -1211,6 +1223,18 @@ public: float strokeMiterlimit() const noexcept; /** + * @brief Gets the trim of the stroke along the defined path segment. + * + * @param[out] begin The starting point of the segment to display along the path. + * @param[out] end Specifies the end of the segment to display along the path. + * + * @retval @c true if trimming is applied simultaneously to all paths of the shape, @c false otherwise. + * + * @note Experimental API + */ + bool strokeTrim(float* begin, float* end) const noexcept; + + /** * @brief Creates a new Shape object. * * @return A new Shape object. diff --git a/thirdparty/thorvg/src/common/tvgCompressor.cpp b/thirdparty/thorvg/src/common/tvgCompressor.cpp index 9a1dc54632..b61718f9a7 100644 --- a/thirdparty/thorvg/src/common/tvgCompressor.cpp +++ b/thirdparty/thorvg/src/common/tvgCompressor.cpp @@ -472,4 +472,19 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded) } +/************************************************************************/ +/* DJB2 Implementation */ +/************************************************************************/ + +unsigned long djb2Encode(const char* str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++)) { + hash = ((hash << 5) + hash) + c; // hash * 33 + c + } + return hash; +} + } diff --git a/thirdparty/thorvg/src/common/tvgCompressor.h b/thirdparty/thorvg/src/common/tvgCompressor.h index 0756127ec6..b043cc77de 100644 --- a/thirdparty/thorvg/src/common/tvgCompressor.h +++ b/thirdparty/thorvg/src/common/tvgCompressor.h @@ -30,6 +30,7 @@ namespace tvg uint8_t* lzwEncode(const uint8_t* uncompressed, uint32_t uncompressedSizeBytes, uint32_t* compressedSizeBytes, uint32_t* compressedSizeBits); uint8_t* lzwDecode(const uint8_t* compressed, uint32_t compressedSizeBytes, uint32_t compressedSizeBits, uint32_t uncompressedSizeBytes); size_t b64Decode(const char* encoded, const size_t len, char** decoded); + unsigned long djb2Encode(const char* str); } #endif //_TVG_COMPRESSOR_H_ diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index 37a8879cb5..e6b5d47050 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -47,23 +47,14 @@ bool mathInverse(const Matrix* m, Matrix* out) } -Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) +bool mathIdentity(const Matrix* m) { - Matrix m; - - m.e11 = lhs->e11 * rhs->e11 + lhs->e12 * rhs->e21 + lhs->e13 * rhs->e31; - m.e12 = lhs->e11 * rhs->e12 + lhs->e12 * rhs->e22 + lhs->e13 * rhs->e32; - m.e13 = lhs->e11 * rhs->e13 + lhs->e12 * rhs->e23 + lhs->e13 * rhs->e33; - - m.e21 = lhs->e21 * rhs->e11 + lhs->e22 * rhs->e21 + lhs->e23 * rhs->e31; - m.e22 = lhs->e21 * rhs->e12 + lhs->e22 * rhs->e22 + lhs->e23 * rhs->e32; - m.e23 = lhs->e21 * rhs->e13 + lhs->e22 * rhs->e23 + lhs->e23 * rhs->e33; - - m.e31 = lhs->e31 * rhs->e11 + lhs->e32 * rhs->e21 + lhs->e33 * rhs->e31; - m.e32 = lhs->e31 * rhs->e12 + lhs->e32 * rhs->e22 + lhs->e33 * rhs->e32; - m.e33 = lhs->e31 * rhs->e13 + lhs->e32 * rhs->e23 + lhs->e33 * rhs->e33; - - return m; + if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || + m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || + m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { + return false; + } + return true; } @@ -82,21 +73,41 @@ void mathRotate(Matrix* m, float degree) } -bool mathIdentity(const Matrix* m) +Matrix operator*(const Matrix& lhs, const Matrix& rhs) { - if (m->e11 != 1.0f || m->e12 != 0.0f || m->e13 != 0.0f || - m->e21 != 0.0f || m->e22 != 1.0f || m->e23 != 0.0f || - m->e31 != 0.0f || m->e32 != 0.0f || m->e33 != 1.0f) { - return false; + Matrix m; + + m.e11 = lhs.e11 * rhs.e11 + lhs.e12 * rhs.e21 + lhs.e13 * rhs.e31; + m.e12 = lhs.e11 * rhs.e12 + lhs.e12 * rhs.e22 + lhs.e13 * rhs.e32; + m.e13 = lhs.e11 * rhs.e13 + lhs.e12 * rhs.e23 + lhs.e13 * rhs.e33; + + m.e21 = lhs.e21 * rhs.e11 + lhs.e22 * rhs.e21 + lhs.e23 * rhs.e31; + m.e22 = lhs.e21 * rhs.e12 + lhs.e22 * rhs.e22 + lhs.e23 * rhs.e32; + m.e23 = lhs.e21 * rhs.e13 + lhs.e22 * rhs.e23 + lhs.e23 * rhs.e33; + + m.e31 = lhs.e31 * rhs.e11 + lhs.e32 * rhs.e21 + lhs.e33 * rhs.e31; + m.e32 = lhs.e31 * rhs.e12 + lhs.e32 * rhs.e22 + lhs.e33 * rhs.e32; + m.e33 = lhs.e31 * rhs.e13 + lhs.e32 * rhs.e23 + lhs.e33 * rhs.e33; + + return m; +} + + +bool operator==(const Matrix& lhs, const Matrix& rhs) +{ + if (!mathEqual(lhs.e11, rhs.e11) || !mathEqual(lhs.e12, rhs.e12) || !mathEqual(lhs.e13, rhs.e13) || + !mathEqual(lhs.e21, rhs.e21) || !mathEqual(lhs.e22, rhs.e22) || !mathEqual(lhs.e23, rhs.e23) || + !mathEqual(lhs.e31, rhs.e31) || !mathEqual(lhs.e32, rhs.e32) || !mathEqual(lhs.e33, rhs.e33)) { + return false; } return true; } -void mathMultiply(Point* pt, const Matrix* transform) +void operator*=(Point& pt, const Matrix& m) { - auto tx = pt->x * transform->e11 + pt->y * transform->e12 + transform->e13; - auto ty = pt->x * transform->e21 + pt->y * transform->e22 + transform->e23; - pt->x = tx; - pt->y = ty; + auto tx = pt.x * m.e11 + pt.y * m.e12 + m.e13; + auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23; + pt.x = tx; + pt.y = ty; } diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 5c2966956a..0f877d919e 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -38,11 +38,9 @@ #define mathMax(x, y) (((x) > (y)) ? (x) : (y)) -bool mathInverse(const Matrix* m, Matrix* out); -Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs); -void mathRotate(Matrix* m, float degree); -bool mathIdentity(const Matrix* m); -void mathMultiply(Point* pt, const Matrix* transform); +/************************************************************************/ +/* General functions */ +/************************************************************************/ static inline float mathDeg2Rad(float degree) @@ -63,28 +61,21 @@ static inline bool mathZero(float a) } -static inline bool mathZero(const Point& p) -{ - return mathZero(p.x) && mathZero(p.y); -} - - static inline bool mathEqual(float a, float b) { return mathZero(a - b); } -static inline bool mathEqual(const Matrix& a, const Matrix& b) -{ - if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) || - !mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) || - !mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) { - return false; - } - return true; -} +/************************************************************************/ +/* Matrix functions */ +/************************************************************************/ +void mathRotate(Matrix* m, float degree); +bool mathInverse(const Matrix* m, Matrix* out); +bool mathIdentity(const Matrix* m); +Matrix operator*(const Matrix& lhs, const Matrix& rhs); +bool operator==(const Matrix& lhs, const Matrix& rhs); static inline bool mathRightAngle(const Matrix* m) { @@ -114,15 +105,6 @@ static inline void mathIdentity(Matrix* m) } -static inline void mathTransform(Matrix* transform, Point* coord) -{ - auto x = coord->x; - auto y = coord->y; - coord->x = x * transform->e11 + y * transform->e12 + transform->e13; - coord->y = x * transform->e21 + y * transform->e22 + transform->e23; -} - - static inline void mathScale(Matrix* m, float sx, float sy) { m->e11 *= sx; @@ -158,12 +140,37 @@ static inline void mathTranslateR(Matrix* m, float x, float y) } +static inline bool operator!=(const Matrix& lhs, const Matrix& rhs) +{ + return !(lhs == rhs); +} + + +static inline void operator*=(Matrix& lhs, const Matrix& rhs) +{ + lhs = lhs * rhs; +} + + static inline void mathLog(Matrix* m) { TVGLOG("MATH", "Matrix: [%f %f %f] [%f %f %f] [%f %f %f]", m->e11, m->e12, m->e13, m->e21, m->e22, m->e23, m->e31, m->e32, m->e33); } +/************************************************************************/ +/* Point functions */ +/************************************************************************/ + +void operator*=(Point& pt, const Matrix& m); + + +static inline bool mathZero(const Point& p) +{ + return mathZero(p.x) && mathZero(p.y); +} + + static inline float mathLength(const Point* a, const Point* b) { auto x = b->x - a->x; @@ -182,6 +189,18 @@ static inline float mathLength(const Point& a) } +static inline bool operator==(const Point& lhs, const Point& rhs) +{ + return mathEqual(lhs.x, rhs.x) && mathEqual(lhs.y, rhs.y); +} + + +static inline bool operator!=(const Point& lhs, const Point& rhs) +{ + return !(lhs == rhs); +} + + static inline Point operator-(const Point& lhs, const Point& rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y}; @@ -212,6 +231,10 @@ static inline Point operator/(const Point& lhs, const float rhs) } +/************************************************************************/ +/* Interpolation functions */ +/************************************************************************/ + template <typename T> static inline T mathLerp(const T &start, const T &end, float t) { diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 0a47112084..f59994aae6 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -709,7 +709,7 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** *ref = _idFromUrl((const char*)(str + 3)); return true; } else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') { - float th, ts, tb; + float_t th, ts, tb; const char *content, *hue, *satuation, *brightness; content = str + 4; content = _skipSpace(content, nullptr); @@ -840,14 +840,14 @@ static Matrix* _parseTransformationMatrix(const char* value) if (state == MatrixState::Matrix) { if (ptCount != 6) goto error; Matrix tmp = {points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::Translate) { if (ptCount == 1) { Matrix tmp = {1, 0, points[0], 0, 1, 0, 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (ptCount == 2) { Matrix tmp = {1, 0, points[0], 0, 1, points[1], 0, 0, 1}; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else goto error; } else if (state == MatrixState::Rotate) { //Transform to signed. @@ -857,14 +857,14 @@ static Matrix* _parseTransformationMatrix(const char* value) auto s = sinf(mathDeg2Rad(points[0])); if (ptCount == 1) { Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (ptCount == 3) { Matrix tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else { goto error; } @@ -874,17 +874,17 @@ static Matrix* _parseTransformationMatrix(const char* value) auto sy = sx; if (ptCount == 2) sy = points[1]; Matrix tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::SkewX) { if (ptCount != 1) goto error; auto deg = tanf(mathDeg2Rad(points[0])); Matrix tmp = { 1, deg, 0, 0, 1, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } else if (state == MatrixState::SkewY) { if (ptCount != 1) goto error; auto deg = tanf(mathDeg2Rad(points[0])); Matrix tmp = { 1, 0, 0, deg, 1, 0, 0, 0, 1 }; - *matrix = mathMultiply(matrix, &tmp); + *matrix *= tmp; } } return matrix; diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp index 0b2113dd07..7e7efed3fc 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -203,9 +203,9 @@ static bool _appendClipUseNode(SvgLoaderData& loaderData, SvgNode* node, Shape* if (node->transform) finalTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { Matrix m = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - finalTransform = mathMultiply(&finalTransform, &m); + finalTransform *= m; } - if (child->transform) finalTransform = mathMultiply(child->transform, &finalTransform); + if (child->transform) finalTransform = *child->transform * finalTransform; return _appendClipShape(loaderData, child, shape, vBox, svgPath, mathIdentity((const Matrix*)(&finalTransform)) ? nullptr : &finalTransform); } @@ -228,13 +228,13 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg m = *node->transform; } if (compNode->transform) { - m = mathMultiply(&m, compNode->transform); + m *= *compNode->transform; } if (!compNode->node.clip.userSpace) { float x, y, w, h; P(paint)->bounds(&x, &y, &w, &h, false, false); Matrix mBBox = {w, 0, x, 0, h, y, 0, 0, 1}; - m = mathMultiply(&m, &mBBox); + m *= mBBox; } return m; } @@ -474,7 +474,10 @@ static bool _appendClipShape(SvgLoaderData& loaderData, SvgNode* node, Shape* sh auto ptsCnt = shape->pathCoords(&pts); auto p = const_cast<Point*>(pts) + currentPtsCnt; - while (currentPtsCnt++ < ptsCnt) mathMultiply(p++, m); + while (currentPtsCnt++ < ptsCnt) { + *p *= *m; + ++p; + } } _applyProperty(loaderData, node, shape, vBox, svgPath, true); @@ -505,6 +508,7 @@ static constexpr struct } imageMimeTypes[] = { {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64}, {"png", sizeof("png"), imageMimeTypeEncoding::base64}, + {"webp", sizeof("webp"), imageMimeTypeEncoding::base64}, {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8}, }; @@ -615,7 +619,7 @@ static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* auto sy = node->node.image.h / h; m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; } - if (node->transform) m = mathMultiply(node->transform, &m); + if (node->transform) m = *node->transform * m; picture->transform(m); _applyComposition(loaderData, picture.get(), node, vBox, svgPath); @@ -708,7 +712,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod if (node->transform) mUseTransform = *node->transform; if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; - mUseTransform = mathMultiply(&mUseTransform, &mTranslate); + mUseTransform *= mTranslate; } if (node->node.use.symbol) { @@ -732,9 +736,9 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod // mSceneTransform = mUseTransform * mSymbolTransform * mViewBox Matrix mSceneTransform = mViewBox; if (node->node.use.symbol->transform) { - mSceneTransform = mathMultiply(node->node.use.symbol->transform, &mViewBox); + mSceneTransform = *node->node.use.symbol->transform * mViewBox; } - mSceneTransform = mathMultiply(&mUseTransform, &mSceneTransform); + mSceneTransform = mUseTransform * mSceneTransform; scene->transform(mSceneTransform); if (node->node.use.symbol->node.symbol.overflowVisible) { @@ -746,7 +750,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod // mClipTransform = mUseTransform * mSymbolTransform Matrix mClipTransform = mUseTransform; if (node->node.use.symbol->transform) { - mClipTransform = mathMultiply(&mUseTransform, node->node.use.symbol->transform); + mClipTransform = mUseTransform * *node->node.use.symbol->transform; } viewBoxClip->transform(mClipTransform); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp index 3d582d291c..be1662daeb 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp @@ -150,7 +150,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); if (isTransformation) { - if (transform) gradTransform = mathMultiply(transform, &gradTransform); + if (transform) gradTransform = *transform * gradTransform; } else if (transform) { gradTransform = *transform; isTransformation = true; @@ -216,7 +216,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform)); if (transform) { - if (isTransformation) gradTransform = mathMultiply(transform, &gradTransform); + if (isTransformation) gradTransform = *transform * gradTransform; else { gradTransform = *transform; isTransformation = true; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp index b85d943873..3431f03411 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMemPool.cpp @@ -81,7 +81,7 @@ SwMpool* mpoolInit(uint32_t threads) { auto allocSize = threads + 1; - auto mpool = static_cast<SwMpool*>(calloc(sizeof(SwMpool), 1)); + auto mpool = static_cast<SwMpool*>(calloc(1, sizeof(SwMpool))); mpool->outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); mpool->strokeOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); mpool->dashOutline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline) * allocSize)); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h index 731f6984e3..8ec2bc0c47 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h @@ -1108,7 +1108,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const float ys = FLT_MAX, ye = -1.0f; for (int i = 0; i < 4; i++) { - if (transform) mathMultiply(&vertices[i].pt, transform); + if (transform) vertices[i].pt *= *transform; if (vertices[i].pt.y < ys) ys = vertices[i].pt.y; if (vertices[i].pt.y > ye) ye = vertices[i].pt.y; } @@ -1169,9 +1169,9 @@ static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, c float ys = FLT_MAX, ye = -1.0f; for (uint32_t i = 0; i < mesh->triangleCnt; i++) { transformedTris[i] = mesh->triangles[i]; - mathMultiply(&transformedTris[i].vertex[0].pt, transform); - mathMultiply(&transformedTris[i].vertex[1].pt, transform); - mathMultiply(&transformedTris[i].vertex[2].pt, transform); + transformedTris[i].vertex[0].pt *= *transform; + transformedTris[i].vertex[1].pt *= *transform; + transformedTris[i].vertex[2].pt *= *transform; if (transformedTris[i].vertex[0].pt.y < ys) ys = transformedTris[i].vertex[0].pt.y; else if (transformedTris[i].vertex[0].pt.y > ye) ye = transformedTris[i].vertex[0].pt.y; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 0b2940c32d..f689179928 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -78,7 +78,6 @@ struct SwShapeTask : SwTask { SwShape shape; const RenderShape* rshape = nullptr; - bool cmpStroking = false; bool clipper = false; /* We assume that if the stroke width is greater than 2, diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp index 386cc594b4..1e5c4ef409 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp @@ -928,7 +928,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; rw.ySpan = 0; rw.outline = const_cast<SwOutline*>(outline); - rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 + rw.bandSize = rw.bufferSize / (sizeof(Cell) * 2); //bandSize: 256 rw.bandShoot = 0; rw.antiAlias = antiAlias; @@ -966,10 +966,7 @@ SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& ren if (cellMod > 0) cellStart += sizeof(Cell) - cellMod; - auto cellEnd = rw.bufferSize; - cellEnd -= cellEnd % sizeof(Cell); - - auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + cellEnd); + auto cellsMax = reinterpret_cast<Cell*>((char*)rw.buffer + rw.bufferSize); rw.cells = reinterpret_cast<Cell*>((char*)rw.buffer + cellStart); if (rw.cells >= cellsMax) goto reduce_bands; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index b9327374b6..d8dd40d45b 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -107,7 +107,7 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans if (mathZero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); //draw the current line fully - } else if (len < dash.curLen) { + } else if (len <= dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { @@ -168,7 +168,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct //draw the current line fully if (mathZero(len)) { _outlineMoveTo(*dash.outline, &dash.ptCur, transform); - } else if (len < dash.curLen) { + } else if (len <= dash.curLen) { dash.curLen -= len; if (!dash.curOpGap) { if (dash.move) { @@ -245,7 +245,86 @@ static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const } -static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, float length, SwMpool* mpool, unsigned tid) +static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length) +{ + auto begin = length * rshape->stroke->trim.begin; + auto end = length * rshape->stroke->trim.end; + + //default + if (end > begin) { + if (begin > 0.0f) dash->cnt = 4; + else dash->cnt = 2; + //looping + } else dash->cnt = 3; + + if (dash->cnt == 2) { + dash->pattern[0] = end - begin; + dash->pattern[1] = length - (end - begin); + } else if (dash->cnt == 3) { + dash->pattern[0] = end; + dash->pattern[1] = (begin - end); + dash->pattern[2] = length - begin; + } else { + dash->pattern[0] = 0; //zero dash to start with a space. + dash->pattern[1] = begin; + dash->pattern[2] = end - begin; + dash->pattern[3] = length - end; + } +} + + +static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32_t shiftCmds, bool subpath) +{ + const PathCommand* cmds = rshape->path.cmds.data + shiftCmds; + auto cmdCnt = rshape->path.cmds.count - shiftCmds; + const Point* pts = rshape->path.pts.data + shiftPts; + auto ptsCnt = rshape->path.pts.count - shiftPts; + + //No actual shape data + if (cmdCnt <= 0 || ptsCnt <= 0) return 0.0f; + + const Point* close = nullptr; + auto length = 0.0f; + + //must begin with moveTo + if (cmds[0] == PathCommand::MoveTo) { + close = pts; + cmds++; + pts++; + cmdCnt--; + } + + while (cmdCnt-- > 0) { + switch (*cmds) { + case PathCommand::Close: { + length += mathLength(pts - 1, close); + if (subpath) return length; + break; + } + case PathCommand::MoveTo: { + if (subpath) return length; + close = pts; + ++pts; + break; + } + case PathCommand::LineTo: { + length += mathLength(pts - 1, pts); + ++pts; + break; + } + case PathCommand::CubicTo: { + length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); + pts += 3; + break; + } + } + ++cmds; + } + return length; +} + + +static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, bool trimmed, SwMpool* mpool, unsigned tid) { const PathCommand* cmds = rshape->path.cmds.data; auto cmdCnt = rshape->path.cmds.count; @@ -255,49 +334,23 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans //No actual shape data if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + auto startPts = pts; + auto startCmds = cmds; + SwDashStroke dash; auto offset = 0.0f; - auto trimmed = false; - dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset); + auto simultaneous = rshape->stroke->trim.simultaneous; - //dash by trimming. - if (length > 0.0f && dash.cnt == 0) { - auto begin = length * rshape->stroke->trim.begin; - auto end = length * rshape->stroke->trim.end; - - //TODO: mix trimming + dash style - - //default - if (end > begin) { - if (begin > 0.0f) dash.cnt += 4; - else dash.cnt += 2; - //looping - } else dash.cnt += 3; - - dash.pattern = (float*)malloc(sizeof(float) * dash.cnt); - - if (dash.cnt == 2) { - dash.pattern[0] = end - begin; - dash.pattern[1] = length - (end - begin); - } else if (dash.cnt == 3) { - dash.pattern[0] = end; - dash.pattern[1] = (begin - end); - dash.pattern[2] = length - begin; - } else { - dash.pattern[0] = 0; //zero dash to start with a space. - dash.pattern[1] = begin; - dash.pattern[2] = end - begin; - dash.pattern[3] = length - end; - } - - trimmed = true; - //just a dasy style. + if (dash.cnt == 0) { + if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4); + else return nullptr; } else { - if (dash.cnt == 0) return nullptr; + //TODO: handle dash + trim - for now trimming ignoring is forced + trimmed = false; } - //offset? + //offset auto patternLength = 0.0f; uint32_t offIdx = 0; if (!mathZero(offset)) { @@ -319,6 +372,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans //must begin with moveTo if (cmds[0] == PathCommand::MoveTo) { + if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous)); _dashMoveTo(dash, offIdx, offset, pts); cmds++; pts++; @@ -331,8 +385,12 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans break; } case PathCommand::MoveTo: { - if (rshape->stroke->trim.individual) _dashMoveTo(dash, pts); - else _dashMoveTo(dash, offIdx, offset, pts); + if (trimmed) { + if (simultaneous) { + _trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true)); + _dashMoveTo(dash, offIdx, offset, pts); + } else _dashMoveTo(dash, pts); + } else _dashMoveTo(dash, offIdx, offset, pts); ++pts; break; } @@ -358,56 +416,6 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans } -static float _outlineLength(const RenderShape* rshape) -{ - const PathCommand* cmds = rshape->path.cmds.data; - auto cmdCnt = rshape->path.cmds.count; - const Point* pts = rshape->path.pts.data; - auto ptsCnt = rshape->path.pts.count; - - //No actual shape data - if (cmdCnt == 0 || ptsCnt == 0) return 0.0f; - - const Point* close = nullptr; - auto length = 0.0f; - auto slength = -1.0f; - auto simultaneous = !rshape->stroke->trim.individual; - - //Compute the whole length - while (cmdCnt-- > 0) { - switch (*cmds) { - case PathCommand::Close: { - length += mathLength(pts - 1, close); - //retrieve the max length of the shape if the simultaneous mode. - if (simultaneous) { - if (slength < length) slength = length; - length = 0.0f; - } - break; - } - case PathCommand::MoveTo: { - close = pts; - ++pts; - break; - } - case PathCommand::LineTo: { - length += mathLength(pts - 1, pts); - ++pts; - break; - } - case PathCommand::CubicTo: { - length += bezLength({*(pts - 1), *pts, *(pts + 1), *(pts + 2)}); - pts += 3; - break; - } - } - ++cmds; - } - if (simultaneous && slength > length) return slength; - else return length; -} - - static bool _axisAlignedRect(const SwOutline* outline) { //Fast Track: axis-aligned rectangle? @@ -584,11 +592,10 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* auto dashStroking = false; auto ret = true; - auto length = rshape->strokeTrim() ? _outlineLength(rshape) : 0.0f; - //Dash style (+trimming) - if (rshape->stroke->dashCnt > 0 || length > 0) { - shapeOutline = _genDashOutline(rshape, transform, length, mpool, tid); + auto trimmed = rshape->strokeTrim(); + if (rshape->stroke->dashCnt > 0 || trimmed) { + shapeOutline = _genDashOutline(rshape, transform, trimmed, mpool, tid); if (!shapeOutline) return false; dashStroking = true; //Normal style diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 227ce10a0d..fcb632e2b1 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -75,13 +75,13 @@ static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform auto v2 = *pt3; if (rTransform) { - mathMultiply(&v1, &rTransform->m); - mathMultiply(&v2, &rTransform->m); + v1 *= rTransform->m; + v2 *= rTransform->m; } if (pTransform) { - mathMultiply(&v1, &pTransform->m); - mathMultiply(&v2, &pTransform->m); + v1 *= pTransform->m; + v2 *= pTransform->m; } //sorting @@ -327,7 +327,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme //Compute the AABB after transformation for (int i = 0; i < 4; i++) { - mathMultiply(&pt[i], m); + pt[i] *= *m; if (pt[i].x < x1) x1 = pt[i].x; if (pt[i].x > x2) x2 = pt[i].x; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.cpp b/thirdparty/thorvg/src/renderer/tvgRender.cpp index 14f77571fb..9c779f7c00 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.cpp +++ b/thirdparty/thorvg/src/renderer/tvgRender.cpp @@ -47,7 +47,7 @@ void RenderTransform::update() mathScale(&m, scale, scale); - if (!mathZero(degree)) mathRotate(&m, degree); + mathRotate(&m, degree); mathTranslate(&m, x, y); } @@ -55,7 +55,7 @@ void RenderTransform::update() RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs) { - if (lhs && rhs) m = mathMultiply(&lhs->m, &rhs->m); + if (lhs && rhs) m = lhs->m * rhs->m; else if (lhs) m = lhs->m; else if (rhs) m = rhs->m; else mathIdentity(&m); diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index 6ea516c2f9..8f28d37dbc 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -142,7 +142,7 @@ struct RenderStroke struct { float begin = 0.0f; float end = 1.0f; - bool individual = false; + bool simultaneous = true; } trim; ~RenderStroke() diff --git a/thirdparty/thorvg/src/renderer/tvgShape.cpp b/thirdparty/thorvg/src/renderer/tvgShape.cpp index 4cc8f64900..c010aa7bbf 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.cpp +++ b/thirdparty/thorvg/src/renderer/tvgShape.cpp @@ -287,16 +287,14 @@ const Fill* Shape::fill() const noexcept Result Shape::order(bool strokeFirst) noexcept { - if (!pImpl->strokeFirst(strokeFirst)) return Result::FailedAllocation; - + pImpl->strokeFirst(strokeFirst); return Result::Success; } Result Shape::stroke(float width) noexcept { - if (!pImpl->strokeWidth(width)) return Result::FailedAllocation; - + pImpl->strokeWidth(width); return Result::Success; } @@ -309,8 +307,7 @@ float Shape::strokeWidth() const noexcept Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept { - if (!pImpl->strokeColor(r, g, b, a)) return Result::FailedAllocation; - + pImpl->strokeColor(r, g, b, a); return Result::Success; } @@ -349,27 +346,25 @@ uint32_t Shape::strokeDash(const float** dashPattern) const noexcept Result Shape::stroke(StrokeCap cap) noexcept { - if (!pImpl->strokeCap(cap)) return Result::FailedAllocation; - + pImpl->strokeCap(cap); return Result::Success; } Result Shape::stroke(StrokeJoin join) noexcept { - if (!pImpl->strokeJoin(join)) return Result::FailedAllocation; - + pImpl->strokeJoin(join); return Result::Success; } + Result Shape::strokeMiterlimit(float miterlimit) noexcept { // https://www.w3.org/TR/SVG2/painting.html#LineJoin // - A negative value for stroke-miterlimit must be treated as an illegal value. - if (miterlimit < 0.0f) return Result::NonSupport; + if (miterlimit < 0.0f) return Result::InvalidArguments; // TODO Find out a reasonable max value. - if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation; - + pImpl->strokeMiterlimit(miterlimit); return Result::Success; } @@ -385,12 +380,26 @@ StrokeJoin Shape::strokeJoin() const noexcept return pImpl->rs.strokeJoin(); } + float Shape::strokeMiterlimit() const noexcept { return pImpl->rs.strokeMiterlimit(); } +Result Shape::strokeTrim(float begin, float end, bool simultaneous) noexcept +{ + pImpl->strokeTrim(begin, end, simultaneous); + return Result::Success; +} + + +bool Shape::strokeTrim(float* begin, float* end) const noexcept +{ + return pImpl->strokeTrim(begin, end); +} + + Result Shape::fill(FillRule r) noexcept { pImpl->rs.rule = r; diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index 55335214be..4e85db37d0 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -207,60 +207,81 @@ struct Shape::Impl flag |= RenderUpdateFlag::Path; } - bool strokeWidth(float width) + void strokeWidth(float width) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->width = width; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeTrim(float begin, float end, bool individual) + void strokeTrim(float begin, float end, bool simultaneous) { if (!rs.stroke) { - if (begin == 0.0f && end == 1.0f) return true; + if (begin == 0.0f && end == 1.0f) return; rs.stroke = new RenderStroke(); } - if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end)) return true; + if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end) && + rs.stroke->trim.simultaneous == simultaneous) return; + + auto loop = true; + + if (begin > 1.0f && end > 1.0f) loop = false; + if (begin < 0.0f && end < 0.0f) loop = false; + if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false; + + if (begin > 1.0f) begin -= 1.0f; + if (begin < 0.0f) begin += 1.0f; + if (end > 1.0f) end -= 1.0f; + if (end < 0.0f) end += 1.0f; + + if ((loop && begin < end) || (!loop && begin > end)) { + auto tmp = begin; + begin = end; + end = tmp; + } rs.stroke->trim.begin = begin; rs.stroke->trim.end = end; - rs.stroke->trim.individual = individual; + rs.stroke->trim.simultaneous = simultaneous; flag |= RenderUpdateFlag::Stroke; + } - return true; + bool strokeTrim(float* begin, float* end) + { + if (rs.stroke) { + if (begin) *begin = rs.stroke->trim.begin; + if (end) *end = rs.stroke->trim.end; + return rs.stroke->trim.simultaneous; + } else { + if (begin) *begin = 0.0f; + if (end) *end = 1.0f; + return false; + } } - bool strokeCap(StrokeCap cap) + void strokeCap(StrokeCap cap) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->cap = cap; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeJoin(StrokeJoin join) + void strokeJoin(StrokeJoin join) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->join = join; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeMiterlimit(float miterlimit) + void strokeMiterlimit(float miterlimit) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->miterlimit = miterlimit; flag |= RenderUpdateFlag::Stroke; - - return true; } - bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + void strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!rs.stroke) rs.stroke = new RenderStroke(); if (rs.stroke->fill) { @@ -275,8 +296,6 @@ struct Shape::Impl rs.stroke->color[3] = a; flag |= RenderUpdateFlag::Stroke; - - return true; } Result strokeFill(unique_ptr<Fill> f) @@ -335,13 +354,11 @@ struct Shape::Impl return rs.stroke->strokeFirst; } - bool strokeFirst(bool strokeFirst) + void strokeFirst(bool strokeFirst) { if (!rs.stroke) rs.stroke = new RenderStroke(); rs.stroke->strokeFirst = strokeFirst; flag |= RenderUpdateFlag::Stroke; - - return true; } void update(RenderUpdateFlag flag) diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index cd1aeadec0..c980b89c4b 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -VERSION=0.13.5 +VERSION=0.13.7 cd thirdparty/thorvg/ || true rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ |
