Skip to content

Commit ccccb9d

Browse files
committed
Merge pull request #111258 from Koyper/fix_rich_text_label_bullet_list_issues
[RichTextLabel] Fix bullet list font color and formatting issues
2 parents cb164a3 + faf1ab1 commit ccccb9d

File tree

2 files changed

+132
-85
lines changed

2 files changed

+132
-85
lines changed

scene/gui/rich_text_label.cpp

Lines changed: 100 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -266,57 +266,119 @@ String RichTextLabel::_get_prefix(Item *p_item, const Vector<int> &p_list_index,
266266
return prefix + " ";
267267
}
268268

269-
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
270-
ERR_FAIL_NULL(p_frame);
271-
ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
272-
273-
Line &l = p_frame->lines[p_line];
274-
MutexLock lock(l.text_buf->get_mutex());
275-
276-
// Prefix.
269+
void RichTextLabel::_add_list_prefixes(ItemFrame *p_frame, int p_line, Line &r_l) {
277270
Vector<int> list_index;
278271
Vector<int> list_count;
279272
Vector<ItemList *> list_items;
280-
_find_list(l.from, list_index, list_count, list_items);
281-
273+
_find_list(r_l.from, list_index, list_count, list_items);
282274
if (list_items.size() > 0) {
275+
ItemList *this_list = list_items[0];
283276
if (list_index[0] == 1) {
284277
// List level start, shape all prefixes for this level and compute max. prefix width.
285278
list_items[0]->max_width = 0;
286279
int index = 0;
287-
for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
288-
Line &list_l = p_frame->lines[i];
289-
if (_find_list_item(list_l.from) == list_items[0]) {
280+
for (int i = p_line; i < (int)p_frame->lines.size(); i++) { // For all the list rows in all lists in this frame.
281+
Line &list_row_line = p_frame->lines[i];
282+
if (_find_list_item(list_row_line.from) == this_list) { // Is a row inside this list.
290283
index++;
291-
292284
Ref<Font> font = theme_cache.normal_font;
293285
int font_size = theme_cache.normal_font_size;
294-
295-
ItemFont *font_it = _find_font(list_l.from);
296-
if (font_it) {
297-
if (font_it->font.is_valid()) {
298-
font = font_it->font;
286+
int list_row_char_ofs = list_row_line.from->char_ofs;
287+
int item_font_size = -1;
288+
ItemFont *found_font_item = nullptr;
289+
Vector<Item *> formatting_items_info;
290+
ItemText *this_row_text_item = nullptr;
291+
Item *it = _get_next_item(this_list);
292+
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.
293+
if (it->type == ITEM_TEXT && it->char_ofs == list_row_char_ofs) {
294+
ItemText *text_item = static_cast<ItemText *>(it);
295+
this_row_text_item = text_item;
296+
// `parent` is the enclosing item tag, if any, which itself can be further enclosed by another tag and so on,
297+
// all of which will be applied to the text item. The `parent` is an interval predecessor, not a hierarchical parent.
298+
Item *parent = text_item->parent;
299+
while (parent && parent != main) {
300+
// `formatting_items` is an Array of all ITEM types affecting glyph appearance, like ITEM_FONT, ITEM_COLOR, etc.
301+
if (formatting_items.has(parent->type)) {
302+
formatting_items_info.push_back(parent);
303+
}
304+
parent = parent->parent;
305+
}
299306
}
300-
if (font_it->font_size > 0) {
301-
font_size = font_it->font_size;
307+
it = _get_next_item(it);
308+
}
309+
if (this_row_text_item == nullptr) { // If the row doesn't have any text yet.
310+
it = _get_next_item(this_list);
311+
// All format items at the same char location should be applied to the prefix.
312+
// This won't add any earlier tags.
313+
while (it && it->char_ofs <= list_row_char_ofs) {
314+
if (formatting_items.has(it->type) && it->char_ofs == list_row_char_ofs) {
315+
formatting_items_info.push_back(it);
316+
}
317+
it = _get_next_item(it);
302318
}
303319
}
304-
ItemFontSize *font_size_it = _find_font_size(list_l.from);
305-
if (font_size_it && font_size_it->font_size > 0) {
306-
font_size = font_size_it->font_size;
320+
for (Item *format_item : formatting_items_info) {
321+
switch (format_item->type) {
322+
case ITEM_FONT: {
323+
ItemFont *font_item = static_cast<ItemFont *>(format_item);
324+
if (font_item->def_font != RTL_CUSTOM_FONT) {
325+
font_item = _find_font(format_item); // Sets `def_font` based on font type.
326+
}
327+
if (font_item->font.is_valid()) {
328+
if (font_item->def_font == RTL_BOLD_ITALICS_FONT) { // Always set bold italic.
329+
found_font_item = font_item;
330+
} else if (found_font_item == nullptr || found_font_item->def_font != RTL_BOLD_ITALICS_FONT) { // Don't overwrite BOLD_ITALIC with BOLD or ITALIC.
331+
found_font_item = font_item;
332+
}
333+
}
334+
if (found_font_item->font_size > 0) {
335+
font_size = found_font_item->font_size;
336+
}
337+
} break;
338+
case ITEM_FONT_SIZE: {
339+
ItemFontSize *font_size_item = static_cast<ItemFontSize *>(format_item);
340+
item_font_size = font_size_item->font_size;
341+
} break;
342+
case ITEM_COLOR: {
343+
ItemColor *color_item = static_cast<ItemColor *>(format_item);
344+
list_row_line.prefix_color = color_item->color;
345+
} break;
346+
case ITEM_OUTLINE_SIZE: {
347+
ItemOutlineSize *outline_size_item = static_cast<ItemOutlineSize *>(format_item);
348+
list_row_line.prefix_outline_size = outline_size_item->outline_size;
349+
} break;
350+
case ITEM_OUTLINE_COLOR: {
351+
ItemOutlineColor *outline_color_item = static_cast<ItemOutlineColor *>(format_item);
352+
list_row_line.prefix_outline_color = outline_color_item->color;
353+
} break;
354+
default: {
355+
} break;
356+
}
307357
}
308-
358+
font = found_font_item != nullptr ? found_font_item->font : font;
359+
font_size = item_font_size != -1 ? item_font_size : font_size;
309360
list_index.write[0] = index;
310-
String prefix = _get_prefix(list_l.from, list_index, list_items);
311-
list_l.text_prefix.instantiate();
312-
list_l.text_prefix->set_direction(_find_direction(list_l.from));
313-
list_l.text_prefix->add_string(prefix, font, font_size);
314-
list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
361+
String prefix = _get_prefix(list_row_line.from, list_index, list_items);
362+
list_row_line.text_prefix.instantiate();
363+
list_row_line.text_prefix->set_direction(_find_direction(list_row_line.from));
364+
list_row_line.text_prefix->add_string(prefix, font, font_size);
365+
list_items.write[0]->max_width = MAX(this_list->max_width, list_row_line.text_prefix->get_size().x);
315366
}
316367
}
317368
}
318-
l.prefix_width = list_items[0]->max_width;
369+
r_l.prefix_width = this_list->max_width;
319370
}
371+
}
372+
373+
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
374+
ERR_FAIL_NULL(p_frame);
375+
ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
376+
377+
Line &l = p_frame->lines[p_line];
378+
MutexLock lock(l.text_buf->get_mutex());
379+
380+
// List.
381+
_add_list_prefixes(p_frame, p_line, l);
320382

321383
RID t = l.text_buf->get_rid();
322384
int spans = TS->shaped_get_span_count(t);
@@ -471,50 +533,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
471533
l.char_offset = *r_char_offset;
472534
l.char_count = 0;
473535

474-
// List prefix.
475-
Vector<int> list_index;
476-
Vector<int> list_count;
477-
Vector<ItemList *> list_items;
478-
_find_list(l.from, list_index, list_count, list_items);
479-
480-
if (list_items.size() > 0) {
481-
if (list_index[0] == 1) {
482-
// List level start, shape all prefixes for this level and compute max. prefix width.
483-
list_items[0]->max_width = 0;
484-
int index = 0;
485-
for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
486-
Line &list_l = p_frame->lines[i];
487-
if (_find_list_item(list_l.from) == list_items[0]) {
488-
index++;
489-
490-
Ref<Font> font = theme_cache.normal_font;
491-
int font_size = theme_cache.normal_font_size;
492-
493-
ItemFont *font_it = _find_font(list_l.from);
494-
if (font_it) {
495-
if (font_it->font.is_valid()) {
496-
font = font_it->font;
497-
}
498-
if (font_it->font_size > 0) {
499-
font_size = font_it->font_size;
500-
}
501-
}
502-
ItemFontSize *font_size_it = _find_font_size(list_l.from);
503-
if (font_size_it && font_size_it->font_size > 0) {
504-
font_size = font_size_it->font_size;
505-
}
506-
507-
list_index.write[0] = index;
508-
String prefix = _get_prefix(list_l.from, list_index, list_items);
509-
list_l.text_prefix.instantiate();
510-
list_l.text_prefix->set_direction(_find_direction(list_l.from));
511-
list_l.text_prefix->add_string(prefix, font, font_size);
512-
list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
513-
}
514-
}
515-
}
516-
l.prefix_width = list_items[0]->max_width;
517-
}
536+
// List.
537+
_add_list_prefixes(p_frame, p_line, l);
518538

519539
// Add indent.
520540
l.indent = _find_margin(l.from, p_base_font, p_base_font_size) + l.prefix_width;
@@ -934,9 +954,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
934954

935955
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));
936956
if (l.text_prefix.is_valid() && line == 0 && !skip_prefix) {
937-
Color font_color = _find_color(l.from, p_base_color);
938-
int outline_size = _find_outline_size(l.from, p_outline_size);
939-
Color font_outline_color = _find_outline_color(l.from, p_outline_color);
957+
Color font_color = l.prefix_color == Color(0, 0, 0, 0) ? _find_color(l.from, p_base_color) : l.prefix_color;
958+
int outline_size = l.prefix_outline_size == -1 ? _find_outline_size(l.from, p_outline_size) : l.prefix_outline_size;
959+
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;
940960
Color font_shadow_color = p_font_shadow_color * Color(1, 1, 1, font_color.a);
941961
if (rtl) {
942962
if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
@@ -3951,13 +3971,13 @@ void RichTextLabel::add_text(const String &p_text) {
39513971
}
39523972

39533973
if (eol) {
3954-
ItemNewline *item = memnew(ItemNewline);
3974+
ItemNewline *item = memnew(ItemNewline); // Sets item->type to ITEM_NEWLINE.
39553975
item->owner = get_instance_id();
39563976
item->rid = items.make_rid(item);
39573977
item->line = current_frame->lines.size();
39583978
_add_item(item, false);
39593979
current_frame->lines.resize(current_frame->lines.size() + 1);
3960-
if (item->type != ITEM_NEWLINE) {
3980+
if (item->type != ITEM_NEWLINE) { // item IS an ITEM_NEWLINE so this will never get called?
39613981
current_frame->lines[current_frame->lines.size() - 1].from = item;
39623982
}
39633983
_invalidate_current_line(current_frame);

scene/gui/rich_text_label.h

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,13 @@ class RichTextLabel : public Control {
157157
private:
158158
struct Item;
159159

160-
struct Line {
161-
Item *from = nullptr;
160+
struct Line { // Line is a paragraph.
161+
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.
162162

163163
Ref<TextLine> text_prefix;
164+
Color prefix_color = Color(0, 0, 0, 0);
165+
int prefix_outline_size = -1;
166+
Color prefix_outline_color = Color(0, 0, 0, 0);
164167
float prefix_width = 0;
165168
Ref<TextParagraph> text_buf;
166169

@@ -196,17 +199,17 @@ class RichTextLabel : public Control {
196199
struct Item {
197200
int index = 0;
198201
int char_ofs = 0;
199-
Item *parent = nullptr;
202+
Item *parent = nullptr; // "parent" means "enclosing item tag", if any. It is an interval predecessor, not a hierarchical parent.
200203
ItemType type = ITEM_FRAME;
201204
List<Item *> subitems;
202205
List<Item *>::Element *E = nullptr;
203206
ObjectID owner;
204-
int line = 0;
207+
int line = 0; // `line` is the index number of the paragraph (Line) this item is inside of (zero if the first paragraph).
205208
RID rid;
206209

207210
RID accessibility_item_element;
208211

209-
void _clear_children() {
212+
void _clear_children() { // Only ever called on main or a paragraph (Line).
210213
RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
211214
while (subitems.size()) {
212215
Item *subitem = subitems.front()->get();
@@ -507,6 +510,29 @@ class RichTextLabel : public Control {
507510
struct ItemContext : public Item {
508511
ItemContext() { type = ITEM_CONTEXT; }
509512
};
513+
const Array formatting_items = {
514+
// all ITEM types affecting glyph appearance.
515+
ITEM_FONT,
516+
ITEM_FONT_SIZE,
517+
ITEM_FONT_FEATURES,
518+
ITEM_COLOR,
519+
ITEM_OUTLINE_SIZE,
520+
ITEM_OUTLINE_COLOR,
521+
ITEM_UNDERLINE,
522+
ITEM_STRIKETHROUGH,
523+
ITEM_FADE,
524+
ITEM_SHAKE,
525+
ITEM_WAVE,
526+
ITEM_TORNADO,
527+
ITEM_RAINBOW,
528+
ITEM_BGCOLOR,
529+
ITEM_FGCOLOR,
530+
ITEM_META,
531+
ITEM_HINT,
532+
ITEM_CUSTOMFX,
533+
ITEM_LANGUAGE,
534+
ITEM_PULSE,
535+
};
510536

511537
ItemFrame *main = nullptr;
512538
Item *current = nullptr;
@@ -711,6 +737,7 @@ class RichTextLabel : public Control {
711737
Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2());
712738

713739
String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items);
740+
void _add_list_prefixes(ItemFrame *p_frame, int p_line, Line &r_l);
714741

715742
static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from);
716743
static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter);

0 commit comments

Comments
 (0)