Skip to content

Commit faf1ab1

Browse files
committed
Fix RichTextLabel bullet list font issues
1 parent 987832b commit faf1ab1

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
@@ -257,57 +257,119 @@ String RichTextLabel::_get_prefix(Item *p_item, const Vector<int> &p_list_index,
257257
return prefix + " ";
258258
}
259259

260-
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
261-
ERR_FAIL_NULL(p_frame);
262-
ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
263-
264-
Line &l = p_frame->lines[p_line];
265-
MutexLock lock(l.text_buf->get_mutex());
266-
267-
// Prefix.
260+
void RichTextLabel::_add_list_prefixes(ItemFrame *p_frame, int p_line, Line &r_l) {
268261
Vector<int> list_index;
269262
Vector<int> list_count;
270263
Vector<ItemList *> list_items;
271-
_find_list(l.from, list_index, list_count, list_items);
272-
264+
_find_list(r_l.from, list_index, list_count, list_items);
273265
if (list_items.size() > 0) {
266+
ItemList *this_list = list_items[0];
274267
if (list_index[0] == 1) {
275268
// List level start, shape all prefixes for this level and compute max. prefix width.
276269
list_items[0]->max_width = 0;
277270
int index = 0;
278-
for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
279-
Line &list_l = p_frame->lines[i];
280-
if (_find_list_item(list_l.from) == list_items[0]) {
271+
for (int i = p_line; i < (int)p_frame->lines.size(); i++) { // For all the list rows in all lists in this frame.
272+
Line &list_row_line = p_frame->lines[i];
273+
if (_find_list_item(list_row_line.from) == this_list) { // Is a row inside this list.
281274
index++;
282-
283275
Ref<Font> font = theme_cache.normal_font;
284276
int font_size = theme_cache.normal_font_size;
285-
286-
ItemFont *font_it = _find_font(list_l.from);
287-
if (font_it) {
288-
if (font_it->font.is_valid()) {
289-
font = font_it->font;
277+
int list_row_char_ofs = list_row_line.from->char_ofs;
278+
int item_font_size = -1;
279+
ItemFont *found_font_item = nullptr;
280+
Vector<Item *> formatting_items_info;
281+
ItemText *this_row_text_item = nullptr;
282+
Item *it = _get_next_item(this_list);
283+
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.
284+
if (it->type == ITEM_TEXT && it->char_ofs == list_row_char_ofs) {
285+
ItemText *text_item = static_cast<ItemText *>(it);
286+
this_row_text_item = text_item;
287+
// `parent` is the enclosing item tag, if any, which itself can be further enclosed by another tag and so on,
288+
// all of which will be applied to the text item. The `parent` is an interval predecessor, not a hierarchical parent.
289+
Item *parent = text_item->parent;
290+
while (parent && parent != main) {
291+
// `formatting_items` is an Array of all ITEM types affecting glyph appearance, like ITEM_FONT, ITEM_COLOR, etc.
292+
if (formatting_items.has(parent->type)) {
293+
formatting_items_info.push_back(parent);
294+
}
295+
parent = parent->parent;
296+
}
290297
}
291-
if (font_it->font_size > 0) {
292-
font_size = font_it->font_size;
298+
it = _get_next_item(it);
299+
}
300+
if (this_row_text_item == nullptr) { // If the row doesn't have any text yet.
301+
it = _get_next_item(this_list);
302+
// All format items at the same char location should be applied to the prefix.
303+
// This won't add any earlier tags.
304+
while (it && it->char_ofs <= list_row_char_ofs) {
305+
if (formatting_items.has(it->type) && it->char_ofs == list_row_char_ofs) {
306+
formatting_items_info.push_back(it);
307+
}
308+
it = _get_next_item(it);
293309
}
294310
}
295-
ItemFontSize *font_size_it = _find_font_size(list_l.from);
296-
if (font_size_it && font_size_it->font_size > 0) {
297-
font_size = font_size_it->font_size;
311+
for (Item *format_item : formatting_items_info) {
312+
switch (format_item->type) {
313+
case ITEM_FONT: {
314+
ItemFont *font_item = static_cast<ItemFont *>(format_item);
315+
if (font_item->def_font != RTL_CUSTOM_FONT) {
316+
font_item = _find_font(format_item); // Sets `def_font` based on font type.
317+
}
318+
if (font_item->font.is_valid()) {
319+
if (font_item->def_font == RTL_BOLD_ITALICS_FONT) { // Always set bold italic.
320+
found_font_item = font_item;
321+
} else if (found_font_item == nullptr || found_font_item->def_font != RTL_BOLD_ITALICS_FONT) { // Don't overwrite BOLD_ITALIC with BOLD or ITALIC.
322+
found_font_item = font_item;
323+
}
324+
}
325+
if (found_font_item->font_size > 0) {
326+
font_size = found_font_item->font_size;
327+
}
328+
} break;
329+
case ITEM_FONT_SIZE: {
330+
ItemFontSize *font_size_item = static_cast<ItemFontSize *>(format_item);
331+
item_font_size = font_size_item->font_size;
332+
} break;
333+
case ITEM_COLOR: {
334+
ItemColor *color_item = static_cast<ItemColor *>(format_item);
335+
list_row_line.prefix_color = color_item->color;
336+
} break;
337+
case ITEM_OUTLINE_SIZE: {
338+
ItemOutlineSize *outline_size_item = static_cast<ItemOutlineSize *>(format_item);
339+
list_row_line.prefix_outline_size = outline_size_item->outline_size;
340+
} break;
341+
case ITEM_OUTLINE_COLOR: {
342+
ItemOutlineColor *outline_color_item = static_cast<ItemOutlineColor *>(format_item);
343+
list_row_line.prefix_outline_color = outline_color_item->color;
344+
} break;
345+
default: {
346+
} break;
347+
}
298348
}
299-
349+
font = found_font_item != nullptr ? found_font_item->font : font;
350+
font_size = item_font_size != -1 ? item_font_size : font_size;
300351
list_index.write[0] = index;
301-
String prefix = _get_prefix(list_l.from, list_index, list_items);
302-
list_l.text_prefix.instantiate();
303-
list_l.text_prefix->set_direction(_find_direction(list_l.from));
304-
list_l.text_prefix->add_string(prefix, font, font_size);
305-
list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
352+
String prefix = _get_prefix(list_row_line.from, list_index, list_items);
353+
list_row_line.text_prefix.instantiate();
354+
list_row_line.text_prefix->set_direction(_find_direction(list_row_line.from));
355+
list_row_line.text_prefix->add_string(prefix, font, font_size);
356+
list_items.write[0]->max_width = MAX(this_list->max_width, list_row_line.text_prefix->get_size().x);
306357
}
307358
}
308359
}
309-
l.prefix_width = list_items[0]->max_width;
360+
r_l.prefix_width = this_list->max_width;
310361
}
362+
}
363+
364+
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
365+
ERR_FAIL_NULL(p_frame);
366+
ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
367+
368+
Line &l = p_frame->lines[p_line];
369+
MutexLock lock(l.text_buf->get_mutex());
370+
371+
// List.
372+
_add_list_prefixes(p_frame, p_line, l);
311373

312374
RID t = l.text_buf->get_rid();
313375
int spans = TS->shaped_get_span_count(t);
@@ -458,50 +520,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
458520
l.char_offset = *r_char_offset;
459521
l.char_count = 0;
460522

461-
// List prefix.
462-
Vector<int> list_index;
463-
Vector<int> list_count;
464-
Vector<ItemList *> list_items;
465-
_find_list(l.from, list_index, list_count, list_items);
466-
467-
if (list_items.size() > 0) {
468-
if (list_index[0] == 1) {
469-
// List level start, shape all prefixes for this level and compute max. prefix width.
470-
list_items[0]->max_width = 0;
471-
int index = 0;
472-
for (int i = p_line; i < (int)p_frame->lines.size(); i++) {
473-
Line &list_l = p_frame->lines[i];
474-
if (_find_list_item(list_l.from) == list_items[0]) {
475-
index++;
476-
477-
Ref<Font> font = theme_cache.normal_font;
478-
int font_size = theme_cache.normal_font_size;
479-
480-
ItemFont *font_it = _find_font(list_l.from);
481-
if (font_it) {
482-
if (font_it->font.is_valid()) {
483-
font = font_it->font;
484-
}
485-
if (font_it->font_size > 0) {
486-
font_size = font_it->font_size;
487-
}
488-
}
489-
ItemFontSize *font_size_it = _find_font_size(list_l.from);
490-
if (font_size_it && font_size_it->font_size > 0) {
491-
font_size = font_size_it->font_size;
492-
}
493-
494-
list_index.write[0] = index;
495-
String prefix = _get_prefix(list_l.from, list_index, list_items);
496-
list_l.text_prefix.instantiate();
497-
list_l.text_prefix->set_direction(_find_direction(list_l.from));
498-
list_l.text_prefix->add_string(prefix, font, font_size);
499-
list_items.write[0]->max_width = MAX(list_items[0]->max_width, list_l.text_prefix->get_size().x);
500-
}
501-
}
502-
}
503-
l.prefix_width = list_items[0]->max_width;
504-
}
523+
// List.
524+
_add_list_prefixes(p_frame, p_line, l);
505525

506526
// Add indent.
507527
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
909929

910930
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));
911931
if (l.text_prefix.is_valid() && line == 0 && !skip_prefix) {
912-
Color font_color = _find_color(l.from, p_base_color);
913-
int outline_size = _find_outline_size(l.from, p_outline_size);
914-
Color font_outline_color = _find_outline_color(l.from, p_outline_color);
932+
Color font_color = l.prefix_color == Color(0, 0, 0, 0) ? _find_color(l.from, p_base_color) : l.prefix_color;
933+
int outline_size = l.prefix_outline_size == -1 ? _find_outline_size(l.from, p_outline_size) : l.prefix_outline_size;
934+
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;
915935
Color font_shadow_color = p_font_shadow_color * Color(1, 1, 1, font_color.a);
916936
if (rtl) {
917937
if (p_shadow_outline_size > 0 && font_shadow_color.a != 0.0) {
@@ -3866,13 +3886,13 @@ void RichTextLabel::add_text(const String &p_text) {
38663886
}
38673887

38683888
if (eol) {
3869-
ItemNewline *item = memnew(ItemNewline);
3889+
ItemNewline *item = memnew(ItemNewline); // Sets item->type to ITEM_NEWLINE.
38703890
item->owner = get_instance_id();
38713891
item->rid = items.make_rid(item);
38723892
item->line = current_frame->lines.size();
38733893
_add_item(item, false);
38743894
current_frame->lines.resize(current_frame->lines.size() + 1);
3875-
if (item->type != ITEM_NEWLINE) {
3895+
if (item->type != ITEM_NEWLINE) { // item IS an ITEM_NEWLINE so this will never get called?
38763896
current_frame->lines[current_frame->lines.size() - 1].from = item;
38773897
}
38783898
_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
@@ -153,10 +153,13 @@ class RichTextLabel : public Control {
153153
private:
154154
struct Item;
155155

156-
struct Line {
157-
Item *from = nullptr;
156+
struct Line { // Line is a paragraph.
157+
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.
158158

159159
Ref<TextLine> text_prefix;
160+
Color prefix_color = Color(0, 0, 0, 0);
161+
int prefix_outline_size = -1;
162+
Color prefix_outline_color = Color(0, 0, 0, 0);
160163
float prefix_width = 0;
161164
Ref<TextParagraph> text_buf;
162165

@@ -192,17 +195,17 @@ class RichTextLabel : public Control {
192195
struct Item {
193196
int index = 0;
194197
int char_ofs = 0;
195-
Item *parent = nullptr;
198+
Item *parent = nullptr; // "parent" means "enclosing item tag", if any. It is an interval predecessor, not a hierarchical parent.
196199
ItemType type = ITEM_FRAME;
197200
List<Item *> subitems;
198201
List<Item *>::Element *E = nullptr;
199202
ObjectID owner;
200-
int line = 0;
203+
int line = 0; // `line` is the index number of the paragraph (Line) this item is inside of (zero if the first paragraph).
201204
RID rid;
202205

203206
RID accessibility_item_element;
204207

205-
void _clear_children() {
208+
void _clear_children() { // Only ever called on main or a paragraph (Line).
206209
RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
207210
while (subitems.size()) {
208211
Item *subitem = subitems.front()->get();
@@ -503,6 +506,29 @@ class RichTextLabel : public Control {
503506
struct ItemContext : public Item {
504507
ItemContext() { type = ITEM_CONTEXT; }
505508
};
509+
const Array formatting_items = {
510+
// all ITEM types affecting glyph appearance.
511+
ITEM_FONT,
512+
ITEM_FONT_SIZE,
513+
ITEM_FONT_FEATURES,
514+
ITEM_COLOR,
515+
ITEM_OUTLINE_SIZE,
516+
ITEM_OUTLINE_COLOR,
517+
ITEM_UNDERLINE,
518+
ITEM_STRIKETHROUGH,
519+
ITEM_FADE,
520+
ITEM_SHAKE,
521+
ITEM_WAVE,
522+
ITEM_TORNADO,
523+
ITEM_RAINBOW,
524+
ITEM_BGCOLOR,
525+
ITEM_FGCOLOR,
526+
ITEM_META,
527+
ITEM_HINT,
528+
ITEM_CUSTOMFX,
529+
ITEM_LANGUAGE,
530+
ITEM_PULSE,
531+
};
506532

507533
ItemFrame *main = nullptr;
508534
Item *current = nullptr;
@@ -691,6 +717,7 @@ class RichTextLabel : public Control {
691717
Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2());
692718

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

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

0 commit comments

Comments
 (0)