diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index c276161863..8f83d8adf8 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -49,6 +49,7 @@ If set to something other than [constant TextServer.AUTOWRAP_OFF], the text gets wrapped inside the node's bounding rectangle. If you resize the node, it will change its height automatically to show all the text. + [b]Note:[/b] Labels with autowrapping enabled must have a custom maximum width configured to work correctly, either through the Label's own [member Control.custom_maximum_size] or as a result of a propagated maximum size from a parent Control with [member Control.propagate_maximum_size] enabled. Autowrap space trimming flags. See [constant TextServer.BREAK_TRIM_START_EDGE_SPACES] and [constant TextServer.BREAK_TRIM_END_EDGE_SPACES] for more info. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 1765a0cb3a..31ae32b1e0 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -715,6 +715,7 @@ If set to something other than [constant TextServer.AUTOWRAP_OFF], the text gets wrapped inside the node's bounding rectangle. + [b]Note:[/b] RichTextLabels with autowrapping and [member fit_content] enabled must have a custom maximum width configured to work correctly, either through the RichTextLabel's own [member Control.custom_maximum_size] or as a result of a propagated maximum size from a parent Control with [member Control.propagate_maximum_size] enabled. Autowrap space trimming flags. See [constant TextServer.BREAK_TRIM_START_EDGE_SPACES] and [constant TextServer.BREAK_TRIM_END_EDGE_SPACES] for more info. @@ -739,6 +740,7 @@ If [code]true[/code], the label's minimum size will be automatically updated to fit its content, matching the behavior of [Label]. + [b]Note:[/b] RichTextLabels with autowrapping and [member fit_content] enabled must have a custom maximum width configured to work correctly, either through the RichTextLabel's own [member Control.custom_maximum_size] or as a result of a propagated maximum size from a parent Control with [member Control.propagate_maximum_size] enabled. diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 9366834a43..345c1fff8f 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -32,7 +32,6 @@ #include "core/object/callable_mp.h" #include "core/object/class_db.h" -#include "scene/gui/container.h" #include "scene/main/scene_tree.h" #include "scene/theme/theme_db.h" #include "servers/display/accessibility_server.h" @@ -145,6 +144,20 @@ void Label::_shape() const { Ref style = theme_cache.normal_style; int width = (get_size().width - style->get_minimum_size().width); + float combined_maximum_width = get_combined_maximum_size().x; + bool wrap_with_max_width = autowrap_mode != TextServer::AUTOWRAP_OFF && combined_maximum_width > 0; + int maximum_width = -1; + if (wrap_with_max_width) { + maximum_width = int(combined_maximum_width - style->get_minimum_size().width); + if (maximum_width <= 0) { + maximum_width = 1; + } + if (width > 0) { + width = MIN(width, maximum_width); + } else { + width = maximum_width; + } + } if (text_dirty) { for (Paragraph ¶ : paragraphs) { @@ -250,7 +263,7 @@ void Label::_shape() const { bool lines_hidden = visible_lines > 0 && visible_lines < total_line_count; int line_index = 0; - if (autowrap_mode == TextServer::AUTOWRAP_OFF) { + if (autowrap_mode == TextServer::AUTOWRAP_OFF || wrap_with_max_width) { minsize.width = 0.0f; } for (Paragraph ¶ : paragraphs) { @@ -260,6 +273,8 @@ void Label::_shape() const { minsize.width = TS->shaped_text_get_size(line_rid).x; } } + } else if (wrap_with_max_width) { + minsize.width = MAX(minsize.width, TS->shaped_text_get_size(para.text_rid).x); } if (para.lines_dirty) { @@ -339,6 +354,9 @@ void Label::_shape() const { } line_index += para.lines_rid.size(); } + if (wrap_with_max_width && maximum_width > 0) { + minsize.width = MIN(minsize.width, maximum_width); + } _update_visible(); @@ -631,12 +649,8 @@ PackedStringArray Label::get_configuration_warnings() const { // but for now we have to warn about this impossible to resolve combination. // See GH-83546. if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) { - // If the Label happens to be the root node of the edited scene, we don't need - // to check what its parent is. It's going to be some node from the editor tree - // and it can be a container, but that makes no difference to the user. - Container *parent_container = Object::cast_to(get_parent_control()); - if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) { - warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container.")); + if (autowrap_mode != TextServer::AUTOWRAP_OFF && get_combined_maximum_size().width <= 0) { + warnings.push_back(RTR("Labels with autowrapping enabled must have a positive custom maximum width configured to work correctly.")); } } @@ -979,6 +993,9 @@ Size2 Label::get_minimum_size() const { _ensure_shaped(); Size2 min_size = minsize; + Size2 combined_maximum_size = get_combined_maximum_size(); + bool wrap_with_max_width = autowrap_mode != TextServer::AUTOWRAP_OFF && combined_maximum_size.x > 0; + bool overrun_with_max_width = autowrap_mode == TextServer::AUTOWRAP_OFF && combined_maximum_size.x > 0 && (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING); const Ref &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; @@ -993,12 +1010,25 @@ Size2 Label::get_minimum_size() const { } else if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { min_size.height = 1; } - return Size2(1, min_size.height) + min_style; + if (wrap_with_max_width) { + min_size.width = MAX(1, min_size.width); + return min_size + min_style; + } else { + return Size2(1, min_size.height) + min_style; + } } else { if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { - min_size.width = 1; + if (overrun_with_max_width) { + min_size.width = MAX(1, min_size.width); + } else { + min_size.width = 1; + } } - return min_size + min_style; + Size2 computed_min_size = min_size + min_style; + if (overrun_with_max_width) { + computed_min_size.width = MIN(computed_min_size.width, combined_maximum_size.x); + } + return computed_min_size; } } @@ -1123,6 +1153,19 @@ void Label::_invalidate() { update_configuration_warnings(); } +void Label::_maximum_size_changed() { + if (autowrap_mode == TextServer::AUTOWRAP_OFF && overrun_behavior == TextServer::OVERRUN_NO_TRIMMING) { + return; + } + + for (Paragraph ¶ : paragraphs) { + para.lines_dirty = true; + } + queue_redraw(); + update_minimum_size(); + update_configuration_warnings(); +} + void Label::set_label_settings(const Ref &p_settings) { if (settings != p_settings) { if (settings.is_valid()) { @@ -1479,6 +1522,8 @@ void Label::_bind_methods() { } Label::Label(const String &p_text) { + connect(SceneStringName(maximum_size_changed), callable_mp(this, &Label::_maximum_size_changed)); + set_mouse_filter(MOUSE_FILTER_IGNORE); set_text(p_text); set_v_size_flags(SIZE_SHRINK_CENTER); diff --git a/scene/gui/label.h b/scene/gui/label.h index be05c98e0e..472c709afb 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -101,6 +101,7 @@ private: void _update_visible() const; void _shape() const; void _invalidate(); + void _maximum_size_changed(); protected: RID get_line_rid(int p_line) const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 032544dee3..d679f63727 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -206,6 +206,20 @@ Rect2 RichTextLabel::_get_text_rect() { return Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size()); } +int RichTextLabel::_get_wrap_width(const Rect2 &p_text_rect) const { + int wrap_width = p_text_rect.get_size().width; + float combined_maximum_width = get_combined_maximum_size().x; + if (autowrap_mode != TextServer::AUTOWRAP_OFF && combined_maximum_width > 0.0) { + int maximum_width = int(combined_maximum_width - theme_cache.normal_style->get_minimum_size().width); + if (maximum_width <= 0) { + maximum_width = 1; + } + wrap_width = MIN(wrap_width, maximum_width); + wrap_width = MAX(wrap_width, 1); + } + return wrap_width; +} + RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item_from, RichTextLabel::Item *p_item_to, int p_position) { int offset = 0; for (Item *it = p_item_from; it && it != p_item_to; it = _get_next_item(it)) { @@ -4015,6 +4029,7 @@ bool RichTextLabel::_validate_line_caches() { if (main->first_invalid_line.load() == (int)main->lines.size()) { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); + int wrap_width = _get_wrap_width(text_rect); float ctrl_height = get_size().height; @@ -4043,8 +4058,8 @@ bool RichTextLabel::_validate_line_caches() { float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); for (int i = fi; i < (int)main->lines.size(); i++) { - total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); - total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height); + total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, wrap_width - scroll_w, total_height); + total_height = _update_scroll_exceeds(total_height, ctrl_height, wrap_width, i, old_scroll, text_rect.size.height); main->first_resized_line.store(i); } @@ -4091,6 +4106,7 @@ void RichTextLabel::_process_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); + int wrap_width = _get_wrap_width(text_rect); float ctrl_height = get_size().height; int fi = main->first_invalid_line.load(); @@ -4119,8 +4135,8 @@ void RichTextLabel::_process_line_caches() { } for (int i = sr; i < fi; i++) { - total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); - total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height); + total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, wrap_width - scroll_w, total_height); + total_height = _update_scroll_exceeds(total_height, ctrl_height, wrap_width, i, old_scroll, text_rect.size.height); main->first_resized_line.store(i); @@ -4133,8 +4149,8 @@ void RichTextLabel::_process_line_caches() { total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); for (int i = fi; i < (int)main->lines.size(); i++) { - total_height = _shape_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars); - total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height); + total_height = _shape_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, wrap_width - scroll_w, total_height, &total_chars); + total_height = _update_scroll_exceeds(total_height, ctrl_height, wrap_width, i, old_scroll, text_rect.size.height); main->first_invalid_line.store(i); main->first_resized_line.store(i); @@ -7571,6 +7587,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { _invalidate_accessibility(); _validate_line_caches(); queue_redraw(); + update_minimum_size(); } } @@ -7586,6 +7603,7 @@ void RichTextLabel::set_autowrap_trim_flags(BitField main->first_invalid_line = 0; // Invalidate all lines. _validate_line_caches(); queue_redraw(); + update_minimum_size(); } } @@ -7763,6 +7781,19 @@ bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) { } #endif +void RichTextLabel::_maximum_size_changed() { + if (!fit_content || autowrap_mode == TextServer::AUTOWRAP_OFF) { + return; + } + + _stop_thread(); + main->first_resized_line.store(0); // Invalidate all lines. + _invalidate_accessibility(); + _validate_line_caches(); + queue_redraw(); + update_minimum_size(); +} + void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); @@ -8231,14 +8262,34 @@ int RichTextLabel::get_total_glyph_count() const { Size2 RichTextLabel::get_minimum_size() const { Size2 sb_min_size = theme_cache.normal_style->get_minimum_size(); Size2 min_size; + bool wrap_with_max_width = autowrap_mode != TextServer::AUTOWRAP_OFF && get_combined_maximum_size().x > 0.0; if (fit_content) { - min_size.x = get_content_width(); + if (!wrap_with_max_width) { + min_size.x = get_content_width(); + } min_size.y = get_content_height(); } + if (wrap_with_max_width) { + const_cast(this)->_validate_line_caches(); + + int natural_width = 0; + int to_line = main->first_invalid_line.load(); + for (int i = 0; i < to_line; i++) { + MutexLock lock(main->lines[i].text_buf->get_mutex()); + natural_width = MAX(natural_width, int(Math::ceil(main->lines[i].offset.x + main->lines[i].text_buf->get_non_wrapped_size().x))); + } + + int maximum_width = int(get_combined_maximum_size().x - sb_min_size.width); + if (maximum_width <= 0) { + maximum_width = 1; + } + min_size.x = MIN(natural_width, maximum_width); + } + return sb_min_size + - ((autowrap_mode != TextServer::AUTOWRAP_OFF) ? Size2(1, min_size.height) : min_size); + ((autowrap_mode != TextServer::AUTOWRAP_OFF) ? Size2(wrap_with_max_width ? MAX(1, min_size.x) : 1, min_size.height) : min_size); } // Context menu. @@ -8387,6 +8438,8 @@ Dictionary RichTextLabel::parse_expressions_for_values(Vector p_expressi } RichTextLabel::RichTextLabel(const String &p_text) { + connect(SceneStringName(maximum_size_changed), callable_mp(this, &RichTextLabel::_maximum_size_changed)); + main = memnew(ItemFrame); main->owner = get_instance_id(); main->rid = items.make_rid(main); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 2712a69a4e..48c95d6fbf 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -716,6 +716,10 @@ private: Item *_get_prev_item(Item *p_item, bool p_free = false) const; Rect2 _get_text_rect(); + int _get_wrap_width(const Rect2 &p_text_rect) const; + + void _maximum_size_changed(); + Ref _get_custom_effect_by_code(String p_bbcode_identifier); virtual Dictionary parse_expressions_for_values(Vector p_expressions); diff --git a/tests/scene/test_label.cpp b/tests/scene/test_label.cpp new file mode 100644 index 0000000000..3a634bd893 --- /dev/null +++ b/tests/scene/test_label.cpp @@ -0,0 +1,261 @@ +/**************************************************************************/ +/* test_label.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_label) + +#if defined(MODULE_TEXT_SERVER_FB_ENABLED) || defined(MODULE_TEXT_SERVER_ADV_ENABLED) + +#include "scene/gui/label.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestLabel { + +TEST_CASE("[SceneTree][Label] Custom minimum size") { + // This is an anti-regression test case introduced in GH-116640. When the old minimum size behavior is removed, this test case should be removed too. + Label *test_label = memnew(Label); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_label); + + real_t min_width = 50; + + test_label->set_custom_minimum_size(Size2(min_width, 0)); + test_label->set_text("This is a long text that should be wrapped and exceeds minimum size."); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase to fit the text with AUTOWRAP_OFF."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_ARBITRARY); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_ARBITRARY."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_WORD."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_WORD_SMART."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_OFF); + test_label->set_clip_text(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with clip_text."); + + test_label->set_clip_text(false); + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_CHAR); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_CHAR."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_WORD."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_ELLIPSIS."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_WORD_ELLIPSIS."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS_FORCE); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_ELLIPSIS_FORCE."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS_FORCE); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with OVERRUN_TRIM_WORD_ELLIPSIS_FORCE."); + + memdelete(test_label); +} + +TEST_CASE("[SceneTree][Label] Sizing") { + Label *test_label = memnew(Label); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_label); + + real_t max_width = 50; + real_t min_width = 25; + + test_label->set_custom_maximum_size(Size2(max_width, 0)); + test_label->set_custom_minimum_size(Size2(min_width, 0)); + test_label->set_text("This is a long text that should be wrapped and exceeds minimum size."); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_OFF."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_OFF."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_ARBITRARY); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_ARBITRARY."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_ARBITRARY."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_WORD."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_WORD."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_WORD_SMART."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_WORD_SMART."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_OFF); + test_label->set_clip_text(true); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with clip_text."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with clip_text."); + + test_label->set_clip_text(false); + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_CHAR); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_CHAR."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_CHAR."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_WORD."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_WORD."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_ELLIPSIS."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_ELLIPSIS."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_WORD_ELLIPSIS."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_WORD_ELLIPSIS."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS_FORCE); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_ELLIPSIS_FORCE."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_ELLIPSIS_FORCE."); + + test_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS_FORCE); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with OVERRUN_TRIM_WORD_ELLIPSIS_FORCE."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with OVERRUN_TRIM_WORD_ELLIPSIS_FORCE."); + + memdelete(test_label); +} + +} // namespace TestLabel + +#endif // MODULE_TEXT_SERVER_FB_ENABLED || MODULE_TEXT_SERVER_ADV_ENABLED diff --git a/tests/scene/test_rich_text_label.cpp b/tests/scene/test_rich_text_label.cpp new file mode 100644 index 0000000000..8daf0f5eb5 --- /dev/null +++ b/tests/scene/test_rich_text_label.cpp @@ -0,0 +1,142 @@ +/**************************************************************************/ +/* test_rich_text_label.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "tests/test_macros.h" + +TEST_FORCE_LINK(test_rich_text_label) + +#ifndef ADVANCED_GUI_DISABLED + +#include "scene/gui/rich_text_label.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" + +namespace TestRichTextLabel { + +TEST_CASE("[SceneTree][RichTextLabel] Custom minimum size with fit content") { + // This is an anti-regression test case introduced in GH-116640. When the old minimum size behavior is removed, this test case should be removed too. + RichTextLabel *test_label = memnew(RichTextLabel); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_label); + + real_t min_width = 50; + + test_label->set_fit_content(true); + test_label->set_custom_minimum_size(Size2(min_width, 0)); + test_label->set_autowrap_mode(TextServer::AUTOWRAP_OFF); + test_label->set_text("This is a long text that should be wrapped and exceeds minimum size."); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase to fit the text with AUTOWRAP_OFF."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_ARBITRARY); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_ARBITRARY."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_WORD."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + Math::is_equal_approx(test_label->get_size().width, min_width), + "Label width will be equal to custom minimum width with AUTOWRAP_WORD_SMART."); + + memdelete(test_label); +} + +TEST_CASE("[SceneTree][RichTextLabel] Sizing with fit content") { + RichTextLabel *test_label = memnew(RichTextLabel); + Window *root = SceneTree::get_singleton()->get_root(); + root->add_child(test_label); + + real_t max_width = 50; + real_t min_width = 25; + + test_label->set_fit_content(true); + test_label->set_custom_maximum_size(Size2(max_width, 0)); + test_label->set_custom_minimum_size(Size2(min_width, 0)); + test_label->set_autowrap_mode(TextServer::AUTOWRAP_OFF); + test_label->set_text("This is a long text that should be wrapped and exceeds minimum size."); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_OFF."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_OFF."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_ARBITRARY); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_ARBITRARY."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_ARBITRARY."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_WORD."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_WORD."); + + test_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); + SceneTree::get_singleton()->process(0); + + CHECK_MESSAGE( + test_label->get_size().width <= max_width, + "Label width will increase up to the custom maximum width with AUTOWRAP_WORD_SMART."); + CHECK_MESSAGE( + test_label->get_size().width > min_width, + "Label width will increase beyond the custom minimum width with AUTOWRAP_WORD_SMART."); + + memdelete(test_label); +} + +} // namespace TestRichTextLabel + +#endif // ADVANCED_GUI_DISABLED