diff options
112 files changed, 1094 insertions, 719 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/.gitignore b/.gitignore index 3946cc96e5..32a43b8c63 100644 --- a/.gitignore +++ b/.gitignore @@ -159,6 +159,7 @@ gmon.out # Kate *.kate-swp +.kateproject.build # Kdevelop *.kdev4 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/NativeMenu.xml b/doc/classes/NativeMenu.xml index 475874dee7..2b9e414106 100644 --- a/doc/classes/NativeMenu.xml +++ b/doc/classes/NativeMenu.xml @@ -473,6 +473,14 @@ [b]Note:[/b] This method is implemented on macOS and Windows. </description> </method> + <method name="is_opened" qualifiers="const"> + <return type="bool" /> + <param index="0" name="rid" type="RID" /> + <description> + Returns [code]true[/code] if the menu is currently opened. + [b]Note:[/b] This method is implemented only on macOS. + </description> + </method> <method name="is_system_menu" qualifiers="const"> <return type="bool" /> <param index="0" name="rid" type="RID" /> @@ -699,6 +707,7 @@ <param index="1" name="callback" type="Callable" /> <description> Registers callable to emit when the menu is about to show. + [b]Note:[/b] The OS can simulate menu opening to track menu item changes and global shortcuts, in which case the corresponding close callback is not triggered. Use [method is_opened] to check if the menu is currently opened. [b]Note:[/b] This method is implemented only on macOS. </description> </method> @@ -707,7 +716,7 @@ <param index="0" name="rid" type="RID" /> <param index="1" name="callback" type="Callable" /> <description> - Registers callable to emit when the menu is about to closed. + Registers callable to emit after the menu is closed. [b]Note:[/b] This method is implemented only on macOS. </description> </method> diff --git a/doc/classes/PhysicalSkyMaterial.xml b/doc/classes/PhysicalSkyMaterial.xml index 13f9d93e66..460134e5ba 100644 --- a/doc/classes/PhysicalSkyMaterial.xml +++ b/doc/classes/PhysicalSkyMaterial.xml @@ -6,7 +6,6 @@ <description> The [PhysicalSkyMaterial] uses the Preetham analytic daylight model to draw a sky based on physical properties. This results in a substantially more realistic sky than the [ProceduralSkyMaterial], but it is slightly slower and less flexible. The [PhysicalSkyMaterial] only supports one sun. The color, energy, and direction of the sun are taken from the first [DirectionalLight3D] in the scene tree. - As it is based on a daylight model, the sky fades to black as the sunset ends. If you want a full day/night cycle, you will have to add a night sky by converting this to a [ShaderMaterial] and adding a night sky directly into the resulting shader. </description> <tutorials> </tutorials> 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/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 6cbdf5e935..6a51ce6b70 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -3654,7 +3654,7 @@ void RasterizerSceneGLES3::_render_uv2(const PagedArray<RenderGeometryInstance * glDrawBuffers(draw_buffers.size(), draw_buffers.ptr()); glClearColor(0.0, 0.0, 0.0, 0.0); - RasterizerGLES3::clear_depth(1.0); + RasterizerGLES3::clear_depth(0.0); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); uint64_t base_spec_constant = 0; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 2d497a281f..58dd659e86 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -57,6 +57,9 @@ #include "scene/main/window.h" #include "servers/audio/audio_stream.h" +constexpr double FPS_DECIMAL = 1; +constexpr double SECOND_DECIMAL = 0.0001; + void AnimationTrackKeyEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj); ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed); @@ -1322,7 +1325,7 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) { return; } - p_new_len = MAX(0.0001, p_new_len); + p_new_len = MAX(SECOND_DECIMAL, p_new_len); if (use_fps && animation->get_step() > 0) { p_new_len *= animation->get_step(); } @@ -1442,7 +1445,7 @@ void AnimationTimelineEdit::_notification(int p_what) { float l = animation->get_length(); if (l <= 0) { - l = 0.0001; // Avoid crashor. + l = SECOND_DECIMAL; // Avoid crashor. } Ref<Texture2D> hsize_icon = get_editor_theme_icon(SNAME("Hsize")); @@ -1723,7 +1726,7 @@ void AnimationTimelineEdit::update_values() { editing = true; if (use_fps && animation->get_step() > 0) { length->set_value(animation->get_length() / animation->get_step()); - length->set_step(1); + length->set_step(FPS_DECIMAL); length->set_tooltip_text(TTR("Animation length (frames)")); time_icon->set_tooltip_text(TTR("Animation length (frames)")); if (track_edit) { @@ -1731,7 +1734,7 @@ void AnimationTimelineEdit::update_values() { } } else { length->set_value(animation->get_length()); - length->set_step(0.0001); + length->set_step(SECOND_DECIMAL); length->set_tooltip_text(TTR("Animation length (seconds)")); time_icon->set_tooltip_text(TTR("Animation length (seconds)")); } @@ -1912,9 +1915,9 @@ AnimationTimelineEdit::AnimationTimelineEdit() { time_icon->set_tooltip_text(TTR("Animation length (seconds)")); len_hb->add_child(time_icon); length = memnew(EditorSpinSlider); - length->set_min(0.0001); + length->set_min(SECOND_DECIMAL); length->set_max(36000); - length->set_step(0.0001); + length->set_step(SECOND_DECIMAL); length->set_allow_greater(true); length->set_custom_minimum_size(Vector2(70 * EDSCALE, 0)); length->set_hide_slider(true); @@ -2645,7 +2648,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } if (key_idx != -1) { - String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), 0.0001))) + "\n"; + String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), SECOND_DECIMAL))) + "\n"; switch (animation->track_get_type(track)) { case Animation::TYPE_POSITION_3D: { Vector3 t = animation->track_get_key_value(track, key_idx); @@ -4758,10 +4761,12 @@ void AnimationTrackEditor::_animation_changed() { } void AnimationTrackEditor::_snap_mode_changed(int p_mode) { - timeline->set_use_fps(p_mode == 1); + bool use_fps = p_mode == 1; + timeline->set_use_fps(use_fps); if (key_edit) { - key_edit->set_use_fps(p_mode == 1); + key_edit->set_use_fps(use_fps); } + step->set_step(use_fps ? FPS_DECIMAL : SECOND_DECIMAL); _update_step_spinbox(); } @@ -4775,7 +4780,9 @@ void AnimationTrackEditor::_update_step_spinbox() { if (animation->get_step() == 0) { step->set_value(0); } else { - step->set_value(1.0 / animation->get_step()); + // The value stored within tscn cannot restored the original FPS due to lack of precision, + // so the value should be limited to integer. + step->set_value(Math::round(1.0 / animation->get_step())); } } else { @@ -4783,6 +4790,7 @@ void AnimationTrackEditor::_update_step_spinbox() { } step->set_block_signals(false); + _update_snap_unit(); } void AnimationTrackEditor::_animation_update() { @@ -4876,6 +4884,8 @@ void AnimationTrackEditor::_update_step(double p_new_step) { return; } + _update_snap_unit(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Change Animation Step")); float step_value = p_new_step; @@ -5162,7 +5172,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { p_ofs = snap_time(p_ofs); } while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid. - p_ofs += 0.0001; + p_ofs += SECOND_DECIMAL; } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -7007,25 +7017,31 @@ void AnimationTrackEditor::_selection_changed() { } } +void AnimationTrackEditor::_update_snap_unit() { + if (step->get_value() <= 0) { + snap_unit = 0; + return; // Avoid zero div. + } + + if (timeline->is_using_fps()) { + snap_unit = 1.0 / step->get_value(); + } else { + snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor. + } +} + float AnimationTrackEditor::snap_time(float p_value, bool p_relative) { if (is_snap_enabled()) { - double snap_increment; - if (timeline->is_using_fps() && step->get_value() > 0) { - snap_increment = 1.0 / step->get_value(); - } else { - snap_increment = step->get_value(); - } - if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { // Use more precise snapping when holding Shift. - snap_increment *= 0.25; + snap_unit *= 0.25; } if (p_relative) { - double rel = Math::fmod(timeline->get_value(), snap_increment); - p_value = Math::snapped(p_value + rel, snap_increment) - rel; + double rel = Math::fmod(timeline->get_value(), snap_unit); + p_value = Math::snapped(p_value + rel, snap_unit) - rel; } else { - p_value = Math::snapped(p_value, snap_increment); + p_value = Math::snapped(p_value, snap_unit); } } @@ -7282,7 +7298,7 @@ AnimationTrackEditor::AnimationTrackEditor() { step = memnew(EditorSpinSlider); step->set_min(0); step->set_max(1000000); - step->set_step(0.0001); + step->set_step(SECOND_DECIMAL); step->set_hide_slider(true); step->set_custom_minimum_size(Size2(100, 0) * EDSCALE); step->set_tooltip_text(TTR("Animation step value.")); @@ -7524,9 +7540,9 @@ AnimationTrackEditor::AnimationTrackEditor() { ease_selection->select(Tween::EASE_IN_OUT); // Default ease_selection->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Translation context is needed. ease_fps = memnew(SpinBox); - ease_fps->set_min(1); + ease_fps->set_min(FPS_DECIMAL); ease_fps->set_max(999); - ease_fps->set_step(1); + ease_fps->set_step(FPS_DECIMAL); ease_fps->set_value(30); // Default ease_grid->add_child(memnew(Label(TTR("Transition Type:")))); ease_grid->add_child(transition_selection); @@ -7550,9 +7566,9 @@ AnimationTrackEditor::AnimationTrackEditor() { bake_value = memnew(CheckBox); bake_value->set_pressed(true); bake_fps = memnew(SpinBox); - bake_fps->set_min(1); + bake_fps->set_min(FPS_DECIMAL); bake_fps->set_max(999); - bake_fps->set_step(1); + bake_fps->set_step(FPS_DECIMAL); bake_fps->set_value(30); // Default bake_grid->add_child(memnew(Label(TTR("3D Pos/Rot/Scl Track:")))); bake_grid->add_child(bake_trs); @@ -7671,15 +7687,14 @@ AnimationTrackKeyEditEditor::AnimationTrackKeyEditEditor(Ref<Animation> p_animat spinner->set_allow_lesser(true); if (use_fps) { - spinner->set_step(1); - spinner->set_hide_slider(true); + spinner->set_step(FPS_DECIMAL); real_t fps = animation->get_step(); if (fps > 0) { fps = 1.0 / fps; } spinner->set_value(key_ofs * fps); } else { - spinner->set_step(0.0001); + spinner->set_step(SECOND_DECIMAL); spinner->set_value(key_ofs); spinner->set_max(animation->get_length()); } diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index f449b51b81..09f751c8dd 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -649,6 +649,9 @@ class AnimationTrackEditor : public VBoxContainer { void _pick_track_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates); void _pick_track_filter_input(const Ref<InputEvent> &p_ie); + double snap_unit; + void _update_snap_unit(); + protected: static void _bind_methods(); void _notification(int p_what); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 1145a10f71..cede2c0ab6 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -294,6 +294,13 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me } for (const MethodInfo &mi : p_methods) { + if (mi.name.begins_with("@")) { + // GH-92782. GDScript inline setters/getters are historically present in `get_method_list()` + // and can be called using `Object.call()`. However, these functions are meant to be internal + // and their names are not valid identifiers, so let's hide them from the user. + continue; + } + if (!p_search_string.is_empty() && !mi.name.containsn(p_search_string)) { continue; } @@ -324,8 +331,10 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me continue; } } + ret.push_back(mi); } + return ret; } 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_inspector.cpp b/editor/editor_inspector.cpp index 80e2302e91..33f460e23d 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -652,6 +652,19 @@ void EditorProperty::add_focusable(Control *p_control) { focusables.push_back(p_control); } +void EditorProperty::grab_focus(int p_focusable) { + if (focusables.is_empty()) { + return; + } + + if (p_focusable >= 0) { + ERR_FAIL_INDEX(p_focusable, focusables.size()); + focusables[p_focusable]->grab_focus(); + } else { + focusables[0]->grab_focus(); + } +} + void EditorProperty::select(int p_focusable) { bool already_selected = selected; if (!selectable) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index a0ced55bd8..f9b0d1f094 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -192,6 +192,7 @@ public: void set_deletable(bool p_enable); bool is_deletable() const; void add_focusable(Control *p_control); + void grab_focus(int p_focusable = -1); void select(int p_focusable = -1); void deselect(); bool is_selected() const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ebdad467ff..45d276dd79 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3706,12 +3706,15 @@ void EditorNode::_remove_scene(int index, bool p_change_tab) { } void EditorNode::set_edited_scene(Node *p_scene) { + set_edited_scene_root(p_scene, true); +} + +void EditorNode::set_edited_scene_root(Node *p_scene, bool p_auto_add) { Node *old_edited_scene_root = get_editor_data().get_edited_scene_root(); - if (old_edited_scene_root) { - if (old_edited_scene_root->get_parent() == scene_root) { - scene_root->remove_child(old_edited_scene_root); - } - old_edited_scene_root->disconnect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); + ERR_FAIL_COND_MSG(p_scene && p_scene != old_edited_scene_root && p_scene->get_parent(), "Non-null nodes that are set as edited scene should not have a parent node."); + + if (p_auto_add && old_edited_scene_root && old_edited_scene_root->get_parent() == scene_root) { + scene_root->remove_child(old_edited_scene_root); } get_editor_data().set_edited_scene_root(p_scene); @@ -3723,11 +3726,8 @@ void EditorNode::set_edited_scene(Node *p_scene) { get_tree()->set_edited_scene_root(p_scene); } - if (p_scene) { - if (p_scene->get_parent() != scene_root) { - scene_root->add_child(p_scene, true); - } - p_scene->connect(SNAME("replacing_by"), callable_mp(this, &EditorNode::set_edited_scene)); + if (p_auto_add && p_scene) { + scene_root->add_child(p_scene, true); } } @@ -5967,6 +5967,8 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins instantiated_node->set_scene_file_path(String()); } current_edited_scene = instantiated_node; + + editor_data.set_edited_scene_root(current_edited_scene); } // Replace the original node with the instantiated version. diff --git a/editor/editor_node.h b/editor/editor_node.h index 5d7bd5b4f8..899da99450 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -783,6 +783,7 @@ public: SubViewport *get_scene_root() { return scene_root; } // Root of the scene being edited. void set_edited_scene(Node *p_scene); + void set_edited_scene_root(Node *p_scene, bool p_auto_add); Node *get_edited_scene() { return editor_data.get_edited_scene_root(); } void fix_dependencies(const String &p_for_file); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 555165c156..53a10a779f 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -255,6 +255,10 @@ void EditorPropertyArray::_change_type_menu(int p_index) { return; } + ERR_FAIL_COND_MSG( + changing_type_index == EditorPropertyArrayObject::NOT_CHANGING_TYPE, + "Tried to change type of an array item, but no item was selected."); + Variant value; VariantInternal::initialize(&value, Variant::Type(p_index)); @@ -444,6 +448,10 @@ void EditorPropertyArray::update_property() { slot.prop = new_prop; slot.set_index(idx); } + if (slot.index == changing_type_index) { + callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0); + changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE; + } slot.prop->update_property(); } @@ -921,6 +929,10 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { } void EditorPropertyDictionary::_change_type_menu(int p_index) { + ERR_FAIL_COND_MSG( + changing_type_index == EditorPropertyDictionaryObject::NOT_CHANGING_TYPE, + "Tried to change the type of a dict key or value, but nothing was selected."); + Variant value; switch (changing_type_index) { case EditorPropertyDictionaryObject::NEW_KEY_INDEX: @@ -1062,6 +1074,14 @@ void EditorPropertyDictionary::update_property() { new_prop->set_read_only(is_read_only()); slot.set_prop(new_prop); } + + // We need to grab the focus of the property that is being changed, even if the type didn't actually changed. + // Otherwise, focus will stay on the change type button, which is not very user friendly. + if (changing_type_index == slot.index) { + callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0); + changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again. + } + slot.prop->update_property(); } updating = false; diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 8b939ab0b0..267cb1e86d 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -49,6 +49,10 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; public: + enum { + NOT_CHANGING_TYPE = -1, + }; + void set_array(const Variant &p_array); Variant get_array(); @@ -68,7 +72,8 @@ protected: public: enum { - NEW_KEY_INDEX = -2, + NOT_CHANGING_TYPE = -3, + NEW_KEY_INDEX, NEW_VALUE_INDEX, }; @@ -111,7 +116,7 @@ class EditorPropertyArray : public EditorProperty { int page_length = 20; int page_index = 0; - int changing_type_index; + int changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE; Button *edit = nullptr; PanelContainer *container = nullptr; VBoxContainer *property_vbox = nullptr; @@ -206,7 +211,7 @@ class EditorPropertyDictionary : public EditorProperty { Ref<EditorPropertyDictionaryObject> object; int page_length = 20; int page_index = 0; - int changing_type_index; + int changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; Button *edit = nullptr; PanelContainer *container = nullptr; VBoxContainer *property_vbox = nullptr; diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index dd698d74b6..742d29fef0 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -138,7 +138,6 @@ void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, con } Item item; - item.order = order++; item.preview = p_texture; item.small_preview = p_small_texture; item.last_hash = p_hash; @@ -412,7 +411,6 @@ void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p String path_id = "ID:" + itos(p_res->get_instance_id()); if (cache.has(path_id) && cache[path_id].last_hash == p_res->hash_edited_version_for_preview()) { - cache[path_id].order = order++; p_receiver->call(p_receiver_func, path_id, cache[path_id].preview, cache[path_id].small_preview, p_userdata); return; } @@ -439,7 +437,6 @@ void EditorResourcePreview::queue_resource_preview(const String &p_path, Object MutexLock lock(preview_mutex); if (cache.has(p_path)) { - cache[p_path].order = order++; p_receiver->call(p_receiver_func, p_path, cache[p_path].preview, cache[p_path].small_preview, p_userdata); return; } @@ -533,7 +530,6 @@ void EditorResourcePreview::stop() { EditorResourcePreview::EditorResourcePreview() { singleton = this; - order = 0; } EditorResourcePreview::~EditorResourcePreview() { diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index 6b67acceaa..2870f9a201 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -99,13 +99,10 @@ class EditorResourcePreview : public Node { Ref<Texture2D> preview; Ref<Texture2D> small_preview; Dictionary preview_metadata; - int order = 0; uint32_t last_hash = 0; uint64_t modified_time = 0; }; - int order; - HashMap<String, Item> cache; void _preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata); diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index 2622645d7c..4862b3436e 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -405,7 +405,7 @@ EditorSceneTabs::EditorSceneTabs() { scene_tabs->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorSceneTabs::_scene_tab_exit)); scene_tabs->connect(SceneStringName(gui_input), callable_mp(this, &EditorSceneTabs::_scene_tab_input)); scene_tabs->connect("active_tab_rearranged", callable_mp(this, &EditorSceneTabs::_reposition_active_tab)); - scene_tabs->connect(SceneStringName(resized), callable_mp(this, &EditorSceneTabs::_scene_tabs_resized)); + scene_tabs->connect(SceneStringName(resized), callable_mp(this, &EditorSceneTabs::_scene_tabs_resized), CONNECT_DEFERRED); scene_tabs_context_menu = memnew(PopupMenu); tabbar_container->add_child(scene_tabs_context_menu); 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/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index fe56f48889..0a2c192ea4 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -243,12 +243,12 @@ void AnimationPlayerEditor::_play_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); + double time = player->get_current_animation_position(); if (current == player->get_assigned_animation() && player->is_playing()) { - player->stop(); //so it won't blend with itself + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play(current); } @@ -281,12 +281,12 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { String current = _get_current(); if (!current.is_empty()) { - float time = player->get_current_animation_position(); - if (current == player->get_assigned_animation()) { - player->stop(); //so it won't blend with itself + double time = player->get_current_animation_position(); + if (current == player->get_assigned_animation() && player->is_playing()) { + player->clear_caches(); //so it won't blend with itself } ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); - player->seek(time); + player->seek(time, true, true); player->play_backwards(current); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 202817f6ee..e4eaab7325 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -922,7 +922,7 @@ void vertex() { VERTEX = VERTEX; POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); - POSITION.z = mix(POSITION.z, 0.0, 0.999); + POSITION.z = mix(POSITION.z, POSITION.w, 0.999); POINT_SIZE = point_size; } @@ -1201,7 +1201,7 @@ void vertex() { } VERTEX = VERTEX; POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); - POSITION.z = mix(POSITION.z, 0, 0.998); + POSITION.z = mix(POSITION.z, POSITION.w, 0.998); } void fragment() { ALBEDO = COLOR.rgb; diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index cf1ad36adc..6a6e2b83ab 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -743,20 +743,24 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { localization_editor->connect("localization_changed", callable_mp(this, &ProjectSettingsEditor::queue_save)); tab_container->add_child(localization_editor); + TabContainer *globals_container = memnew(TabContainer); + globals_container->set_name(TTR("Globals")); + tab_container->add_child(globals_container); + autoload_settings = memnew(EditorAutoloadSettings); autoload_settings->set_name(TTR("Autoload")); autoload_settings->connect("autoload_changed", callable_mp(this, &ProjectSettingsEditor::queue_save)); - tab_container->add_child(autoload_settings); + globals_container->add_child(autoload_settings); shaders_global_shader_uniforms_editor = memnew(ShaderGlobalsEditor); shaders_global_shader_uniforms_editor->set_name(TTR("Shader Globals")); shaders_global_shader_uniforms_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::queue_save)); - tab_container->add_child(shaders_global_shader_uniforms_editor); + globals_container->add_child(shaders_global_shader_uniforms_editor); group_settings = memnew(GroupSettingsEditor); - group_settings->set_name(TTR("Global Groups")); + group_settings->set_name(TTR("Groups")); group_settings->connect("group_changed", callable_mp(this, &ProjectSettingsEditor::queue_save)); - tab_container->add_child(group_settings); + globals_container->add_child(group_settings); plugin_settings = memnew(EditorPluginSettings); plugin_settings->set_name(TTR("Plugins")); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index d123d8ef59..a5157bd394 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -225,11 +225,24 @@ void PropertySelector::_update_search() { } else { Ref<Script> script_ref = Object::cast_to<Script>(ObjectDB::get_instance(script)); if (script_ref.is_valid()) { - methods.push_back(MethodInfo("*Script Methods")); if (script_ref->is_built_in()) { script_ref->reload(true); } - script_ref->get_script_method_list(&methods); + + List<MethodInfo> script_methods; + script_ref->get_script_method_list(&script_methods); + + methods.push_back(MethodInfo("*Script Methods")); // TODO: Split by inheritance. + + for (const MethodInfo &mi : script_methods) { + if (mi.name.begins_with("@")) { + // GH-92782. GDScript inline setters/getters are historically present in `get_method_list()` + // and can be called using `Object.call()`. However, these functions are meant to be internal + // and their names are not valid identifiers, so let's hide them from the user. + continue; + } + methods.push_back(mi); + } } StringName base = base_type; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 2f57dc5610..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."); @@ -2992,6 +3013,10 @@ void SceneTreeDock::_replace_node(Node *p_node, Node *p_by_node, bool p_keep_pro to_erase.push_back(oldnode->get_child(i)); } } + + if (oldnode == edited_scene) { + EditorNode::get_singleton()->set_edited_scene_root(newnode, false); + } oldnode->replace_by(newnode, true); // Re-apply size of anchored control. @@ -3396,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 @@ -3415,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/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs index 04b6c2e743..6b5d7fed78 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -143,7 +143,7 @@ namespace Godot.NativeInterop if (error.Error != godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_OK) { using godot_variant instanceVariant = VariantUtils.CreateFromGodotObjectPtr(instance); - string where = GetCallErrorWhere(method, &instanceVariant, args, argCount); + string where = GetCallErrorWhere(ref error, method, &instanceVariant, args, argCount); string errorText = GetCallErrorMessage(error, where, args); GD.PushError(errorText); } @@ -161,7 +161,7 @@ namespace Godot.NativeInterop } } - private unsafe static string GetCallErrorWhere(godot_string_name method, godot_variant* instance, godot_variant** args, int argCount) + private unsafe static string GetCallErrorWhere(ref godot_variant_call_error error, godot_string_name method, godot_variant* instance, godot_variant** args, int argCount) { string? methodstr = null; string basestr = GetVariantTypeName(instance); @@ -171,6 +171,10 @@ namespace Godot.NativeInterop if (argCount >= 1) { methodstr = VariantUtils.ConvertToString(*args[0]); + if (error.Error == godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT) + { + error.Argument += 1; + } } } 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/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index dfbc92a919..1779be2cc2 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -617,7 +617,7 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); Vector3 inters; if (f.intersects_segment(p_from, p_to, &inters)) { - const real_t d = closest_point_d = p_from.distance_to(inters); + const real_t d = p_from.distance_to(inters); if (use_collision == false) { closest_point = inters; use_collision = true; diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index b8a2f58935..d9a66aa827 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -293,7 +293,12 @@ void OpenXRHandTrackingExtension::on_process() { } godot_tracker->set_hand_tracking_source(source); - godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) { + godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); + } else { + godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); + } } } } else { diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 01da0c7935..087fd1bace 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -219,19 +219,12 @@ public: } void add_signal(const StringName &p_name, const Vector<Variant::Type> &p_args) { - if (p_args.size() == 0) { - ADD_SIGNAL(MethodInfo(p_name)); - } else if (p_args.size() == 1) { - ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"))); - } else if (p_args.size() == 2) { - ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"))); - } else if (p_args.size() == 3) { - ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"))); - } else if (p_args.size() == 4) { - ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"))); - } else if (p_args.size() == 5) { - ADD_SIGNAL(MethodInfo(p_name, PropertyInfo(p_args[0], "arg1"), PropertyInfo(p_args[1], "arg2"), PropertyInfo(p_args[2], "arg3"), PropertyInfo(p_args[3], "arg4"), PropertyInfo(p_args[4], "arg5"))); + MethodInfo mi; + mi.name = p_name; + for (int i = 0; i < p_args.size(); i++) { + mi.arguments.push_back(PropertyInfo(p_args[i], "arg" + itos(i + 1))); } + ADD_SIGNAL(mi); } #endif diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 12d3a6fd2f..aff83ddeee 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1018,8 +1018,7 @@ void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor wayland_thread.cursor_shape_clear_custom_image(p_shape); } - Rect2 atlas_rect; - Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot); ERR_FAIL_COND(image.is_null()); CustomCursor &cursor = custom_cursors[p_shape]; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 34b6d0219a..0e4c723a87 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -3108,8 +3108,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu cursors_cache.erase(p_shape); } - Rect2 atlas_rect; - Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot); ERR_FAIL_COND(image.is_null()); Vector2i texture_size = image->get_size(); @@ -3127,13 +3126,8 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu cursor_image->pixels = (XcursorPixel *)memalloc(size); for (XcursorPixel index = 0; index < image_size; index++) { - int row_index = floor(index / texture_size.width) + atlas_rect.position.y; - int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; - - if (atlas_rect.has_area()) { - column_index = MIN(column_index, atlas_rect.size.width - 1); - row_index = MIN(row_index, atlas_rect.size.height - 1); - } + int row_index = floor(index / texture_size.width); + int column_index = index % int(texture_size.width); *(cursor_image->pixels + index) = image->get_pixel(column_index, row_index).to_argb32(); } @@ -4190,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 50313cfe67..a1a91345ac 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2845,8 +2845,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, cursors_cache.erase(p_shape); } - Rect2 atlas_rect; - Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot); ERR_FAIL_COND(image.is_null()); Vector2i texture_size = image->get_size(); @@ -2868,13 +2867,8 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor, int len = int(texture_size.width * texture_size.height); for (int i = 0; i < len; i++) { - int row_index = floor(i / texture_size.width) + atlas_rect.position.y; - int column_index = (i % int(texture_size.width)) + atlas_rect.position.x; - - if (atlas_rect.has_area()) { - column_index = MIN(column_index, atlas_rect.size.width - 1); - row_index = MIN(row_index, atlas_rect.size.height - 1); - } + int row_index = floor(i / texture_size.width); + int column_index = i % int(texture_size.width); uint32_t color = image->get_pixel(column_index, row_index).to_argb32(); @@ -3172,42 +3166,13 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) { DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) { NSImage *nsimg = nullptr; - if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { Ref<Image> img = p_icon->get_image(); img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:nullptr - pixelsWide:img->get_width() - pixelsHigh:img->get_height() - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:img->get_width() * 4 - bitsPerPixel:32]; - if (imgrep) { - uint8_t *pixels = [imgrep bitmapData]; - - int len = img->get_width() * img->get_height(); - const uint8_t *r = img->get_data().ptr(); - - /* Premultiply the alpha channel */ - for (int i = 0; i < len; i++) { - uint8_t alpha = r[i * 4 + 3]; - pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); - pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); - pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); - pixels[i * 4 + 3] = alpha; - } - - nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; - if (nsimg) { - [nsimg addRepresentation:imgrep]; - } + if (img->is_compressed()) { + img->decompress(); } + nsimg = _convert_to_nsimg(img); } IndicatorData idat; @@ -3235,42 +3200,13 @@ void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<T ERR_FAIL_COND(!indicators.has(p_id)); NSImage *nsimg = nullptr; - if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { Ref<Image> img = p_icon->get_image(); img = img->duplicate(); - img->convert(Image::FORMAT_RGBA8); - - NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:nullptr - pixelsWide:img->get_width() - pixelsHigh:img->get_height() - bitsPerSample:8 - samplesPerPixel:4 - hasAlpha:YES - isPlanar:NO - colorSpaceName:NSDeviceRGBColorSpace - bytesPerRow:img->get_width() * 4 - bitsPerPixel:32]; - if (imgrep) { - uint8_t *pixels = [imgrep bitmapData]; - - int len = img->get_width() * img->get_height(); - const uint8_t *r = img->get_data().ptr(); - - /* Premultiply the alpha channel */ - for (int i = 0; i < len; i++) { - uint8_t alpha = r[i * 4 + 3]; - pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); - pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); - pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); - pixels[i * 4 + 3] = alpha; - } - - nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; - if (nsimg) { - [nsimg addRepresentation:imgrep]; - } + if (img->is_compressed()) { + img->decompress(); } + nsimg = _convert_to_nsimg(img); } NSStatusItem *item = indicators[p_id].item; @@ -3430,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/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm index 5c1e849715..3f7dfac3de 100644 --- a/platform/macos/godot_menu_delegate.mm +++ b/platform/macos/godot_menu_delegate.mm @@ -40,13 +40,20 @@ - (void)doNothing:(id)sender { } -- (void)menuNeedsUpdate:(NSMenu *)menu { +- (void)menuWillOpen:(NSMenu *)menu { if (NativeMenu::get_singleton()) { NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); nmenu->_menu_open(menu); } } +- (void)menuNeedsUpdate:(NSMenu *)menu { + if (NativeMenu::get_singleton()) { + NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); + nmenu->_menu_need_update(menu); + } +} + - (void)menuDidClose:(NSMenu *)menu { if (NativeMenu::get_singleton()) { NativeMenuMacOS *nmenu = (NativeMenuMacOS *)NativeMenu::get_singleton(); diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index b5dbb8b9b0..42cf6740d9 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -73,8 +73,11 @@ class NativeMenuMacOS : public NativeMenu { public: void _register_system_menus(NSMenu *p_main_menu, NSMenu *p_application_menu, NSMenu *p_window_menu, NSMenu *p_help_menu, NSMenu *p_dock_menu); NSMenu *_get_dock_menu(); + + void _menu_need_update(NSMenu *p_menu); void _menu_open(NSMenu *p_menu); void _menu_close(NSMenu *p_menu); + void _menu_close_cb(const RID &p_rid); virtual bool has_feature(Feature p_feature) const override; @@ -98,6 +101,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width) override; virtual float get_minimum_width(const RID &p_rid) const override; + virtual bool is_opened(const RID &p_rid) const override; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override; virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 1cf13a2d69..1ae1137ca0 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -91,11 +91,22 @@ void NativeMenuMacOS::_menu_open(NSMenu *p_menu) { if (menu_lookup.has(p_menu)) { MenuData *md = menus.get_or_null(menu_lookup[p_menu]); if (md) { + // Note: Set "is_open" flag, but do not call callback, menu items can't be modified during this call and "_menu_need_update" will be called right before it. md->is_open = true; + } + } +} + +void NativeMenuMacOS::_menu_need_update(NSMenu *p_menu) { + if (menu_lookup.has(p_menu)) { + MenuData *md = menus.get_or_null(menu_lookup[p_menu]); + if (md) { + // Note: "is_open" flag is set by "_menu_open", this method is always called before menu is shown, but might be called for the other reasons as well. if (md->open_cb.is_valid()) { Variant ret; Callable::CallError ce; + // Callback is called directly, since it's expected to modify menu items before it's shown. md->open_cb.callp(nullptr, 0, ret, ce); if (ce.error != Callable::CallError::CALL_OK) { ERR_PRINT(vformat("Failed to execute menu open callback: %s.", Variant::get_callable_error_text(md->open_cb, nullptr, 0, ce))); @@ -110,15 +121,22 @@ void NativeMenuMacOS::_menu_close(NSMenu *p_menu) { MenuData *md = menus.get_or_null(menu_lookup[p_menu]); if (md) { md->is_open = false; - if (md->close_cb.is_valid()) { - Variant ret; - Callable::CallError ce; - md->close_cb.callp(nullptr, 0, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce))); - } - } + // Callback called deferred, since it should not modify menu items during "_menu_close" call. + callable_mp(this, &NativeMenuMacOS::_menu_close_cb).call_deferred(menu_lookup[p_menu]); + } + } +} + +void NativeMenuMacOS::_menu_close_cb(const RID &p_rid) { + MenuData *md = menus.get_or_null(p_rid); + if (md->close_cb.is_valid()) { + Variant ret; + Callable::CallError ce; + + md->close_cb.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute menu close callback: %s.", Variant::get_callable_error_text(md->close_cb, nullptr, 0, ce))); } } } @@ -328,6 +346,13 @@ float NativeMenuMacOS::get_minimum_width(const RID &p_rid) const { return md->menu.minimumWidth * DisplayServer::get_singleton()->screen_get_max_scale(); } +bool NativeMenuMacOS::is_opened(const RID &p_rid) const { + const MenuData *md = menus.get_or_null(p_rid); + ERR_FAIL_NULL_V(md, false); + + return md->is_open; +} + int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) { MenuData *md = menus.get_or_null(p_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid); @@ -436,7 +461,7 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico obj->max_states = 0; obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); - if (ds && p_icon.is_valid()) { + if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); obj->img = obj->img->duplicate(); if (obj->img->is_compressed()) { @@ -467,7 +492,7 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D> obj->max_states = 0; obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); - if (ds && p_icon.is_valid()) { + if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); obj->img = obj->img->duplicate(); if (obj->img->is_compressed()) { @@ -518,7 +543,7 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu obj->max_states = 0; obj->state = 0; DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); - if (ds && p_icon.is_valid()) { + if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); obj->img = obj->img->duplicate(); if (obj->img->is_compressed()) { @@ -1212,7 +1237,7 @@ void NativeMenuMacOS::set_item_icon(const RID &p_rid, int p_idx, const Ref<Textu GodotMenuItem *obj = [menu_item representedObject]; ERR_FAIL_NULL(obj); DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); - if (ds && p_icon.is_valid()) { + if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { obj->img = p_icon->get_image(); obj->img = obj->img->duplicate(); if (obj->img->is_compressed()) { diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index fab92b1894..40de4e523b 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -517,19 +517,10 @@ DisplayServer::CursorShape DisplayServerWeb::cursor_get_shape() const { void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); if (p_cursor.is_valid()) { - Rect2 atlas_rect; - Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot); ERR_FAIL_COND(image.is_null()); Vector2i texture_size = image->get_size(); - if (atlas_rect.has_area()) { - image->crop_from_point( - atlas_rect.position.x, - atlas_rect.position.y, - texture_size.width, - texture_size.height); - } - if (image->get_format() != Image::FORMAT_RGBA8) { image->convert(Image::FORMAT_RGBA8); } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index c114adff84..10cec9f5ed 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2408,8 +2408,7 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor cursors_cache.erase(p_shape); } - Rect2 atlas_rect; - Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect); + Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot); ERR_FAIL_COND(image.is_null()); Vector2i texture_size = image->get_size(); @@ -2437,13 +2436,9 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor bool fully_transparent = true; for (UINT index = 0; index < image_size; index++) { - int row_index = floor(index / texture_size.width) + atlas_rect.position.y; - int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; + int row_index = floor(index / texture_size.width); + int column_index = index % int(texture_size.width); - if (atlas_rect.has_area()) { - column_index = MIN(column_index, atlas_rect.size.width - 1); - row_index = MIN(row_index, atlas_rect.size.height - 1); - } const Color &c = image->get_pixel(column_index, row_index); fully_transparent = fully_transparent && (c.a == 0.f); @@ -3171,9 +3166,12 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Texture2D> &p_icon, const String &p_tooltip, const Callable &p_callback) { HICON hicon = nullptr; - if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { Ref<Image> img = p_icon->get_image(); img = img->duplicate(); + if (img->is_compressed()) { + img->decompress(); + } img->convert(Image::FORMAT_RGBA8); int w = img->get_width(); @@ -3241,9 +3239,12 @@ void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref ERR_FAIL_COND(!indicators.has(p_id)); HICON hicon = nullptr; - if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { Ref<Image> img = p_icon->get_image(); img = img->duplicate(); + if (img->is_compressed()) { + img->decompress(); + } img->convert(Image::FORMAT_RGBA8); int w = img->get_width(); @@ -3574,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(); @@ -4727,6 +4731,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_EXITSIZEMOVE: { KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id); + windows[window_id].move_timer_id = 0; } break; case WM_TIMER: { if (wParam == windows[window_id].move_timer_id) { diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp index 40a08f87df..d9dc28e9d9 100644 --- a/platform/windows/native_menu_windows.cpp +++ b/platform/windows/native_menu_windows.cpp @@ -35,6 +35,8 @@ #include "scene/resources/image_texture.h" HBITMAP NativeMenuWindows::_make_bitmap(const Ref<Image> &p_img) const { + p_img->convert(Image::FORMAT_RGBA8); + Vector2i texture_size = p_img->get_size(); UINT image_size = texture_size.width * texture_size.height; @@ -231,6 +233,11 @@ float NativeMenuWindows::get_minimum_width(const RID &p_rid) const { return 0.f; } +bool NativeMenuWindows::is_opened(const RID &p_rid) const { + // Not supported. + return false; +} + int NativeMenuWindows::add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag, int p_index) { MenuData *md = menus.get_or_null(p_rid); MenuData *md_sub = menus.get_or_null(p_submenu_rid); @@ -349,12 +356,14 @@ int NativeMenuWindows::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_i item_data->checkable_type = CHECKABLE_TYPE_NONE; item_data->max_states = 0; item_data->state = 0; - item_data->img = p_icon->get_image(); - item_data->img = item_data->img->duplicate(); - if (item_data->img->is_compressed()) { - item_data->img->decompress(); + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { + item_data->img = p_icon->get_image(); + item_data->img = item_data->img->duplicate(); + if (item_data->img->is_compressed()) { + item_data->img->decompress(); + } + item_data->bmp = _make_bitmap(item_data->img); } - item_data->bmp = _make_bitmap(item_data->img); Char16String label = p_label.utf16(); MENUITEMINFOW item; @@ -389,12 +398,14 @@ int NativeMenuWindows::add_icon_check_item(const RID &p_rid, const Ref<Texture2D item_data->checkable_type = CHECKABLE_TYPE_CHECK_BOX; item_data->max_states = 0; item_data->state = 0; - item_data->img = p_icon->get_image(); - item_data->img = item_data->img->duplicate(); - if (item_data->img->is_compressed()) { - item_data->img->decompress(); + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { + item_data->img = p_icon->get_image(); + item_data->img = item_data->img->duplicate(); + if (item_data->img->is_compressed()) { + item_data->img->decompress(); + } + item_data->bmp = _make_bitmap(item_data->img); } - item_data->bmp = _make_bitmap(item_data->img); Char16String label = p_label.utf16(); MENUITEMINFOW item; @@ -462,12 +473,14 @@ int NativeMenuWindows::add_icon_radio_check_item(const RID &p_rid, const Ref<Tex item_data->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; item_data->max_states = 0; item_data->state = 0; - item_data->img = p_icon->get_image(); - item_data->img = item_data->img->duplicate(); - if (item_data->img->is_compressed()) { - item_data->img->decompress(); + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { + item_data->img = p_icon->get_image(); + item_data->img = item_data->img->duplicate(); + if (item_data->img->is_compressed()) { + item_data->img->decompress(); + } + item_data->bmp = _make_bitmap(item_data->img); } - item_data->bmp = _make_bitmap(item_data->img); Char16String label = p_label.utf16(); MENUITEMINFOW item; @@ -1082,7 +1095,7 @@ void NativeMenuWindows::set_item_icon(const RID &p_rid, int p_idx, const Ref<Tex if (item_data->bmp) { DeleteObject(item_data->bmp); } - if (p_icon.is_valid()) { + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) { item_data->img = p_icon->get_image(); item_data->img = item_data->img->duplicate(); if (item_data->img->is_compressed()) { diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 74fd231903..5c4aaa52c8 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -90,6 +90,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width) override; virtual float get_minimum_width(const RID &p_rid) const override; + virtual bool is_opened(const RID &p_rid) const override; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1) override; virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; 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/box_container.cpp b/scene/gui/box_container.cpp index d8fcbbb883..b7db730b49 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -243,8 +243,8 @@ Size2 BoxContainer::get_minimum_size() const { bool first = true; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible() || c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); + if (!c) { continue; } diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index f1faf3e899..c328022d4f 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -139,9 +139,15 @@ void Container::queue_sort() { pending_sort = true; } -Control *Container::as_sortable_control(Node *p_node) const { +Control *Container::as_sortable_control(Node *p_node, SortableVisbilityMode p_visibility_mode) const { Control *c = Object::cast_to<Control>(p_node); - if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { + if (!c || c->is_set_as_top_level()) { + return nullptr; + } + if (p_visibility_mode == SortableVisbilityMode::VISIBLE && !c->is_visible()) { + return nullptr; + } + if (p_visibility_mode == SortableVisbilityMode::VISIBLE_IN_TREE && !c->is_visible_in_tree()) { return nullptr; } return c; @@ -177,13 +183,9 @@ Vector<int> Container::get_allowed_size_flags_vertical() const { void Container::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - pending_sort = false; - queue_sort(); - } break; - case NOTIFICATION_RESIZED: - case NOTIFICATION_THEME_CHANGED: { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { queue_sort(); } break; diff --git a/scene/gui/container.h b/scene/gui/container.h index 405220cee6..0561d0d219 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -41,8 +41,14 @@ class Container : public Control { void _child_minsize_changed(); protected: + enum class SortableVisbilityMode { + VISIBLE, + VISIBLE_IN_TREE, + IGNORE, + }; + void queue_sort(); - Control *as_sortable_control(Node *p_node) const; + Control *as_sortable_control(Node *p_node, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const; virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 0c146ce173..a23ee6db71 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -99,7 +99,9 @@ void FileDialog::set_visible(bool p_visible) { #endif if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { - _native_popup(); + if (p_visible) { + _native_popup(); + } } else { ConfirmationDialog::set_visible(p_visible); } diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp index e231b05d7f..b63ed8d1ad 100644 --- a/scene/gui/graph_element.cpp +++ b/scene/gui/graph_element.cpp @@ -60,8 +60,8 @@ void GraphElement::_resort() { Size2 GraphElement::get_minimum_size() const { Size2 minsize; for (int i = 0; i < get_child_count(); i++) { - Control *child = Object::cast_to<Control>(get_child(i)); - if (!child || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i), SortableVisbilityMode::IGNORE); + if (!child) { continue; } diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index d804f83e1c..72e59bfc8a 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -130,8 +130,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { int idx = 0; for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - if (!child || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false), SortableVisbilityMode::IGNORE); + if (!child) { continue; } @@ -658,8 +658,8 @@ void GraphNode::_port_pos_update() { int slot_index = 0; for (int i = 0; i < get_child_count(false); i++) { - Control *child = Object::cast_to<Control>(get_child(i, false)); - if (!child || child->is_set_as_top_level()) { + Control *child = as_sortable_control(get_child(i, false), SortableVisbilityMode::IGNORE); + if (!child) { continue; } diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 06e4a7cc13..a47b131708 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -36,8 +36,8 @@ Size2 MarginContainer::get_minimum_size() const { Size2 max; for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible() || c->is_set_as_top_level()) { + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); + if (!c) { continue; } diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index 76fde26b26..2c39e148a0 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -35,7 +35,7 @@ Size2 PanelContainer::get_minimum_size() const { Size2 ms; for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 1f4d1dbf52..824bb77694 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -42,7 +42,7 @@ Size2 ScrollContainer::get_minimum_size() const { largest_child_min_size = Size2(); for (int i = 0; i < get_child_count(); i++) { - Control *c = as_sortable_control(get_child(i)); + Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE); if (!c) { continue; } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index d0c3f3d65e..58724cf4e9 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -362,8 +362,8 @@ void TabContainer::_on_mouse_exited() { Vector<Control *> TabContainer::_get_tab_controls() const { Vector<Control *> controls; for (int i = 0; i < get_child_count(); i++) { - Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_set_as_top_level() || control == tab_bar || children_removing.has(control)) { + Control *control = as_sortable_control(get_child(i), SortableVisbilityMode::IGNORE); + if (!control || control == tab_bar || children_removing.has(control)) { continue; } @@ -539,8 +539,8 @@ void TabContainer::add_child_notify(Node *p_child) { return; } - Control *c = Object::cast_to<Control>(p_child); - if (!c || c->is_set_as_top_level()) { + Control *c = as_sortable_control(p_child, SortableVisbilityMode::IGNORE); + if (!c) { return; } c->hide(); @@ -569,8 +569,8 @@ void TabContainer::move_child_notify(Node *p_child) { return; } - Control *c = Object::cast_to<Control>(p_child); - if (c && !c->is_set_as_top_level()) { + Control *c = as_sortable_control(p_child, SortableVisbilityMode::IGNORE); + if (c) { tab_bar->move_tab(c->get_meta("_tab_index"), get_tab_idx_from_control(c)); } @@ -584,8 +584,8 @@ void TabContainer::remove_child_notify(Node *p_child) { return; } - Control *c = Object::cast_to<Control>(p_child); - if (!c || c->is_set_as_top_level()) { + Control *c = as_sortable_control(p_child, SortableVisbilityMode::IGNORE); + if (!c) { return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index a46d77c61e..2b2ea54dde 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -286,6 +286,7 @@ void TextEdit::Text::invalidate_all_lines() { } tab_size_dirty = false; + max_width = -1; _calculate_max_line_width(); } @@ -404,10 +405,12 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { text.resize(text.size() - diff); if (dirty_height) { + line_height = -1; _calculate_line_height(); } if (dirty_width) { + max_width = -1; _calculate_max_line_width(); } } @@ -5174,7 +5177,7 @@ void TextEdit::add_selection_for_next_occurrence() { const String &highlighted_text = get_selected_text(caret); int column = get_selection_from_column(caret) + 1; - int line = get_caret_line(caret); + int line = get_selection_from_line(caret); const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column); @@ -5188,6 +5191,7 @@ void TextEdit::add_selection_for_next_occurrence() { if (new_caret != -1) { select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + _unhide_carets(); adjust_viewport_to_caret(new_caret); merge_overlapping_carets(); } @@ -5211,8 +5215,8 @@ void TextEdit::skip_selection_for_next_occurrence() { // Due to const and &(reference) presence, ternary operator is a way to avoid errors and warnings. const String &searched_text = has_selection(caret) ? get_selected_text(caret) : get_word_under_caret(caret); - int column = (has_selection(caret) ? get_selection_from_column(caret) : get_caret_column(caret)) + 1; - int line = get_caret_line(caret); + int column = get_selection_from_column(caret) + 1; + int line = get_selection_from_line(caret); const Point2i next_occurrence = search(searched_text, SEARCH_MATCH_CASE, line, column); @@ -5220,12 +5224,13 @@ void TextEdit::skip_selection_for_next_occurrence() { return; } - int to_column = (has_selection(caret) ? get_selection_to_column(caret) : get_caret_column(caret)) + 1; + int to_column = get_selection_to_column(caret) + 1; int end = next_occurrence.x + (to_column - column); int new_caret = add_caret(next_occurrence.y, end); if (new_caret != -1) { select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + _unhide_carets(); adjust_viewport_to_caret(new_caret); merge_overlapping_carets(); } diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 38bd7141c2..468d4e3c0f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1430,7 +1430,7 @@ void Viewport::_gui_show_tooltip() { Control *tooltip_owner = nullptr; gui.tooltip_text = _gui_get_tooltip( gui.tooltip_control, - gui.tooltip_control->get_global_transform().xform_inv(gui.last_mouse_pos), + gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos), &tooltip_owner); gui.tooltip_text = gui.tooltip_text.strip_edges(); @@ -1910,7 +1910,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { if (gui.tooltip_popup) { if (gui.tooltip_control) { - String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform().xform_inv(mpos)); + String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform_with_canvas().affine_inverse().xform(mpos)); tooltip = tooltip.strip_edges(); if (tooltip.is_empty() || tooltip != gui.tooltip_text) { diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 1c5f9cc663..39e5b6de33 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1229,14 +1229,12 @@ void Window::_update_viewport_size() { } if (!Math::is_equal_approx(TS->font_get_global_oversampling(), font_oversampling)) { TS->font_set_global_oversampling(font_oversampling); - ci_updated = false; + if (!ci_updated) { + update_canvas_items(); + } } } - if (!ci_updated) { - update_canvas_items(); - } - notification(NOTIFICATION_WM_SIZE_CHANGED); if (embedder) { diff --git a/scene/resources/animation.compat.inc b/scene/resources/animation.compat.inc index d7d8867748..fc2672bb25 100644 --- a/scene/resources/animation.compat.inc +++ b/scene/resources/animation.compat.inc @@ -50,8 +50,8 @@ Variant Animation::_value_track_interpolate_bind_compat_86629(int p_track, doubl return value_track_interpolate(p_track, p_time, false); } -int Animation::_track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode) const { - return track_find_key(p_track, p_time, p_find_mode, false); +int Animation::_track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode) const { + return track_find_key(p_track, p_time, p_find_mode, false, false); } void Animation::_bind_compatibility_methods() { @@ -60,7 +60,7 @@ void Animation::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("scale_track_interpolate", "track_idx", "time_sec"), &Animation::_scale_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("blend_shape_track_interpolate", "track_idx", "time_sec"), &Animation::_blend_shape_track_interpolate_bind_compat_86629); ClassDB::bind_compatibility_method(D_METHOD("value_track_interpolate", "track_idx", "time_sec"), &Animation::_value_track_interpolate_bind_compat_86629); - ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_86661, DEFVAL(FIND_MODE_NEAREST)); + ClassDB::bind_compatibility_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::_track_find_key_bind_compat_92861, DEFVAL(FIND_MODE_NEAREST)); } #endif // DISABLE_DEPRECATED diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index a3bfa987c6..254bd38be7 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1496,7 +1496,7 @@ void Animation::track_remove_key(int p_track, int p_idx) { emit_changed(); } -int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit) const { +int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, bool p_limit, bool p_backward) const { ERR_FAIL_INDEX_V(p_track, tracks.size(), -1); Track *t = tracks[p_track]; @@ -1518,7 +1518,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(tt->positions, p_time, false, p_limit); + int k = _find(tt->positions, p_time, p_backward, p_limit); if (k < 0 || k >= tt->positions.size()) { return -1; } @@ -1545,7 +1545,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(rt->rotations, p_time, false, p_limit); + int k = _find(rt->rotations, p_time, p_backward, p_limit); if (k < 0 || k >= rt->rotations.size()) { return -1; } @@ -1572,7 +1572,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(st->scales, p_time, false, p_limit); + int k = _find(st->scales, p_time, p_backward, p_limit); if (k < 0 || k >= st->scales.size()) { return -1; } @@ -1599,7 +1599,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, return key_index; } - int k = _find(bst->blend_shapes, p_time, false, p_limit); + int k = _find(bst->blend_shapes, p_time, p_backward, p_limit); if (k < 0 || k >= bst->blend_shapes.size()) { return -1; } @@ -1611,7 +1611,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_VALUE: { ValueTrack *vt = static_cast<ValueTrack *>(t); - int k = _find(vt->values, p_time, false, p_limit); + int k = _find(vt->values, p_time, p_backward, p_limit); if (k < 0 || k >= vt->values.size()) { return -1; } @@ -1623,7 +1623,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_METHOD: { MethodTrack *mt = static_cast<MethodTrack *>(t); - int k = _find(mt->methods, p_time, false, p_limit); + int k = _find(mt->methods, p_time, p_backward, p_limit); if (k < 0 || k >= mt->methods.size()) { return -1; } @@ -1635,7 +1635,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_BEZIER: { BezierTrack *bt = static_cast<BezierTrack *>(t); - int k = _find(bt->values, p_time, false, p_limit); + int k = _find(bt->values, p_time, p_backward, p_limit); if (k < 0 || k >= bt->values.size()) { return -1; } @@ -1647,7 +1647,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_AUDIO: { AudioTrack *at = static_cast<AudioTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -1659,7 +1659,7 @@ int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode, } break; case TYPE_ANIMATION: { AnimationTrack *at = static_cast<AnimationTrack *>(t); - int k = _find(at->values, p_time, false, p_limit); + int k = _find(at->values, p_time, p_backward, p_limit); if (k < 0 || k >= at->values.size()) { return -1; } @@ -3836,7 +3836,7 @@ void Animation::_bind_methods() { ClassDB::bind_method(D_METHOD("track_get_key_count", "track_idx"), &Animation::track_get_key_count); ClassDB::bind_method(D_METHOD("track_get_key_value", "track_idx", "key_idx"), &Animation::track_get_key_value); ClassDB::bind_method(D_METHOD("track_get_key_time", "track_idx", "key_idx"), &Animation::track_get_key_time); - ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode", "limit", "backward"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("track_set_interpolation_type", "track_idx", "interpolation"), &Animation::track_set_interpolation_type); ClassDB::bind_method(D_METHOD("track_get_interpolation_type", "track_idx"), &Animation::track_get_interpolation_type); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 604bce497a..e9bfc298a5 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -388,7 +388,7 @@ protected: Vector3 _scale_track_interpolate_bind_compat_86629(int p_track, double p_time) const; float _blend_shape_track_interpolate_bind_compat_86629(int p_track, double p_time) const; Variant _value_track_interpolate_bind_compat_86629(int p_track, double p_time) const; - int _track_find_key_bind_compat_86661(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; + int _track_find_key_bind_compat_92861(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const; static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -423,7 +423,7 @@ public: void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition); void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value); void track_set_key_time(int p_track, int p_key_idx, double p_time); - int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false) const; + int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST, bool p_limit = false, bool p_backward = false) const; void track_remove_key(int p_track, int p_idx); void track_remove_key_at_time(int p_track, double p_time); int track_get_key_count(int p_track) const; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 601e8c52a4..277d568aaf 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -2204,6 +2204,10 @@ Error VisualShader::_write_node(Type type, StringBuilder *p_global_code, StringB Vector3 val = defval; inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); node_code += " vec3 " + inputs[i] + " = " + vformat("vec3(%.5f, %.5f, %.5f);\n", val.x, val.y, val.z); + } else if (defval.get_type() == Variant::VECTOR4) { + Vector4 val = defval; + inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); + node_code += " vec4 " + inputs[i] + " = " + vformat("vec4(%.5f, %.5f, %.5f, %.5f);\n", val.x, val.y, val.z, val.w); } else if (defval.get_type() == Variant::QUATERNION) { Quaternion val = defval; inputs[i] = "n_in" + itos(p_node) + "p" + itos(i); diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index d5394c8af5..cb8719fbef 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -7825,7 +7825,7 @@ bool VisualShaderNodeDistanceFade::has_output_port_preview(int p_port) const { String VisualShaderNodeDistanceFade::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { String code; - code += vformat(" %s = clamp(smoothstep(%s, %s,-VERTEX.z),0.0,1.0);\n", p_output_vars[0], p_input_vars[0], p_input_vars[1]); + code += vformat(" %s = clamp(smoothstep(%s, %s, length(VERTEX)), 0.0, 1.0);\n", p_output_vars[0], p_input_vars[0], p_input_vars[1]); return code; } diff --git a/servers/display/native_menu.cpp b/servers/display/native_menu.cpp index ca46560c7c..c7346637d8 100644 --- a/servers/display/native_menu.cpp +++ b/servers/display/native_menu.cpp @@ -56,6 +56,8 @@ void NativeMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_minimum_width", "rid", "width"), &NativeMenu::set_minimum_width); ClassDB::bind_method(D_METHOD("get_minimum_width", "rid"), &NativeMenu::get_minimum_width); + ClassDB::bind_method(D_METHOD("is_opened", "rid"), &NativeMenu::is_opened); + ClassDB::bind_method(D_METHOD("add_submenu_item", "rid", "label", "submenu_rid", "tag", "index"), &NativeMenu::add_submenu_item, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_item", "rid", "label", "callback", "key_callback", "tag", "accelerator", "index"), &NativeMenu::add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("add_check_item", "rid", "label", "callback", "key_callback", "tag", "accelerator", "index"), &NativeMenu::add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1)); @@ -200,6 +202,11 @@ Callable NativeMenu::get_popup_close_callback(const RID &p_rid) const { return Callable(); } +bool NativeMenu::is_opened(const RID &p_rid) const { + WARN_PRINT("Global menus are not supported on this platform."); + return false; +} + void NativeMenu::set_minimum_width(const RID &p_rid, float p_width) { WARN_PRINT("Global menus are not supported on this platform."); } diff --git a/servers/display/native_menu.h b/servers/display/native_menu.h index 2bc061a216..29d22e03aa 100644 --- a/servers/display/native_menu.h +++ b/servers/display/native_menu.h @@ -90,6 +90,8 @@ public: virtual void set_minimum_width(const RID &p_rid, float p_width); virtual float get_minimum_width(const RID &p_rid) const; + virtual bool is_opened(const RID &p_rid) const; + virtual int add_submenu_item(const RID &p_rid, const String &p_label, const RID &p_submenu_rid, const Variant &p_tag = Variant(), int p_index = -1); virtual int add_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); virtual int add_check_item(const RID &p_rid, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 2150a3038e..b6e0d58af7 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -31,7 +31,6 @@ #include "display_server.h" #include "core/input/input.h" -#include "scene/resources/atlas_texture.h" #include "scene/resources/texture.h" #include "servers/display_server_headless.h" @@ -1124,35 +1123,22 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(TTS_UTTERANCE_BOUNDARY); } -Ref<Image> DisplayServer::_get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect) { +Ref<Image> DisplayServer::_get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot) { Ref<Image> image; ERR_FAIL_COND_V_MSG(p_hotspot.x < 0 || p_hotspot.y < 0, image, "Hotspot outside cursor image."); - Size2 texture_size; - Ref<Texture2D> texture = p_cursor; if (texture.is_valid()) { - Ref<AtlasTexture> atlas_texture = p_cursor; - - if (atlas_texture.is_valid()) { - texture = atlas_texture->get_atlas(); - r_atlas_rect.size = texture->get_size(); - r_atlas_rect.position = atlas_texture->get_region().position; - texture_size = atlas_texture->get_region().size; - } else { - texture_size = texture->get_size(); - } image = texture->get_image(); } else { image = p_cursor; - ERR_FAIL_COND_V(image.is_null(), image); - texture_size = image->get_size(); } + ERR_FAIL_COND_V(image.is_null(), image); - ERR_FAIL_COND_V_MSG(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height, image, "Hotspot outside cursor image."); - ERR_FAIL_COND_V_MSG(texture_size.width > 256 || texture_size.height > 256, image, "Cursor image too big. Max supported size is 256x256."); + Size2 image_size = image->get_size(); + ERR_FAIL_COND_V_MSG(p_hotspot.x > image_size.width || p_hotspot.y > image_size.height, image, "Hotspot outside cursor image."); + ERR_FAIL_COND_V_MSG(image_size.width > 256 || image_size.height > 256, image, "Cursor image too big. Max supported size is 256x256."); - ERR_FAIL_COND_V(image.is_null(), image); if (image->is_compressed()) { image = image->duplicate(true); Error err = image->decompress(); diff --git a/servers/display_server.h b/servers/display_server.h index 5224d59c04..5d82b6c13c 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -101,7 +101,7 @@ private: protected: static void _bind_methods(); - static Ref<Image> _get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect); + static Ref<Image> _get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot); enum { MAX_SERVERS = 64 @@ -248,8 +248,8 @@ public: virtual void tts_set_utterance_callback(TTSUtteranceEvent p_event, const Callable &p_callable); virtual void tts_post_utterance_event(TTSUtteranceEvent p_event, int p_id, int p_pos = 0); - virtual bool is_dark_mode_supported() const { return false; }; - virtual bool is_dark_mode() const { return false; }; + virtual bool is_dark_mode_supported() const { return false; } + virtual bool is_dark_mode() const { return false; } virtual Color get_accent_color() const { return Color(0, 0, 0, 0); } virtual Color get_base_color() const { return Color(0, 0, 0, 0); } virtual void set_system_theme_change_callback(const Callable &p_callable) {} @@ -338,8 +338,8 @@ public: return scale; } virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0; - virtual Color screen_get_pixel(const Point2i &p_position) const { return Color(); }; - virtual Ref<Image> screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return Ref<Image>(); }; + virtual Color screen_get_pixel(const Point2i &p_position) const { return Color(); } + virtual Ref<Image> screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return Ref<Image>(); } virtual bool is_touchscreen_available() const; // Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation` @@ -398,9 +398,9 @@ public: virtual void show_window(WindowID p_id); virtual void delete_sub_window(WindowID p_id); - virtual WindowID window_get_active_popup() const { return INVALID_WINDOW_ID; }; - virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect){}; - virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const { return Rect2i(); }; + virtual WindowID window_get_active_popup() const { return INVALID_WINDOW_ID; } + virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {} + virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const { return Rect2i(); } virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const; @@ -555,10 +555,10 @@ public: virtual Key keyboard_get_keycode_from_physical(Key p_keycode) const; virtual Key keyboard_get_label_from_physical(Key p_keycode) const; - virtual int tablet_get_driver_count() const { return 1; }; - virtual String tablet_get_driver_name(int p_driver) const { return "default"; }; - virtual String tablet_get_current_driver() const { return "default"; }; - virtual void tablet_set_current_driver(const String &p_driver){}; + virtual int tablet_get_driver_count() const { return 1; } + virtual String tablet_get_driver_name(int p_driver) const { return "default"; } + virtual String tablet_get_current_driver() const { return "default"; } + virtual void tablet_set_current_driver(const String &p_driver) {} virtual void process_events() = 0; diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index c02830b6df..317dbe9ab9 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -3331,6 +3331,45 @@ TEST_CASE("[SceneTree][CodeEdit] folding") { CHECK_FALSE(code_edit->is_line_folded(1)); } + SUBCASE("[CodeEdit] actions unfold") { + // add_selection_for_next_occurrence unfolds. + code_edit->set_text("test\n\tline1 test\n\t\tline 2\ntest2"); + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->add_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 2); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 0); + CHECK(code_edit->get_selection_origin_line() == 0); + CHECK(code_edit->get_caret_column() == 4); + CHECK(code_edit->get_selection_origin_column() == 0); + CHECK(code_edit->has_selection(1)); + CHECK(code_edit->get_caret_line(1) == 1); + CHECK(code_edit->get_selection_origin_line(1) == 1); + CHECK(code_edit->get_caret_column(1) == 11); + CHECK(code_edit->get_selection_origin_column(1) == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + + // skip_selection_for_next_occurrence unfolds. + code_edit->select(0, 0, 0, 4); + code_edit->fold_line(0); + CHECK(code_edit->is_line_folded(0)); + code_edit->skip_selection_for_next_occurrence(); + + CHECK(code_edit->get_caret_count() == 1); + CHECK(code_edit->has_selection(0)); + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_selection_origin_line() == 1); + CHECK(code_edit->get_caret_column() == 11); + CHECK(code_edit->get_selection_origin_column() == 7); + CHECK_FALSE(code_edit->is_line_folded(0)); + code_edit->remove_secondary_carets(); + code_edit->deselect(); + } + SUBCASE("[CodeEdit] toggle folding carets") { code_edit->set_text("test\n\tline1\ntest2\n\tline2"); diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 8778ea86a6..cf6b89c330 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -697,12 +697,16 @@ TEST_SUITE("[Navigation]") { CHECK_NE(navigation_server->map_get_closest_point(map, Vector3(0, 0, 0)), Vector3(0, 0, 0)); CHECK_NE(navigation_server->map_get_closest_point_normal(map, Vector3(0, 0, 0)), Vector3()); CHECK(navigation_server->map_get_closest_point_owner(map, Vector3(0, 0, 0)).is_valid()); - // TODO: Test map_get_closest_point_to_segment() with p_use_collision=true as well. CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), false), Vector3()); + CHECK_NE(navigation_server->map_get_closest_point_to_segment(map, Vector3(0, 0, 0), Vector3(1, 1, 1), true), Vector3()); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), true).size(), 0); CHECK_NE(navigation_server->map_get_path(map, Vector3(0, 0, 0), Vector3(10, 0, 10), false).size(), 0); } + SUBCASE("'map_get_closest_point_to_segment' with 'use_collision' should return default if segment doesn't intersect map") { + CHECK_EQ(navigation_server->map_get_closest_point_to_segment(map, Vector3(1, 2, 1), Vector3(1, 1, 1), true), Vector3()); + } + SUBCASE("Elaborate query with 'CORRIDORFUNNEL' post-processing should yield non-empty result") { Ref<NavigationPathQueryParameters3D> query_parameters = memnew(NavigationPathQueryParameters3D); query_parameters->set_map(map); 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/embree/common/sys/sysinfo.cpp b/thirdparty/embree/common/sys/sysinfo.cpp index d01eab3c9d..4ecab05265 100644 --- a/thirdparty/embree/common/sys/sysinfo.cpp +++ b/thirdparty/embree/common/sys/sysinfo.cpp @@ -295,7 +295,7 @@ namespace embree if (nIds >= 1) __cpuid (cpuid_leaf_1,0x00000001); #if _WIN32 #if _MSC_VER && (_MSC_FULL_VER < 160040219) -#else +#elif defined(_MSC_VER) if (nIds >= 7) __cpuidex(cpuid_leaf_7,0x00000007,0); #endif #else diff --git a/thirdparty/embree/patches/mingw-no-cpuidex.patch b/thirdparty/embree/patches/mingw-no-cpuidex.patch new file mode 100644 index 0000000000..5480334ceb --- /dev/null +++ b/thirdparty/embree/patches/mingw-no-cpuidex.patch @@ -0,0 +1,13 @@ +diff --git a/thirdparty/embree/common/sys/sysinfo.cpp b/thirdparty/embree/common/sys/sysinfo.cpp +index d01eab3c9d..4ecab05265 100644 +--- a/thirdparty/embree/common/sys/sysinfo.cpp ++++ b/thirdparty/embree/common/sys/sysinfo.cpp +@@ -295,7 +295,7 @@ namespace embree + if (nIds >= 1) __cpuid (cpuid_leaf_1,0x00000001); + #if _WIN32 + #if _MSC_VER && (_MSC_FULL_VER < 160040219) +-#else ++#elif defined(_MSC_VER) + if (nIds >= 7) __cpuidex(cpuid_leaf_7,0x00000007,0); + #endif + #else diff --git a/thirdparty/glslang/glslang/Include/InfoSink.h b/thirdparty/glslang/glslang/Include/InfoSink.h index b1b537df54..137ede8510 100644 --- a/thirdparty/glslang/glslang/Include/InfoSink.h +++ b/thirdparty/glslang/glslang/Include/InfoSink.h @@ -36,7 +36,7 @@ #define _INFOSINK_INCLUDED_ #include "../Include/Common.h" -#include <filesystem> +//#include <filesystem> #include <cmath> namespace glslang { diff --git a/thirdparty/glslang/glslang/patches/disable-absolute-paths-for-apple-compat.patch b/thirdparty/glslang/patches/disable-absolute-paths-for-apple-compat.patch index d15a531b7c..135020737e 100644 --- a/thirdparty/glslang/glslang/patches/disable-absolute-paths-for-apple-compat.patch +++ b/thirdparty/glslang/patches/disable-absolute-paths-for-apple-compat.patch @@ -1,7 +1,16 @@ diff --git a/thirdparty/glslang/glslang/Include/InfoSink.h b/thirdparty/glslang/glslang/Include/InfoSink.h -index 23f495dcb7..b1b537df54 100644 +index 23f495dc..137ede85 100644 --- a/thirdparty/glslang/glslang/Include/InfoSink.h +++ b/thirdparty/glslang/glslang/Include/InfoSink.h +@@ -36,7 +36,7 @@ + #define _INFOSINK_INCLUDED_ + + #include "../Include/Common.h" +-#include <filesystem> ++//#include <filesystem> + #include <cmath> + + namespace glslang { @@ -101,14 +101,14 @@ public: snprintf(locText, maxSize, ":%d", loc.line); 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/ |
