From faf1ab126dbdc18adcfacaf035c2f0126bf67f1f Mon Sep 17 00:00:00 2001 From: Koyper Date: Sat, 4 Oct 2025 12:03:01 -0500 Subject: [PATCH] Fix RichTextLabel bullet list font issues --- scene/gui/rich_text_label.cpp | 206 +++++++++++++++++++--------------- scene/gui/rich_text_label.h | 37 +++++- 2 files changed, 145 insertions(+), 98 deletions(-) diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 9e5d253bb99..87dbdfc13d4 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -257,6 +257,110 @@ String RichTextLabel::_get_prefix(Item *p_item, const Vector &p_list_index, return prefix + " "; } +void RichTextLabel::_add_list_prefixes(ItemFrame *p_frame, int p_line, Line &r_l) { + Vector list_index; + Vector list_count; + Vector list_items; + _find_list(r_l.from, list_index, list_count, list_items); + if (list_items.size() > 0) { + ItemList *this_list = list_items[0]; + if (list_index[0] == 1) { + // List level start, shape all prefixes for this level and compute max. prefix width. + list_items[0]->max_width = 0; + int index = 0; + for (int i = p_line; i < (int)p_frame->lines.size(); i++) { // For all the list rows in all lists in this frame. + Line &list_row_line = p_frame->lines[i]; + if (_find_list_item(list_row_line.from) == this_list) { // Is a row inside this list. + index++; + Ref font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; + int list_row_char_ofs = list_row_line.from->char_ofs; + int item_font_size = -1; + ItemFont *found_font_item = nullptr; + Vector formatting_items_info; + ItemText *this_row_text_item = nullptr; + Item *it = _get_next_item(this_list); + while (it && (this_row_text_item != nullptr || it->char_ofs <= list_row_char_ofs)) { // Find the ItemText for this list row. There is only one per row or none. + if (it->type == ITEM_TEXT && it->char_ofs == list_row_char_ofs) { + ItemText *text_item = static_cast(it); + this_row_text_item = text_item; + // `parent` is the enclosing item tag, if any, which itself can be further enclosed by another tag and so on, + // all of which will be applied to the text item. The `parent` is an interval predecessor, not a hierarchical parent. + Item *parent = text_item->parent; + while (parent && parent != main) { + // `formatting_items` is an Array of all ITEM types affecting glyph appearance, like ITEM_FONT, ITEM_COLOR, etc. + if (formatting_items.has(parent->type)) { + formatting_items_info.push_back(parent); + } + parent = parent->parent; + } + } + it = _get_next_item(it); + } + if (this_row_text_item == nullptr) { // If the row doesn't have any text yet. + it = _get_next_item(this_list); + // All format items at the same char location should be applied to the prefix. + // This won't add any earlier tags. + while (it && it->char_ofs <= list_row_char_ofs) { + if (formatting_items.has(it->type) && it->char_ofs == list_row_char_ofs) { + formatting_items_info.push_back(it); + } + it = _get_next_item(it); + } + } + for (Item *format_item : formatting_items_info) { + switch (format_item->type) { + case ITEM_FONT: { + ItemFont *font_item = static_cast(format_item); + if (font_item->def_font != RTL_CUSTOM_FONT) { + font_item = _find_font(format_item); // Sets `def_font` based on font type. + } + if (font_item->font.is_valid()) { + if (font_item->def_font == RTL_BOLD_ITALICS_FONT) { // Always set bold italic. + found_font_item = font_item; + } else if (found_font_item == nullptr || found_font_item->def_font != RTL_BOLD_ITALICS_FONT) { // Don't overwrite BOLD_ITALIC with BOLD or ITALIC. + found_font_item = font_item; + } + } + if (found_font_item->font_size > 0) { + font_size = found_font_item->font_size; + } + } break; + case ITEM_FONT_SIZE: { + ItemFontSize *font_size_item = static_cast(format_item); + item_font_size = font_size_item->font_size; + } break; + case ITEM_COLOR: { + ItemColor *color_item = static_cast(format_item); + list_row_line.prefix_color = color_item->color; + } break; + case ITEM_OUTLINE_SIZE: { + ItemOutlineSize *outline_size_item = static_cast(format_item); + list_row_line.prefix_outline_size = outline_size_item->outline_size; + } break; + case ITEM_OUTLINE_COLOR: { + ItemOutlineColor *outline_color_item = static_cast(format_item); + list_row_line.prefix_outline_color = outline_color_item->color; + } break; + default: { + } break; + } + } + font = found_font_item != nullptr ? found_font_item->font : font; + font_size = item_font_size != -1 ? item_font_size : font_size; + list_index.write[0] = index; + String prefix = _get_prefix(list_row_line.from, list_index, list_items); + list_row_line.text_prefix.instantiate(); + list_row_line.text_prefix->set_direction(_find_direction(list_row_line.from)); + list_row_line.text_prefix->add_string(prefix, font, font_size); + list_items.write[0]->max_width = MAX(this_list->max_width, list_row_line.text_prefix->get_size().x); + } + } + } + r_l.prefix_width = this_list->max_width; + } +} + void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref &p_base_font, int p_base_font_size) { ERR_FAIL_NULL(p_frame); ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size()); @@ -264,50 +368,8 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); - // Prefix. - Vector list_index; - Vector list_count; - Vector list_items; - _find_list(l.from, list_index, list_count, list_items); - - if (list_items.size() > 0) { - if (list_index[0] == 1) { - // List level start, shape all prefixes for this level and compute max. prefix width. - list_items[0]->max_width = 0; - int index = 0; - for (int i = p_line; i < (int)p_frame->lines.size(); i++) { - Line &list_l = p_frame->lines[i]; - if (_find_list_item(list_l.from) == list_items[0]) { - index++; - - Ref font = theme_cache.normal_font; - int font_size = theme_cache.normal_font_size; - - ItemFont *font_it = _find_font(list_l.from); - if (font_it) { - if (font_it->font.is_valid()) { - font = font_it->font; - } - if (font_it->font_size > 0) { - font_size = font_it->font_size; - } - } - ItemFontSize *font_size_it = _find_font_size(list_l.from); - if (font_size_it && font_size_it->font_size > 0) { - font_size = font_size_it->font_size; - } - - list_index.write[0] = index; - String prefix = _get_prefix(list_l.from, list_index, list_items); - list_l.text_prefix.instantiate(); - list_l.text_prefix->set_direction(_find_direction(list_l.from)); - list_l.text_prefix->add_string(prefix, font, font_size); - list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x); - } - } - } - l.prefix_width = list_items[0]->max_width; - } + // List. + _add_list_prefixes(p_frame, p_line, l); RID t = l.text_buf->get_rid(); int spans = TS->shaped_get_span_count(t); @@ -458,50 +520,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref l.char_offset = *r_char_offset; l.char_count = 0; - // List prefix. - Vector list_index; - Vector list_count; - Vector list_items; - _find_list(l.from, list_index, list_count, list_items); - - if (list_items.size() > 0) { - if (list_index[0] == 1) { - // List level start, shape all prefixes for this level and compute max. prefix width. - list_items[0]->max_width = 0; - int index = 0; - for (int i = p_line; i < (int)p_frame->lines.size(); i++) { - Line &list_l = p_frame->lines[i]; - if (_find_list_item(list_l.from) == list_items[0]) { - index++; - - Ref font = theme_cache.normal_font; - int font_size = theme_cache.normal_font_size; - - ItemFont *font_it = _find_font(list_l.from); - if (font_it) { - if (font_it->font.is_valid()) { - font = font_it->font; - } - if (font_it->font_size > 0) { - font_size = font_it->font_size; - } - } - ItemFontSize *font_size_it = _find_font_size(list_l.from); - if (font_size_it && font_size_it->font_size > 0) { - font_size = font_size_it->font_size; - } - - list_index.write[0] = index; - String prefix = _get_prefix(list_l.from, list_index, list_items); - list_l.text_prefix.instantiate(); - list_l.text_prefix->set_direction(_find_direction(list_l.from)); - list_l.text_prefix->add_string(prefix, font, font_size); - list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x); - } - } - } - l.prefix_width = list_items[0]->max_width; - } + // List. + _add_list_prefixes(p_frame, p_line, l); // Add indent. l.indent = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width; @@ -909,9 +929,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool skip_prefix = (trim_chars && l.char_offset > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); if (l.text_prefix.is_valid() && line == 0 && !skip_prefix) { - Color font_color = _find_color(l.from, p_base_color); - int outline_size = _find_outline_size(l.from, p_outline_size); - Color font_outline_color = _find_outline_color(l.from, p_outline_color); + Color font_color = l.prefix_color == Color(0, 0, 0, 0) ? _find_color(l.from, p_base_color) : l.prefix_color; + int outline_size = l.prefix_outline_size == -1 ? _find_outline_size(l.from, p_outline_size) : l.prefix_outline_size; + Color font_outline_color = l.prefix_outline_color == Color(0, 0, 0, 0) ? _find_outline_color(l.from, p_base_color) : l.prefix_outline_color; Color font_shadow_color = p_font_shadow_color * Color(1, 1, 1, font_color.a); if (rtl) { if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) { @@ -3866,13 +3886,13 @@ void RichTextLabel::add_text(const String &p_text) { } if (eol) { - ItemNewline *item = memnew(ItemNewline); + ItemNewline *item = memnew(ItemNewline); // Sets item->type to ITEM_NEWLINE. item->owner = get_instance_id(); item->rid = items.make_rid(item); item->line = current_frame->lines.size(); _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); - if (item->type != ITEM_NEWLINE) { + if (item->type != ITEM_NEWLINE) { // item IS an ITEM_NEWLINE so this will never get called? current_frame->lines[current_frame->lines.size() - 1].from = item; } _invalidate_current_line(current_frame); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index e84d3d2c508..7d2cb20c439 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -153,10 +153,13 @@ protected: private: struct Item; - struct Line { - Item *from = nullptr; + struct Line { // Line is a paragraph. + Item *from = nullptr; // `from` is main if this Line is the first Line in the doc, otherwise `from` is the previous Item in the doc of any type. Ref text_prefix; + Color prefix_color = Color(0, 0, 0, 0); + int prefix_outline_size = -1; + Color prefix_outline_color = Color(0, 0, 0, 0); float prefix_width = 0; Ref text_buf; @@ -192,17 +195,17 @@ private: struct Item { int index = 0; int char_ofs = 0; - Item *parent = nullptr; + Item *parent = nullptr; // "parent" means "enclosing item tag", if any. It is an interval predecessor, not a hierarchical parent. ItemType type = ITEM_FRAME; List subitems; List::Element *E = nullptr; ObjectID owner; - int line = 0; + int line = 0; // `line` is the index number of the paragraph (Line) this item is inside of (zero if the first paragraph). RID rid; RID accessibility_item_element; - void _clear_children() { + void _clear_children() { // Only ever called on main or a paragraph (Line). RichTextLabel *owner_rtl = ObjectDB::get_instance(owner); while (subitems.size()) { Item *subitem = subitems.front()->get(); @@ -503,6 +506,29 @@ private: struct ItemContext : public Item { ItemContext() { type = ITEM_CONTEXT; } }; + const Array formatting_items = { + // all ITEM types affecting glyph appearance. + ITEM_FONT, + ITEM_FONT_SIZE, + ITEM_FONT_FEATURES, + ITEM_COLOR, + ITEM_OUTLINE_SIZE, + ITEM_OUTLINE_COLOR, + ITEM_UNDERLINE, + ITEM_STRIKETHROUGH, + ITEM_FADE, + ITEM_SHAKE, + ITEM_WAVE, + ITEM_TORNADO, + ITEM_RAINBOW, + ITEM_BGCOLOR, + ITEM_FGCOLOR, + ITEM_META, + ITEM_HINT, + ITEM_CUSTOMFX, + ITEM_LANGUAGE, + ITEM_PULSE, + }; ItemFrame *main = nullptr; Item *current = nullptr; @@ -691,6 +717,7 @@ private: Size2 _get_image_size(const Ref &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2()); String _get_prefix(Item *p_item, const Vector &p_list_index, const Vector &p_list_items); + void _add_list_prefixes(ItemFrame *p_frame, int p_line, Line &r_l); static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from); static Vector _split_unquoted(const String &p_src, char32_t p_splitter);