diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 489fd526dec..d20e3e0e827 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -255,8 +255,8 @@ bool InputMap::event_is_action(const Ref &p_event, const StringName int InputMap::event_get_index(const Ref &p_event, const StringName &p_action, bool p_exact_match) const { int index = -1; - event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); - return index; + bool valid = event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); + return valid ? index : -1; } bool InputMap::event_get_action_status(const Ref &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const { diff --git a/core/math/color.h b/core/math/color.h index c578e6e2ec6..8f7c9ca5390 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -176,16 +176,16 @@ struct [[nodiscard]] Color { _FORCE_INLINE_ Color srgb_to_linear() const { return Color( - r < 0.04045f ? r * (1.0f / 12.92f) : Math::pow((r + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), - g < 0.04045f ? g * (1.0f / 12.92f) : Math::pow((g + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), - b < 0.04045f ? b * (1.0f / 12.92f) : Math::pow((b + 0.055f) * (float)(1.0 / (1.0 + 0.055)), 2.4f), + r < 0.04045f ? r * (1.0f / 12.92f) : Math::pow(float((r + 0.055) * (1.0 / (1.0 + 0.055))), 2.4f), + g < 0.04045f ? g * (1.0f / 12.92f) : Math::pow(float((g + 0.055) * (1.0 / (1.0 + 0.055))), 2.4f), + b < 0.04045f ? b * (1.0f / 12.92f) : Math::pow(float((b + 0.055) * (1.0 / (1.0 + 0.055))), 2.4f), a); } _FORCE_INLINE_ Color linear_to_srgb() const { return Color( - r < 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * Math::pow(r, 1.0f / 2.4f) - 0.055f, - g < 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * Math::pow(g, 1.0f / 2.4f) - 0.055f, - b < 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * Math::pow(b, 1.0f / 2.4f) - 0.055f, a); + r < 0.0031308f ? 12.92f * r : (1.0 + 0.055) * Math::pow(r, 1.0f / 2.4f) - 0.055, + g < 0.0031308f ? 12.92f * g : (1.0 + 0.055) * Math::pow(g, 1.0f / 2.4f) - 0.055, + b < 0.0031308f ? 12.92f * b : (1.0 + 0.055) * Math::pow(b, 1.0f / 2.4f) - 0.055, a); } static Color hex(uint32_t p_hex); diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp index 9a781bcc508..8fcde893cde 100644 --- a/core/math/geometry_2d.cpp +++ b/core/math/geometry_2d.cpp @@ -37,7 +37,8 @@ #define STB_RECT_PACK_IMPLEMENTATION #include "thirdparty/misc/stb_rect_pack.h" -#define PRECISION 5 // Based on CMP_EPSILON. +const int clipper_precision = 5; // Based on CMP_EPSILON. +const double clipper_scale = Math::pow(10.0, clipper_precision); Vector> Geometry2D::decompose_polygon_in_convex(const Vector &polygon) { Vector> decomp; @@ -226,7 +227,7 @@ Vector> Geometry2D::_polypaths_do_operation(PolyBooleanOperation path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y); } - ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision. + ClipperD clp(clipper_precision); // Scale points up internally to attain the desired precision. clp.PreserveCollinear(false); // Remove redundant vertices. if (is_a_open) { clp.AddOpenSubject({ path_a }); @@ -300,9 +301,10 @@ Vector> Geometry2D::_polypath_offset(const Vector &p_poly } // Inflate/deflate. - PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0); - // Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults, - // and the PRECISION is used to scale points up internally, to attain the desired precision. + PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, clipper_precision, 0.25 * clipper_scale); + // Here the points are scaled up internally and + // the arc_tolerance is scaled accordingly + // to attain the desired precision. Vector> polypaths; for (PathsD::size_type i = 0; i < paths.size(); ++i) { diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml index cfdc172fd1d..6b25be490ed 100644 --- a/doc/classes/EditorInspector.xml +++ b/doc/classes/EditorInspector.xml @@ -28,6 +28,7 @@ + diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index 5ec65640a02..f91006f69a0 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -59,7 +59,7 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. - Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information. A [LabelSettings] resource that can be shared between multiple [Label] nodes. Takes priority over theme properties. diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml index 4c708974525..ff26c5490dd 100644 --- a/doc/classes/Label3D.xml +++ b/doc/classes/Label3D.xml @@ -73,7 +73,7 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. - Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information. Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 7e0c39ac7c8..953c28657b2 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -625,6 +625,12 @@ If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code]. + + Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. + + + Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information. + Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. @@ -656,6 +662,9 @@ The number of spaces associated with a single tab length. Does not affect [code]\t[/code] in text tags, only indent tags. + + Aligns text to the given tab-stops. + The label's text in BBCode format. Is not representative of manual modifications to the internal tag stack. Erases changes made by other methods when edited. [b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [member text] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call. diff --git a/doc/classes/TextMesh.xml b/doc/classes/TextMesh.xml index 9e705311c56..898d19aed39 100644 --- a/doc/classes/TextMesh.xml +++ b/doc/classes/TextMesh.xml @@ -31,7 +31,7 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. - Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information. Language code used for text shaping algorithms, if left empty current locale is used instead. diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index c6511a2b8e9..46197f19b88 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -278,7 +278,7 @@ Ellipsis character used for text clipping. - Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information. Limits the lines of text shown. diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index edb92ce0dfa..df362039b8a 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -2186,7 +2186,9 @@ void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backb glBindFramebuffer(GL_FRAMEBUFFER, render_target->fbo); glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 4); glBindTexture(GL_TEXTURE_2D, render_target->backbuffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0); + if (render_target->backbuffer != 0) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0); + } } if (render_target->is_transparent || p_to_backbuffer) { diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 331f6956f12..cbd1d3bb8c4 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -351,9 +351,6 @@ RasterizerGLES3::RasterizerGLES3() { } } - // Disable OpenGL linear to sRGB conversion, because Redot will always do this conversion itself. - glDisable(GL_FRAMEBUFFER_SRGB); - // OpenGL needs to be initialized before initializing the Rasterizers config = memnew(GLES3::Config); utilities = memnew(GLES3::Utilities); @@ -370,6 +367,11 @@ RasterizerGLES3::RasterizerGLES3() { fog = memnew(GLES3::Fog); canvas = memnew(RasterizerCanvasGLES3()); scene = memnew(RasterizerSceneGLES3()); + + // Disable OpenGL linear to sRGB conversion, because Godot will always do this conversion itself. + if (config->srgb_framebuffer_supported) { + glDisable(GL_FRAMEBUFFER_SRGB); + } } RasterizerGLES3::~RasterizerGLES3() { diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index e903f822545..bbcd29e60ec 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -90,6 +90,7 @@ Config::Config() { etc2_supported = false; s3tc_supported = true; rgtc_supported = true; //RGTC - core since OpenGL version 3.0 + srgb_framebuffer_supported = true; } else { float_texture_supported = extensions.has("GL_EXT_color_buffer_float"); etc2_supported = true; @@ -102,6 +103,7 @@ Config::Config() { s3tc_supported = extensions.has("GL_EXT_texture_compression_dxt1") || extensions.has("GL_EXT_texture_compression_s3tc") || extensions.has("WEBGL_compressed_texture_s3tc"); #endif rgtc_supported = extensions.has("GL_EXT_texture_compression_rgtc") || extensions.has("GL_ARB_texture_compression_rgtc") || extensions.has("EXT_texture_compression_rgtc"); + srgb_framebuffer_supported = extensions.has("GL_EXT_sRGB_write_control"); } glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &max_vertex_texture_image_units); diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h index 90181d9b5d6..3cf38915b94 100644 --- a/drivers/gles3/storage/config.h +++ b/drivers/gles3/storage/config.h @@ -81,6 +81,7 @@ class Config { bool astc_supported = false; bool astc_hdr_supported = false; bool astc_layered_supported = false; + bool srgb_framebuffer_supported = false; bool force_vertex_shading = false; diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp index 150ffc4ccab..9c1482372d0 100644 --- a/drivers/gles3/storage/light_storage.cpp +++ b/drivers/gles3/storage/light_storage.cpp @@ -1387,7 +1387,7 @@ bool LightStorage::shadow_atlas_update_light(RID p_atlas, RID p_light_instance, old_shadow = old_key & SHADOW_INDEX_MASK; // Only re-allocate if a better option is available, and enough time has passed. - should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); + should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (tick - shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick > shadow_atlas_realloc_tolerance_msec); should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version; if (!should_realloc) { diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index cd075cf1360..cbbc1181587 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -586,7 +586,7 @@ ActionMapEditor::ActionMapEditor() { show_builtin_actions_checkbutton = memnew(CheckButton); show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions")); - show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions)); + show_builtin_actions_checkbutton->connect(SceneStringName(toggled), callable_mp(this, &ActionMapEditor::set_show_builtin_actions)); add_hbox->add_child(show_builtin_actions_checkbutton); show_builtin_actions = EditorSettings::get_singleton()->get_project_metadata("project_settings", "show_builtin_actions", false); diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 1ac9afa147c..96d56343db3 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -1444,6 +1444,11 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { i++; } + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); } else if (select_single_attempt != IntPair(-1, -1)) { @@ -1968,15 +1973,6 @@ void AnimationBezierTrackEdit::delete_selection() { void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) { int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle); p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode); - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Animation Bezier Curve Change Call")); - AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); - if (ape) { - undo_redo->add_do_method(ape, "_animation_update_key_frame"); - undo_redo->add_undo_method(ape, "_animation_update_key_frame"); - } - undo_redo->commit_action(); } void AnimationBezierTrackEdit::_bind_methods() { diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 5f3780a58d1..764137e5ff8 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -757,13 +757,13 @@ FindReplaceBar::FindReplaceBar() { hbc_option_search->add_child(case_sensitive); case_sensitive->set_text(TTR("Match Case")); case_sensitive->set_focus_mode(FOCUS_NONE); - case_sensitive->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed)); + case_sensitive->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed)); whole_words = memnew(CheckBox); hbc_option_search->add_child(whole_words); whole_words->set_text(TTR("Whole Words")); whole_words->set_focus_mode(FOCUS_NONE); - whole_words->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed)); + whole_words->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed)); // Replace toolbar replace_text = memnew(LineEdit); @@ -788,7 +788,7 @@ FindReplaceBar::FindReplaceBar() { hbc_option_replace->add_child(selection_only); selection_only->set_text(TTR("Selection Only")); selection_only->set_focus_mode(FOCUS_NONE); - selection_only->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed)); + selection_only->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed)); hide_button = memnew(TextureButton); add_child(hide_button); diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp index 9ce74f2879d..0bc1e45136a 100644 --- a/editor/editor_asset_installer.cpp +++ b/editor/editor_asset_installer.cpp @@ -690,7 +690,7 @@ EditorAssetInstaller::EditorAssetInstaller() { show_source_files_button->set_toggle_mode(true); show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install.")); remapping_tools->add_child(show_source_files_button); - show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false)); + show_source_files_button->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false)); Button *target_dir_button = memnew(Button); target_dir_button->set_text(TTR("Change Install Folder")); @@ -703,7 +703,7 @@ EditorAssetInstaller::EditorAssetInstaller() { skip_toplevel_check = memnew(CheckBox); skip_toplevel_check->set_text(TTR("Ignore asset root")); skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files.")); - skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel)); + skip_toplevel_check->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel)); remapping_tools->add_child(skip_toplevel_check); remapping_tools->add_spacer(); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index e84eb2ac069..2817a9cb0c3 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2768,8 +2768,9 @@ void EditorInspector::update_tree() { // TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit. StringName current_selected = property_selected; int current_focusable = -1; - // Temporarily disable focus following to avoid jumping while the inspector is updating. - set_follow_focus(false); + + // Temporarily disable focus following on the root inspector to avoid jumping while the inspector is updating. + get_root_inspector()->set_follow_focus(false); if (property_focusable != -1) { // Check that focusable is actually focusable. @@ -2797,6 +2798,7 @@ void EditorInspector::update_tree() { _clear(!object); if (!object) { + get_root_inspector()->set_follow_focus(true); return; } @@ -3534,7 +3536,8 @@ void EditorInspector::update_tree() { // Updating inspector might invalidate some editing owners. EditorNode::get_singleton()->hide_unused_editors(); } - set_follow_focus(true); + + get_root_inspector()->set_follow_focus(true); } void EditorInspector::update_property(const String &p_prop) { @@ -3779,11 +3782,10 @@ void EditorInspector::set_use_wide_editors(bool p_enable) { wide_editors = p_enable; } -void EditorInspector::set_sub_inspector(bool p_enable) { - sub_inspector = p_enable; - if (!is_inside_tree()) { - return; - } +void EditorInspector::set_root_inspector(EditorInspector *p_root_inspector) { + root_inspector = p_root_inspector; + // Only the root inspector should follow focus. + set_follow_focus(false); } void EditorInspector::set_use_deletable_properties(bool p_enabled) { @@ -4101,13 +4103,13 @@ void EditorInspector::_notification(int p_what) { EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &EditorInspector::_feature_profile_changed)); set_process(is_visible_in_tree()); add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); - if (!sub_inspector) { + if (!is_sub_inspector()) { get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed)); } } break; case NOTIFICATION_PREDELETE: { - if (!sub_inspector && is_inside_tree()) { + if (!is_sub_inspector() && is_inside_tree()) { get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed)); } edit(nullptr); @@ -4166,7 +4168,7 @@ void EditorInspector::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { bool needs_update = false; - if (EditorThemeManager::is_generated_theme_outdated() && !sub_inspector) { + if (!is_sub_inspector() && EditorThemeManager::is_generated_theme_outdated()) { add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree"))); } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index fb8b604fb7f..5d5bb6a8787 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -488,6 +488,7 @@ class EditorInspector : public ScrollContainer { static Ref inspector_plugins[MAX_PLUGINS]; static int inspector_plugin_count; + EditorInspector *root_inspector = nullptr; VBoxContainer *main_vbox = nullptr; // Map used to cache the instantiated editors. @@ -516,7 +517,6 @@ class EditorInspector : public ScrollContainer { bool update_all_pending = false; bool read_only = false; bool keying = false; - bool sub_inspector = false; bool wide_editors = false; bool deletable_properties = false; @@ -647,8 +647,9 @@ class EditorInspector : public ScrollContainer { String get_object_class() const; void set_use_wide_editors(bool p_enable); - void set_sub_inspector(bool p_enable); - bool is_sub_inspector() const { return sub_inspector; } + void set_root_inspector(EditorInspector *p_root_inspector); + EditorInspector *get_root_inspector() { return is_sub_inspector() ? root_inspector : this; } + bool is_sub_inspector() const { return root_inspector != nullptr; } void set_use_deletable_properties(bool p_enabled); diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp index 9d3148339b5..4a334f10075 100644 --- a/editor/editor_locale_dialog.cpp +++ b/editor/editor_locale_dialog.cpp @@ -409,7 +409,7 @@ EditorLocaleDialog::EditorLocaleDialog() { edit_filters->set_text(TTR("Edit Filters")); edit_filters->set_toggle_mode(true); edit_filters->set_pressed(false); - edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters)); + edit_filters->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_edit_filters)); hb_filter->add_child(edit_filters); } { @@ -417,7 +417,7 @@ EditorLocaleDialog::EditorLocaleDialog() { advanced->set_text(TTR("Advanced")); advanced->set_toggle_mode(true); advanced->set_pressed(false); - advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced)); + advanced->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_toggle_advanced)); hb_filter->add_child(advanced); } vb->add_child(hb_filter); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 8d32f30ca1d..86b60bc5a36 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -516,7 +516,7 @@ EditorLog::EditorLog() { collapse_button->set_tooltip_text(TTR("Collapse duplicate messages into one log entry. Shows number of occurrences.")); collapse_button->set_toggle_mode(true); collapse_button->set_pressed(false); - collapse_button->connect("toggled", callable_mp(this, &EditorLog::_set_collapse)); + collapse_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_collapse)); hb_tools2->add_child(collapse_button); // Show Search. @@ -527,7 +527,7 @@ EditorLog::EditorLog() { show_search_button->set_pressed(true); show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTR("Focus Search/Filter Bar"), KeyModifierMask::CMD_OR_CTRL | Key::F)); show_search_button->set_shortcut_context(this); - show_search_button->connect("toggled", callable_mp(this, &EditorLog::_set_search_visible)); + show_search_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_search_visible)); hb_tools2->add_child(show_search_button); // Message Type Filters. diff --git a/editor/editor_log.h b/editor/editor_log.h index a6d74712502..dfebfcb3a78 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -104,7 +104,7 @@ class EditorLog : public HBoxContainer { toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1)); toggle_button->set_focus_mode(FOCUS_NONE); // When toggled call the callback and pass the MessageType this button is for. - toggle_button->connect("toggled", p_toggled_callback.bind(type)); + toggle_button->connect(SceneStringName(toggled), p_toggled_callback.bind(type)); } int get_message_count() { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index fd57a982f7b..19f4a5c86ae 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3319,7 +3319,10 @@ void EditorPropertyResource::update_property() { sub_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); sub_inspector->set_use_doc_hints(true); - sub_inspector->set_sub_inspector(true); + EditorInspector *parent_inspector = get_parent_inspector(); + ERR_FAIL_NULL(parent_inspector); + sub_inspector->set_root_inspector(parent_inspector->get_root_inspector()); + sub_inspector->set_property_name_style(InspectorDock::get_singleton()->get_property_name_style()); sub_inspector->connect("property_keyed", callable_mp(this, &EditorPropertyResource::_sub_inspector_property_keyed)); @@ -3423,7 +3426,8 @@ void EditorPropertyResource::_notification(int p_what) { switch (p_what) { case NOTIFICATION_EXIT_TREE: { const EditorInspector *ei = get_parent_inspector(); - if (ei && !ei->is_main_editor_inspector()) { + const EditorInspector *main_ei = InspectorDock::get_inspector_singleton(); + if (ei && main_ei && ei != main_ei && !main_ei->is_ancestor_of(ei)) { fold_resource(); } } break; diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp index e7864884a0a..069e280b970 100644 --- a/editor/editor_properties_vector.cpp +++ b/editor/editor_properties_vector.cpp @@ -238,7 +238,7 @@ EditorPropertyVectorN::EditorPropertyVectorN(Variant::Type p_type, bool p_force_ linked->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED); linked->set_tooltip_text(TTR("Lock/Unlock Component Ratio")); linked->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyVectorN::_update_ratio)); - linked->connect(SNAME("toggled"), callable_mp(this, &EditorPropertyVectorN::_store_link)); + linked->connect(SceneStringName(toggled), callable_mp(this, &EditorPropertyVectorN::_store_link)); hb->add_child(linked); add_child(hb); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 9249820276a..83177ab8425 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -1414,12 +1414,12 @@ ProjectExportDialog::ProjectExportDialog() { sec_scroll_container->add_child(sec_vb); enc_pck = memnew(CheckButton); - enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); + enc_pck->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_pck_changed)); enc_pck->set_text(TTR("Encrypt Exported PCK")); sec_vb->add_child(enc_pck); enc_directory = memnew(CheckButton); - enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed)); + enc_directory->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_directory_changed)); enc_directory->set_text(TTR("Encrypt Index (File Names and Info)")); sec_vb->add_child(enc_directory); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 5a9ef32e989..5e1b9e8e047 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1180,7 +1180,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } } -void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorites) { +void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorites, bool p_navigate) { String fpath = p_path; if (fpath.ends_with("/")) { // Ignore a directory. @@ -1247,7 +1247,9 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit EditorNode::get_singleton()->load_resource(fpath); } } - _navigate_to_path(fpath, p_select_in_favorites); + if (p_navigate) { + _navigate_to_path(fpath, p_select_in_favorites); + } } void FileSystemDock::_tree_activate_file() { @@ -1261,7 +1263,7 @@ void FileSystemDock::_tree_activate_file() { bool collapsed = selected->is_collapsed(); selected->set_collapsed(!collapsed); } else { - _select_file(file_path, is_favorite && !file_path.ends_with("/")); + _select_file(file_path, is_favorite && !file_path.ends_with("/"), false); } } } diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 94735fefa40..8f2b58b7089 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -263,7 +263,7 @@ class FileSystemDock : public VBoxContainer { void _set_file_display(bool p_active); void _fs_changed(); - void _select_file(const String &p_path, bool p_select_in_favorites = false); + void _select_file(const String &p_path, bool p_select_in_favorites = false, bool p_navigate = true); void _tree_activate_file(); void _file_list_activate_file(int p_idx); void _file_multi_selected(int p_index, bool p_selected); diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp index 4bd2c3e801e..0e7c2d361e4 100644 --- a/editor/groups_editor.cpp +++ b/editor/groups_editor.cpp @@ -650,7 +650,7 @@ void GroupsEditor::_show_add_group_dialog() { add_group_description->set_editable(false); gc->add_child(add_group_description); - global_group_button->connect("toggled", callable_mp(add_group_description, &LineEdit::set_editable)); + global_group_button->connect(SceneStringName(toggled), callable_mp(add_group_description, &LineEdit::set_editable)); add_group_dialog->register_text_enter(add_group_name); add_group_dialog->register_text_enter(add_group_description); diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp index 44c182d6653..413909102af 100644 --- a/editor/gui/editor_bottom_panel.cpp +++ b/editor/gui/editor_bottom_panel.cpp @@ -160,7 +160,7 @@ void EditorBottomPanel::load_layout_from_config(Ref p_config_file, c Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref &p_shortcut, bool p_at_front) { Button *tb = memnew(Button); tb->set_theme_type_variation("BottomPanelButton"); - tb->connect("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item)); + tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item)); tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable()); tb->set_text(p_text); tb->set_shortcut(p_shortcut); @@ -297,5 +297,5 @@ EditorBottomPanel::EditorBottomPanel() { expand_button->set_theme_type_variation("FlatMenuButton"); expand_button->set_toggle_mode(true); expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12)); - expand_button->connect("toggled", callable_mp(this, &EditorBottomPanel::_expand_button_toggled)); + expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled)); } diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 97870b12f62..2a9ef7a254c 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -45,6 +45,7 @@ #include "editor/themes/editor_scale.h" #include "scene/gui/center_container.h" #include "scene/gui/check_box.h" +#include "scene/gui/flow_container.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/margin_container.h" @@ -1767,30 +1768,38 @@ void EditorFileDialog::_update_option_controls() { } options_dirty = false; - while (grid_options->get_child_count() > 0) { - Node *child = grid_options->get_child(0); - grid_options->remove_child(child); + while (flow_checkbox_options->get_child_count() > 0) { + Node *child = flow_checkbox_options->get_child(0); + flow_checkbox_options->remove_child(child); child->queue_free(); } + while (grid_select_options->get_child_count() > 0) { + Node *child = grid_select_options->get_child(0); + grid_select_options->remove_child(child); + child->queue_free(); + } + selected_options.clear(); for (const EditorFileDialog::Option &opt : options) { - Label *lbl = memnew(Label); - lbl->set_text(opt.name); - grid_options->add_child(lbl); if (opt.values.is_empty()) { CheckBox *cb = memnew(CheckBox); cb->set_pressed(opt.default_idx); - grid_options->add_child(cb); - cb->connect("toggled", callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name)); + cb->set_text(opt.name); + flow_checkbox_options->add_child(cb); + cb->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name)); selected_options[opt.name] = (bool)opt.default_idx; } else { + Label *lbl = memnew(Label); + lbl->set_text(opt.name); + grid_select_options->add_child(lbl); + OptionButton *ob = memnew(OptionButton); for (const String &val : opt.values) { ob->add_item(val); } ob->select(opt.default_idx); - grid_options->add_child(ob); + grid_select_options->add_child(ob); ob->connect(SceneStringName(item_selected), callable_mp(this, &EditorFileDialog::_option_changed_item_selected).bind(opt.name)); selected_options[opt.name] = opt.default_idx; } @@ -2060,11 +2069,13 @@ void EditorFileDialog::add_side_menu(Control *p_menu, const String &p_title) { void EditorFileDialog::_update_side_menu_visibility(bool p_native_dlg) { if (p_native_dlg) { pathhb->set_visible(false); - grid_options->set_visible(false); + flow_checkbox_options->set_visible(false); + grid_select_options->set_visible(false); list_hb->set_visible(false); } else { pathhb->set_visible(true); - grid_options->set_visible(true); + flow_checkbox_options->set_visible(true); + grid_select_options->set_visible(true); list_hb->set_visible(true); } } @@ -2149,7 +2160,7 @@ EditorFileDialog::EditorFileDialog() { show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); show_hidden->set_tooltip_text(TTR("Toggle the visibility of hidden files.")); - show_hidden->connect("toggled", callable_mp(this, &EditorFileDialog::set_show_hidden_files)); + show_hidden->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::set_show_hidden_files)); pathhb->add_child(show_hidden); pathhb->add_child(memnew(VSeparator)); @@ -2194,10 +2205,15 @@ EditorFileDialog::EditorFileDialog() { body_hsplit->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbc->add_child(body_hsplit); - grid_options = memnew(GridContainer); - grid_options->set_h_size_flags(Control::SIZE_SHRINK_CENTER); - grid_options->set_columns(2); - vbc->add_child(grid_options); + flow_checkbox_options = memnew(HFlowContainer); + flow_checkbox_options->set_h_size_flags(Control::SIZE_EXPAND_FILL); + flow_checkbox_options->set_alignment(FlowContainer::ALIGNMENT_CENTER); + vbc->add_child(flow_checkbox_options); + + grid_select_options = memnew(GridContainer); + grid_select_options->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + grid_select_options->set_columns(2); + vbc->add_child(grid_select_options); list_hb = memnew(HSplitContainer); list_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h index 9b81ec0da5e..49e53946636 100644 --- a/editor/gui/editor_file_dialog.h +++ b/editor/gui/editor_file_dialog.h @@ -40,6 +40,7 @@ class GridContainer; class DependencyRemoveDialog; class HSplitContainer; +class HFlowContainer; class ItemList; class OptionButton; class PopupMenu; @@ -91,7 +92,8 @@ class EditorFileDialog : public ConfirmationDialog { Button *makedir = nullptr; Access access = ACCESS_RESOURCES; - GridContainer *grid_options = nullptr; + HFlowContainer *flow_checkbox_options = nullptr; + GridContainer *grid_select_options = nullptr; VBoxContainer *vbox = nullptr; FileMode mode = FILE_MODE_SAVE_FILE; bool can_create_dir = false; diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index c18f06ce992..e79e8ecf9bc 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -447,7 +447,7 @@ EditorRunBar::EditorRunBar() { write_movie_button->set_pressed(false); write_movie_button->set_focus_mode(Control::FOCUS_NONE); write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file.")); - write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled)); + write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled)); quick_run = memnew(EditorQuickOpen); add_child(quick_run); diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 5c130179c3b..63e5743d2aa 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -38,10 +38,6 @@ #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" -bool EditorSpinSlider::is_text_field() const { - return true; -} - String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const { if (!read_only && grabber->is_visible()) { Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL; diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h index 235c0b01104..956f262a833 100644 --- a/editor/gui/editor_spin_slider.h +++ b/editor/gui/editor_spin_slider.h @@ -98,8 +98,6 @@ class EditorSpinSlider : public Range { void _focus_entered(); public: - virtual bool is_text_field() const override; - String get_tooltip(const Point2 &p_pos) const override; String get_text_value() const; diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index bb149b81312..0947cfcce32 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -1774,7 +1774,7 @@ SceneTreeDialog::SceneTreeDialog() { // Add 'Show All' button to HBoxContainer next to the filter, visible only when valid_types is defined. show_all_nodes = memnew(CheckButton); show_all_nodes->set_text(TTR("Show All")); - show_all_nodes->connect("toggled", callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed)); + show_all_nodes->connect(SceneStringName(toggled), callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed)); show_all_nodes->set_h_size_flags(Control::SIZE_SHRINK_BEGIN); show_all_nodes->hide(); filter_hbc->add_child(show_all_nodes); diff --git a/editor/history_dock.cpp b/editor/history_dock.cpp index bdad87066d3..2ba023733cb 100644 --- a/editor/history_dock.cpp +++ b/editor/history_dock.cpp @@ -247,8 +247,8 @@ HistoryDock::HistoryDock() { current_scene_checkbox->set_text(TTR("Scene")); current_scene_checkbox->set_h_size_flags(SIZE_EXPAND_FILL); current_scene_checkbox->set_clip_text(true); - current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1)); - current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1)); + current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1)); + current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1)); global_history_checkbox = memnew(CheckBox); mode_hb->add_child(global_history_checkbox); @@ -257,8 +257,8 @@ HistoryDock::HistoryDock() { global_history_checkbox->set_text(TTR("Global")); global_history_checkbox->set_h_size_flags(SIZE_EXPAND_FILL); global_history_checkbox->set_clip_text(true); - global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1)); - global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1)); + global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1)); + global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1)); action_list = memnew(ItemList); action_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index e94c2625bd5..826267f52e9 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -248,6 +248,8 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, bool smoothing = true; const uint32_t no_smoothing_smooth_group = (uint32_t)-1; + bool uses_uvs = false; + while (true) { String l = f->get_line().strip_edges(); while (l.length() && l[l.length() - 1] == '\\') { @@ -322,6 +324,17 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, idx = 1 ^ idx; } + // Check UVs before faces as we may need to generate dummy tangents if there are no UVs. + if (face[idx].size() >= 2 && !face[idx][1].is_empty()) { + int uv = face[idx][1].to_int() - 1; + if (uv < 0) { + uv += uvs.size() + 1; + } + ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_FILE_CORRUPT); + surf_tool->set_uv(uvs[uv]); + uses_uvs = true; + } + if (face[idx].size() == 3) { int norm = face[idx][2].to_int() - 1; if (norm < 0) { @@ -329,28 +342,19 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, } ERR_FAIL_INDEX_V(norm, normals.size(), ERR_FILE_CORRUPT); surf_tool->set_normal(normals[norm]); - if (generate_tangents && uvs.is_empty()) { + if (generate_tangents && !uses_uvs) { // We can't generate tangents without UVs, so create dummy tangents. Vector3 tan = Vector3(normals[norm].z, -normals[norm].x, normals[norm].y).cross(normals[norm].normalized()).normalized(); surf_tool->set_tangent(Plane(tan.x, tan.y, tan.z, 1.0)); } } else { // No normals, use a dummy tangent since normals and tangents will be generated. - if (generate_tangents && uvs.is_empty()) { + if (generate_tangents && !uses_uvs) { // We can't generate tangents without UVs, so create dummy tangents. surf_tool->set_tangent(Plane(1.0, 0.0, 0.0, 1.0)); } } - if (face[idx].size() >= 2 && !face[idx][1].is_empty()) { - int uv = face[idx][1].to_int() - 1; - if (uv < 0) { - uv += uvs.size() + 1; - } - ERR_FAIL_INDEX_V(uv, uvs.size(), ERR_FILE_CORRUPT); - surf_tool->set_uv(uvs[uv]); - } - int vtx = face[idx][0].to_int() - 1; if (vtx < 0) { vtx += vertices.size() + 1; @@ -409,7 +413,7 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, surf_tool->generate_normals(); } - if (generate_tangents && uvs.size()) { + if (generate_tangents && uses_uvs) { surf_tool->generate_tangents(); } @@ -428,10 +432,11 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, Array array = surf_tool->commit_to_arrays(); - if (mesh_flags & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES && generate_tangents) { - // Compression is enabled, so let's validate that the normals and tangents are correct. + if (mesh_flags & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES && generate_tangents && uses_uvs) { + // Compression is enabled, so let's validate that the normals and generated tangents are correct. Vector norms = array[Mesh::ARRAY_NORMAL]; Vector tangents = array[Mesh::ARRAY_TANGENT]; + ERR_FAIL_COND_V(tangents.is_empty(), ERR_FILE_CORRUPT); for (int vert = 0; vert < norms.size(); vert++) { Vector3 tan = Vector3(tangents[vert * 4 + 0], tangents[vert * 4 + 1], tangents[vert * 4 + 2]); if (abs(tan.dot(norms[vert])) > 0.0001) { @@ -456,6 +461,7 @@ static Error _parse_obj(const String &p_path, List> &r_meshes, surf_tool->clear(); surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES); + uses_uvs = false; } if (l.begins_with("o ") || f->eof_reached()) { diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp index 605d22e9762..def66a3362d 100644 --- a/editor/import/audio_stream_import_settings.cpp +++ b/editor/import/audio_stream_import_settings.cpp @@ -539,7 +539,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() { loop = memnew(CheckBox); loop->set_text(TTR("Enable")); loop->set_tooltip_text(TTR("Enable looping.")); - loop->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); + loop->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); loop_hb->add_child(loop); loop_hb->add_spacer(); loop_hb->add_child(memnew(Label(TTR("Offset:")))); @@ -556,7 +556,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() { interactive_hb->add_theme_constant_override("separation", 4 * EDSCALE); bpm_enabled = memnew(CheckBox); bpm_enabled->set_text((TTR("BPM:"))); - bpm_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); + bpm_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); interactive_hb->add_child(bpm_enabled); bpm_edit = memnew(SpinBox); bpm_edit->set_max(400); @@ -567,7 +567,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() { interactive_hb->add_spacer(); beats_enabled = memnew(CheckBox); beats_enabled->set_text(TTR("Beat Count:")); - beats_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); + beats_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1)); interactive_hb->add_child(beats_enabled); beats_edit = memnew(SpinBox); beats_edit->set_tooltip_text(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly.")); diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 0753afcc642..c98f288f06d 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -722,7 +722,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { for (int i = 0; i < MOD_MAX; i++) { String name = mods[i]; mod_checkboxes[i] = memnew(CheckBox); - mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); + mod_checkboxes[i]->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i)); mod_checkboxes[i]->set_text(name); mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i])); mod_container->add_child(mod_checkboxes[i]); @@ -731,7 +731,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() { mod_container->add_child(memnew(VSeparator)); autoremap_command_or_control_checkbox = memnew(CheckBox); - autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); + autoremap_command_or_control_checkbox->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled)); autoremap_command_or_control_checkbox->set_pressed(false); autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)")); autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform.")); diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index da3678e1ace..8c5686479f6 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -800,7 +800,7 @@ InspectorDock::InspectorDock(EditorData &p_editor_data) { inspector->set_use_folding(!bool(EDITOR_GET("interface/inspector/disable_folding"))); inspector->register_text_enter(search); - inspector->set_use_filter(true); // TODO: check me + inspector->set_use_filter(true); inspector->connect("resource_selected", callable_mp(this, &InspectorDock::_resource_selected)); diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index b0d9c58b347..427e6330dbe 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -715,7 +715,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { top_hb->add_child(memnew(Label(TTR("Sync:")))); sync = memnew(CheckBox); top_hb->add_child(sync); - sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); + sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed)); top_hb->add_child(memnew(VSeparator)); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index dc52b5023f1..d0086cda71c 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -963,7 +963,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { top_hb->add_child(memnew(Label(TTR("Sync:")))); sync = memnew(CheckBox); top_hb->add_child(sync); - sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed)); + sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed)); top_hb->add_child(memnew(VSeparator)); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 01c141f20e6..b818d74c0b6 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -166,6 +166,7 @@ void AnimationNodeBlendTreeEditor::update_graph() { name->set_text(E); name->set_editable(!read_only); name->set_expand_to_text_length_enabled(true); + name->set_custom_minimum_size(Vector2(100, 0) * EDSCALE); node->add_child(name); node->set_slot(0, false, 0, Color(), true, read_only ? -1 : 0, get_theme_color(SceneStringName(font_color), SNAME("Label"))); name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed).bind(agnode), CONNECT_DEFERRED); @@ -205,6 +206,14 @@ void AnimationNodeBlendTreeEditor::update_graph() { prop->update_property(); prop->set_name_split_ratio(0); prop->connect("property_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_property_changed)); + + if (F.hint == PROPERTY_HINT_RESOURCE_TYPE) { + // Give the resource editor some more space to make the inside readable. + prop->set_custom_minimum_size(Vector2(180, 0) * EDSCALE); + // Align the size of the node with the resource editor, its un-expanding does not trigger a resize. + prop->connect(SceneStringName(resized), Callable(node, "reset_size")); + } + node->add_child(prop); visible_properties.push_back(prop); } @@ -266,17 +275,14 @@ void AnimationNodeBlendTreeEditor::update_graph() { mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED); } - // TODO: Avoid using strings, expose a method on GraphNode instead. - Ref sb = node->get_theme_stylebox(SceneStringName(panel)); - Color c = sb->get_border_color(); - Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); - mono_color.a = 0.85; - c = mono_color; - - node->add_theme_color_override("title_color", c); - c.a = 0.7; - node->add_theme_color_override("close_color", c); - node->add_theme_color_override("resizer_color", c); + Ref sb_panel = node->get_theme_stylebox(SceneStringName(panel), "GraphNode")->duplicate(); + if (sb_panel.is_valid()) { + sb_panel->set_content_margin(SIDE_TOP, 12 * EDSCALE); + sb_panel->set_content_margin(SIDE_BOTTOM, 12 * EDSCALE); + node->add_theme_style_override(SceneStringName(panel), sb_panel); + } + + node->add_theme_constant_override("separation", 4 * EDSCALE); } List node_connections; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 8211b6c953c..b5d3411e209 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5411,7 +5411,7 @@ CanvasItemEditor::CanvasItemEditor() { smart_snap_button->set_theme_type_variation("FlatButton"); main_menu_hbox->add_child(smart_snap_button); smart_snap_button->set_toggle_mode(true); - smart_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap)); + smart_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap)); smart_snap_button->set_tooltip_text(TTR("Toggle smart snapping.")); smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S)); smart_snap_button->set_shortcut_context(this); @@ -5420,7 +5420,7 @@ CanvasItemEditor::CanvasItemEditor() { grid_snap_button->set_theme_type_variation("FlatButton"); main_menu_hbox->add_child(grid_snap_button); grid_snap_button->set_toggle_mode(true); - grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap)); + grid_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap)); grid_snap_button->set_tooltip_text(TTR("Toggle grid snapping.")); grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G)); grid_snap_button->set_shortcut_context(this); @@ -5513,7 +5513,7 @@ CanvasItemEditor::CanvasItemEditor() { override_camera_button = memnew(Button); override_camera_button->set_theme_type_variation("FlatButton"); main_menu_hbox->add_child(override_camera_button); - override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera)); + override_camera_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_override_camera)); override_camera_button->set_toggle_mode(true); override_camera_button->set_disabled(true); _update_override_camera_button(false); diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp index bc8412aa5c7..14be5e36855 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -1086,7 +1086,7 @@ ControlEditorToolbar::ControlEditorToolbar() { anchor_mode_button->set_toggle_mode(true); anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets.")); add_child(anchor_mode_button); - anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); + anchor_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); // Container tools. containers_button = memnew(ControlEditorPopupButton); diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index b35e4107bbe..08c3b236075 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -1005,7 +1005,7 @@ CurveEditor::CurveEditor() { snap_button->set_tooltip_text(TTR("Toggle Grid Snap")); snap_button->set_toggle_mode(true); toolbar->add_child(snap_button); - snap_button->connect("toggled", callable_mp(this, &CurveEditor::_set_snap_enabled)); + snap_button->connect(SceneStringName(toggled), callable_mp(this, &CurveEditor::_set_snap_enabled)); toolbar->add_child(memnew(VSeparator)); diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 2b32f913004..2759fd678ff 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -634,7 +634,7 @@ GradientEditor::GradientEditor() { snap_button->set_tooltip_text(TTR("Toggle Grid Snap")); snap_button->set_toggle_mode(true); toolbar->add_child(snap_button); - snap_button->connect("toggled", callable_mp(this, &GradientEditor::_set_snap_enabled)); + snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientEditor::_set_snap_enabled)); snap_count_edit = memnew(EditorSpinSlider); snap_count_edit->set_min(2); diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp index dc20dd6f9f9..6c685ba4c73 100644 --- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -292,7 +292,7 @@ GradientTexture2DEditor::GradientTexture2DEditor() { snap_button->set_tooltip_text(TTR("Toggle Grid Snap")); snap_button->set_toggle_mode(true); toolbar->add_child(snap_button); - snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled)); + snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled)); snap_count_edit = memnew(EditorSpinSlider); snap_count_edit->set_min(2); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 5ad8a038158..3373d8d232b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3697,10 +3697,10 @@ void Node3DEditorViewport::_set_auto_orthogonal() { } void Node3DEditorViewport::_preview_exited_scene() { - preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); + preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); preview_camera->set_pressed(false); _toggle_camera_preview(false); - preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); + preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); view_menu->show(); } @@ -4064,8 +4064,8 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { view_menu->get_popup()->set_item_checked(idx, previewing_cinema); } - if (preview_camera->is_connected("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) { - preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); + if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) { + preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); } if (p_state.has("previewing")) { Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]); @@ -4078,7 +4078,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { preview_camera->show(); } } - preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); + preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); } Dictionary Node3DEditorViewport::get_state() const { @@ -5415,7 +5415,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p vbox->add_child(preview_camera); preview_camera->set_h_size_flags(0); preview_camera->hide(); - preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); + preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview)); previewing = nullptr; gizmo_scale = 1.0; @@ -8632,7 +8632,7 @@ Node3DEditor::Node3DEditor() { main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation("FlatButton"); - tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS)); + tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS)); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), Key::T)); tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this); @@ -8640,7 +8640,7 @@ Node3DEditor::Node3DEditor() { main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]); tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true); tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation("FlatButton"); - tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP)); + tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP)); tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), Key::Y)); tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this); @@ -8651,7 +8651,7 @@ Node3DEditor::Node3DEditor() { tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_theme_type_variation("FlatButton"); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true); - tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA)); + tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA)); _update_camera_override_button(false); main_menu_hbox->add_child(memnew(VSeparator)); diff --git a/editor/plugins/particle_process_material_editor_plugin.cpp b/editor/plugins/particle_process_material_editor_plugin.cpp index 69fdda2dd83..97e3542fe32 100644 --- a/editor/plugins/particle_process_material_editor_plugin.cpp +++ b/editor/plugins/particle_process_material_editor_plugin.cpp @@ -440,7 +440,7 @@ ParticleProcessMaterialMinMaxPropertyEditor::ParticleProcessMaterialMinMaxProper toggle_mode_button->set_toggle_mode(true); toggle_mode_button->set_tooltip_text(TTR("Toggle between minimum/maximum and base value/spread modes.")); hb->add_child(toggle_mode_button); - toggle_mode_button->connect(SNAME("toggled"), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode)); + toggle_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode)); set_bottom_editor(content_vb); } diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index 9c60c590d29..a291ae1c607 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -67,7 +67,7 @@ PhysicalBone3DEditor::PhysicalBone3DEditor() { // when the editor theme updates. button_transform_joint->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("PhysicalBone3D"), EditorStringName(EditorIcons))); button_transform_joint->set_toggle_mode(true); - button_transform_joint->connect("toggled", callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint)); + button_transform_joint->connect(SceneStringName(toggled), callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint)); hide(); } diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 595b8ff622c..977d7ac5599 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -1470,7 +1470,7 @@ Polygon2DEditor::Polygon2DEditor() { b_snap_enable->set_toggle_mode(true); b_snap_enable->set_pressed(use_snap); b_snap_enable->set_tooltip_text(TTR("Enable Snap")); - b_snap_enable->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_use_snap)); + b_snap_enable->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_use_snap)); b_snap_grid = memnew(Button); b_snap_grid->set_theme_type_variation("FlatButton"); @@ -1480,7 +1480,7 @@ Polygon2DEditor::Polygon2DEditor() { b_snap_grid->set_toggle_mode(true); b_snap_grid->set_pressed(snap_show_grid); b_snap_grid->set_tooltip_text(TTR("Show Grid")); - b_snap_grid->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_show_grid)); + b_snap_grid->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_show_grid)); grid_settings = memnew(AcceptDialog); grid_settings->set_title(TTR("Configure Grid:")); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index e88f2bc89a6..6553d166619 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -4062,7 +4062,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { members_overview_alphabeta_sort_button->set_tooltip_text(TTR("Toggle alphabetical sorting of the method list.")); members_overview_alphabeta_sort_button->set_toggle_mode(true); members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically")); - members_overview_alphabeta_sort_button->connect("toggled", callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort)); + members_overview_alphabeta_sort_button->connect(SceneStringName(toggled), callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort)); buttons_hbox->add_child(members_overview_alphabeta_sort_button); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index f13d0ff7357..92e17dd4915 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -749,7 +749,7 @@ void Skeleton3DEditor::create_editors() { edit_mode_button->set_toggle_mode(true); edit_mode_button->set_focus_mode(FOCUS_NONE); edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints.")); - edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); + edit_mode_button->connect(SceneStringName(toggled), callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); edit_mode = false; diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index 1889b85f78a..51c10120719 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -115,7 +115,7 @@ StyleBoxPreview::StyleBoxPreview() { // This theme variation works better than the normal theme because there's no focus highlight. grid_preview->set_theme_type_variation("PreviewLightButton"); grid_preview->set_toggle_mode(true); - grid_preview->connect("toggled", callable_mp(this, &StyleBoxPreview::_grid_preview_toggled)); + grid_preview->connect(SceneStringName(toggled), callable_mp(this, &StyleBoxPreview::_grid_preview_toggled)); grid_preview->set_pressed(grid_preview_enabled); add_child(grid_preview); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index f643124232c..1bb910e8299 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -904,7 +904,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_expand->set_toggle_mode(true); button_expand->set_pressed(false); button_expand->set_tooltip_text(TTR("Expand editor")); - button_expand->connect("toggled", callable_mp(this, &GenericTilePolygonEditor::_toggle_expand)); + button_expand->connect(SceneStringName(toggled), callable_mp(this, &GenericTilePolygonEditor::_toggle_expand)); toolbar->add_child(button_expand); toolbar->add_child(memnew(VSeparator)); diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 20dae1ace30..1009f3b7563 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -2334,7 +2334,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() { random_tile_toggle->set_theme_type_variation("FlatButton"); random_tile_toggle->set_toggle_mode(true); random_tile_toggle->set_tooltip_text(TTR("Place Random Tile")); - random_tile_toggle->connect("toggled", callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled)); + random_tile_toggle->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled)); tools_settings->add_child(random_tile_toggle); // Random tile scattering. @@ -4490,7 +4490,7 @@ TileMapLayerEditor::TileMapLayerEditor() { toggle_highlight_selected_layer_button->set_theme_type_variation("FlatButton"); toggle_highlight_selected_layer_button->set_toggle_mode(true); toggle_highlight_selected_layer_button->set_pressed(true); - toggle_highlight_selected_layer_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled)); + toggle_highlight_selected_layer_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled)); toggle_highlight_selected_layer_button->set_tooltip_text(TTR("Highlight Selected TileMap Layer")); tile_map_toolbar->add_child(toggle_highlight_selected_layer_button); @@ -4501,7 +4501,7 @@ TileMapLayerEditor::TileMapLayerEditor() { toggle_grid_button->set_theme_type_variation("FlatButton"); toggle_grid_button->set_toggle_mode(true); toggle_grid_button->set_tooltip_text(TTR("Toggle grid visibility.")); - toggle_grid_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_on_grid_toggled)); + toggle_grid_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_on_grid_toggled)); tile_map_toolbar->add_child(toggle_grid_button); // Advanced settings menu button. diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index b97be2e779b..a01b4907e2b 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -1008,7 +1008,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { toggle_vcs_choice = memnew(CheckButton); toggle_vcs_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL); toggle_vcs_choice->set_pressed_no_signal(false); - toggle_vcs_choice->connect(SNAME("toggled"), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration)); + toggle_vcs_choice->connect(SceneStringName(toggled), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration)); toggle_vcs_hbc->add_child(toggle_vcs_choice); set_up_vbc->add_child(memnew(HSeparator)); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 5496ab012dc..107a698d55c 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -47,6 +47,7 @@ #include "editor/plugins/curve_editor_plugin.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/themes/editor_scale.h" +#include "editor/themes/editor_theme_manager.h" #include "scene/animation/tween.h" #include "scene/gui/button.h" #include "scene/gui/check_box.h" @@ -571,6 +572,10 @@ void VisualShaderGraphPlugin::update_theme() { Ref label_bold_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_bold_msdf", EditorStringName(EditorFonts)); vs_msdf_fonts_theme->set_font(SceneStringName(font), "Label", label_font); vs_msdf_fonts_theme->set_font(SceneStringName(font), "GraphNodeTitleLabel", label_bold_font); + if (!EditorThemeManager::is_dark_theme()) { + // Override the color to white for light themes. + vs_msdf_fonts_theme->set_color(SceneStringName(font_color), "GraphNodeTitleLabel", Color(1, 1, 1)); + } vs_msdf_fonts_theme->set_font(SceneStringName(font), "LineEdit", label_font); vs_msdf_fonts_theme->set_font(SceneStringName(font), "Button", label_font); } @@ -6157,7 +6162,7 @@ VisualShaderEditor::VisualShaderEditor() { custom_mode_box->set_text(TTR("Custom")); custom_mode_box->set_pressed(false); custom_mode_box->set_visible(false); - custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled)); + custom_mode_box->connect(SceneStringName(toggled), callable_mp(this, &VisualShaderEditor::_custom_mode_toggled)); edit_type_standard = memnew(OptionButton); edit_type_standard->add_item(TTR("Vertex")); diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 2b046f9157d..fc3cb24fe77 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -852,7 +852,7 @@ ProjectDialog::ProjectDialog() { create_dir->set_text(TTR("Create Folder")); create_dir->set_pressed(true); pphb_label->add_child(create_dir); - create_dir->connect("toggled", callable_mp(this, &ProjectDialog::_create_dir_toggled)); + create_dir->connect(SceneStringName(toggled), callable_mp(this, &ProjectDialog::_create_dir_toggled)); HBoxContainer *pphb = memnew(HBoxContainer); project_path_container->add_child(pphb); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 036305b9204..80980e78edf 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -654,7 +654,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { advanced = memnew(CheckButton); advanced->set_text(TTR("Advanced Settings")); - advanced->connect("toggled", callable_mp(this, &ProjectSettingsEditor::_advanced_toggled)); + advanced->connect(SceneStringName(toggled), callable_mp(this, &ProjectSettingsEditor::_advanced_toggled)); search_bar->add_child(advanced); custom_properties = memnew(HBoxContainer); diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 374f7229b5b..f8d7512b375 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -306,7 +306,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) { // ---- Connections - cbut_collapse_features->connect("toggled", callable_mp(this, &RenameDialog::_features_toggled)); + cbut_collapse_features->connect(SceneStringName(toggled), callable_mp(this, &RenameDialog::_features_toggled)); // Substitute Buttons diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp index e8f513a26a1..1ecad119bc1 100644 --- a/editor/run_instances_dialog.cpp +++ b/editor/run_instances_dialog.cpp @@ -306,7 +306,7 @@ RunInstancesDialog::RunInstancesDialog() { args_gc->add_child(instance_count); instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1)); instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1)); - enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable)); + enable_multiple_instances_checkbox->connect(SceneStringName(toggled), callable_mp(instance_count, &SpinBox::set_editable)); instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed()); main_args_edit = memnew(LineEdit); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 872be4a91ee..3894925377a 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -161,11 +161,12 @@ void SceneTreeDock::shortcut_input(const Ref &p_event) { } if (ED_IS_SHORTCUT("scene_tree/rename", p_event)) { - // Prevent renaming if a button is focused - // to avoid conflict with Enter shortcut on macOS - if (!focus_owner || !Object::cast_to(focus_owner)) { - _tool_selected(TOOL_RENAME); + // Prevent renaming if a button or a range is focused + // to avoid conflict with Enter shortcut on macOS. + if (focus_owner && (Object::cast_to(focus_owner) || Object::cast_to(focus_owner))) { + return; } + _tool_selected(TOOL_RENAME); #ifdef MODULE_REGEX_ENABLED } else if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { _tool_selected(TOOL_BATCH_RENAME); diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 97809839a9d..af2c6bbd43e 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -631,7 +631,7 @@ ShaderCreateDialog::ShaderCreateDialog() { internal = memnew(CheckBox); internal->set_text(TTR("On")); - internal->connect("toggled", callable_mp(this, &ShaderCreateDialog::_built_in_toggled)); + internal->connect(SceneStringName(toggled), callable_mp(this, &ShaderCreateDialog::_built_in_toggled)); gc->add_child(memnew(Label(TTR("Built-in Shader:")))); gc->add_child(internal); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index a171324226d..9d46b1b67e2 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1646,8 +1646,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref &p_the // GraphNode's title Label. p_theme->set_type_variation("GraphNodeTitleLabel", "Label"); p_theme->set_stylebox(CoreStringName(normal), "GraphNodeTitleLabel", make_empty_stylebox(0, 0, 0, 0)); - p_theme->set_color(SceneStringName(font_color), "GraphNodeTitleLabel", p_config.dark_theme ? p_config.font_color : Color(1, 1, 1)); // Also use a bright font color for light themes. - p_theme->set_color("font_shadow_color", "GraphNodeTitleLabel", Color(0, 0, 0, 0.35)); + p_theme->set_color("font_shadow_color", "GraphNodeTitleLabel", p_config.shadow_color); p_theme->set_constant("shadow_outline_size", "GraphNodeTitleLabel", 4); p_theme->set_constant("shadow_offset_x", "GraphNodeTitleLabel", 0); p_theme->set_constant("shadow_offset_y", "GraphNodeTitleLabel", 1); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index f3e0680736a..ff336007ac1 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -299,6 +299,7 @@ Vector GDScriptCache::get_binary_tokens(const String &p_path) { Ref GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { MutexLock lock(singleton->mutex); + if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); } @@ -309,7 +310,7 @@ Ref GDScriptCache::get_shallow_script(const String &p_path, Error &r_e return singleton->shallow_gdscript_cache[p_path]; } - String remapped_path = ResourceLoader::path_remap(p_path); + const String remapped_path = ResourceLoader::path_remap(p_path); Ref script; script.instantiate(); @@ -334,6 +335,7 @@ Ref GDScriptCache::get_shallow_script(const String &p_path, Error &r_e } singleton->shallow_gdscript_cache[p_path] = script; + return script; } @@ -361,16 +363,18 @@ Ref GDScriptCache::get_full_script(const String &p_path, Error &r_erro } } + const String remapped_path = ResourceLoader::path_remap(p_path); + if (p_update_from_disk) { - if (p_path.get_extension().to_lower() == "gdc") { - Vector buffer = get_binary_tokens(p_path); + if (remapped_path.get_extension().to_lower() == "gdc") { + Vector buffer = get_binary_tokens(remapped_path); if (buffer.is_empty()) { r_error = ERR_FILE_CANT_READ; return script; } script->set_binary_tokens_source(buffer); } else { - r_error = script->load_source_code(p_path); + r_error = script->load_source_code(remapped_path); if (r_error) { return script; } diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 0863ee4275c..f6f5001ccef 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2973,11 +2973,6 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co } break; case GDScriptParser::Node::IDENTIFIER: { - if (p_subscript->base->datatype.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT) { - // Annotated type takes precedence. - return false; - } - const GDScriptParser::IdentifierNode *identifier_node = static_cast(p_subscript->base); switch (identifier_node->source) { @@ -3015,6 +3010,14 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co if (get_node != nullptr) { const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); if (node != nullptr) { + GDScriptParser::DataType assigned_type = _type_from_variant(node, p_context).type; + GDScriptParser::DataType base_type = p_subscript->base->datatype; + + if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && base_type.type_source == GDScriptParser::DataType::ANNOTATED_EXPLICIT && (assigned_type.kind != base_type.kind || assigned_type.script_path != base_type.script_path || assigned_type.native_type != base_type.native_type)) { + // Annotated type takes precedence. + return false; + } + if (r_base != nullptr) { *r_base = node; } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 6ec8e732fca..92846741103 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -3048,7 +3048,7 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win XWindowAttributes xwa; XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa); - if (xwa.map_state == IsViewable) { + if (xwa.map_state == IsViewable && _window_focus_check()) { _set_input_focus(wd.x11_xim_window, RevertToParent); } XSetICFocus(wd.xic); @@ -4317,7 +4317,7 @@ bool DisplayServerX11::_window_focus_check() { bool has_focus = false; for (const KeyValue &wid : windows) { - if (wid.value.x11_window == focused_window) { + if (wid.value.x11_window == focused_window || (wid.value.xic && wid.value.ime_active && wid.value.x11_xim_window == focused_window)) { has_focus = true; break; } diff --git a/scene/3d/physics/character_body_3d.cpp b/scene/3d/physics/character_body_3d.cpp index 38b7892d48c..90d46a839b6 100644 --- a/scene/3d/physics/character_body_3d.cpp +++ b/scene/3d/physics/character_body_3d.cpp @@ -62,8 +62,13 @@ bool CharacterBody3D::move_and_slide() { // We need to check the platform_rid object still exists before accessing. // A valid RID is no guarantee that the object has not been deleted. - if (ObjectDB::get_instance(platform_object_id)) { - //this approach makes sure there is less delay between the actual body velocity and the one we saved + + // We can only perform the ObjectDB lifetime check on Object derived objects. + // Note that physics also creates RIDs for non-Object derived objects, these cannot + // be lifetime checked through ObjectDB, and therefore there is a still a vulnerability + // to dangling RIDs (access after free) in this scenario. + if (platform_object_id.is_null() || ObjectDB::get_instance(platform_object_id)) { + // This approach makes sure there is less delay between the actual body velocity and the one we saved. bs = PhysicsServer3D::get_singleton()->body_get_direct_state(platform_rid); } diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 8bf0992e7b4..35693e37116 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -142,7 +142,7 @@ void BaseButton::_pressed() { void BaseButton::_toggled(bool p_pressed) { GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); - emit_signal(SNAME("toggled"), p_pressed); + emit_signal(SceneStringName(toggled), p_pressed); } void BaseButton::on_action_event(Ref p_event) { diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 7c7b6082cf9..e2b36158214 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -786,7 +786,7 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { recent_preset_hbc->add_child(btn_preset_new); recent_preset_hbc->move_child(btn_preset_new, 0); btn_preset_new->set_pressed(true); - btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); + btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); } void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { @@ -1976,7 +1976,7 @@ ColorPicker::ColorPicker() { btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); real_vbox->add_child(btn_preset); real_vbox->add_child(preset_container); @@ -1993,7 +1993,7 @@ ColorPicker::ColorPicker() { btn_recent_preset->set_toggle_mode(true); btn_recent_preset->set_focus_mode(FOCUS_NONE); btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + btn_recent_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); real_vbox->add_child(btn_recent_preset); real_vbox->add_child(recent_preset_hbc); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index a0074ebe669..5978d55838b 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1146,7 +1146,7 @@ void FileDialog::_update_option_controls() { CheckBox *cb = memnew(CheckBox); cb->set_pressed(opt.default_idx); grid_options->add_child(cb); - cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); + cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); selected_options[opt.name] = (bool)opt.default_idx; } else { OptionButton *ob = memnew(OptionButton); @@ -1444,7 +1444,7 @@ FileDialog::FileDialog() { show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files.")); - show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); + show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); shortcuts_container = memnew(HBoxContainer); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 97485555cb7..e57e9d2364d 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -655,7 +655,9 @@ void GraphEdit::remove_child_notify(Node *p_child) { minimap = nullptr; } else if (p_child == connections_layer) { connections_layer = nullptr; - WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons."); + if (is_inside_tree()) { + WARN_PRINT("GraphEdit's connection_layer removed. This should not be done. If you like to remove all GraphElements from a GraphEdit node, do not simply remove all non-internal children but check their type since the connection layer has to be kept non-internal due to technical reasons."); + } } if (top_layer != nullptr && is_inside_tree()) { diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 5b100e34bde..fb6ac2d2119 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -1041,7 +1041,7 @@ void ItemList::_notification(int p_what) { scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM)); Size2 size = get_size(); - int width = size.width - theme_cache.panel_style->get_minimum_size().width; + int width = size.width - theme_cache.panel_style->get_margin(SIDE_RIGHT); if (scroll_bar->is_visible()) { width -= scroll_bar_minwidth; } @@ -1194,9 +1194,9 @@ void ItemList::_notification(int p_what) { Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs; if (icon_mode == ICON_MODE_TOP) { - pos.y += theme_cache.v_separation / 2; + pos.y += MAX(theme_cache.v_separation, 0) / 2; } else { - pos.x += theme_cache.h_separation / 2; + pos.x += MAX(theme_cache.h_separation, 0) / 2; } if (icon_mode == ICON_MODE_TOP) { @@ -1245,8 +1245,8 @@ void ItemList::_notification(int p_what) { } Point2 draw_pos = items[i].rect_cache.position; - draw_pos.x += theme_cache.h_separation / 2; - draw_pos.y += theme_cache.v_separation / 2; + draw_pos.x += MAX(theme_cache.h_separation, 0) / 2; + draw_pos.y += MAX(theme_cache.v_separation, 0) / 2; if (rtl) { draw_pos.x = size.width - draw_pos.x - tag_icon_size.x; } @@ -1285,8 +1285,7 @@ void ItemList::_notification(int p_what) { text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; - text_ofs.x += theme_cache.h_separation / 2; - text_ofs.y += theme_cache.v_separation / 2; + text_ofs.y += MAX(theme_cache.v_separation, 0) / 2; if (rtl) { text_ofs.x = size.width - text_ofs.x - max_len; @@ -1294,7 +1293,7 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); - float text_w = items[i].rect_cache.size.width - theme_cache.h_separation; + float text_w = items[i].rect_cache.size.width; items.write[i].text_buf->set_width(text_w); if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { @@ -1309,17 +1308,17 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; - text_ofs.x += theme_cache.h_separation / 2; - text_ofs.y += theme_cache.v_separation / 2; + text_ofs.x += MAX(theme_cache.h_separation, 0) / 2; + text_ofs.y += MAX(theme_cache.v_separation, 0) / 2; } else { text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; - text_ofs.x += theme_cache.h_separation / 2; + text_ofs.x += MAX(theme_cache.h_separation, 0) / 2; } text_ofs += base_ofs; text_ofs += items[i].rect_cache.position; - float text_w = width - text_ofs.x - theme_cache.h_separation; + float text_w = width - text_ofs.x; items.write[i].text_buf->set_width(text_w); if (rtl) { @@ -1412,14 +1411,14 @@ void ItemList::force_update_list_size() { max_column_width = MAX(max_column_width, minsize.x); // Elements need to adapt to the selected size. - minsize.y += theme_cache.v_separation; - minsize.x += theme_cache.h_separation; + minsize.y += MAX(theme_cache.v_separation, 0); + minsize.x += MAX(theme_cache.h_separation, 0); items.write[i].rect_cache.size = minsize; items.write[i].min_rect_cache.size = minsize; } - int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - scroll_bar_minwidth; + int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width; //2-attempt best fit current_columns = 0x7FFFFFFF; @@ -1445,7 +1444,7 @@ void ItemList::force_update_list_size() { } if (same_column_width) { - items.write[i].rect_cache.size.x = max_column_width + theme_cache.h_separation; + items.write[i].rect_cache.size.x = max_column_width + MAX(theme_cache.h_separation, 0); } items.write[i].rect_cache.position = ofs; @@ -1470,13 +1469,17 @@ void ItemList::force_update_list_size() { } } + float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); + float max = MAX(page, ofs.y + max_h); + if (page >= max) { + fit_size -= scroll_bar_minwidth; + } + if (all_fit) { for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { items.write[j].rect_cache.size.y = max_h; } - float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); - float max = MAX(page, ofs.y + max_h); if (auto_height) { auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height; } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index e09de0f51c3..94ebba4f23b 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1804,13 +1804,13 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_RESIZED: { _stop_thread(); - main->first_resized_line.store(0); //invalidate ALL + main->first_resized_line.store(0); // Invalidate all lines. queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _stop_thread(); - main->first_invalid_font_line.store(0); //invalidate ALL + main->first_invalid_font_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1820,7 +1820,7 @@ void RichTextLabel::_notification(int p_what) { set_text(text); } - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1831,8 +1831,7 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { - // If `text` is empty, it could mean that the tag stack is being used instead. Leave it be. - if (!text.is_empty()) { + if (!stack_externally_modified) { _apply_translation(); } @@ -2532,7 +2531,7 @@ PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) { item = item->parent; } - return PackedFloat32Array(); + return default_tab_stops; } HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { @@ -3094,6 +3093,10 @@ void RichTextLabel::add_text(const String &p_text) { } void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) { + if (!internal_stack_editing) { + stack_externally_modified = true; + } + p_item->parent = current; p_item->E = current->subitems.push_back(p_item); p_item->index = current_idx++; @@ -3367,6 +3370,8 @@ bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { return false; } + stack_externally_modified = true; + if (main->lines.size() == 1) { // Clear all. main->_clear_children(); @@ -3985,6 +3990,8 @@ void RichTextLabel::clear() { _stop_thread(); MutexLock data_lock(data_mutex); + stack_externally_modified = false; + main->_clear_children(); current = main; current_frame = main; @@ -4448,19 +4455,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { add_text(String::chr(0x00AD)); pos = brk_end + 1; } else if (tag == "center") { - push_paragraph(HORIZONTAL_ALIGNMENT_CENTER); + push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "fill") { - push_paragraph(HORIZONTAL_ALIGNMENT_FILL); + push_paragraph(HORIZONTAL_ALIGNMENT_FILL, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "left") { - push_paragraph(HORIZONTAL_ALIGNMENT_LEFT); + push_paragraph(HORIZONTAL_ALIGNMENT_LEFT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "right") { - push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT); + push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "ul") { @@ -4519,8 +4526,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; - String lang; - PackedFloat32Array tab_stops; + String lang = language; + PackedFloat32Array tab_stops = default_tab_stops; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; BitField jst_flags = default_jst_flags; for (int i = 0; i < subtag.size(); i++) { @@ -5675,11 +5682,19 @@ void RichTextLabel::set_text(const String &p_bbcode) { return; } + stack_externally_modified = false; + text = p_bbcode; _apply_translation(); } void RichTextLabel::_apply_translation() { + if (text.is_empty()) { + return; + } + + internal_stack_editing = true; + String xl_text = atr(text); if (use_bbcode) { parse_bbcode(xl_text); @@ -5687,6 +5702,8 @@ void RichTextLabel::_apply_translation() { clear(); add_text(xl_text); } + + internal_stack_editing = false; } String RichTextLabel::get_text() const { @@ -5700,8 +5717,7 @@ void RichTextLabel::set_use_bbcode(bool p_enable) { use_bbcode = p_enable; notify_property_list_changed(); - // If `text` is empty, it could mean that the tag stack is being used instead. Leave it be. - if (!text.is_empty()) { + if (!stack_externally_modified) { _apply_translation(); } } @@ -5711,7 +5727,7 @@ bool RichTextLabel::is_using_bbcode() const { } String RichTextLabel::get_parsed_text() const { - String txt = ""; + String txt; Item *it = main; while (it) { if (it->type == ITEM_DROPCAP) { @@ -5738,19 +5754,89 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) if (text_direction != p_text_direction) { text_direction = p_text_direction; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +Control::TextDirection RichTextLabel::get_text_direction() const { + return text_direction; +} + +void RichTextLabel::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + _stop_thread(); + + if (default_alignment != p_alignment) { + default_alignment = p_alignment; + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +HorizontalAlignment RichTextLabel::get_horizontal_alignment() const { + return default_alignment; +} + +void RichTextLabel::set_justification_flags(BitField p_flags) { + _stop_thread(); + + if (default_jst_flags != p_flags) { + default_jst_flags = p_flags; + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } +BitField RichTextLabel::get_justification_flags() const { + return default_jst_flags; +} + +void RichTextLabel::set_tab_stops(const PackedFloat32Array &p_tab_stops) { + _stop_thread(); + + if (default_tab_stops != p_tab_stops) { + default_tab_stops = p_tab_stops; + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +PackedFloat32Array RichTextLabel::get_tab_stops() const { + return default_tab_stops; +} + void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { _stop_thread(); st_parser = p_parser; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5764,7 +5850,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { _stop_thread(); st_args = p_args; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5774,17 +5860,17 @@ Array RichTextLabel::get_structured_text_bidi_override_options() const { return st_args; } -Control::TextDirection RichTextLabel::get_text_direction() const { - return text_direction; -} - void RichTextLabel::set_language(const String &p_language) { if (language != p_language) { _stop_thread(); language = p_language; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!stack_externally_modified) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5798,7 +5884,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { _stop_thread(); autowrap_mode = p_mode; - main->first_invalid_line = 0; //invalidate ALL + main->first_invalid_line = 0; // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5824,7 +5910,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) { } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); // Invalidate ALL. + main->first_invalid_line.store(0); // Invalidate all lines.. _validate_line_caches(); } queue_redraw(); @@ -5837,7 +5923,7 @@ float RichTextLabel::get_visible_ratio() const { void RichTextLabel::set_effects(Array p_effects) { custom_effects = p_effects; - if ((!text.is_empty()) && use_bbcode) { + if (!stack_externally_modified && use_bbcode) { parse_bbcode(atr(text)); } } @@ -5852,7 +5938,7 @@ void RichTextLabel::install_effect(const Variant effect) { ERR_FAIL_COND_MSG(rteffect.is_null(), "Invalid RichTextEffect resource."); custom_effects.push_back(effect); - if ((!text.is_empty()) && use_bbcode) { + if (!stack_externally_modified && use_bbcode) { parse_bbcode(atr(text)); } } @@ -5952,6 +6038,13 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language); ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &RichTextLabel::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &RichTextLabel::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &RichTextLabel::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &RichTextLabel::get_justification_flags); + ClassDB::bind_method(D_METHOD("set_tab_stops", "tab_stops"), &RichTextLabel::set_tab_stops); + ClassDB::bind_method(D_METHOD("get_tab_stops"), &RichTextLabel::get_tab_stops); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode); ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode); @@ -6069,6 +6162,10 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops"); + ADD_GROUP("Markup", ""); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); @@ -6171,7 +6268,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter _stop_thread(); visible_chars_behavior = p_behavior; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -6191,7 +6288,7 @@ void RichTextLabel::set_visible_characters(int p_visible) { } } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); } queue_redraw(); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 9c25ff35b38..0ef6136d0b2 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -484,6 +484,7 @@ class RichTextLabel : public Control { HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; BitField default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; + PackedFloat32Array default_tab_stops; ItemMeta *meta_hovering = nullptr; Variant current_meta; @@ -623,6 +624,9 @@ class RichTextLabel : public Control { String text; void _apply_translation(); + bool internal_stack_editing = false; + bool stack_externally_modified = false; + bool fit_content = false; struct ThemeCache { @@ -810,6 +814,15 @@ class RichTextLabel : public Control { void set_text(const String &p_bbcode); String get_text() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_justification_flags(BitField p_flags); + BitField get_justification_flags() const; + + void set_tab_stops(const PackedFloat32Array &p_tab_stops); + PackedFloat32Array get_tab_stops() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 4ccbba994eb..814e426230a 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -66,6 +66,10 @@ void SpinBox::_update_text(bool p_keep_line_edit) { } void SpinBox::_text_submitted(const String &p_string) { + if (p_string.is_empty()) { + return; + } + Ref expr; expr.instantiate(); @@ -232,8 +236,8 @@ void SpinBox::_line_edit_focus_exit() { if (line_edit->is_menu_visible()) { return; } - // Discontinue because the focus_exit was caused by canceling. - if (Input::get_singleton()->is_action_pressed("ui_cancel")) { + // Discontinue because the focus_exit was caused by canceling or the text is empty. + if (Input::get_singleton()->is_action_pressed("ui_cancel") || line_edit->get_text().is_empty()) { _update_text(); return; } diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index f9aca999695..0a66c449ad6 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -356,6 +356,8 @@ void TabBar::_notification(int p_what) { if (scroll_to_selected) { ensure_tab_visible(current); } + // Set initialized even if no tabs were set. + initialized = true; } break; case NOTIFICATION_INTERNAL_PROCESS: { @@ -657,10 +659,10 @@ void TabBar::set_tab_count(int p_count) { } if (!initialized) { - if (queued_current != current) { - current = queued_current; - } initialized = true; + if (queued_current != CURRENT_TAB_UNINITIALIZED && queued_current != current) { + set_current_tab(queued_current); + } } queue_redraw(); @@ -742,6 +744,13 @@ bool TabBar::select_next_available() { return false; } +void TabBar::set_tab_offset(int p_offset) { + ERR_FAIL_INDEX(p_offset, tabs.size()); + offset = p_offset; + _update_cache(); + queue_redraw(); +} + int TabBar::get_tab_offset() const { return offset; } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 6b29fa46078..f86b34bdc8d 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -119,8 +119,9 @@ class TabBar : public Control { bool scroll_to_selected = true; int tabs_rearrange_group = -1; + static const int CURRENT_TAB_UNINITIALIZED = -2; bool initialized = false; - int queued_current = -1; + int queued_current = CURRENT_TAB_UNINITIALIZED; const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5; const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20; @@ -251,6 +252,7 @@ class TabBar : public Control { bool select_previous_available(); bool select_next_available(); + void set_tab_offset(int p_offset); int get_tab_offset() const; bool get_offset_buttons_visible() const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index e0f57fe4196..253f5cdac8b 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -192,7 +192,7 @@ void TabContainer::_notification(int p_what) { } break; case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible() || setup_current_tab > -2) { + if (!is_visible()) { return; } @@ -202,7 +202,7 @@ void TabContainer::_notification(int p_what) { // beat it to the punch and make sure that the correct node is the only one visible first. // Otherwise, it can prevent a tab change done right before this container was made visible. Vector controls = _get_tab_controls(); - int current = get_current_tab(); + int current = setup_current_tab > -2 ? setup_current_tab : get_current_tab(); for (int i = 0; i < controls.size(); i++) { controls[i]->set_visible(i == current); } diff --git a/scene/main/window.cpp b/scene/main/window.cpp index e7dfaa07440..0f05d6a02bd 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -390,6 +390,12 @@ void Window::move_to_center() { void Window::set_size(const Size2i &p_size) { ERR_MAIN_THREAD_GUARD; +#if defined(ANDROID_ENABLED) + if (!get_parent() && is_inside_tree()) { + // Can't set root window size on Android. + return; + } +#endif size = p_size; _update_window_size(); @@ -460,6 +466,12 @@ void Window::_validate_limit_size() { void Window::set_max_size(const Size2i &p_max_size) { ERR_MAIN_THREAD_GUARD; +#if defined(ANDROID_ENABLED) + if (!get_parent() && is_inside_tree()) { + // Can't set root window size on Android. + return; + } +#endif Size2i max_size_clamped = _clamp_limit_size(p_max_size); if (max_size == max_size_clamped) { return; @@ -477,6 +489,12 @@ Size2i Window::get_max_size() const { void Window::set_min_size(const Size2i &p_min_size) { ERR_MAIN_THREAD_GUARD; +#if defined(ANDROID_ENABLED) + if (!get_parent() && is_inside_tree()) { + // Can't set root window size on Android. + return; + } +#endif Size2i min_size_clamped = _clamp_limit_size(p_min_size); if (min_size == min_size_clamped) { return; diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp index 02a9d792800..9d486c34da6 100644 --- a/scene/resources/syntax_highlighter.cpp +++ b/scene/resources/syntax_highlighter.cpp @@ -444,7 +444,6 @@ Color CodeHighlighter::get_keyword_color(const String &p_keyword) const { } void CodeHighlighter::set_keyword_colors(const Dictionary p_keywords) { - keywords.clear(); keywords = p_keywords; clear_highlighting_cache(); } @@ -478,7 +477,6 @@ Color CodeHighlighter::get_member_keyword_color(const String &p_member_keyword) } void CodeHighlighter::set_member_keyword_colors(const Dictionary &p_member_keywords) { - member_keywords.clear(); member_keywords = p_member_keywords; clear_highlighting_cache(); } diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index fe267b64b4f..6a360eea4fa 100644 --- a/scene/scene_string_names.cpp +++ b/scene/scene_string_names.cpp @@ -134,6 +134,7 @@ SceneStringNames::SceneStringNames() { pressed = StaticCString::create("pressed"); id_pressed = StaticCString::create("id_pressed"); + toggled = StaticCString::create("toggled"); panel = StaticCString::create("panel"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index b963923f7ef..2acff5a5a2c 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -147,6 +147,7 @@ class SceneStringNames { StringName pressed; StringName id_pressed; + StringName toggled; StringName panel; diff --git a/servers/physics_3d/godot_area_3d.h b/servers/physics_3d/godot_area_3d.h index 3bdfa841af1..d8c1cea75f7 100644 --- a/servers/physics_3d/godot_area_3d.h +++ b/servers/physics_3d/godot_area_3d.h @@ -190,7 +190,7 @@ void GodotArea3D::add_soft_body_to_query(GodotSoftBody3D *p_soft_body, uint32_t void GodotArea3D::remove_soft_body_from_query(GodotSoftBody3D *p_soft_body, uint32_t p_soft_body_shape, uint32_t p_area_shape) { BodyKey bk(p_soft_body, p_soft_body_shape, p_area_shape); monitored_soft_bodies[bk].dec(); - if (!monitor_query_list.in_list()) { + if (get_space() && !monitor_query_list.in_list()) { _queue_monitor_update(); } } diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 2b9f41f515f..90fc2999e24 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -53,7 +53,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *)); for (int i = 0; i < p_child_item_count; i++) { - _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, Point2(), 1, nullptr); + _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, false, p_canvas_cull_mask, Point2(), 1, nullptr); } RendererCanvasRender::Item *list = nullptr; @@ -81,45 +81,71 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas } } -void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, const Transform2D &p_transform, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) { +void RendererCanvasCull::_collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z) { int child_item_count = p_canvas_item->child_items.size(); RendererCanvasCull::Item **child_items = p_canvas_item->child_items.ptrw(); for (int i = 0; i < child_item_count; i++) { - int abs_z = 0; if (child_items[i]->visible) { - if (r_items) { - r_items[r_index] = child_items[i]; - child_items[i]->ysort_xform = p_transform; - child_items[i]->ysort_pos = p_transform.xform(child_items[i]->xform_curr.columns[2]); - child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr; - child_items[i]->ysort_modulate = p_modulate; - child_items[i]->ysort_index = r_index; - child_items[i]->ysort_parent_abs_z_index = p_z; - - if (!child_items[i]->repeat_source) { - child_items[i]->repeat_size = p_canvas_item->repeat_size; - child_items[i]->repeat_times = p_canvas_item->repeat_times; - child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item; - } + // To y-sort according to the item's final position, physics interpolation + // and transform snapping need to be applied before y-sorting. + Transform2D child_xform; + if (!_interpolation_data.interpolation_enabled || !child_items[i]->interpolated) { + child_xform = child_items[i]->xform_curr; + } else { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + TransformInterpolator::interpolate_transform_2d(child_items[i]->xform_prev, child_items[i]->xform_curr, child_xform, f); + } - // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items. - if (child_items[i]->z_relative) { - abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX); - } else { - abs_z = child_items[i]->z_index; - } + if (snapping_2d_transforms_to_pixel) { + child_xform.columns[2] = (child_xform.columns[2] + Point2(0.5, 0.5)).floor(); + } + + r_items[r_index] = child_items[i]; + child_items[i]->ysort_xform = p_canvas_item->ysort_xform * child_xform; + child_items[i]->material_owner = child_items[i]->use_parent_material ? p_material_owner : nullptr; + child_items[i]->ysort_modulate = p_modulate; + child_items[i]->ysort_index = r_index; + child_items[i]->ysort_parent_abs_z_index = p_z; + + if (!child_items[i]->repeat_source) { + child_items[i]->repeat_size = p_canvas_item->repeat_size; + child_items[i]->repeat_times = p_canvas_item->repeat_times; + child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item; + } + + // Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items. + int abs_z = 0; + if (child_items[i]->z_relative) { + abs_z = CLAMP(p_z + child_items[i]->z_index, RS::CANVAS_ITEM_Z_MIN, RS::CANVAS_ITEM_Z_MAX); + } else { + abs_z = child_items[i]->z_index; } r_index++; if (child_items[i]->sort_y) { - _collect_ysort_children(child_items[i], p_transform * child_items[i]->xform_curr, child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z); + _collect_ysort_children(child_items[i], child_items[i]->use_parent_material ? p_material_owner : child_items[i], p_modulate * child_items[i]->modulate, r_items, r_index, abs_z); } } } } -void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner, RID_Owner &canvas_item_owner) { +int RendererCanvasCull::_count_ysort_children(RendererCanvasCull::Item *p_canvas_item) { + int ysort_children_count = 0; + int child_item_count = p_canvas_item->child_items.size(); + RendererCanvasCull::Item *const *child_items = p_canvas_item->child_items.ptr(); + for (int i = 0; i < child_item_count; i++) { + if (child_items[i]->visible) { + ysort_children_count++; + if (child_items[i]->sort_y) { + ysort_children_count += _count_ysort_children(child_items[i]); + } + } + } + return ysort_children_count; +} + +void RendererCanvasCull::_mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner) { do { ysort_owner->ysort_children_count = -1; ysort_owner = canvas_item_owner.owns(ysort_owner->parent) ? canvas_item_owner.get_or_null(ysort_owner->parent) : nullptr; @@ -238,7 +264,7 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item * } } -void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) { +void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) { Item *ci = p_canvas_item; if (!ci->visible) { @@ -262,15 +288,29 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 } } + Transform2D self_xform; Transform2D final_xform; - if (!_interpolation_data.interpolation_enabled || !ci->interpolated) { - final_xform = ci->xform_curr; + if (p_is_already_y_sorted) { + // Y-sorted item's final transform is calculated before y-sorting, + // and is passed as `p_parent_xform` afterwards. No need to recalculate. + final_xform = p_parent_xform; } else { - real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); - TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, final_xform, f); - } + if (!_interpolation_data.interpolation_enabled || !ci->interpolated) { + self_xform = ci->xform_curr; + } else { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + TransformInterpolator::interpolate_transform_2d(ci->xform_prev, ci->xform_curr, self_xform, f); + } - Transform2D parent_xform = p_parent_xform; + Transform2D parent_xform = p_parent_xform; + + if (snapping_2d_transforms_to_pixel) { + self_xform.columns[2] = (self_xform.columns[2] + Point2(0.5, 0.5)).floor(); + parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor(); + } + + final_xform = parent_xform * self_xform; + } Point2 repeat_size = p_repeat_size; int repeat_times = p_repeat_times; @@ -286,13 +326,6 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 ci->repeat_source_item = repeat_source_item; } - if (snapping_2d_transforms_to_pixel) { - final_xform.columns[2] = (final_xform.columns[2] + Point2(0.5, 0.5)).floor(); - parent_xform.columns[2] = (parent_xform.columns[2] + Point2(0.5, 0.5)).floor(); - } - - final_xform = parent_xform * final_xform; - Rect2 global_rect = final_xform.xform(rect); if (repeat_source_item && (repeat_size.x || repeat_size.y)) { // Top-left repeated rect. @@ -357,29 +390,27 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 } if (ci->sort_y) { - if (p_allow_y_sort) { + if (!p_is_already_y_sorted) { if (ci->ysort_children_count == -1) { - ci->ysort_children_count = 0; - _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), nullptr, ci->ysort_children_count, p_z); + ci->ysort_children_count = _count_ysort_children(ci); } child_item_count = ci->ysort_children_count + 1; child_items = (Item **)alloca(child_item_count * sizeof(Item *)); - ci->ysort_xform = ci->xform_curr.affine_inverse(); - ci->ysort_pos = Vector2(); + ci->ysort_xform = Transform2D(); ci->ysort_modulate = Color(1, 1, 1, 1); ci->ysort_index = 0; ci->ysort_parent_abs_z_index = parent_z; child_items[0] = ci; int i = 1; - _collect_ysort_children(ci, Transform2D(), p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z); + _collect_ysort_children(ci, p_material_owner, Color(1, 1, 1, 1), child_items, i, p_z); - SortArray sorter; + SortArray sorter; sorter.sort(child_items, child_item_count); for (i = 0; i < child_item_count; i++) { - _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item); + _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, true, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item); } } else { RendererCanvasRender::Item *canvas_group_from = nullptr; @@ -403,14 +434,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2 if (!child_items[i]->behind && !use_canvas_group) { continue; } - _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item); + _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item); } _attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from); for (int i = 0; i < child_item_count; i++) { if (child_items[i]->behind || use_canvas_group) { continue; } - _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item); + _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, false, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item); } } } @@ -511,7 +542,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) { item_owner->child_items.erase(canvas_item); if (item_owner->sort_y) { - _mark_ysort_dirty(item_owner, canvas_item_owner); + _mark_ysort_dirty(item_owner); } } @@ -531,7 +562,7 @@ void RendererCanvasCull::canvas_item_set_parent(RID p_item, RID p_parent) { item_owner->children_order_dirty = true; if (item_owner->sort_y) { - _mark_ysort_dirty(item_owner, canvas_item_owner); + _mark_ysort_dirty(item_owner); } } else { @@ -548,7 +579,7 @@ void RendererCanvasCull::canvas_item_set_visible(RID p_item, bool p_visible) { canvas_item->visible = p_visible; - _mark_ysort_dirty(canvas_item, canvas_item_owner); + _mark_ysort_dirty(canvas_item); } void RendererCanvasCull::canvas_item_set_light_mask(RID p_item, int p_mask) { @@ -1744,7 +1775,7 @@ void RendererCanvasCull::canvas_item_set_sort_children_by_y(RID p_item, bool p_e canvas_item->sort_y = p_enable; - _mark_ysort_dirty(canvas_item, canvas_item_owner); + _mark_ysort_dirty(canvas_item); } void RendererCanvasCull::canvas_item_set_z_index(RID p_item, int p_z) { @@ -2425,7 +2456,7 @@ bool RendererCanvasCull::free(RID p_rid) { item_owner->child_items.erase(canvas_item); if (item_owner->sort_y) { - _mark_ysort_dirty(item_owner, canvas_item_owner); + _mark_ysort_dirty(item_owner); } } } diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h index f96397d369a..9dae2627c32 100644 --- a/servers/rendering/renderer_canvas_cull.h +++ b/servers/rendering/renderer_canvas_cull.h @@ -52,8 +52,7 @@ class RendererCanvasCull { bool children_order_dirty; int ysort_children_count; Color ysort_modulate; - Transform2D ysort_xform; - Vector2 ysort_pos; + Transform2D ysort_xform; // Relative to y-sorted subtree's root item (identity for such root). Its `origin.y` is used for sorting. int ysort_index; int ysort_parent_abs_z_index; // Absolute Z index of parent. Only populated and used when y-sorting. uint32_t visibility_layer = 0xffffffff; @@ -86,7 +85,6 @@ class RendererCanvasCull { index = 0; ysort_children_count = -1; ysort_xform = Transform2D(); - ysort_pos = Vector2(); ysort_index = 0; ysort_parent_abs_z_index = 0; } @@ -98,13 +96,15 @@ class RendererCanvasCull { } }; - struct ItemPtrSort { + struct ItemYSort { _FORCE_INLINE_ bool operator()(const Item *p_left, const Item *p_right) const { - if (Math::is_equal_approx(p_left->ysort_pos.y, p_right->ysort_pos.y)) { + const real_t left_y = p_left->ysort_xform.columns[2].y; + const real_t right_y = p_right->ysort_xform.columns[2].y; + if (Math::is_equal_approx(left_y, right_y)) { return p_left->ysort_index < p_right->ysort_index; } - return p_left->ysort_pos.y < p_right->ysort_pos.y; + return left_y < right_y; } }; @@ -189,7 +189,11 @@ class RendererCanvasCull { private: void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr); - void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item); + void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_is_already_y_sorted, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item); + + void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, RendererCanvasCull::Item *p_material_owner, const Color &p_modulate, RendererCanvasCull::Item **r_items, int &r_index, int p_z); + int _count_ysort_children(RendererCanvasCull::Item *p_canvas_item); + void _mark_ysort_dirty(RendererCanvasCull::Item *ysort_owner); static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1; diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index 0604b3c1ef9..a3bb75a3a0d 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -2291,7 +2291,7 @@ bool LightStorage::shadow_atlas_update_light(RID p_atlas, RID p_light_instance, old_quadrant = (old_key >> QUADRANT_SHIFT) & 0x3; old_shadow = old_key & SHADOW_INDEX_MASK; - should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick - tick > shadow_atlas_realloc_tolerance_msec); + should_realloc = shadow_atlas->quadrants[old_quadrant].subdivision != (uint32_t)best_subdiv && (tick - shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].alloc_tick > shadow_atlas_realloc_tolerance_msec); should_redraw = shadow_atlas->quadrants[old_quadrant].shadows[old_shadow].version != p_light_version; if (!should_realloc) { diff --git a/tests/core/math/test_color.h b/tests/core/math/test_color.h index 988ec66f9d0..6c68c2917e3 100644 --- a/tests/core/math/test_color.h +++ b/tests/core/math/test_color.h @@ -162,6 +162,12 @@ TEST_CASE("[Color] Linear <-> sRGB conversion") { CHECK_MESSAGE( color_srgb.srgb_to_linear().is_equal_approx(Color(0.35, 0.5, 0.6, 0.7)), "The sRGB color converted back to linear color space should match the expected value."); + CHECK_MESSAGE( + Color(1.0, 1.0, 1.0, 1.0).srgb_to_linear() == (Color(1.0, 1.0, 1.0, 1.0)), + "White converted from sRGB to linear should remain white."); + CHECK_MESSAGE( + Color(1.0, 1.0, 1.0, 1.0).linear_to_srgb() == (Color(1.0, 1.0, 1.0, 1.0)), + "White converted from linear to sRGB should remain white."); } TEST_CASE("[Color] Named colors") { diff --git a/tests/scene/test_tab_bar.h b/tests/scene/test_tab_bar.h new file mode 100644 index 00000000000..1b634d4296c --- /dev/null +++ b/tests/scene/test_tab_bar.h @@ -0,0 +1,866 @@ +/**************************************************************************/ +/* test_tab_bar.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_TAB_BAR_H +#define TEST_TAB_BAR_H + +#include "scene/gui/tab_bar.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestTabBar { +static inline Array build_array() { + return Array(); +} +template +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} + +TEST_CASE("[SceneTree][TabBar] tab operations") { + TabBar *tab_bar = memnew(TabBar); + SceneTree::get_singleton()->get_root()->add_child(tab_bar); + tab_bar->set_clip_tabs(false); + MessageQueue::get_singleton()->flush(); + + SIGNAL_WATCH(tab_bar, "tab_selected"); + SIGNAL_WATCH(tab_bar, "tab_changed"); + + SUBCASE("[TabBar] no tabs") { + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + } + + SUBCASE("[TabBar] add tabs") { + tab_bar->add_tab("tab0"); + CHECK(tab_bar->get_tab_count() == 1); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + tab_bar->add_tab("tab1"); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_bar->add_tab("tab2"); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + CHECK(tab_bar->get_tab_title(0) == "tab0"); + CHECK(tab_bar->get_tab_tooltip(0) == ""); + CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(0)); + CHECK_FALSE(tab_bar->is_tab_hidden(0)); + + CHECK(tab_bar->get_tab_title(1) == "tab1"); + CHECK(tab_bar->get_tab_tooltip(1) == ""); + CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(1)); + CHECK_FALSE(tab_bar->is_tab_hidden(1)); + + CHECK(tab_bar->get_tab_title(2) == "tab2"); + CHECK(tab_bar->get_tab_tooltip(2) == ""); + CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(2)); + CHECK_FALSE(tab_bar->is_tab_hidden(2)); + } + + SUBCASE("[TabBar] set tab count") { + // Adds multiple tabs at once. + tab_bar->set_tab_count(3); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + CHECK(tab_bar->get_tab_title(0) == ""); + CHECK(tab_bar->get_tab_tooltip(0) == ""); + CHECK(tab_bar->get_tab_text_direction(0) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(0)); + CHECK_FALSE(tab_bar->is_tab_hidden(0)); + + CHECK(tab_bar->get_tab_title(1) == ""); + CHECK(tab_bar->get_tab_tooltip(1) == ""); + CHECK(tab_bar->get_tab_text_direction(1) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(1)); + CHECK_FALSE(tab_bar->is_tab_hidden(1)); + + CHECK(tab_bar->get_tab_title(2) == ""); + CHECK(tab_bar->get_tab_tooltip(2) == ""); + CHECK(tab_bar->get_tab_text_direction(2) == Control::TEXT_DIRECTION_INHERITED); + CHECK_FALSE(tab_bar->is_tab_disabled(2)); + CHECK_FALSE(tab_bar->is_tab_hidden(2)); + + // Setting to less tabs than there are removes from the end. + tab_bar->set_tab_title(0, "tab0"); + tab_bar->set_tab_title(1, "tab1"); + tab_bar->set_tab_title(2, "tab2"); + + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + CHECK(tab_bar->get_tab_title(0) == "tab0"); + CHECK(tab_bar->get_tab_title(1) == "tab1"); + + // Remove all tabs. + tab_bar->set_tab_count(0); + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + } + + SUBCASE("[TabBar] clear tabs") { + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + tab_bar->clear_tabs(); + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabBar] remove tabs") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Remove first tab. + tab_bar->remove_tab(0); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_tab_title(0) == "tab1"); + CHECK(tab_bar->get_tab_title(1) == "tab2"); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Remove last tab. + tab_bar->remove_tab(1); + CHECK(tab_bar->get_tab_count() == 1); + CHECK(tab_bar->get_tab_title(0) == "tab1"); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Remove only tab. + tab_bar->remove_tab(0); + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK("tab_changed", build_array(build_array(-1))); + + // Remove current tab when there are other tabs. + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->set_current_tab(1); + tab_bar->set_current_tab(2); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 2); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + tab_bar->remove_tab(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + } + + SUBCASE("[TabBar] move tabs") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Don't move if index is the same. + tab_bar->move_tab(0, 0); + CHECK(tab_bar->get_tab_title(0) == "tab0"); + CHECK(tab_bar->get_tab_title(1) == "tab1"); + CHECK(tab_bar->get_tab_title(2) == "tab2"); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Move the first tab to the end. + tab_bar->move_tab(0, 2); + CHECK(tab_bar->get_tab_title(0) == "tab1"); + CHECK(tab_bar->get_tab_title(1) == "tab2"); + CHECK(tab_bar->get_tab_title(2) == "tab0"); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 2); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Move the second tab to the front. + tab_bar->move_tab(1, 0); + CHECK(tab_bar->get_tab_title(0) == "tab2"); + CHECK(tab_bar->get_tab_title(1) == "tab1"); + CHECK(tab_bar->get_tab_title(2) == "tab0"); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 2); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabBar] set current tab") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + // Set the current tab. + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + + // Set to same tab. + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Out of bounds. + ERR_PRINT_OFF; + tab_bar->set_current_tab(-5); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_bar->set_current_tab(5); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + ERR_PRINT_ON; + } + + SUBCASE("[TabBar] deselection enabled") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Setting deselect enabled doesn't change current tab. + tab_bar->set_deselect_enabled(true); + CHECK(tab_bar->get_deselect_enabled()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Can deselect all tabs by setting current to -1. + tab_bar->set_current_tab(-1); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(-1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(-1))); + + // Adding a tab will still set the current tab to 0. + tab_bar->clear_tabs(); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + tab_bar->set_current_tab(-1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Disabling while at -1 will select the first available tab. + tab_bar->set_deselect_enabled(false); + CHECK_FALSE(tab_bar->get_deselect_enabled()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + // Cannot set to -1 if disabled. + ERR_PRINT_OFF; + tab_bar->set_current_tab(-1); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + ERR_PRINT_ON; + + // Disabling while at -1 skips any disabled or hidden tabs. + tab_bar->set_deselect_enabled(true); + tab_bar->set_tab_disabled(0, true); + tab_bar->set_tab_hidden(1, true); + tab_bar->set_current_tab(-1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + tab_bar->set_deselect_enabled(false); + CHECK(tab_bar->get_current_tab() == 2); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(2))); + SIGNAL_CHECK("tab_changed", build_array(build_array(2))); + } + + SUBCASE("[TabBar] hidden tabs") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + CHECK_FALSE(tab_bar->is_tab_hidden(1)); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + MessageQueue::get_singleton()->flush(); + Vector tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + + // Hiding a tab does not affect current tab. + tab_bar->set_tab_hidden(1, true); + CHECK(tab_bar->is_tab_hidden(1)); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // The tabs after are moved over. + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]); + CHECK(tab_bar->get_tab_rect(2) == tab_rects[1]); + + // Unhiding a tab does not affect current tab. + tab_bar->set_tab_hidden(1, false); + CHECK_FALSE(tab_bar->is_tab_hidden(1)); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // The tabs are back where they were. + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_rect(0) == tab_rects[0]); + CHECK(tab_bar->get_tab_rect(1) == tab_rects[1]); + CHECK(tab_bar->get_tab_rect(2) == tab_rects[2]); + } + + SUBCASE("[TabBar] disabled tabs") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + CHECK_FALSE(tab_bar->is_tab_disabled(1)); + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + CHECK_FALSE(tab_bar->is_tab_hidden(1)); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Disabling a tab does not affect current tab. + tab_bar->set_tab_disabled(1, true); + CHECK(tab_bar->is_tab_disabled(1)); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Enabling a tab does not affect current tab. + tab_bar->set_tab_disabled(1, false); + CHECK_FALSE(tab_bar->is_tab_disabled(1)); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabBar] select next available") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->add_tab("tab3"); + tab_bar->add_tab("tab4"); + tab_bar->set_tab_disabled(2, true); + tab_bar->set_tab_hidden(3, true); + tab_bar->set_current_tab(0); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Selects the next tab. + CHECK(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + + // Skips over disabled and hidden tabs. + CHECK(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == 4); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK("tab_selected", build_array(build_array(4))); + SIGNAL_CHECK("tab_changed", build_array(build_array(4))); + + // Does not wrap around. + CHECK_FALSE(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == 4); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there is only one valid tab. + tab_bar->remove_tab(0); + tab_bar->remove_tab(3); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there are no valid tabs. + tab_bar->remove_tab(0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there are no tabs. + tab_bar->clear_tabs(); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_next_available()); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabBar] select previous available") { + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1"); + tab_bar->add_tab("tab2"); + tab_bar->add_tab("tab3"); + tab_bar->add_tab("tab4"); + tab_bar->set_tab_disabled(1, true); + tab_bar->set_tab_hidden(2, true); + tab_bar->set_current_tab(4); + CHECK(tab_bar->get_current_tab() == 4); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Selects the previous tab. + CHECK(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == 3); + CHECK(tab_bar->get_previous_tab() == 4); + SIGNAL_CHECK("tab_selected", build_array(build_array(3))); + SIGNAL_CHECK("tab_changed", build_array(build_array(3))); + + // Skips over disabled and hidden tabs. + CHECK(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 3); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + // Does not wrap around. + CHECK_FALSE(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 3); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there is only one valid tab. + tab_bar->remove_tab(4); + tab_bar->remove_tab(3); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 2); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == 2); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there are no valid tabs. + tab_bar->remove_tab(0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Fails if there are no tabs. + tab_bar->clear_tabs(); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + CHECK_FALSE(tab_bar->select_previous_available()); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SIGNAL_UNWATCH(tab_bar, "tab_selected"); + SIGNAL_UNWATCH(tab_bar, "tab_changed"); + + memdelete(tab_bar); +} + +TEST_CASE("[SceneTree][TabBar] initialization") { + TabBar *tab_bar = memnew(TabBar); + + SIGNAL_WATCH(tab_bar, "tab_selected"); + SIGNAL_WATCH(tab_bar, "tab_changed"); + + SUBCASE("[TabBar] current tab can be set before tabs are set") { + // This queues the current tab to update on when tabs are set. + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + + // Does not work again. + ERR_PRINT_OFF; + tab_bar->set_current_tab(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_bar->set_tab_count(3); + CHECK(tab_bar->get_tab_count() == 3); + CHECK(tab_bar->get_current_tab() == 1); + CHECK(tab_bar->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + ERR_PRINT_ON; + } + + SUBCASE("[TabBar] setting tabs works normally if no current tab was set") { + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabBar] cannot set current tab to an invalid value before tabs are set") { + tab_bar->set_current_tab(100); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // This will print an error message as if `set_current_tab` was called after. + ERR_PRINT_OFF; + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + ERR_PRINT_ON; + } + + SUBCASE("[TabBar] setting the current tab before tabs only works when out of tree") { + tab_bar->set_current_tab(1); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + SceneTree::get_singleton()->get_root()->add_child(tab_bar); + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_count() == 0); + CHECK(tab_bar->get_current_tab() == -1); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Works normally. + tab_bar->set_tab_count(2); + CHECK(tab_bar->get_tab_count() == 2); + CHECK(tab_bar->get_current_tab() == 0); + CHECK(tab_bar->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SIGNAL_UNWATCH(tab_bar, "tab_selected"); + SIGNAL_UNWATCH(tab_bar, "tab_changed"); + + memdelete(tab_bar); +} + +TEST_CASE("[SceneTree][TabBar] layout and offset") { + TabBar *tab_bar = memnew(TabBar); + SceneTree::get_singleton()->get_root()->add_child(tab_bar); + + tab_bar->set_clip_tabs(false); + tab_bar->add_tab("tab0"); + tab_bar->add_tab("tab1 "); + tab_bar->add_tab("tab2 "); + MessageQueue::get_singleton()->flush(); + Size2 all_tabs_size = tab_bar->get_size(); + + Vector tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + + SUBCASE("[TabBar] tabs are arranged next to each other") { + // Horizontal positions are next to each other. + CHECK(tab_rects[0].position.x == 0); + CHECK(tab_rects[1].position.x == tab_rects[0].size.x); + CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x); + + // Fills the entire width. + CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x); + + // Horizontal sizes are positive. + CHECK(tab_rects[0].size.x > 0); + CHECK(tab_rects[1].size.x > 0); + CHECK(tab_rects[2].size.x > 0); + + // Vertical positions are at 0. + CHECK(tab_rects[0].position.y == 0); + CHECK(tab_rects[1].position.y == 0); + CHECK(tab_rects[2].position.y == 0); + + // Vertical sizes are the same. + CHECK(tab_rects[0].size.y == tab_rects[1].size.y); + CHECK(tab_rects[1].size.y == tab_rects[2].size.y); + } + + SUBCASE("[TabBar] tab alignment") { + // Add extra space so the alignment can be seen. + tab_bar->set_size(Size2(all_tabs_size.x + 100, all_tabs_size.y)); + + // Left alignment. + tab_bar->set_tab_alignment(TabBar::ALIGNMENT_LEFT); + MessageQueue::get_singleton()->flush(); + tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_LEFT); + CHECK(tab_rects[0].position.x == 0); + CHECK(tab_rects[1].position.x == tab_rects[0].size.x); + CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x); + + // Right alignment. + tab_bar->set_tab_alignment(TabBar::ALIGNMENT_RIGHT); + MessageQueue::get_singleton()->flush(); + tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_RIGHT); + CHECK(tab_rects[2].position.x == tab_bar->get_size().x - tab_rects[2].size.x); + CHECK(tab_rects[1].position.x == tab_rects[2].position.x - tab_rects[1].size.x); + CHECK(tab_rects[0].position.x == tab_rects[1].position.x - tab_rects[0].size.x); + + // Center alignment. + tab_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER); + MessageQueue::get_singleton()->flush(); + tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + CHECK(tab_bar->get_tab_alignment() == TabBar::ALIGNMENT_CENTER); + float center_pos = tab_bar->get_size().x / 2; + CHECK(tab_rects[0].position.x == center_pos - all_tabs_size.x / 2); + CHECK(tab_rects[1].position.x == tab_rects[0].position.x + tab_rects[0].size.x); + CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x); + } + + SUBCASE("[TabBar] clip tabs") { + // Clip tabs disabled means all tabs are visible and the minimum size holds all of them. + tab_bar->set_clip_tabs(false); + CHECK_FALSE(tab_bar->get_clip_tabs()); + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_offset() == 0); + CHECK(tab_bar->get_minimum_size() == tab_bar->get_size()); + CHECK(tab_bar->get_size().x == tab_rects[0].size.x + tab_rects[1].size.x + tab_rects[2].size.x); + CHECK(tab_bar->get_size().y == MAX(tab_rects[0].size.y, MAX(tab_rects[1].size.y, tab_rects[2].size.y))); + + tab_bar->set_clip_tabs(true); + CHECK(tab_bar->get_clip_tabs()); + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_offset() == 0); + + // Horizontal size and minimum size get set to 0. + CHECK(tab_bar->get_minimum_size().x == 0); + CHECK(tab_bar->get_minimum_size().y == all_tabs_size.y); + CHECK(tab_bar->get_size().x == 0); + CHECK(tab_bar->get_size().y == all_tabs_size.y); + } + + SUBCASE("[TabBar] ensure tab visible") { + tab_bar->set_scroll_to_selected(false); + tab_bar->set_clip_tabs(true); + + // Resize tab bar to only be able to fit 2 tabs. + const float offset_button_size = tab_bar->get_theme_icon("decrement_icon")->get_width() + tab_bar->get_theme_icon("increment_icon")->get_width(); + tab_bar->set_size(Size2(tab_rects[2].size.x + tab_rects[1].size.x + offset_button_size, all_tabs_size.y)); + MessageQueue::get_singleton()->flush(); + CHECK(tab_bar->get_tab_offset() == 0); + CHECK(tab_bar->get_offset_buttons_visible()); + + // Scroll right to a tab that is not visible. + tab_bar->ensure_tab_visible(2); + CHECK(tab_bar->get_tab_offset() == 1); + CHECK(tab_bar->get_tab_rect(1).position.x == 0); + CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x); + + tab_bar->set_tab_offset(2); + CHECK(tab_bar->get_tab_offset() == 2); + CHECK(tab_bar->get_tab_rect(2).position.x == 0); + + // Scroll left to a previous tab. + tab_bar->ensure_tab_visible(1); + CHECK(tab_bar->get_tab_offset() == 1); + CHECK(tab_bar->get_tab_rect(1).position.x == 0); + CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x); + + // Will not scroll if the tab is already visible. + tab_bar->ensure_tab_visible(2); + CHECK(tab_bar->get_tab_offset() == 1); + CHECK(tab_bar->get_tab_rect(1).position.x == 0); + CHECK(tab_bar->get_tab_rect(2).position.x == tab_rects[1].size.x); + } + + memdelete(tab_bar); +} + +// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop. + +} // namespace TestTabBar + +#endif // TEST_TAB_BAR_H diff --git a/tests/scene/test_tab_container.h b/tests/scene/test_tab_container.h new file mode 100644 index 00000000000..75666fa3d8f --- /dev/null +++ b/tests/scene/test_tab_container.h @@ -0,0 +1,673 @@ +/**************************************************************************/ +/* test_tab_container.h */ +/**************************************************************************/ +/* This file is part of: */ +/* REDOT ENGINE */ +/* https://redotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2024-present Redot Engine contributors */ +/* (see REDOT_AUTHORS.md) */ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_TAB_CONTAINER_H +#define TEST_TAB_CONTAINER_H + +#include "scene/gui/tab_container.h" + +#include "tests/test_macros.h" + +namespace TestTabContainer { +static inline Array build_array() { + return Array(); +} +template +static inline Array build_array(Variant item, Targs... Fargs) { + Array a = build_array(Fargs...); + a.push_front(item); + return a; +} + +TEST_CASE("[SceneTree][TabContainer] tab operations") { + TabContainer *tab_container = memnew(TabContainer); + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + SIGNAL_WATCH(tab_container, "tab_selected"); + SIGNAL_WATCH(tab_container, "tab_changed"); + + Control *tab0 = memnew(Control); + tab0->set_name("tab0"); + Control *tab1 = memnew(Control); + tab1->set_name("tab1"); + Control *tab2 = memnew(Control); + tab2->set_name("tab2"); + + SUBCASE("[TabContainer] add tabs by adding children") { + CHECK(tab_container->get_tab_count() == 0); + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + + // Add first tab child. + tab_container->add_child(tab0); + // MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + + // Add second tab child. + tab_container->add_child(tab1); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Check default values, the title is the name of the child. + CHECK(tab_container->get_tab_control(0) == tab0); + CHECK(tab_container->get_tab_idx_from_control(tab0) == 0); + CHECK(tab_container->get_tab_title(0) == "tab0"); + CHECK(tab_container->get_tab_tooltip(0) == ""); + CHECK_FALSE(tab_container->is_tab_disabled(0)); + CHECK_FALSE(tab_container->is_tab_hidden(0)); + + CHECK(tab_container->get_tab_control(1) == tab1); + CHECK(tab_container->get_tab_idx_from_control(tab1) == 1); + CHECK(tab_container->get_tab_title(1) == "tab1"); + CHECK(tab_container->get_tab_tooltip(1) == ""); + CHECK_FALSE(tab_container->is_tab_disabled(1)); + CHECK_FALSE(tab_container->is_tab_hidden(1)); + } + + SUBCASE("[TabContainer] remove tabs by removing children") { + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + tab_container->set_current_tab(1); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Remove first tab. + tab_container->remove_child(tab0); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_tab_title(0) == "tab1"); + CHECK(tab_container->get_tab_title(1) == "tab2"); + CHECK(tab_container->get_tab_idx_from_control(tab1) == 0); + CHECK(tab_container->get_tab_idx_from_control(tab2) == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Remove last tab. + tab_container->remove_child(tab2); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_tab_title(0) == "tab1"); + CHECK(tab_container->get_tab_idx_from_control(tab1) == 0); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Remove only tab. + tab_container->remove_child(tab1); + CHECK(tab_container->get_tab_count() == 0); + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK("tab_changed", build_array(build_array(-1))); + + // Remove current tab when there are other tabs. + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + tab_container->set_current_tab(1); + tab_container->set_current_tab(2); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 2); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + tab_container->remove_child(tab2); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + } + + SUBCASE("[TabContainer] move tabs by moving children") { + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + tab_container->set_current_tab(1); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + + // Move the first tab to the end. + tab_container->move_child(tab0, 2); + CHECK(tab_container->get_tab_idx_from_control(tab0) == 2); + CHECK(tab_container->get_tab_idx_from_control(tab1) == 0); + CHECK(tab_container->get_tab_idx_from_control(tab2) == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == 2); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Move the second tab to the front. + tab_container->move_child(tab2, 0); + CHECK(tab_container->get_tab_idx_from_control(tab0) == 2); + CHECK(tab_container->get_tab_idx_from_control(tab1) == 1); + CHECK(tab_container->get_tab_idx_from_control(tab2) == 0); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 2); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + } + + SUBCASE("[TabContainer] set current tab") { + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK("tab_selected", build_array(build_array(0))); + SIGNAL_CHECK("tab_changed", build_array(build_array(0))); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Set the current tab. + tab_container->set_current_tab(1); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Set to same tab. + tab_container->set_current_tab(1); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK_FALSE("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Out of bounds. + ERR_PRINT_OFF; + tab_container->set_current_tab(-5); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + tab_container->set_current_tab(5); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + ERR_PRINT_ON; + } + + SUBCASE("[TabContainer] change current tab by changing visibility of children") { + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Show a child to make it the current tab. + tab1->show(); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Hide the visible child to select the next tab. + tab1->hide(); + CHECK(tab_container->get_current_tab() == 2); + CHECK(tab_container->get_previous_tab() == 1); + SIGNAL_CHECK("tab_selected", build_array(build_array(2))); + SIGNAL_CHECK("tab_changed", build_array(build_array(2))); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + CHECK(tab2->is_visible()); + + // Hide the visible child to select the previous tab if there is no next. + tab2->hide(); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 2); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + + // Cannot hide if there is only one valid child since deselection is not enabled. + tab_container->remove_child(tab1); + tab_container->remove_child(tab2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_DISCARD("tab_selected"); + SIGNAL_DISCARD("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + + tab0->hide(); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + + // Can hide the last tab if deselection is enabled. + tab_container->set_deselect_enabled(true); + tab0->hide(); + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(-1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(-1))); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(tab0->is_visible()); + } + + SIGNAL_UNWATCH(tab_container, "tab_selected"); + SIGNAL_UNWATCH(tab_container, "tab_changed"); + + memdelete(tab2); + memdelete(tab1); + memdelete(tab0); + memdelete(tab_container); +} + +TEST_CASE("[SceneTree][TabContainer] initialization") { + TabContainer *tab_container = memnew(TabContainer); + + Control *tab0 = memnew(Control); + tab0->set_name("tab0"); + Control *tab1 = memnew(Control); + tab1->set_name("tab1 "); + Control *tab2 = memnew(Control); + tab2->set_name("tab2 "); + + SIGNAL_WATCH(tab_container, "tab_selected"); + SIGNAL_WATCH(tab_container, "tab_changed"); + + SUBCASE("[TabContainer] add children before entering tree") { + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + + tab_container->add_child(tab0); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + tab_container->add_child(tab1); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + CHECK(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + } + + SUBCASE("[TabContainer] current tab can be set before children are added") { + // Set the current tab before there are any tabs. + // This queues the current tab to update on entering the tree. + tab_container->set_current_tab(1); + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_container->add_child(tab0); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + tab_container->add_child(tab1); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + tab_container->add_child(tab2); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // Current tab is set when entering the tree. + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + } + + SUBCASE("[TabContainer] cannot set current tab to an invalid value before tabs are set") { + tab_container->set_current_tab(100); + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_container->add_child(tab0); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + tab_container->add_child(tab1); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + + // This will print an error message as if `set_current_tab` was called after. + ERR_PRINT_OFF; + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + ERR_PRINT_ON; + } + + SUBCASE("[TabContainer] children visibility before entering tree") { + CHECK(tab_container->get_current_tab() == -1); + CHECK(tab_container->get_previous_tab() == -1); + + // Adding a hidden child first will change visibility because it is the current tab. + tab0->hide(); + tab_container->add_child(tab0); + CHECK(tab_container->get_tab_count() == 1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + + // Adding a visible child after will hide it because it is not the current tab. + tab_container->add_child(tab1); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + MessageQueue::get_singleton()->flush(); + CHECK(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + + // Can change current by showing child now after children have been added. + // This queues the current tab to update on entering the tree. + tab1->show(); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + CHECK(tab0->is_visible()); + CHECK(tab1->is_visible()); + + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 2); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + } + + SUBCASE("[TabContainer] setting current tab and changing child visibility after adding") { + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + tab2->show(); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + SIGNAL_CHECK_FALSE("tab_selected"); + SIGNAL_CHECK_FALSE("tab_changed"); + CHECK(tab0->is_visible()); + CHECK_FALSE(tab1->is_visible()); + CHECK(tab2->is_visible()); + + // Whichever happens last will have priority. + tab_container->set_current_tab(1); + CHECK(tab_container->get_current_tab() == 0); + CHECK(tab_container->get_previous_tab() == -1); + + // Current tab is set when entering the tree. + SceneTree::get_singleton()->get_root()->add_child(tab_container); + MessageQueue::get_singleton()->flush(); + CHECK(tab_container->get_tab_count() == 3); + CHECK(tab_container->get_current_tab() == 1); + CHECK(tab_container->get_previous_tab() == 0); + SIGNAL_CHECK("tab_selected", build_array(build_array(1))); + SIGNAL_CHECK("tab_changed", build_array(build_array(1))); + CHECK_FALSE(tab0->is_visible()); + CHECK(tab1->is_visible()); + CHECK_FALSE(tab2->is_visible()); + } + + SIGNAL_UNWATCH(tab_container, "tab_selected"); + SIGNAL_UNWATCH(tab_container, "tab_changed"); + + memdelete(tab2); + memdelete(tab1); + memdelete(tab0); + memdelete(tab_container); +} + +TEST_CASE("[SceneTree][TabContainer] layout and offset") { + TabContainer *tab_container = memnew(TabContainer); + SceneTree::get_singleton()->get_root()->add_child(tab_container); + tab_container->set_clip_tabs(false); + + Control *tab0 = memnew(Control); + tab0->set_name("tab0"); + Control *tab1 = memnew(Control); + tab1->set_name("tab1 "); + Control *tab2 = memnew(Control); + tab2->set_name("tab2 "); + + tab_container->add_child(tab0); + tab_container->add_child(tab1); + tab_container->add_child(tab2); + + MessageQueue::get_singleton()->flush(); + + Size2 all_tabs_size = tab_container->get_size(); + const float side_margin = tab_container->get_theme_constant("side_margin"); + + TabBar *tab_bar = tab_container->get_tab_bar(); + + Vector tab_rects = { + tab_bar->get_tab_rect(0), + tab_bar->get_tab_rect(1), + tab_bar->get_tab_rect(2) + }; + + SUBCASE("[TabContainer] tabs are arranged next to each other") { + // Horizontal positions are next to each other. + CHECK(tab_rects[0].position.x == 0); + CHECK(tab_rects[1].position.x == tab_rects[0].size.x); + CHECK(tab_rects[2].position.x == tab_rects[1].position.x + tab_rects[1].size.x); + + // Fills the entire width. + CHECK(tab_rects[2].position.x + tab_rects[2].size.x == all_tabs_size.x - side_margin); + + // Horizontal sizes are positive. + CHECK(tab_rects[0].size.x > 0); + CHECK(tab_rects[1].size.x > 0); + CHECK(tab_rects[2].size.x > 0); + + // Vertical positions are at 0. + CHECK(tab_rects[0].position.y == 0); + CHECK(tab_rects[1].position.y == 0); + CHECK(tab_rects[2].position.y == 0); + + // Vertical sizes are the same. + CHECK(tab_rects[0].size.y == tab_rects[1].size.y); + CHECK(tab_rects[1].size.y == tab_rects[2].size.y); + } + + SUBCASE("[TabContainer] tab position") { + float tab_height = tab_rects[0].size.y; + Ref panel_style = tab_container->get_theme_stylebox("panel_style"); + + // Initial position, same as top position. + // Tab bar is at the top. + CHECK(tab_bar->get_anchor(SIDE_TOP) == 0); + CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0); + CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0); + CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab_bar->get_offset(SIDE_TOP) == 0); + CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height); + CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin); + CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0); + + // Child is expanded and below the tab bar. + CHECK(tab0->get_anchor(SIDE_TOP) == 0); + CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1); + CHECK(tab0->get_anchor(SIDE_LEFT) == 0); + CHECK(tab0->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab0->get_offset(SIDE_TOP) == tab_height); + CHECK(tab0->get_offset(SIDE_BOTTOM) == 0); + CHECK(tab0->get_offset(SIDE_LEFT) == 0); + CHECK(tab0->get_offset(SIDE_RIGHT) == 0); + + // Bottom position. + tab_container->set_tabs_position(TabContainer::POSITION_BOTTOM); + CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_BOTTOM); + MessageQueue::get_singleton()->flush(); + + // Tab bar is at the bottom. + CHECK(tab_bar->get_anchor(SIDE_TOP) == 1); + CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 1); + CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0); + CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab_bar->get_offset(SIDE_TOP) == -tab_height); + CHECK(tab_bar->get_offset(SIDE_BOTTOM) == 0); + CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin); + CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0); + + // Child is expanded and above the tab bar. + CHECK(tab0->get_anchor(SIDE_TOP) == 0); + CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1); + CHECK(tab0->get_anchor(SIDE_LEFT) == 0); + CHECK(tab0->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab0->get_offset(SIDE_TOP) == 0); + CHECK(tab0->get_offset(SIDE_BOTTOM) == -tab_height); + CHECK(tab0->get_offset(SIDE_LEFT) == 0); + CHECK(tab0->get_offset(SIDE_RIGHT) == 0); + + // Top position. + tab_container->set_tabs_position(TabContainer::POSITION_TOP); + CHECK(tab_container->get_tabs_position() == TabContainer::POSITION_TOP); + MessageQueue::get_singleton()->flush(); + + // Tab bar is at the top. + CHECK(tab_bar->get_anchor(SIDE_TOP) == 0); + CHECK(tab_bar->get_anchor(SIDE_BOTTOM) == 0); + CHECK(tab_bar->get_anchor(SIDE_LEFT) == 0); + CHECK(tab_bar->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab_bar->get_offset(SIDE_TOP) == 0); + CHECK(tab_bar->get_offset(SIDE_BOTTOM) == tab_height); + CHECK(tab_bar->get_offset(SIDE_LEFT) == side_margin); + CHECK(tab_bar->get_offset(SIDE_RIGHT) == 0); + + // Child is expanded and below the tab bar. + CHECK(tab0->get_anchor(SIDE_TOP) == 0); + CHECK(tab0->get_anchor(SIDE_BOTTOM) == 1); + CHECK(tab0->get_anchor(SIDE_LEFT) == 0); + CHECK(tab0->get_anchor(SIDE_RIGHT) == 1); + CHECK(tab0->get_offset(SIDE_TOP) == tab_height); + CHECK(tab0->get_offset(SIDE_BOTTOM) == 0); + CHECK(tab0->get_offset(SIDE_LEFT) == 0); + CHECK(tab0->get_offset(SIDE_RIGHT) == 0); + } + + memdelete(tab_container); +} + +// FIXME: Add tests for mouse click, keyboard navigation, and drag and drop. + +} // namespace TestTabContainer + +#endif // TEST_TAB_CONTAINER_H diff --git a/tests/test_macros.h b/tests/test_macros.h index f925f0b8fa3..a7360302938 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -378,6 +378,9 @@ class SignalWatcher : public Object { bool check_false(const String &p_name) { bool has = _signals.has(p_name); + if (has) { + MESSAGE("Signal has " << _signals[p_name] << " expected none."); + } discard_signal(p_name); return !has; } diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 1f6375be26b..6eb1ea889b0 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -135,6 +135,8 @@ #include "tests/scene/test_code_edit.h" #include "tests/scene/test_color_picker.h" #include "tests/scene/test_graph_node.h" +#include "tests/scene/test_tab_bar.h" +#include "tests/scene/test_tab_container.h" #include "tests/scene/test_text_edit.h" #endif // ADVANCED_GUI_DISABLED