diff options
36 files changed, 948 insertions, 460 deletions
diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 4f8e79038f..203566579d 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -197,6 +197,7 @@ Error Expression::_get_token(Token &r_token) { case '\'': case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -234,9 +235,11 @@ Error Expression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -273,12 +276,46 @@ Error Expression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 57875bf50f..96cdc0678e 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -217,6 +217,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri } case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = p_stream->get_char(); @@ -252,10 +253,13 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri case 'r': res = 13; break; + case 'U': case 'u': { - //hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = p_stream->get_char(); + if (c == 0) { r_err_str = "Unterminated String"; r_token.type = TK_ERROR; @@ -290,15 +294,49 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + r_err_str = "Invalid UTF-16 sequence in string, unpaired trail surrogate"; + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } if (ch == '\n') { line++; } str += ch; } } + if (prev != 0) { + r_err_str = "Invalid UTF-16 sequence in string, unpaired lead surrogate"; + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } if (p_stream->is_utf8()) { str.parse_utf8(str.ascii(true).get_data()); diff --git a/doc/classes/TabBar.xml b/doc/classes/TabBar.xml index 59a4743d80..c286629395 100644 --- a/doc/classes/TabBar.xml +++ b/doc/classes/TabBar.xml @@ -43,10 +43,11 @@ Returns the previously active tab index. </description> </method> - <method name="get_select_with_rmb" qualifiers="const"> - <return type="bool" /> + <method name="get_tab_button_icon" qualifiers="const"> + <return type="Texture2D" /> + <argument index="0" name="tab_idx" type="int" /> <description> - Returns [code]true[/code] if select with right mouse button is enabled. + Returns the [Texture2D] for the right button of the tab at index [code]tab_idx[/code] or [code]null[/code] if the button has no [Texture2D]. </description> </method> <method name="get_tab_icon" qualifiers="const"> @@ -111,6 +112,13 @@ Returns [code]true[/code] if the tab at index [code]tab_idx[/code] is disabled. </description> </method> + <method name="is_tab_hidden" qualifiers="const"> + <return type="bool" /> + <argument index="0" name="tab_idx" type="int" /> + <description> + Returns [code]true[/code] if the tab at index [code]tab_idx[/code] is hidden. + </description> + </method> <method name="move_tab"> <return type="void" /> <argument index="0" name="from" type="int" /> @@ -126,11 +134,12 @@ Removes the tab at index [code]tab_idx[/code]. </description> </method> - <method name="set_select_with_rmb"> + <method name="set_tab_button_icon"> <return type="void" /> - <argument index="0" name="enabled" type="bool" /> + <argument index="0" name="tab_idx" type="int" /> + <argument index="1" name="icon" type="Texture2D" /> <description> - If [code]true[/code], enables selecting a tab with the right mouse button. + Sets an [code]icon[/code] for the button of the tab at index [code]tab_idx[/code] (located to the right, before the close button), making it visible and clickable (See [signal tab_button_pressed]). Giving it a [code]null[/code] value will hide the button. </description> </method> <method name="set_tab_disabled"> @@ -141,6 +150,14 @@ If [code]disabled[/code] is [code]true[/code], disables the tab at index [code]tab_idx[/code], making it non-interactable. </description> </method> + <method name="set_tab_hidden"> + <return type="void" /> + <argument index="0" name="tab_idx" type="int" /> + <argument index="1" name="hidden" type="bool" /> + <description> + If [code]hidden[/code] is [code]true[/code], hides the tab at index [code]tab_idx[/code], making it disappear from the tab area. + </description> + </method> <method name="set_tab_icon"> <return type="void" /> <argument index="0" name="tab_idx" type="int" /> @@ -200,10 +217,17 @@ <member name="drag_to_rearrange_enabled" type="bool" setter="set_drag_to_rearrange_enabled" getter="get_drag_to_rearrange_enabled" default="false"> If [code]true[/code], tabs can be rearranged with mouse drag. </member> + <member name="scroll_to_selected" type="bool" setter="set_scroll_to_selected" getter="get_scroll_to_selected" default="true"> + If [code]true[/code], the tab offset will be changed to keep the the currently selected tab visible. + </member> <member name="scrolling_enabled" type="bool" setter="set_scrolling_enabled" getter="get_scrolling_enabled" default="true"> if [code]true[/code], the mouse's scroll wheel can be used to navigate the scroll view. </member> + <member name="select_with_rmb" type="bool" setter="set_select_with_rmb" getter="get_select_with_rmb" default="false"> + If [code]true[/code], enables selecting a tab with the right mouse button. + </member> <member name="tab_alignment" type="int" setter="set_tab_alignment" getter="get_tab_alignment" enum="TabBar.AlignmentMode" default="1"> + Sets the position at which tabs will be placed. See [enum AlignmentMode] for details. </member> <member name="tab_close_display_policy" type="int" setter="set_tab_close_display_policy" getter="get_tab_close_display_policy" enum="TabBar.CloseButtonDisplayPolicy" default="0"> Sets when the close button will appear on the tabs. See [enum CloseButtonDisplayPolicy] for details. @@ -219,6 +243,12 @@ Emitted when the active tab is rearranged via mouse drag. See [member drag_to_rearrange_enabled]. </description> </signal> + <signal name="tab_button_pressed"> + <argument index="0" name="tab" type="int" /> + <description> + Emitted when a tab's right button is pressed. See [method set_tab_button_icon]. + </description> + </signal> <signal name="tab_changed"> <argument index="0" name="tab" type="int" /> <description> @@ -255,18 +285,28 @@ <signal name="tab_rmb_clicked"> <argument index="0" name="tab" type="int" /> <description> - Emitted when a tab is right-clicked. + Emitted when a tab is right-clicked. [member select_with_rmb] must be enabled. + </description> + </signal> + <signal name="tab_selected"> + <argument index="0" name="tab" type="int" /> + <description> + Emitted when a tab is selected via click or script, even if it is the current tab. </description> </signal> </signals> <constants> <constant name="ALIGNMENT_LEFT" value="0" enum="AlignmentMode"> + Places tabs to the left. </constant> <constant name="ALIGNMENT_CENTER" value="1" enum="AlignmentMode"> + Places tabs in the middle. </constant> <constant name="ALIGNMENT_RIGHT" value="2" enum="AlignmentMode"> + Places tabs to the right. </constant> <constant name="ALIGNMENT_MAX" value="3" enum="AlignmentMode"> + Represents the size of the [enum AlignmentMode] enum. </constant> <constant name="CLOSE_BUTTON_SHOW_NEVER" value="0" enum="CloseButtonDisplayPolicy"> Never show the close buttons. @@ -321,11 +361,11 @@ <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> Icon for the right arrow button that appears when there are too many tabs to fit in the container width. Used when the button is being hovered with the cursor. </theme_item> - <theme_item name="close_bg_highlight" data_type="style" type="StyleBox"> - Background of the close button when it's being hovered with the cursor. + <theme_item name="button_highlight" data_type="style" type="StyleBox"> + Background of the tab and close buttons when they're being hovered with the cursor. </theme_item> - <theme_item name="close_bg_pressed" data_type="style" type="StyleBox"> - Background of the close button when it's being pressed. + <theme_item name="button_pressed" data_type="style" type="StyleBox"> + Background of the tab and close buttons when it's being pressed. </theme_item> <theme_item name="tab_disabled" data_type="style" type="StyleBox"> The style of disabled tabs. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a1f259c864..deafe65baf 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -340,7 +340,7 @@ void EditorNode::_update_scene_tabs() { } if (show_rb && editor_data.get_scene_root_script(i).is_valid()) { - scene_tabs->set_tab_right_button(i, script_icon); + scene_tabs->set_tab_button_icon(i, script_icon); } } @@ -4222,6 +4222,8 @@ void EditorNode::_dock_floating_close_request(Control *p_control) { _update_dock_containers(); floating_docks.erase(p_control); + + _edit_current(); } void EditorNode::_dock_make_float() { @@ -4264,6 +4266,8 @@ void EditorNode::_dock_make_float() { _update_dock_containers(); floating_docks.push_back(dock); + + _edit_current(); } void EditorNode::_update_dock_containers() { @@ -6233,7 +6237,7 @@ EditorNode::EditorNode() { scene_tabs->set_min_width(int(EDITOR_DEF("interface/scene_tabs/minimum_width", 50)) * EDSCALE); scene_tabs->set_drag_to_rearrange_enabled(true); scene_tabs->connect("tab_changed", callable_mp(this, &EditorNode::_scene_tab_changed)); - scene_tabs->connect("tab_rmb_clicked", callable_mp(this, &EditorNode::_scene_tab_script_edited)); + scene_tabs->connect("tab_button_pressed", callable_mp(this, &EditorNode::_scene_tab_script_edited)); scene_tabs->connect("tab_close_pressed", callable_mp(this, &EditorNode::_scene_tab_closed), varray(SCENE_TAB_CLOSE)); scene_tabs->connect("tab_hovered", callable_mp(this, &EditorNode::_scene_tab_hovered)); scene_tabs->connect("mouse_exited", callable_mp(this, &EditorNode::_scene_tab_exit)); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1c927db4be..308a268e42 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1017,8 +1017,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("SceneTabFG", "EditorStyles", style_tab_selected); theme->set_stylebox("SceneTabBG", "EditorStyles", style_tab_unselected); theme->set_icon("close", "TabBar", theme->get_icon("GuiClose", "EditorIcons")); - theme->set_stylebox("close_bg_pressed", "TabBar", style_menu); - theme->set_stylebox("close_bg_highlight", "TabBar", style_menu); + theme->set_stylebox("button_pressed", "TabBar", style_menu); + theme->set_stylebox("button_highlight", "TabBar", style_menu); theme->set_icon("increment", "TabContainer", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); theme->set_icon("decrement", "TabContainer", theme->get_icon("GuiScrollArrowLeft", "EditorIcons")); theme->set_icon("increment", "TabBar", theme->get_icon("GuiScrollArrowRight", "EditorIcons")); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 169ce29438..5dd24983ff 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -383,7 +383,7 @@ void Skeleton3DEditor::create_physical_skeleton() { if (!bones_infos[parent].physical_bone) { bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); - ur->create_action(TTR("Create physical bones")); + ur->create_action(TTR("Create physical bones"), UndoRedo::MERGE_ALL); ur->add_do_method(skeleton, "add_child", bones_infos[parent].physical_bone); ur->add_do_reference(bones_infos[parent].physical_bone); ur->add_undo_method(skeleton, "remove_child", bones_infos[parent].physical_bone); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 611f81db33..bc2739bdac 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -3469,7 +3469,7 @@ void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const Stri preview_tabs->add_tab(p_preview_name, p_icon); preview_tabs_content->add_child(p_preview_tab); - preview_tabs->set_tab_right_button(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("TabBar"))); + preview_tabs->set_tab_button_icon(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("TabBar"))); p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1); @@ -3600,7 +3600,7 @@ ThemeEditor::ThemeEditor() { preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("tab_rmb_clicked", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 05ea061798..9977b88aa1 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -786,6 +786,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } String result; + char32_t prev = 0; + int prev_pos = 0; for (;;) { // Consume actual string. @@ -852,9 +854,11 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { case '\\': escaped = '\\'; break; - case 'u': + case 'U': + case 'u': { // Hexadecimal sequence. - for (int i = 0; i < 4; i++) { + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { if (_is_at_end()) { return make_error("Unterminated string."); } @@ -886,7 +890,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); } - break; + } break; case '\r': if (_peek() != '\n') { // Carriage return without newline in string. (???) @@ -909,11 +913,53 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { valid_escape = false; break; } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } + } if (valid_escape) { result += escaped; } } else if (ch == quote_char) { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } _advance(); if (is_multiline) { if (_peek() == quote_char && _peek(1) == quote_char) { @@ -930,6 +976,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { break; } } else { + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } result += ch; _advance(); if (ch == '\n') { @@ -937,6 +990,13 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { } } } + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + error.start_column = prev_pos; + error.leftmost_column = error.start_column; + push_error(error); + prev = 0; + } // Make the literal. Variant string; diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index 77fe9cdd19..52d5379e8b 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -140,12 +140,12 @@ void NavigationMeshGenerator::_add_faces(const PackedVector3Array &p_faces, cons } } -void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { +void NavigationMeshGenerator::_parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children) { if (Object::cast_to<MeshInstance3D>(p_node) && p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(p_node); Ref<Mesh> mesh = mesh_instance->get_mesh(); if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * mesh_instance->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * mesh_instance->get_global_transform(), p_vertices, p_indices); } } @@ -159,7 +159,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor n = multimesh->get_instance_count(); } for (int i = 0; i < n; i++) { - _add_mesh(mesh, p_accumulated_transform * multimesh->get_instance_transform(i), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * multimesh_instance->get_global_transform() * multimesh->get_instance_transform(i), p_vertices, p_indices); } } } @@ -171,7 +171,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (!meshes.is_empty()) { Ref<Mesh> mesh = meshes[1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * csg_shape->get_transform(), p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * csg_shape->get_global_transform(), p_vertices, p_indices); } } } @@ -186,7 +186,7 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (Object::cast_to<CollisionShape3D>(child)) { CollisionShape3D *col_shape = Object::cast_to<CollisionShape3D>(child); - Transform3D transform = p_accumulated_transform * static_body->get_transform() * col_shape->get_transform(); + Transform3D transform = p_navmesh_transform * static_body->get_global_transform() * col_shape->get_transform(); Ref<Mesh> mesh; Ref<Shape3D> s = col_shape->get_shape(); @@ -270,11 +270,11 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor if (gridmap) { if (p_generate_from != NavigationMesh::PARSED_GEOMETRY_STATIC_COLLIDERS) { Array meshes = gridmap->get_meshes(); - Transform3D xform = gridmap->get_transform(); + Transform3D xform = gridmap->get_global_transform(); for (int i = 0; i < meshes.size(); i += 2) { Ref<Mesh> mesh = meshes[i + 1]; if (mesh.is_valid()) { - _add_mesh(mesh, p_accumulated_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); + _add_mesh(mesh, p_navmesh_transform * xform * (Transform3D)meshes[i], p_vertices, p_indices); } } } @@ -364,14 +364,9 @@ void NavigationMeshGenerator::_parse_geometry(Transform3D p_accumulated_transfor } #endif - if (Object::cast_to<Node3D>(p_node)) { - Node3D *spatial = Object::cast_to<Node3D>(p_node); - p_accumulated_transform = p_accumulated_transform * spatial->get_transform(); - } - if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { - _parse_geometry(p_accumulated_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); + _parse_geometry(p_navmesh_transform, p_node->get_child(i), p_vertices, p_indices, p_generate_from, p_collision_mask, p_recurse_children); } } } @@ -616,7 +611,7 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) p_node->get_tree()->get_nodes_in_group(p_nav_mesh->get_source_group_name(), &parse_nodes); } - Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_transform().affine_inverse(); + Transform3D navmesh_xform = Object::cast_to<Node3D>(p_node)->get_global_transform().affine_inverse(); for (Node *E : parse_nodes) { NavigationMesh::ParsedGeometryType geometry_type = p_nav_mesh->get_parsed_geometry_type(); uint32_t collision_mask = p_nav_mesh->get_collision_mask(); diff --git a/modules/navigation/navigation_mesh_generator.h b/modules/navigation/navigation_mesh_generator.h index 1ffdd39aee..21f7a4941b 100644 --- a/modules/navigation/navigation_mesh_generator.h +++ b/modules/navigation/navigation_mesh_generator.h @@ -52,7 +52,7 @@ protected: static void _add_vertex(const Vector3 &p_vec3, Vector<float> &p_vertices); static void _add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); static void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform, Vector<float> &p_vertices, Vector<int> &p_indices); - static void _parse_geometry(Transform3D p_accumulated_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); + static void _parse_geometry(const Transform3D &p_navmesh_transform, Node *p_node, Vector<float> &p_vertices, Vector<int> &p_indices, NavigationMesh::ParsedGeometryType p_generate_from, uint32_t p_collision_mask, bool p_recurse_children); static void _convert_detail_mesh_to_native_navigation_mesh(const rcPolyMeshDetail *p_detail_mesh, Ref<NavigationMesh> p_nav_mesh); static void _build_recast_navigation_mesh( diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp index 2abbd19e12..17a3566ed2 100644 --- a/modules/visual_script/visual_script_expression.cpp +++ b/modules/visual_script/visual_script_expression.cpp @@ -328,6 +328,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) { }; case '"': { String str; + char32_t prev = 0; while (true) { char32_t ch = GET_CHAR(); @@ -364,9 +365,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) { case 'r': res = 13; break; + case 'U': case 'u': { - // hex number - for (int j = 0; j < 4; j++) { + // Hexadecimal sequence. + int hex_len = (next == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { char32_t c = GET_CHAR(); if (c == 0) { @@ -403,12 +406,46 @@ Error VisualScriptExpression::_get_token(Token &r_token) { } break; } + // Parse UTF-16 pair. + if ((res & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = res; + continue; + } else { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } + } else if ((res & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } else { + res = (prev << 10UL) + res - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } + } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += res; - } else { + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } str += ch; } } + if (prev != 0) { + _set_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + r_token.type = TK_ERROR; + return ERR_PARSE_ERROR; + } r_token.type = TK_CONSTANT; r_token.value = str; diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index de5e872837..445b35cda8 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -143,7 +143,7 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; - bool focused = false; + bool focused = true; bool minimized = false; unsigned int focus_order = 0; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 5511a1d910..22def607ed 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -571,6 +571,8 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); + String selection_text = (had_selection ? get_selected_text() : ""); + if (had_selection) { begin_complex_operation(); delete_selection(); @@ -591,27 +593,38 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (auto_brace_completion_enabled) { int cl = get_caret_line(); int cc = get_caret_column(); - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + if (had_selection) { insert_text_at_caret(chr); + + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key); + set_caret_column(get_caret_column() - 1); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr); + } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { + insert_text_at_caret(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr); + } else { + insert_text_at_caret(chr); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + } } + set_caret_column(cc + caret_move_offset); } - set_caret_column(cc + caret_move_offset); } else { insert_text_at_caret(chr); } diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 9da030f0a2..5a551ec5a5 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -38,48 +38,71 @@ #include "scene/gui/texture_rect.h" Size2 TabBar::get_minimum_size() const { + Size2 ms; + + if (tabs.is_empty()) { + return ms; + } + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); + int hseparation = get_theme_constant(SNAME("hseparation")); int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); - Size2 ms(0, 0); - for (int i = 0; i < tabs.size(); i++) { - Ref<Texture2D> tex = tabs[i].icon; - if (tex.is_valid()) { - ms.height = MAX(ms.height, tex->get_size().height); - if (!tabs[i].text.is_empty()) { - ms.width += get_theme_constant(SNAME("hseparation")); - } + if (tabs[i].hidden) { + continue; } - ms.width += Math::ceil(tabs[i].text_buf->get_size().x); - ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + int ofs = ms.width; + Ref<StyleBox> style; if (tabs[i].disabled) { - ms.width += tab_disabled->get_minimum_size().width; + style = tab_disabled; } else if (current == i) { - ms.width += tab_selected->get_minimum_size().width; + style = tab_selected; } else { - ms.width += tab_unselected->get_minimum_size().width; + style = tab_unselected; + } + ms.width += style->get_minimum_size().width; + + Ref<Texture2D> tex = tabs[i].icon; + if (tex.is_valid()) { + ms.height = MAX(ms.height, tex->get_size().height); + ms.width += tex->get_size().width + hseparation; } + if (!tabs[i].text.is_empty()) { + ms.width += tabs[i].size_text + hseparation; + } + ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current); + if (tabs[i].right_button.is_valid()) { Ref<Texture2D> rb = tabs[i].right_button; - Size2 bms = rb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + + if (close_visible) { + ms.width += button_highlight->get_minimum_size().width + rb->get_width(); + } else { + ms.width += button_highlight->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } + + ms.height = MAX(rb->get_height() + style->get_minimum_size().height, ms.height); + } + + if (close_visible) { + ms.width += button_highlight->get_margin(SIDE_LEFT) + close->get_width() + hseparation; + + ms.height = MAX(close->get_height() + style->get_minimum_size().height, ms.height); } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - Size2 bms = cb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + if (ms.width - ofs > style->get_minimum_size().width) { + ms.width -= hseparation; } } @@ -165,8 +188,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (rb_hover != -1) { - // Right mouse button clicked. - emit_signal(SNAME("tab_rmb_clicked"), rb_hover); + emit_signal(SNAME("tab_button_pressed"), rb_hover); } rb_pressing = false; @@ -175,7 +197,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (cb_hover != -1) { - // Close button pressed. emit_signal(SNAME("tab_close_pressed"), cb_hover); } @@ -184,7 +205,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) { - // Clicks. Point2 pos = mb->get_position(); if (buttons_visible) { @@ -234,6 +254,10 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { int found = -1; for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + if (tabs[i].rb_rect.has_point(pos)) { rb_pressing = true; update(); @@ -256,6 +280,12 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (found != -1) { set_current_tab(found); + + if (mb->get_button_index() == MouseButton::RIGHT) { + // Right mouse button clicked. + emit_signal(SNAME("tab_rmb_clicked"), found); + } + emit_signal(SNAME("tab_clicked"), found); } } @@ -275,13 +305,12 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); } void TabBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - _update_cache(); update(); } break; case NOTIFICATION_THEME_CHANGED: @@ -289,14 +318,19 @@ void TabBar::_notification(int p_what) { for (int i = 0; i < tabs.size(); ++i) { _shape(i); } - _update_cache(); - update_minimum_size(); - update(); - } break; + + [[fallthrough]]; + } case NOTIFICATION_RESIZED: { + int ofs_old = offset; + int max_old = max_drawn_tab; + _update_cache(); _ensure_no_over_offset(); - ensure_tab_visible(current); + + if (scroll_to_selected && (offset != ofs_old || max_drawn_tab != max_old)) { + ensure_tab_visible(current); + } } break; case NOTIFICATION_DRAW: { if (tabs.is_empty()) { @@ -322,6 +356,10 @@ void TabBar::_notification(int p_what) { // Draw unselected tabs in the back. for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + if (i != current) { Ref<StyleBox> sb; Color col; @@ -344,14 +382,14 @@ void TabBar::_notification(int p_what) { } // Draw selected tab in the front, but only if it's visible. - if (current >= offset && current <= max_drawn_tab) { + if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) { Ref<StyleBox> sb = tabs[current].disabled ? tab_disabled : tab_selected; float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache; _draw_tab(sb, font_selected_color, current, x); } - if (offset > 0 || missing_right) { + if (buttons_visible) { int vofs = (get_size().height - incr->get_size().height) / 2; if (rtl) { @@ -386,47 +424,52 @@ void TabBar::_notification(int p_what) { void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { RID ci = get_canvas_item(); + bool rtl = is_layout_rtl(); Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); + int hseparation = get_theme_constant(SNAME("hseparation")); - Tab tab = tabs[p_index]; - - Rect2 sb_rect = Rect2(p_x, 0, tab.size_cache, get_size().height); + Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); p_tab_style->draw(ci, sb_rect); - p_x += p_tab_style->get_margin(SIDE_LEFT); + p_x += rtl ? tabs[p_index].size_cache - p_tab_style->get_margin(SIDE_LEFT) : p_tab_style->get_margin(SIDE_LEFT); Size2i sb_ms = p_tab_style->get_minimum_size(); - Ref<Texture2D> icon = tab.icon; + // Draw the icon. + Ref<Texture2D> icon = tabs[p_index].icon; if (icon.is_valid()) { - icon->draw(ci, Point2i(p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); + icon->draw(ci, Point2i(rtl ? p_x - icon->get_width() : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); - if (!tab.text.is_empty()) { - p_x += icon->get_width() + get_theme_constant(SNAME("hseparation")); - } + p_x = rtl ? p_x - icon->get_width() - hseparation : p_x + icon->get_width() + hseparation; } - Vector2 text_pos = Point2i(p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tab.text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - tab.text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); - } - tab.text_buf->draw(ci, text_pos, p_font_color); + // Draw the text. + if (!tabs[p_index].text.is_empty()) { + Point2i text_pos = Point2i(rtl ? p_x - tabs[p_index].size_text : p_x, + p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[p_index].text_buf->get_size().y) / 2); - p_x += tab.size_text; + if (outline_size > 0 && font_outline_color.a > 0) { + tabs[p_index].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + } + tabs[p_index].text_buf->draw(ci, text_pos, p_font_color); - if (tab.right_button.is_valid()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); - Ref<Texture2D> rb = tab.right_button; + p_x = rtl ? p_x - tabs[p_index].size_text - hseparation : p_x + tabs[p_index].size_text + hseparation; + } - p_x += get_theme_constant(SNAME("hseparation")); + // Draw and calculate rect of the right button. + if (tabs[p_index].right_button.is_valid()) { + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> rb = tabs[p_index].right_button; Rect2 rb_rect; rb_rect.size = style->get_minimum_size() + rb->get_size(); - rb_rect.position.x = p_x; + rb_rect.position.x = rtl ? p_x - rb_rect.size.width : p_x; rb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (rb_rect.size.y)) / 2; + tabs.write[p_index].rb_rect = rb_rect; + if (rb_hover == p_index) { if (rb_pressing) { get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); @@ -435,41 +478,61 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in } } - rb->draw(ci, Point2i(p_x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); - p_x += rb->get_width(); - tabs.write[p_index].rb_rect = rb_rect; + rb->draw(ci, Point2i(rb_rect.position.x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); + + p_x = rtl ? rb_rect.position.x : rb_rect.position.x + rb_rect.size.width; } + // Draw and calculate rect of the close button. if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_index == current)) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - p_x += get_theme_constant(SNAME("hseparation")); - Rect2 cb_rect; cb_rect.size = style->get_minimum_size() + cb->get_size(); - cb_rect.position.x = p_x; + cb_rect.position.x = rtl ? p_x - cb_rect.size.width : p_x; cb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (cb_rect.size.y)) / 2; - if (!tab.disabled && cb_hover == p_index) { + tabs.write[p_index].cb_rect = cb_rect; + + if (!tabs[p_index].disabled && cb_hover == p_index) { if (cb_pressing) { - get_theme_stylebox(SNAME("close_bg_pressed"))->draw(ci, cb_rect); + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); } else { style->draw(ci, cb_rect); } } - cb->draw(ci, Point2i(p_x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); - p_x += cb->get_width(); - tabs.write[p_index].cb_rect = cb_rect; + cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); } } void TabBar::set_tab_count(int p_count) { + if (p_count == tabs.size()) { + return; + } + ERR_FAIL_COND(p_count < 0); tabs.resize(p_count); + + if (p_count == 0) { + offset = 0; + max_drawn_tab = 0; + current = 0; + previous = 0; + } else { + offset = MIN(offset, p_count - 1); + max_drawn_tab = MIN(max_drawn_tab, p_count - 1); + current = MIN(current, p_count - 1); + } + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); notify_property_list_changed(); } @@ -478,15 +541,26 @@ int TabBar::get_tab_count() const { } void TabBar::set_current_tab(int p_current) { - if (current == p_current) { - return; - } ERR_FAIL_INDEX(p_current, get_tab_count()); previous = current; current = p_current; + if (current == previous) { + emit_signal(SNAME("tab_selected"), current); + return; + } + // Triggered by dragging a tab from another TabBar to the selected index, to ensure that tab_changed is emitted. + if (previous == -1) { + previous = current; + } + + emit_signal(SNAME("tab_selected"), current); + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); emit_signal(SNAME("tab_changed"), p_current); @@ -515,8 +589,13 @@ bool TabBar::get_offset_buttons_visible() const { void TabBar::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -529,6 +608,7 @@ String TabBar::get_tab_title(int p_tab) const { void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_tab, tabs.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (tabs[p_tab].text_direction != p_text_direction) { tabs.write[p_tab].text_direction = p_text_direction; _shape(p_tab); @@ -544,24 +624,38 @@ Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const { void TabBar::clear_tab_opentype_features(int p_tab) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].opentype_features.clear(); + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) { ERR_FAIL_INDEX(p_tab, tabs.size()); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) { tabs.write[p_tab].opentype_features[tag] = p_value; + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag)) { return -1; @@ -571,10 +665,17 @@ int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { void TabBar::set_tab_language(int p_tab, const String &p_language) { ERR_FAIL_INDEX(p_tab, tabs.size()); + if (tabs[p_tab].language != p_language) { tabs.write[p_tab].language = p_language; _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } @@ -586,7 +687,12 @@ String TabBar::get_tab_language(int p_tab) const { void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].icon = p_icon; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -599,7 +705,14 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const { void TabBar::set_tab_disabled(int p_tab, bool p_disabled) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].disabled = p_disabled; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } bool TabBar::is_tab_disabled(int p_tab) const { @@ -607,15 +720,38 @@ bool TabBar::is_tab_disabled(int p_tab) const { return tabs[p_tab].disabled; } -void TabBar::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button) { +void TabBar::set_tab_hidden(int p_tab, bool p_hidden) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + tabs.write[p_tab].hidden = p_hidden; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); +} + +bool TabBar::is_tab_hidden(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), false); + return tabs[p_tab].hidden; +} + +void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); - tabs.write[p_tab].right_button = p_right_button; + tabs.write[p_tab].right_button = p_icon; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } -Ref<Texture2D> TabBar::get_tab_right_button(int p_tab) const { +Ref<Texture2D> TabBar::get_tab_button_icon(int p_tab) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>()); return tabs[p_tab].right_button; } @@ -626,34 +762,53 @@ void TabBar::_update_hover() { } const Point2 &pos = get_local_mouse_position(); - // test hovering to display right or close button. + // Test hovering to display right or close button. int hover_now = -1; int hover_buttons = -1; - for (int i = offset; i < tabs.size(); i++) { + for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + Rect2 rect = get_tab_rect(i); if (rect.has_point(pos)) { hover_now = i; } + if (tabs[i].rb_rect.has_point(pos)) { rb_hover = i; cb_hover = -1; hover_buttons = i; - break; } else if (!tabs[i].disabled && tabs[i].cb_rect.has_point(pos)) { cb_hover = i; rb_hover = -1; hover_buttons = i; + } + + if (hover_buttons != -1) { + update(); break; } } + if (hover != hover_now) { hover = hover_now; - emit_signal(SNAME("tab_hovered"), hover); + + if (hover != -1) { + emit_signal(SNAME("tab_hovered"), hover); + } } if (hover_buttons == -1) { // No hover. + int rb_hover_old = rb_hover; + int cb_hover_old = cb_hover; + rb_hover = hover_buttons; cb_hover = hover_buttons; + + if (rb_hover != rb_hover_old || cb_hover != cb_hover_old) { + update(); + } } } @@ -677,16 +832,20 @@ void TabBar::_update_cache() { int count_resize = 0; for (int i = 0; i < tabs.size(); i++) { - tabs.write[i].ofs_cache = 0; - tabs.write[i].size_cache = get_tab_width(i); tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); tabs.write[i].text_buf->set_width(-1); - mw += tabs[i].size_cache; - if (tabs[i].size_cache <= min_width || i == current) { - size_fixed += tabs[i].size_cache; - } else { - count_resize++; + tabs.write[i].ofs_cache = 0; + tabs.write[i].size_cache = get_tab_width(i); + + if (!tabs[i].hidden) { + mw += tabs[i].size_cache; + + if (tabs[i].size_cache <= min_width || i == current) { + size_fixed += tabs[i].size_cache; + } else { + count_resize++; + } } } @@ -696,34 +855,20 @@ void TabBar::_update_cache() { } for (int i = offset; i < tabs.size(); i++) { - Ref<StyleBox> sb; - if (tabs[i].disabled) { - sb = tab_disabled; - } else if (i == current) { - sb = tab_selected; - } else { - sb = tab_unselected; + if (tabs[i].hidden) { + tabs.write[i].ofs_cache = w; + max_drawn_tab = i; + + continue; } int lsize = tabs[i].size_cache; int slen = tabs[i].size_text; - if (min_width > 0 && mw > limit_minus_buttons && i != current) { - if (lsize > m_width) { - slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT)); - if (tabs[i].icon.is_valid()) { - slen -= tabs[i].icon->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); - } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - slen -= cb->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); - } - - slen = MAX(slen, 1); - lsize = m_width; - } + // FIXME: This is completely broken. + if (min_width > 0 && (mw > limit || (offset > 0 && mw > limit_minus_buttons)) && i != current && lsize > m_width) { + slen = MAX(m_width - tabs[i].size_cache + tabs[i].size_text, 1); + lsize = m_width; } tabs.write[i].ofs_cache = w; @@ -735,13 +880,22 @@ void TabBar::_update_cache() { max_drawn_tab = i; // Check if all tabs would fit inside the area. - if (i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { - w -= get_tab_width(i); - max_drawn_tab -= 1; + if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { + tabs.write[i].ofs_cache = 0; + tabs.write[i].text_buf->set_width(-1); + + w -= tabs[i].size_cache; + max_drawn_tab--; while (w > limit_minus_buttons && max_drawn_tab > offset) { - w -= get_tab_width(max_drawn_tab); - max_drawn_tab -= 1; + tabs.write[max_drawn_tab].ofs_cache = 0; + + if (!tabs[max_drawn_tab].hidden) { + tabs.write[max_drawn_tab].text_buf->set_width(-1); + w -= tabs[max_drawn_tab].size_cache; + } + + max_drawn_tab--; } break; @@ -752,21 +906,25 @@ void TabBar::_update_cache() { buttons_visible = offset > 0 || missing_right; if (tab_alignment == ALIGNMENT_LEFT) { + _update_hover(); return; - } else if (tab_alignment == ALIGNMENT_CENTER) { + } + + if (tab_alignment == ALIGNMENT_CENTER) { w = ((buttons_visible ? limit_minus_buttons : limit) - w) / 2; } else if (tab_alignment == ALIGNMENT_RIGHT) { w = (buttons_visible ? limit_minus_buttons : limit) - w; } - if (w < 0) { - w = 0; - } - for (int i = offset; i <= max_drawn_tab; i++) { tabs.write[i].ofs_cache = w; - w += tabs.write[i].size_cache; + + if (!tabs[i].hidden) { + w += tabs[i].size_cache; + } } + + _update_hover(); } void TabBar::_on_mouse_exited() { @@ -784,20 +942,26 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); t.text_buf->add_string(t.xl_text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); t.icon = p_icon; - tabs.push_back(t); + _update_cache(); - call_deferred(SNAME("_update_hover")); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } void TabBar::clear_tabs() { tabs.clear(); + offset = 0; + max_drawn_tab = 0; current = 0; previous = 0; - call_deferred(SNAME("_update_hover")); + + _update_cache(); update(); + update_minimum_size(); notify_property_list_changed(); } @@ -807,10 +971,6 @@ void TabBar::remove_tab(int p_idx) { if (current >= p_idx) { current--; } - _update_cache(); - call_deferred(SNAME("_update_hover")); - update(); - update_minimum_size(); if (current < 0) { current = 0; @@ -820,7 +980,13 @@ void TabBar::remove_tab(int p_idx) { current = tabs.size() - 1; } + _update_cache(); _ensure_no_over_offset(); + if (scroll_to_selected && !tabs.is_empty()) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); notify_property_list_changed(); } @@ -840,15 +1006,13 @@ Variant TabBar::get_drag_data(const Point2 &p_point) { if (!tabs[tab_over].icon.is_null()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(tabs[tab_over].icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); drag_preview->add_child(tf); } + Label *label = memnew(Label(tabs[tab_over].xl_text)); drag_preview->add_child(label); - if (!tabs[tab_over].right_button.is_null()) { - TextureRect *tf = memnew(TextureRect); - tf->set_texture(tabs[tab_over].right_button); - drag_preview->add_child(tf); - } + set_drag_preview(drag_preview); Dictionary drag_data; @@ -901,31 +1065,40 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) { int tab_from_id = d["tab_element"]; NodePath from_path = d["from_path"]; NodePath to_path = get_path(); + if (from_path == to_path) { if (hover_now < 0) { hover_now = get_tab_count() - 1; } + move_tab(tab_from_id, hover_now); emit_signal(SNAME("active_tab_rearranged"), hover_now); set_current_tab(hover_now); } else if (get_tabs_rearrange_group() != -1) { // Drag and drop between Tabs. + Node *from_node = get_node(from_path); TabBar *from_tabs = Object::cast_to<TabBar>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { if (tab_from_id >= from_tabs->get_tab_count()) { return; } + Tab moving_tab = from_tabs->tabs[tab_from_id]; if (hover_now < 0) { hover_now = get_tab_count(); } + + // Workaround to ensure that tab_changed is emitted. + if (current == hover_now) { + current = -1; + } + tabs.insert(hover_now, moving_tab); from_tabs->remove_tab(tab_from_id); set_current_tab(hover_now); - emit_signal(SNAME("tab_changed"), hover_now); - _update_cache(); - update(); + update_minimum_size(); } } } @@ -946,6 +1119,7 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const { void TabBar::set_tab_alignment(AlignmentMode p_alignment) { ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX); tab_alignment = p_alignment; + _update_cache(); update(); } @@ -959,7 +1133,16 @@ void TabBar::set_clip_tabs(bool p_clip_tabs) { return; } clip_tabs = p_clip_tabs; + + if (!clip_tabs) { + offset = 0; + max_drawn_tab = 0; + } + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -981,6 +1164,10 @@ void TabBar::move_tab(int from, int to) { tabs.insert(to, tab_from); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); notify_property_list_changed(); } @@ -991,37 +1178,49 @@ int TabBar::get_tab_width(int p_idx) const { Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + int hseparation = get_theme_constant(SNAME("hseparation")); - int x = 0; + Ref<StyleBox> style; + + if (tabs[p_idx].disabled) { + style = tab_disabled; + } else if (current == p_idx) { + style = tab_selected; + } else { + style = tab_unselected; + } + int x = style->get_minimum_size().width; Ref<Texture2D> tex = tabs[p_idx].icon; if (tex.is_valid()) { - x += tex->get_width(); - if (!tabs[p_idx].text.is_empty()) { - x += get_theme_constant(SNAME("hseparation")); - } + x += tex->get_width() + hseparation; } - x += Math::ceil(tabs[p_idx].text_buf->get_size().x); - - if (tabs[p_idx].disabled) { - x += tab_disabled->get_minimum_size().width; - } else if (current == p_idx) { - x += tab_selected->get_minimum_size().width; - } else { - x += tab_unselected->get_minimum_size().width; + if (!tabs[p_idx].text.is_empty()) { + x += tabs[p_idx].size_text + hseparation; } + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current); + if (tabs[p_idx].right_button.is_valid()) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> rb = tabs[p_idx].right_button; - x += rb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + + if (close_visible) { + x += btn_style->get_minimum_size().width + rb->get_width(); + } else { + x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current)) { + if (close_visible) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - x += cb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation; + } + + if (x > style->get_minimum_size().width) { + x -= hseparation; } return x; @@ -1039,8 +1238,12 @@ void TabBar::_ensure_no_over_offset() { int prev_offset = offset; int total_w = tabs[max_drawn_tab].ofs_cache + tabs[max_drawn_tab].size_cache - tabs[offset].ofs_cache; - while (offset > 0) { - total_w += tabs[offset - 1].size_cache; + for (int i = offset; i > 0; i--) { + if (tabs[i - 1].hidden) { + continue; + } + + total_w += tabs[i - 1].size_cache; if (total_w < limit_minus_buttons) { offset--; @@ -1061,7 +1264,7 @@ void TabBar::ensure_tab_visible(int p_idx) { } ERR_FAIL_INDEX(p_idx, tabs.size()); - if (p_idx >= offset && p_idx <= max_drawn_tab) { + if (tabs[p_idx].hidden || (p_idx >= offset && p_idx <= max_drawn_tab)) { return; } @@ -1079,12 +1282,20 @@ void TabBar::ensure_tab_visible(int p_idx) { int total_w = tabs[max_drawn_tab].ofs_cache - tabs[offset].ofs_cache; for (int i = max_drawn_tab; i <= p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + total_w += tabs[i].size_cache; } int prev_offset = offset; for (int i = offset; i < p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + if (total_w > limit_minus_buttons) { total_w -= tabs[i].size_cache; offset++; @@ -1111,8 +1322,14 @@ Rect2 TabBar::get_tab_rect(int p_tab) const { void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) { ERR_FAIL_INDEX(p_policy, CLOSE_BUTTON_MAX); cb_displaypolicy = p_policy; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } TabBar::CloseButtonDisplayPolicy TabBar::get_tab_close_display_policy() const { @@ -1147,6 +1364,17 @@ int TabBar::get_tabs_rearrange_group() const { return tabs_rearrange_group; } +void TabBar::set_scroll_to_selected(bool p_enabled) { + scroll_to_selected = p_enabled; + if (p_enabled) { + ensure_tab_visible(current); + } +} + +bool TabBar::get_scroll_to_selected() const { + return scroll_to_selected; +} + void TabBar::set_select_with_rmb(bool p_enabled) { select_with_rmb = p_enabled; } @@ -1225,8 +1453,12 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon); + ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabBar::set_tab_button_icon); + ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabBar::get_tab_button_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled); ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabBar::is_tab_disabled); + ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabBar::set_tab_hidden); + ClassDB::bind_method(D_METHOD("is_tab_hidden", "tab_idx"), &TabBar::is_tab_hidden); ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &TabBar::remove_tab); ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &TabBar::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabBar::set_tab_alignment); @@ -1246,16 +1478,19 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group); - + ClassDB::bind_method(D_METHOD("set_scroll_to_selected", "enabled"), &TabBar::set_scroll_to_selected); + ClassDB::bind_method(D_METHOD("get_scroll_to_selected"), &TabBar::get_scroll_to_selected); ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &TabBar::set_select_with_rmb); ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &TabBar::get_select_with_rmb); + ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_rmb_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_close_pressed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_button_pressed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to"))); - ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); @@ -1263,6 +1498,8 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); @@ -1278,5 +1515,6 @@ void TabBar::_bind_methods() { } TabBar::TabBar() { + set_size(Size2(get_size().width, get_minimum_size().height)); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index d0055ae4d2..b428538570 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -63,12 +63,11 @@ private: Ref<TextLine> text_buf; Ref<Texture2D> icon; - int ofs_cache = 0; bool disabled = false; + bool hidden = false; + int ofs_cache = 0; int size_cache = 0; int size_text = 0; - int x_cache = 0; - int x_size_cache = 0; Ref<Texture2D> right_button; Rect2 rb_rect; @@ -102,6 +101,7 @@ private: int min_width = 0; bool scrolling_enabled = true; bool drag_to_rearrange_enabled = false; + bool scroll_to_selected = true; int tabs_rearrange_group = -1; int get_tab_width(int p_idx) const; @@ -150,8 +150,11 @@ public: void set_tab_disabled(int p_tab, bool p_disabled); bool is_tab_disabled(int p_tab) const; - void set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button); - Ref<Texture2D> get_tab_right_button(int p_tab) const; + void set_tab_hidden(int p_tab, bool p_hidden); + bool is_tab_hidden(int p_tab) const; + + void set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_tab_button_icon(int p_tab) const; void set_tab_alignment(AlignmentMode p_alignment); AlignmentMode get_tab_alignment() const; @@ -187,6 +190,9 @@ public: void set_tabs_rearrange_group(int p_group_id); int get_tabs_rearrange_group() const; + void set_scroll_to_selected(bool p_enabled); + bool get_scroll_to_selected() const; + void set_select_with_rmb(bool p_enabled); bool get_select_with_rmb() const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index da202c1c8f..89a17ae854 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -173,7 +173,8 @@ void TextureButton::_notification(int p_what) { bool draw_focus = (has_focus() && focused.is_valid()); // If no other texture is valid, try using focused texture. - if (!texdraw.is_valid() && draw_focus) { + bool draw_focus_only = draw_focus && !texdraw.is_valid(); + if (draw_focus_only) { texdraw = focused; } @@ -232,7 +233,7 @@ void TextureButton::_notification(int p_what) { size.width *= hflip ? -1.0f : 1.0f; size.height *= vflip ? -1.0f : 1.0f; - if (texdraw == focused) { + if (draw_focus_only) { // Do nothing, we only needed to calculate the rectangle. } else if (_tile) { draw_texture_rect(texdraw, Rect2(ofs, size), _tile); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index be4dbfa64d..670b141080 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -821,8 +821,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, Ref<Te theme->set_stylebox("tab_selected", "TabBar", style_tab_selected); theme->set_stylebox("tab_unselected", "TabBar", style_tab_unselected); theme->set_stylebox("tab_disabled", "TabBar", style_tab_disabled); - theme->set_stylebox("close_bg_pressed", "TabBar", button_pressed); - theme->set_stylebox("close_bg_highlight", "TabBar", button_normal); + theme->set_stylebox("button_pressed", "TabBar", button_pressed); + theme->set_stylebox("button_highlight", "TabBar", button_normal); theme->set_icon("increment", "TabBar", icons["scroll_button_right"]); theme->set_icon("increment_highlight", "TabBar", icons["scroll_button_right_hl"]); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 36604073cc..87301a9d3a 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1081,6 +1081,10 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con distance = -distance_max; } + if (p_render_data->cam_ortogonal) { + distance = 1.0; + } + uint32_t indices; surf->sort.lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, &indices); if (p_render_data->render_info) { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 5654fd4ca2..778d7baa5d 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -1429,6 +1429,10 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const distance = -distance_max; } + if (p_render_data->cam_ortogonal) { + distance = 1.0; + } + uint32_t indices; surf->lod_index = storage->mesh_surface_get_lod(surf->surface, inst->lod_model_scale * inst->lod_bias, distance * p_render_data->lod_distance_multiplier, p_render_data->screen_mesh_lod_threshold, &indices); if (p_render_data->render_info) { diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h index 84e71150c7..8bd35df107 100644 --- a/tests/scene/test_code_edit.h +++ b/tests/scene/test_code_edit.h @@ -2786,6 +2786,52 @@ TEST_CASE("[SceneTree][CodeEdit] completion") { SEND_GUI_KEY_EVENT(code_edit, Key::APOSTROPHE); SEND_GUI_KEY_EVENT(code_edit, Key::QUOTEDBL); CHECK(code_edit->get_line(0) == "'\"'"); + + /* Wrap single line selection with brackets */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::BRACKETLEFT); + CHECK(code_edit->get_line(0) == "[abc]"); + + /* Caret should be after the last character of the single line selection */ + CHECK(code_edit->get_caret_column() == 4); + + /* Wrap multi line selection with brackets */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc\nabc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::BRACKETLEFT); + CHECK(code_edit->get_text() == "[abc\nabc]"); + + /* Caret should be after the last character of the multi line selection */ + CHECK(code_edit->get_caret_line() == 1); + CHECK(code_edit->get_caret_column() == 3); + + /* If inserted character is not a auto brace completion open key, replace selected text with the inserted character */ + code_edit->clear(); + code_edit->insert_text_at_caret("abc"); + code_edit->select_all(); + SEND_GUI_KEY_EVENT(code_edit, Key::KEY_1); + CHECK(code_edit->get_text() == "1"); + + /* If potential multichar and single brace completion is matched, it should wrap the single. */ + code_edit->clear(); + code_edit->insert_text_at_caret("\'\'abc"); + code_edit->select(0, 2, 0, 5); + SEND_GUI_KEY_EVENT(code_edit, Key::APOSTROPHE); + CHECK(code_edit->get_text() == "\'\'\'abc\'"); + + /* If only the potential multichar brace completion is matched, it does not wrap or complete. */ + auto_brace_completion_pairs.erase("\'"); + code_edit->set_auto_brace_completion_pairs(auto_brace_completion_pairs); + CHECK_FALSE(code_edit->has_auto_brace_completion_open_key("\'")); + + code_edit->clear(); + code_edit->insert_text_at_caret("\'\'abc"); + code_edit->select(0, 2, 0, 5); + SEND_GUI_KEY_EVENT(code_edit, Key::APOSTROPHE); + CHECK(code_edit->get_text() == "\'\'\'"); } SUBCASE("[CodeEdit] autocomplete") { diff --git a/thirdparty/README.md b/thirdparty/README.md index 6333a0fe87..dc2f174b2b 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -606,7 +606,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/Samsung/thorvg -- Version: 0.7.0 (e527f565b770f0a41df821e6618ccaeea94f465e, 2021) +- Version: 0.7.1 (d53eb2a880002cb770ace1c1ace9c5dfcfc28252, 2022) - License: MIT Files extracted from upstream source: @@ -614,8 +614,6 @@ Files extracted from upstream source: See `thorvg/update-thorvg.sh` for extraction instructions. Set the version number and run the script. -Patches in the `patches` directory should be re-applied after updates. - ## vhacd diff --git a/thirdparty/thorvg/AUTHORS b/thirdparty/thorvg/AUTHORS index 66057232b6..ec06c49118 100644 --- a/thirdparty/thorvg/AUTHORS +++ b/thirdparty/thorvg/AUTHORS @@ -13,3 +13,5 @@ Pankaj Kumar <pankaj.m1@samsung.com> Patryk Kaczmarek <patryk.k@partner.samsung.com> Michal Maciola <m.maciola@samsung.com> Peter Vullings <peter@projectitis.com> +K. S. Ernest (iFire) Lee <ernest.lee@chibifire.com> +RĂ©mi Verschelde <rverschelde@gmail.com> diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 04a450b1bb..41e8f6dafa 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -13,5 +13,5 @@ #define THORVG_JPG_LOADER_SUPPORT 1 -#define THORVG_VERSION_STRING "0.7.0" +#define THORVG_VERSION_STRING "0.7.1" #endif diff --git a/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch b/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch deleted file mode 100644 index a174880306..0000000000 --- a/thirdparty/thorvg/patches/thorvg-pr1159-mingw-fix.patch +++ /dev/null @@ -1,73 +0,0 @@ -diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp -index def8ae169a..cf103774c5 100644 ---- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp -+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp -@@ -51,6 +51,7 @@ - - #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. - -+#include <cstring> - #include <fstream> - #include <float.h> - #include <math.h> -diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp -index 2b62315de8..32685ee620 100644 ---- a/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp -+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgPath.cpp -@@ -50,6 +50,7 @@ - - #define _USE_MATH_DEFINES //Math Constants are not defined in Standard C/C++. - -+#include <cstring> - #include <math.h> - #include <clocale> - #include <ctype.h> -diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp -index 8701fe32b1..ae17634f31 100644 ---- a/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp -+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgSceneBuilder.cpp -@@ -49,6 +49,7 @@ - */ - - -+#include <cstring> - #include <string> - #include "tvgMath.h" - #include "tvgSvgLoaderCommon.h" -diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp -index d5b9cdcf7b..9f269b29a2 100644 ---- a/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp -+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgUtil.cpp -@@ -20,6 +20,7 @@ - * SOFTWARE. - */ - -+#include <cstring> - #include <math.h> - #include <memory.h> - #include "tvgSvgUtil.h" -diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp -index 2e3d5928d9..1571aa4e25 100644 ---- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp -+++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp -@@ -20,6 +20,7 @@ - * SOFTWARE. - */ - -+#include <cstring> - #include <ctype.h> - #include <string> - -diff --git a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp -index 9450d80e88..9dd57e5a89 100644 ---- a/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp -+++ b/thirdparty/thorvg/src/savers/tvg/tvgTvgSaver.cpp -@@ -24,6 +24,8 @@ - #include "tvgTvgSaver.h" - #include "tvgLzw.h" - -+#include <cstring> -+ - #ifdef _WIN32 - #include <malloc.h> - #else diff --git a/thirdparty/thorvg/patches/thorvg-pr1166-vs2017-minmax.patch b/thirdparty/thorvg/patches/thorvg-pr1166-vs2017-minmax.patch deleted file mode 100644 index 0b045bd05a..0000000000 --- a/thirdparty/thorvg/patches/thorvg-pr1166-vs2017-minmax.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp -index 78537e7726..c75e73760e 100644 ---- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp -+++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRenderer.cpp -@@ -23,6 +23,7 @@ - #include "tvgSwCommon.h" - #include "tvgTaskScheduler.h" - #include "tvgSwRenderer.h" -+#include "tvgMath.h" - - /************************************************************************/ - /* Internal Class Implementation */ -@@ -594,10 +595,10 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, - task->surface = surface; - task->mpool = mpool; - task->flags = flags; -- task->bbox.min.x = max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x)); -- task->bbox.min.y = max(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y)); -- task->bbox.max.x = min(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w)); -- task->bbox.max.y = min(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h)); -+ task->bbox.min.x = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.x)); -+ task->bbox.min.y = mathMax(static_cast<SwCoord>(0), static_cast<SwCoord>(vport.y)); -+ task->bbox.max.x = mathMin(static_cast<SwCoord>(surface->w), static_cast<SwCoord>(vport.x + vport.w)); -+ task->bbox.max.y = mathMin(static_cast<SwCoord>(surface->h), static_cast<SwCoord>(vport.y + vport.h)); - - if (!task->pushed) { - task->pushed = true; -diff --git a/thirdparty/thorvg/src/lib/tvgMath.h b/thirdparty/thorvg/src/lib/tvgMath.h -index 9e5c915fc3..94b4fe1cf1 100644 ---- a/thirdparty/thorvg/src/lib/tvgMath.h -+++ b/thirdparty/thorvg/src/lib/tvgMath.h -@@ -29,6 +29,10 @@ - #include "tvgCommon.h" - - -+#define mathMin(x, y) (((x) < (y)) ? (x) : (y)) -+#define mathMax(x, y) (((x) > (y)) ? (x) : (y)) -+ -+ - static inline bool mathZero(float a) - { - return (fabsf(a) < FLT_EPSILON) ? true : false; -@@ -154,4 +158,4 @@ static inline Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs) - } - - --#endif //_TVG_MATH_H_ -\ No newline at end of file -+#endif //_TVG_MATH_H_ diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp index fe22fce017..f9974d9847 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwImage.cpp @@ -84,8 +84,8 @@ bool imagePrepare(SwImage* image, const Matrix* transform, const SwBBox& clipReg //Fast track: Non-transformed image but just shifted. if (image->direct) { - image->ox = -static_cast<uint32_t>(round(transform->e13)); - image->oy = -static_cast<uint32_t>(round(transform->e23)); + image->ox = -static_cast<int32_t>(round(transform->e13)); + image->oy = -static_cast<int32_t>(round(transform->e23)); //Figure out the scale factor by transform matrix } else { auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21)); diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp index deebed16ee..56bc2f77dc 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRaster.cpp @@ -481,7 +481,10 @@ static bool _rasterScaledRleRGBAImage(SwSurface* surface, const SwImage* image, static bool _scaledRleRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity) { Matrix itransform; - if (transform && !mathInverse(transform, &itransform)) return false; + + if (transform) { + if (!mathInverse(transform, &itransform)) return false; + } else mathIdentity(&itransform); auto halfScale = _halfScale(image->scale); @@ -816,7 +819,10 @@ static bool _rasterScaledRGBAImage(SwSurface* surface, const SwImage* image, con static bool _scaledRGBAImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint32_t opacity) { Matrix itransform; - if (transform && !mathInverse(transform, &itransform)) return false; + + if (transform) { + if (!mathInverse(transform, &itransform)) return false; + } else mathIdentity(&itransform); auto halfScale = _halfScale(image->scale); @@ -1113,12 +1119,12 @@ static bool _rasterTranslucentLinearGradientRle(SwSurface* surface, const SwRleD auto dst = &surface->buffer[span->y * surface->stride + span->x]; fillFetchLinear(fill, buffer, span->y, span->x, span->len); if (span->coverage == 255) { - for (uint32_t i = 0; i < span->len; ++i, ++dst) { - *dst = buffer[i] + ALPHA_BLEND(*dst, _ialpha(buffer[i])); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = buffer[x] + ALPHA_BLEND(*dst, _ialpha(buffer[x])); } } else { - for (uint32_t i = 0; i < span->len; ++i, ++dst) { - auto tmp = ALPHA_BLEND(buffer[i], span->coverage); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto tmp = ALPHA_BLEND(buffer[x], span->coverage); *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp)); } } @@ -1142,8 +1148,8 @@ static bool _rasterSolidLinearGradientRle(SwSurface* surface, const SwRleData* r } else { fillFetchLinear(fill, buf, span->y, span->x, span->len); auto dst = &surface->buffer[span->y * surface->stride + span->x]; - for (uint32_t i = 0; i < span->len; ++i) { - dst[i] = INTERPOLATE(span->coverage, buf[i], dst[i]); + for (uint32_t x = 0; x < span->len; ++x) { + dst[x] = INTERPOLATE(span->coverage, buf[x], dst[x]); } } } @@ -1302,12 +1308,12 @@ static bool _rasterTranslucentRadialGradientRle(SwSurface* surface, const SwRleD auto dst = &surface->buffer[span->y * surface->stride + span->x]; fillFetchRadial(fill, buffer, span->y, span->x, span->len); if (span->coverage == 255) { - for (uint32_t i = 0; i < span->len; ++i, ++dst) { - *dst = buffer[i] + ALPHA_BLEND(*dst, _ialpha(buffer[i])); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = buffer[x] + ALPHA_BLEND(*dst, _ialpha(buffer[x])); } } else { - for (uint32_t i = 0; i < span->len; ++i, ++dst) { - auto tmp = ALPHA_BLEND(buffer[i], span->coverage); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + auto tmp = ALPHA_BLEND(buffer[x], span->coverage); *dst = tmp + ALPHA_BLEND(*dst, _ialpha(tmp)); } } @@ -1332,8 +1338,8 @@ static bool _rasterSolidRadialGradientRle(SwSurface* surface, const SwRleData* r } else { fillFetchRadial(fill, buf, span->y, span->x, span->len); auto ialpha = 255 - span->coverage; - for (uint32_t i = 0; i < span->len; ++i, ++dst) { - *dst = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(*dst, ialpha); + for (uint32_t x = 0; x < span->len; ++x, ++dst) { + *dst = ALPHA_BLEND(buf[x], span->coverage) + ALPHA_BLEND(*dst, ialpha); } } } @@ -1487,7 +1493,7 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity) { //Verify Boundary - if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= surface->w || bbox.min.y >= surface->h) return false; + if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= static_cast<SwCoord>(surface->w) || bbox.min.y >= static_cast<SwCoord>(surface->h)) return false; //TOOD: switch (image->format) //TODO: case: _rasterRGBImage() diff --git a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h index 4e8d342137..e96307c874 100644 --- a/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h +++ b/thirdparty/thorvg/src/lib/sw_engine/tvgSwRasterTexmapInternal.h @@ -58,8 +58,8 @@ y = yStart; while (y < yEnd) { - x1 = _xa; - x2 = _xb; + x1 = (int32_t)_xa; + x2 = (int32_t)_xb; if (!region) { minx = INT32_MAX; @@ -160,4 +160,4 @@ next: xb = _xb; ua = _ua; va = _va; -}
\ No newline at end of file +} diff --git a/thirdparty/thorvg/src/lib/tvgMath.h b/thirdparty/thorvg/src/lib/tvgMath.h index 94b4fe1cf1..423fb6eb1b 100644 --- a/thirdparty/thorvg/src/lib/tvgMath.h +++ b/thirdparty/thorvg/src/lib/tvgMath.h @@ -47,7 +47,7 @@ static inline bool mathEqual(float a, float b) static inline bool mathRightAngle(const Matrix* m) { - auto radian = fabsf(atan2(m->e21, m->e11)); + auto radian = fabsf(atan2f(m->e21, m->e11)); if (radian < FLT_EPSILON || mathEqual(radian, float(M_PI_2)) || mathEqual(radian, float(M_PI))) return true; return false; } diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp index 8846613c6b..f27881da42 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgLoader.cpp @@ -47,6 +47,7 @@ JpgLoader::~JpgLoader() { jpgdDelete(decoder); if (freeData) free(data); + free(image); } @@ -128,5 +129,9 @@ unique_ptr<Surface> JpgLoader::bitmap() void JpgLoader::run(unsigned tid) { + if (image) { + free(image); + image = nullptr; + } image = jpgdDecompress(decoder); }
\ No newline at end of file diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp index fa72734ec4..4ccc5788d5 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.cpp @@ -1080,7 +1080,9 @@ namespace DCT_Upsample // Unconditionally frees all allocated m_blocks. void jpeg_decoder::free_all_blocks() { + delete(m_pStream); m_pStream = nullptr; + for (mem_block *b = m_pMem_blocks; b; ) { mem_block *n = b->m_pNext; free(b); @@ -2815,7 +2817,6 @@ int jpeg_decoder::begin_decoding() jpeg_decoder::~jpeg_decoder() { free_all_blocks(); - delete(m_pStream); } @@ -3025,4 +3026,4 @@ unsigned char* jpgdDecompress(jpeg_decoder* decoder) } } return pImage_data; -}
\ No newline at end of file +} diff --git a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h index d32ffd99d4..ca9cb35c32 100644 --- a/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h +++ b/thirdparty/thorvg/src/loaders/jpg/tvgJpgd.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. All rights reserved. + * Copyright (c) 2021 - 2022 Samsung Electronics Co., Ltd. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp index c6d95be5ba..3cc08e902b 100644 --- a/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp +++ b/thirdparty/thorvg/src/loaders/png/tvgPngLoader.cpp @@ -72,6 +72,7 @@ PngLoader::PngLoader() PngLoader::~PngLoader() { if (freeData) free(data); + free(image); } @@ -121,7 +122,7 @@ bool PngLoader::open(const char* data, uint32_t size, bool copy) clear(); lodepng_state_init(&state); - + unsigned int width, height; if (lodepng_inspect(&width, &height, &state, (unsigned char*)(data), size) > 0) return false; @@ -180,10 +181,14 @@ unique_ptr<Surface> PngLoader::bitmap() void PngLoader::run(unsigned tid) { + if (image) { + free(image); + image = nullptr; + } auto width = static_cast<unsigned>(w); auto height = static_cast<unsigned>(h); lodepng_decode(&image, &width, &height, &state, data, size); _premultiply((uint32_t*)(image), width, height); -}
\ No newline at end of file +} diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index cf103774c5..08b3308165 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -541,7 +541,7 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** } } } - } else if (len >= 3 && !strncmp(str, "url", 3)) { + } else if (ref && len >= 3 && !strncmp(str, "url", 3)) { *ref = _idFromUrl((const char*)(str + 3)); } else { //Handle named color @@ -789,7 +789,7 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value) return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); } #ifdef THORVG_LOG_ENABLED - else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON ) { + else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(svgUtilStrtof(value, nullptr)) > FLT_EPSILON) { TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value); } #endif @@ -1611,6 +1611,7 @@ static bool _attrParseImageNode(void* data, const char* key, const char* value) } if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + if (image->href && value) free(image->href); image->href = _idFromHref(value); } else if (!strcmp(key, "id")) { if (node->id && value) free(node->id); @@ -1728,6 +1729,112 @@ error_grad_alloc: } +static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent) +{ + if (parent == nullptr) return; + //Inherit the property of parent if not present in child. + if (!child->curColorSet) { + child->color = parent->color; + child->curColorSet = parent->curColorSet; + } + //Fill + if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { + child->fill.paint.color = parent->fill.paint.color; + child->fill.paint.none = parent->fill.paint.none; + child->fill.paint.curColor = parent->fill.paint.curColor; + if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url); + } + if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { + child->fill.opacity = parent->fill.opacity; + } + if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { + child->fill.fillRule = parent->fill.fillRule; + } + //Stroke + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { + child->stroke.paint.color = parent->stroke.paint.color; + child->stroke.paint.none = parent->stroke.paint.none; + child->stroke.paint.curColor = parent->stroke.paint.curColor; + child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url) : nullptr; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + child->stroke.opacity = parent->stroke.opacity; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { + child->stroke.width = parent->stroke.width; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) { + if (parent->stroke.dash.array.count > 0) { + child->stroke.dash.array.clear(); + child->stroke.dash.array.reserve(parent->stroke.dash.array.count); + for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { + child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); + } + } + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { + child->stroke.cap = parent->stroke.cap; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { + child->stroke.join = parent->stroke.join; + } +} + + +static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) +{ + if (from == nullptr) return; + //Copy the properties of 'from' only if they were explicitly set (not the default ones). + if (from->curColorSet) { + to->color = from->color; + to->curColorSet = true; + } + //Fill + to->fill.flags = (SvgFillFlags)((int)to->fill.flags | (int)from->fill.flags); + if (((int)from->fill.flags & (int)SvgFillFlags::Paint)) { + to->fill.paint.color = from->fill.paint.color; + to->fill.paint.none = from->fill.paint.none; + to->fill.paint.curColor = from->fill.paint.curColor; + if (from->fill.paint.url) to->fill.paint.url = _copyId(from->fill.paint.url); + } + if (((int)from->fill.flags & (int)SvgFillFlags::Opacity)) { + to->fill.opacity = from->fill.opacity; + } + if (((int)from->fill.flags & (int)SvgFillFlags::FillRule)) { + to->fill.fillRule = from->fill.fillRule; + } + //Stroke + to->stroke.flags = (SvgStrokeFlags)((int)to->stroke.flags | (int)from->stroke.flags); + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Paint)) { + to->stroke.paint.color = from->stroke.paint.color; + to->stroke.paint.none = from->stroke.paint.none; + to->stroke.paint.curColor = from->stroke.paint.curColor; + to->stroke.paint.url = from->stroke.paint.url ? _copyId(from->stroke.paint.url) : nullptr; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + to->stroke.opacity = from->stroke.opacity; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Width)) { + to->stroke.width = from->stroke.width; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Dash)) { + if (from->stroke.dash.array.count > 0) { + to->stroke.dash.array.clear(); + to->stroke.dash.array.reserve(from->stroke.dash.array.count); + for (uint32_t i = 0; i < from->stroke.dash.array.count; ++i) { + to->stroke.dash.array.push(from->stroke.dash.array.data[i]); + } + } + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Cap)) { + to->stroke.cap = from->stroke.cap; + } + if (((int)from->stroke.flags & (int)SvgStrokeFlags::Join)) { + to->stroke.join = from->stroke.join; + } +} + + static void _copyAttr(SvgNode* to, const SvgNode* from) { //Copy matrix attribute @@ -1736,7 +1843,8 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) if (to->transform) *to->transform = *from->transform; } //Copy style attribute - *to->style = *from->style; + _styleCopy(to->style, from->style); + to->style->flags = (SvgStyleFlags)((int)to->style->flags | (int)from->style->flags); if (from->style->fill.paint.url) to->style->fill.paint.url = strdup(from->style->fill.paint.url); if (from->style->stroke.paint.url) to->style->stroke.paint.url = strdup(from->style->stroke.paint.url); if (from->style->clipPath.url) to->style->clipPath.url = strdup(from->style->clipPath.url); @@ -1780,15 +1888,17 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) break; } case SvgNodeType::Polygon: { - to->node.polygon.pointsCount = from->node.polygon.pointsCount; - to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); - memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float)); + if ((to->node.polygon.pointsCount = from->node.polygon.pointsCount)) { + to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); + memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float)); + } break; } case SvgNodeType::Polyline: { - to->node.polyline.pointsCount = from->node.polyline.pointsCount; - to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); - memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float)); + if ((to->node.polyline.pointsCount = from->node.polyline.pointsCount)) { + to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); + memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float)); + } break; } case SvgNodeType::Image: { @@ -1806,35 +1916,45 @@ static void _copyAttr(SvgNode* to, const SvgNode* from) } -static void _cloneNode(SvgNode* from, SvgNode* parent) +static void _cloneNode(SvgNode* from, SvgNode* parent, int depth) { + /* Exception handling: Prevent invalid SVG data input. + The size is the arbitrary value, we need an experimental size. */ + if (depth == 8192) { + TVGERR("SVG", "Infinite recursive call - stopped after %d calls! Svg file may be incorrectly formatted.", depth); + return; + } + SvgNode* newNode; - if (!from || !parent) return; + if (!from || !parent || from == parent) return; newNode = _createNode(parent, from->type); - if (!newNode) return; + _styleInherit(newNode->style, parent->style); _copyAttr(newNode, from); auto child = from->child.data; for (uint32_t i = 0; i < from->child.count; ++i, ++child) { - _cloneNode(*child, newNode); + _cloneNode(*child, newNode, depth + 1); } } -static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, char* id) { +static void _postponeCloneNode(SvgLoaderData* loader, SvgNode *node, char* id) +{ loader->cloneNodes.push({node, id}); } -static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes) { +static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc) +{ for (uint32_t i = 0; i < cloneNodes->count; ++i) { auto nodeIdPair = cloneNodes->data[i]; auto defs = _getDefsNode(nodeIdPair.node); auto nodeFrom = _findChildById(defs, nodeIdPair.id); - _cloneNode(nodeFrom, nodeIdPair.node); + if (!nodeFrom) nodeFrom = _findChildById(doc, nodeIdPair.id); + _cloneNode(nodeFrom, nodeIdPair.node, 0); free(nodeIdPair.id); } } @@ -1875,7 +1995,7 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value) defs = _getDefsNode(node); nodeFrom = _findChildById(defs, id); if (nodeFrom) { - _cloneNode(nodeFrom, node); + _cloneNode(nodeFrom, node, 0); free(id); } else { //some svg export software include <defs> element at the end of the file @@ -1883,10 +2003,6 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value) //after the whole file is parsed _postponeCloneNode(loader, node, id); } - } else if (!strcmp(key, "clip-path")) { - _handleClipPathAttr(loader, node, value); - } else if (!strcmp(key, "mask")) { - _handleMaskAttr(loader, node, value); } else { return _attrParseGNode(data, key, value); } @@ -2081,10 +2197,12 @@ static bool _attrParseRadialGradientNode(void* data, const char* key, const char } if (!strcmp(key, "id")) { + if (grad->id && value) free(grad->id); grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + if (grad->ref && value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { grad->userSpace = true; @@ -2269,10 +2387,12 @@ static bool _attrParseLinearGradientNode(void* data, const char* key, const char } if (!strcmp(key, "id")) { + if (grad->id && value) free(grad->id); grad->id = _copyId(value); } else if (!strcmp(key, "spreadMethod")) { grad->spread = _parseSpreadValue(value); } else if (!strcmp(key, "href") || !strcmp(key, "xlink:href")) { + if (grad->ref && value) free(grad->ref); grad->ref = _idFromHref(value); } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { grad->userSpace = true; @@ -2408,6 +2528,7 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, if ((method = _findGroupFactory(tagName))) { //Group + if (empty) return; if (!loader->doc) { if (strcmp(tagName, "svg")) return; //Not a valid svg document node = method(loader, nullptr, attrs, attrsLength); @@ -2493,59 +2614,8 @@ static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content } -static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* parent) +static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node) { - if (parent == nullptr) return; - //Inherit the property of parent if not present in child. - //Fill - if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { - child->fill.paint.color = parent->fill.paint.color; - child->fill.paint.none = parent->fill.paint.none; - child->fill.paint.curColor = parent->fill.paint.curColor; - if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url); - } else if (child->fill.paint.curColor && !child->curColorSet) { - child->color = parent->color; - } - if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { - child->fill.opacity = parent->fill.opacity; - } - if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { - child->fill.fillRule = parent->fill.fillRule; - } - //Stroke - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { - child->stroke.paint.color = parent->stroke.paint.color; - child->stroke.paint.none = parent->stroke.paint.none; - child->stroke.paint.curColor = parent->stroke.paint.curColor; - child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url) : nullptr; - } else if (child->stroke.paint.curColor && !child->curColorSet) { - child->color = parent->color; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { - child->stroke.opacity = parent->stroke.opacity; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { - child->stroke.width = parent->stroke.width; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Dash)) { - if (parent->stroke.dash.array.count > 0) { - child->stroke.dash.array.clear(); - child->stroke.dash.array.reserve(parent->stroke.dash.array.count); - for (uint32_t i = 0; i < parent->stroke.dash.array.count; ++i) { - child->stroke.dash.array.push(parent->stroke.dash.array.data[i]); - } - } - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { - child->stroke.cap = parent->stroke.cap; - } - if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { - child->stroke.join = parent->stroke.join; - } -} - - -static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node){ #ifdef THORVG_LOG_ENABLED auto type = simpleXmlNodeTypeToString(node->type); @@ -2838,14 +2908,14 @@ void SvgLoader::run(unsigned tid) if (loaderData.doc) { _updateStyle(loaderData.doc, nullptr); auto defs = loaderData.doc->node.doc.defs; - if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients); - - if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients); _updateComposite(loaderData.doc, loaderData.doc); if (defs) _updateComposite(loaderData.doc, defs); - if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes); + if (loaderData.cloneNodes.count > 0) _clonePostponedNodes(&loaderData.cloneNodes, loaderData.doc); + + if (loaderData.gradients.count > 0) _updateGradient(loaderData.doc, &loaderData.gradients); + if (defs) _updateGradient(loaderData.doc, &defs->node.defs.gradients); } root = svgSceneBuild(loaderData.doc, vx, vy, vw, vh, w, h, preserveAspect, svgPath); } diff --git a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp index 1571aa4e25..ee199da231 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgXmlParser.cpp @@ -220,15 +220,15 @@ static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &to if ((itr + sizeof("<!DOCTYPE>") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) { toff = sizeof("!DOCTYPE") - 1; return SimpleXMLType::Doctype; - } else if (itr + sizeof("<!>") - 1 < itrEnd) { - toff = sizeof("!") - 1; - return SimpleXMLType::DoctypeChild; } else if ((itr + sizeof("<![CDATA[]]>") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) { toff = sizeof("![CDATA[") - 1; return SimpleXMLType::CData; } else if ((itr + sizeof("<!---->") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) { toff = sizeof("!--") - 1; return SimpleXMLType::Comment; + } else if (itr + sizeof("<!>") - 1 < itrEnd) { + toff = sizeof("!") - 1; + return SimpleXMLType::DoctypeChild; } return SimpleXMLType::Open; } diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index c200131eba..ce3d5eed1c 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,4 +1,4 @@ -VERSION=0.7.0 +VERSION=0.7.1 rm -rf AUTHORS inc LICENSE src *.zip curl -L -O https://github.com/Samsung/thorvg/archive/refs/tags/v$VERSION.zip bsdtar --strip-components=1 -xvf *.zip |