diff options
Diffstat (limited to 'scene/gui/rich_text_label.cpp')
-rw-r--r-- | scene/gui/rich_text_label.cpp | 969 |
1 files changed, 610 insertions, 359 deletions
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 3f1f16cd51..60d107cce6 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -52,7 +52,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } else if (p_item->E->next()) { return p_item->E->next()->get(); } else { - //go up until something with a next is found + // Go up until something with a next is found. while (p_item->parent && !p_item->E->next()) { p_item = p_item->parent; } @@ -72,7 +72,7 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } else if (p_item->E->next()) { return p_item->E->next()->get(); } else { - //go up until something with a next is found + // Go up until something with a next is found. while (p_item->type != ITEM_FRAME && !p_item->E->next()) { p_item = p_item->parent; } @@ -84,8 +84,6 @@ RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) co } } } - - return nullptr; } RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) const { @@ -97,7 +95,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } else if (p_item->E->prev()) { return p_item->E->prev()->get(); } else { - //go back until something with a prev is found + // Go back until something with a prev is found. while (p_item->parent && !p_item->E->prev()) { p_item = p_item->parent; } @@ -117,7 +115,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } else if (p_item->E->prev()) { return p_item->E->prev()->get(); } else { - //go back until something with a prev is found + // Go back until something with a prev is found. while (p_item->type != ITEM_FRAME && !p_item->E->prev()) { p_item = p_item->parent; } @@ -129,13 +127,10 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } } } - - return nullptr; } Rect2 RichTextLabel::_get_text_rect() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - return Rect2(style->get_offset(), get_size() - style->get_minimum_size()); + return Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size()); } RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item_from, RichTextLabel::Item *p_item_to, int p_position) { @@ -172,17 +167,17 @@ String RichTextLabel::_roman(int p_num, bool p_capitalize) const { }; String s; if (p_capitalize) { - const String M[] = { "", "M", "MM", "MMM" }; - const String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; - const String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; - const String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "M", "MM", "MMM" }; + const String roman_C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; + const String roman_X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; + const String roman_I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } else { - const String M[] = { "", "m", "mm", "mmm" }; - const String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; - const String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; - const String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "m", "mm", "mmm" }; + const String roman_C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; + const String roman_X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; + const String roman_I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } return s; } @@ -287,8 +282,6 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font switch (it->type) { case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); for (int i = 0; i < col_count; i++) { @@ -309,12 +302,12 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font } // Compute minimum width for each cell. - const int available_width = p_width - hseparation * (col_count - 1); + const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1); // Compute available width and total ratio (for expanders). int total_ratio = 0; int remaining_width = available_width; - table->total_width = hseparation; + table->total_width = theme_cache.table_h_separation; for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; @@ -332,7 +325,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } - table->total_width += table->columns[i].width + hseparation; + table->total_width += table->columns[i].width + theme_cache.table_h_separation; } // Resize to max_width if needed and distribute the remaining space. @@ -394,9 +387,9 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font frame->lines[i].offset.y = prev_h; frame->lines[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); + float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation; if (i > 0) { - h += get_theme_constant(SNAME("line_separation")); + h += theme_cache.line_separation; } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); @@ -405,15 +398,15 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font h = MIN(h, frame->max_size_over.y); } yofs += h; - prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation; } yofs += frame->padding.size.y; - offset.x += table->columns[column].width + hseparation + frame->padding.size.x; + offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x; row_height = MAX(yofs, row_height); if (column == col_count - 1) { offset.x = 0; - row_height += vseparation; + row_height += theme_cache.table_v_separation; table->total_height += row_height; offset.y += row_height; table->rows.push_back(row_height); @@ -453,6 +446,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> case TextServer::AUTOWRAP_OFF: break; } + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; // Clear cache. l.text_buf->clear(); @@ -474,7 +468,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } // Shape current paragraph. - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { @@ -508,7 +502,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> font_size = font_size_it->font_size; } l.text_buf->add_string("\n", font, font_size); - text += "\n"; + txt += "\n"; l.char_count++; remaining_characters--; } break; @@ -538,20 +532,18 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> remaining_characters -= tx.length(); l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); - text += tx; + txt += tx; l.char_count += tx.length(); } break; case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1); - text += String::chr(0xfffc); + txt += String::chr(0xfffc); l.char_count++; remaining_characters--; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); int t_char_count = 0; // Set minimums to zero. @@ -561,7 +553,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> table->columns[i].width = 0; } // Compute minimum width for each cell. - const int available_width = p_width - hseparation * (col_count - 1); + const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1); int idx = 0; for (Item *E : table->subitems) { @@ -590,7 +582,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Compute available width and total ratio (for expanders). int total_ratio = 0; int remaining_width = available_width; - table->total_width = hseparation; + table->total_width = theme_cache.table_h_separation; for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; @@ -608,7 +600,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } - table->total_width += table->columns[i].width + hseparation; + table->total_width += table->columns[i].width + theme_cache.table_h_separation; } // Resize to max_width if needed and distribute the remaining space. @@ -671,9 +663,9 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> frame->lines[i].offset.y = prev_h; frame->lines[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); + float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation; if (i > 0) { - h += get_theme_constant(SNAME("line_separation")); + h += theme_cache.line_separation; } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); @@ -682,16 +674,16 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> h = MIN(h, frame->max_size_over.y); } yofs += h; - prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation; } yofs += frame->padding.size.y; - offset.x += table->columns[column].width + hseparation + frame->padding.size.x; + offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x; row_height = MAX(yofs, row_height); // Add row height after last column of the row or last cell of the table. if (column == col_count - 1 || E->next() == nullptr) { offset.x = 0; - row_height += vseparation; + row_height += theme_cache.table_v_separation; table->total_height += row_height; offset.y += row_height; table->rows.push_back(row_height); @@ -701,15 +693,15 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count); - text += String::chr(0xfffc).repeat(t_char_count); + txt += String::chr(0xfffc).repeat(t_char_count); } break; default: break; } } - //Apply BiDi override. - l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, text)); + // Apply BiDi override. + l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt)); *r_char_offset = l.char_offset + l.char_count; @@ -722,7 +714,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0); Vector2 off; - int line_spacing = get_theme_constant(SNAME("line_separation")); Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); @@ -761,7 +752,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o prefix = _prefix; break; } else if (list_items[i]->list_type == LIST_NUMBERS) { - segment = TS->format_number(itos(list_index[i]), _find_language(l.from)); + segment = itos(list_index[i]); + if (is_localizing_numeral_system()) { + segment = TS->format_number(segment, _find_language(l.from)); + } } else if (list_items[i]->list_type == LIST_LETTERS) { segment = _letters(list_index[i], list_items[i]->capitalize); } else if (list_items[i]->list_type == LIST_ROMAN) { @@ -774,8 +768,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } if (!prefix.is_empty()) { - Ref<Font> font = get_theme_font(SNAME("normal_font")); - int font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; ItemFont *font_it = _find_font(l.from); if (font_it) { @@ -818,7 +812,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { if (line > 0) { - off.y += line_spacing; + off.y += theme_cache.line_separation; } if (p_ofs.y + off.y >= ctrl_size.height) { @@ -893,10 +887,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = get_theme_color(SNAME("table_odd_row_bg")); - Color even_row_bg = get_theme_color(SNAME("table_even_row_bg")); - Color border = get_theme_color(SNAME("table_border")); - int hseparation = get_theme_constant(SNAME("table_h_separation")); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + int h_separation = theme_cache.table_h_separation; + int col_count = table->columns.size(); int row_count = table->rows.size(); @@ -913,11 +908,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o coff.x = rect.size.width - table->columns[col].width - coff.x; } if (row % 2 == 0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true); + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg), true); } else { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true); + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg), true); } - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false); + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false); } for (int j = 0; j < (int)frame->lines.size(); j++) { @@ -969,17 +964,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint32_t gl = glyphs[i].index; uint16_t gl_fl = glyphs[i].flags; uint8_t gl_cn = glyphs[i].count; - bool cprev = false; + bool cprev_cluster = false; + bool cprev_conn = false; if (gl_cn == 0) { // Parts of the same cluster, always connected. - cprev = true; + cprev_cluster = true; } if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev = true; + cprev_conn = true; } } else { if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev = true; + cprev_conn = true; } } @@ -994,10 +990,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_shadow_color.a = faded_visibility; } - bool visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); + bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; + bool cn = cprev_cluster || (cprev_conn && item_fx->connected); + if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); @@ -1007,7 +1005,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs; + charfx->visibility = txt_visible; charfx->outline = true; charfx->font = frid; charfx->glyph_index = gl; @@ -1023,17 +1022,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - if (!cprev) { + if (!cn) { uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); n_time = (n_time > 1.0) ? 1.0 : n_time; item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; @@ -1042,7 +1041,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - if (!cprev) { + if (!cn) { double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f); item_wave->prev_off = Point2(0, 1) * value; } @@ -1050,7 +1049,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - if (!cprev) { + if (!cn) { double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius); item_tornado->prev_off = Point2(torn_x, torn_y); @@ -1067,7 +1066,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a); const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a); for (int j = 0; j < glyphs[i].repeat; j++) { - if (visible) { + if (txt_visible) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); if (!skip && frid != RID()) { if (modulated_shadow_color.a > 0) { @@ -1092,8 +1091,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = get_theme_color(SNAME("font_selected_color")); - Color selection_bg = get_theme_color(SNAME("selection_color")); + Color selection_bg = theme_cache.selection_color; int sel_start = -1; int sel_end = -1; @@ -1135,7 +1133,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (_find_hint(it, nullptr) && underline_hint) { @@ -1148,7 +1146,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (_find_strikethrough(it)) { @@ -1161,7 +1159,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } @@ -1185,17 +1183,18 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint32_t gl = glyphs[i].index; uint16_t gl_fl = glyphs[i].flags; uint8_t gl_cn = glyphs[i].count; - bool cprev = false; + bool cprev_cluster = false; + bool cprev_conn = false; if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. - cprev = true; + cprev_cluster = true; } if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected. if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) { - cprev = true; + cprev_conn = true; } } else { if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { - cprev = true; + cprev_conn = true; } } @@ -1209,10 +1208,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool txt_visible = (font_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; + bool cn = cprev_cluster || (cprev_conn && item_fx->connected); + if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) { ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx); @@ -1222,7 +1223,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs; + charfx->visibility = txt_visible; charfx->outline = false; charfx->font = frid; charfx->glyph_index = gl; @@ -1238,17 +1240,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - if (!cprev) { + if (!cn) { uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); n_time = (n_time > 1.0) ? 1.0 : n_time; item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; @@ -1257,7 +1259,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - if (!cprev) { + if (!cn) { double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); item_wave->prev_off = Point2(0, 1) * value; } @@ -1265,7 +1267,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - if (!cprev) { + if (!cn) { double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius); item_tornado->prev_off = Point2(torn_x, torn_y); @@ -1278,19 +1280,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - if (selected) { - font_color = override_selected_font_color ? selection_fg : font_color; + if (selected && use_selected_font_color) { + font_color = theme_cache.font_selected_color; } // Draw glyphs. for (int j = 0; j < glyphs[i].repeat; j++) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); - if (visible) { + if (txt_visible) { if (!skip) { if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } r_processed_glyphs++; @@ -1300,19 +1302,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } } @@ -1322,19 +1324,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } // Draw foreground color box @@ -1370,7 +1372,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item while (ofs.y < size.height && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char, false, p_meta); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1448,9 +1450,6 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); - ItemTable *table = static_cast<ItemTable *>(it); int idx = 0; @@ -1468,7 +1467,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rtl) { coff.x = rect.size.width - table->columns[col].width - coff.x; } - Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size); + Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + theme_cache.table_h_separation, table->rows[row] + theme_cache.table_v_separation) + frame->padding.position + frame->padding.size); if (col == col_count - 1) { if (rtl) { crect.size.x = crect.position.x + crect.size.x; @@ -1507,7 +1506,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size); if (p_table) { - rect.size.y += get_theme_constant(SNAME("table_v_separation")); + rect.size.y += theme_cache.table_v_separation; } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { @@ -1546,7 +1545,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V return table_offy; } - off.y += TS->shaped_text_get_descent(rid) + get_theme_constant(SNAME("line_separation")); + off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation; } // Text line hit. @@ -1561,8 +1560,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V int stop = text_rect_begin; *r_click_item = _find_indentable(it); while (*r_click_item) { - Ref<Font> font = get_theme_font(SNAME("normal_font")); - int font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; ItemFont *font_it = _find_font(*r_click_item); if (font_it) { if (font_it->font.is_valid()) { @@ -1621,7 +1620,7 @@ void RichTextLabel::_scroll_changed(double) { scroll_updated = true; - update(); + queue_redraw(); } void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) { @@ -1671,11 +1670,53 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { r = m; } } - return l; + return MIN(l, (int)main->lines.size() - 1); } _FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const { - return line.get_height(get_theme_constant(SNAME("line_separation"))); + return line.get_height(theme_cache.line_separation); +} + +void RichTextLabel::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + theme_cache.focus_style = get_theme_stylebox(SNAME("focus")); + theme_cache.progress_bg_style = get_theme_stylebox(SNAME("background"), SNAME("ProgressBar")); + theme_cache.progress_fg_style = get_theme_stylebox(SNAME("fill"), SNAME("ProgressBar")); + + theme_cache.line_separation = get_theme_constant(SNAME("line_separation")); + + theme_cache.normal_font = get_theme_font(SNAME("normal_font")); + theme_cache.normal_font_size = get_theme_font_size(SNAME("normal_font_size")); + + theme_cache.default_color = get_theme_color(SNAME("default_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0); + theme_cache.selection_color = get_theme_color(SNAME("selection_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + theme_cache.shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + theme_cache.shadow_offset_x = get_theme_constant(SNAME("shadow_offset_x")); + theme_cache.shadow_offset_y = get_theme_constant(SNAME("shadow_offset_y")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.bold_font = get_theme_font(SNAME("bold_font")); + theme_cache.bold_font_size = get_theme_font_size(SNAME("bold_font_size")); + theme_cache.bold_italics_font = get_theme_font(SNAME("bold_italics_font")); + theme_cache.bold_italics_font_size = get_theme_font_size(SNAME("bold_italics_font_size")); + theme_cache.italics_font = get_theme_font(SNAME("italics_font")); + theme_cache.italics_font_size = get_theme_font_size(SNAME("italics_font_size")); + theme_cache.mono_font = get_theme_font(SNAME("mono_font")); + theme_cache.mono_font_size = get_theme_font_size(SNAME("mono_font_size")); + + theme_cache.table_h_separation = get_theme_constant(SNAME("table_h_separation")); + theme_cache.table_v_separation = get_theme_constant(SNAME("table_v_separation")); + theme_cache.table_odd_row_bg = get_theme_color(SNAME("table_odd_row_bg")); + theme_cache.table_even_row_bg = get_theme_color(SNAME("table_even_row_bg")); + theme_cache.table_border = get_theme_color(SNAME("table_border")); + + theme_cache.base_scale = get_theme_default_base_scale(); } void RichTextLabel::_notification(int p_what) { @@ -1685,20 +1726,20 @@ void RichTextLabel::_notification(int p_what) { meta_hovering = nullptr; emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; - update(); + queue_redraw(); } } break; case NOTIFICATION_RESIZED: { _stop_thread(); main->first_resized_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _stop_thread(); main->first_invalid_font_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_ENTER_TREE: { @@ -1708,7 +1749,7 @@ void RichTextLabel::_notification(int p_what) { } main->first_invalid_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_PREDELETE: @@ -1720,22 +1761,22 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { _stop_thread(); main->first_invalid_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2 size = get_size(); - draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size)); + draw_style_box(theme_cache.normal_style, Rect2(Point2(), size)); if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - draw_style_box(get_theme_stylebox(SNAME("focus")), Rect2(Point2(), size)); + draw_style_box(theme_cache.focus_style, Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } @@ -1745,24 +1786,20 @@ void RichTextLabel::_notification(int p_what) { } else { // Draw loading progress bar. if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar")); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Vector2 p_size = Vector2(size.width - (theme_cache.normal_style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width); + Vector2 p_pos = Vector2(theme_cache.normal_style->get_offset().x, size.height - theme_cache.normal_style->get_offset().y - vscroll->get_combined_minimum_size().width); - Vector2 p_size = Vector2(size.width - (style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width); - Vector2 p_pos = Vector2(style->get_offset().x, size.height - style->get_offset().y - vscroll->get_combined_minimum_size().width); - - draw_style_box(bg, Rect2(p_pos, p_size)); + draw_style_box(theme_cache.progress_bg_style, Rect2(p_pos, p_size)); bool right_to_left = is_layout_rtl(); double r = loaded.load(); - int mp = fg->get_minimum_size().width; + int mp = theme_cache.progress_fg_style->get_minimum_size().width; int p = round(r * (p_size.width - mp)); if (right_to_left) { int p_remaining = round((1.0 - r) * (p_size.width - mp)); - draw_style_box(fg, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, p_size.height))); + draw_style_box(theme_cache.progress_fg_style, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + theme_cache.progress_fg_style->get_minimum_size().width, p_size.height))); } else { - draw_style_box(fg, Rect2(p_pos, Size2(p + fg->get_minimum_size().width, p_size.height))); + draw_style_box(theme_cache.progress_fg_style, Rect2(p_pos, Size2(p + theme_cache.progress_fg_style->get_minimum_size().width, p_size.height))); } } } @@ -1775,13 +1812,7 @@ void RichTextLabel::_notification(int p_what) { int to_line = main->first_invalid_line.load(); int from_line = _find_first_line(0, to_line, vofs); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); - Color base_color = get_theme_color(SNAME("default_color")); - Color outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + Point2 shadow_ofs(theme_cache.shadow_offset_x, theme_cache.shadow_offset_y); visible_paragraph_count = 0; visible_line_count = 0; @@ -1793,8 +1824,8 @@ void RichTextLabel::_notification(int p_what) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; - visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs, processed_glyphs); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; from_line++; } } break; @@ -1806,7 +1837,7 @@ void RichTextLabel::_notification(int p_what) { } double dt = get_process_delta_time(); _update_fx(main, dt); - update(); + queue_redraw(); } } break; @@ -1918,7 +1949,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - update(); + queue_redraw(); break; } } @@ -1993,36 +2024,36 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (k->is_pressed()) { bool handled = false; - if (k->is_action("ui_page_up") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() - vscroll->get_page()); handled = true; } - if (k->is_action("ui_page_down") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() + vscroll->get_page()); handled = true; } - if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); + if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) { + vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); + if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { + vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(0); handled = true; } - if (k->is_action("ui_end") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_max()); handled = true; } if (is_shortcut_keys_enabled()) { - if (k->is_action("ui_text_select_all")) { + if (k->is_action("ui_text_select_all", true)) { select_all(); handled = true; } - if (k->is_action("ui_copy")) { + if (k->is_action("ui_copy", true)) { selection_copy(); handled = true; } @@ -2084,7 +2115,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } selection.active = true; - update(); + queue_redraw(); } Variant meta; @@ -2164,6 +2195,75 @@ RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) { while (fontitem) { if (fontitem->type == ITEM_FONT) { ItemFont *fi = static_cast<ItemFont *>(fontitem); + switch (fi->def_font) { + case NORMAL_FONT: { + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.normal_font); + } + } else { + fi->font = theme_cache.normal_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.normal_font_size; + } + } break; + case BOLD_FONT: { + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.bold_font); + } + } else { + fi->font = theme_cache.bold_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.bold_font_size; + } + } break; + case ITALICS_FONT: { + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.italics_font); + } + } else { + fi->font = theme_cache.italics_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.italics_font_size; + } + } break; + case BOLD_ITALICS_FONT: { + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.bold_italics_font); + } + } else { + fi->font = theme_cache.bold_italics_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.bold_italics_font_size; + } + } break; + case MONO_FONT: { + if (fi->variation) { + Ref<FontVariation> fc = fi->font; + if (fc.is_valid()) { + fc->set_base_font(theme_cache.mono_font); + } + } else { + fi->font = theme_cache.mono_font; + } + if (fi->def_size) { + fi->font_size = theme_cache.mono_font_size; + } + } break; + default: { + } break; + } return fi; } @@ -2539,9 +2639,11 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { void RichTextLabel::_thread_function(void *self) { RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self); + rtl->set_physics_process_internal(true); rtl->_process_line_caches(); + rtl->set_physics_process_internal(false); rtl->updating.store(false); - rtl->call_deferred(SNAME("update")); + rtl->call_deferred(SNAME("queue_redraw")); } void RichTextLabel::_stop_thread() { @@ -2562,7 +2664,7 @@ void RichTextLabel::set_threaded(bool p_threaded) { if (threaded != p_threaded) { _stop_thread(); threaded = p_threaded; - update(); + queue_redraw(); } } @@ -2586,20 +2688,20 @@ bool RichTextLabel::_validate_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); - int base_font_size = get_theme_font_size(SNAME("normal_font_size")); int ctrl_height = get_size().height; // Update fonts. + float old_scroll = vscroll->get_value(); if (main->first_invalid_font_line.load() != (int)main->lines.size()) { for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) { - _update_line_font(main, i, base_font, base_font_size); + _update_line_font(main, i, theme_cache.normal_font, theme_cache.normal_font_size); } main->first_resized_line.store(main->first_invalid_font_line.load()); main->first_invalid_font_line.store(main->lines.size()); } if (main->first_resized_line.load() == (int)main->lines.size()) { + vscroll->set_value(old_scroll); return true; } @@ -2608,7 +2710,7 @@ 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, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); + total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); updating_scroll = true; bool exceeds = total_height > ctrl_height && scroll_active; @@ -2628,7 +2730,7 @@ bool RichTextLabel::_validate_line_caches() { total_height = 0; for (int j = 0; j <= i; j++) { - total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); + total_height = _resize_line(main, j, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); main->first_resized_line.store(j); } @@ -2638,6 +2740,8 @@ bool RichTextLabel::_validate_line_caches() { vscroll->set_page(text_rect.size.height); if (scroll_follow && scroll_following) { vscroll->set_value(total_height); + } else { + vscroll->set_value(old_scroll); } updating_scroll = false; @@ -2657,11 +2761,10 @@ bool RichTextLabel::_validate_line_caches() { loaded.store(true); thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this)); loading_started = OS::get_singleton()->get_ticks_msec(); - set_physics_process_internal(true); return false; } else { _process_line_caches(); - update(); + queue_redraw(); return true; } } @@ -2675,15 +2778,13 @@ void RichTextLabel::_process_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - int base_font_size = get_theme_font_size(SNAME("normal_font_size")); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int ctrl_height = get_size().height; int fi = main->first_invalid_line.load(); int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count); 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 = _shape_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars); + 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); updating_scroll = true; bool exceeds = total_height > ctrl_height && scroll_active; if (exceeds != scroll_visible) { @@ -2704,7 +2805,7 @@ void RichTextLabel::_process_line_caches() { // since scroll was added or removed we need to resize all lines total_height = 0; for (int j = 0; j <= i; j++) { - total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); + total_height = _resize_line(main, j, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); main->first_invalid_line.store(j); main->first_resized_line.store(j); @@ -2799,7 +2900,7 @@ void RichTextLabel::add_text(const String &p_text) { pos = end + 1; } - update(); + queue_redraw(); } void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) { @@ -2837,7 +2938,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) if (fixed_width != -1) { update_minimum_size(); } - update(); + queue_redraw(); } void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) { @@ -2864,7 +2965,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub memdelete(p_item); } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -2877,7 +2978,15 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, ERR_FAIL_COND(p_image->get_height() == 0); ItemImage *item = memnew(ItemImage); - item->image = p_image; + if (p_region.has_area()) { + Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); + atlas_tex->set_atlas(p_image); + atlas_tex->set_region(p_region); + item->image = atlas_tex; + } else { + item->image = p_image; + } + item->color = p_color; item->inline_align = p_alignment; @@ -2889,17 +2998,30 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.height = p_height; } else { // calculate height to keep aspect ratio - item->size.height = p_image->get_height() * p_width / p_image->get_width(); + if (p_region.has_area()) { + item->size.height = p_region.get_size().height * p_width / p_region.get_size().width; + } else { + item->size.height = p_image->get_height() * p_width / p_image->get_width(); + } } } else { if (p_height > 0) { // custom height item->size.height = p_height; // calculate width to keep aspect ratio - item->size.width = p_image->get_width() * p_height / p_image->get_height(); + if (p_region.has_area()) { + item->size.width = p_region.get_size().width * p_height / p_region.get_size().height; + } else { + item->size.width = p_image->get_width() * p_height / p_image->get_height(); + } } else { - // keep original width and height - item->size = p_image->get_size(); + if (p_region.has_area()) { + // if the image has a region, keep the region size + item->size = p_region.get_size(); + } else { + // keep original width and height + item->size = p_image->get_size(); + } } } @@ -2918,7 +3040,7 @@ void RichTextLabel::add_newline() { _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); _invalidate_current_line(current_frame); - update(); + queue_redraw(); } bool RichTextLabel::remove_line(const int p_line) { @@ -2957,7 +3079,7 @@ bool RichTextLabel::remove_line(const int p_line) { } main->first_invalid_line.store(0); - update(); + queue_redraw(); return true; } @@ -2983,6 +3105,33 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font _add_item(item, false); } +void RichTextLabel::_push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemFont *item = memnew(ItemFont); + + item->def_font = p_def_font; + item->variation = true; + item->font = p_font; + item->font_size = p_size; + item->def_size = (p_size <= 0); + _add_item(item, true); +} + +void RichTextLabel::_push_def_font(DefaultFont p_def_font) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemFont *item = memnew(ItemFont); + + item->def_font = p_def_font; + item->def_size = true; + _add_item(item, true); +} + void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -2997,38 +3146,33 @@ void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { } void RichTextLabel::push_normal() { - Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); - ERR_FAIL_COND(normal_font.is_null()); + ERR_FAIL_COND(theme_cache.normal_font.is_null()); - push_font(normal_font, get_theme_font_size(SNAME("normal_font_size"))); + _push_def_font(NORMAL_FONT); } void RichTextLabel::push_bold() { - Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); - ERR_FAIL_COND(bold_font.is_null()); + ERR_FAIL_COND(theme_cache.bold_font.is_null()); - push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); + _push_def_font(BOLD_FONT); } void RichTextLabel::push_bold_italics() { - Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); - ERR_FAIL_COND(bold_italics_font.is_null()); + ERR_FAIL_COND(theme_cache.bold_italics_font.is_null()); - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + _push_def_font(BOLD_ITALICS_FONT); } void RichTextLabel::push_italics() { - Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); - ERR_FAIL_COND(italics_font.is_null()); + ERR_FAIL_COND(theme_cache.italics_font.is_null()); - push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); + _push_def_font(ITALICS_FONT); } void RichTextLabel::push_mono() { - Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); - ERR_FAIL_COND(mono_font.is_null()); + ERR_FAIL_COND(theme_cache.mono_font.is_null()); - push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); + _push_def_font(MONO_FONT); } void RichTextLabel::push_font_size(int p_font_size) { @@ -3185,33 +3329,36 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) { _add_item(item, true); } -void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) { +void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemShake *item = memnew(ItemShake); item->strength = p_strength; item->rate = p_rate; + item->connected = p_connected; _add_item(item, true); } -void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) { +void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemWave *item = memnew(ItemWave); item->frequency = p_frequency; item->amplitude = p_amplitude; + item->connected = p_connected; _add_item(item, true); } -void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) { +void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f, bool p_connected = true) { _stop_thread(); MutexLock data_lock(data_mutex); ItemTornado *item = memnew(ItemTornado); item->frequency = p_frequency; item->radius = p_radius; + item->connected = p_connected; _add_item(item, true); } @@ -3386,7 +3533,7 @@ void RichTextLabel::set_tab_size(int p_spaces) { tab_size = p_spaces; main->first_resized_line.store(0); - update(); + queue_redraw(); } int RichTextLabel::get_tab_size() const { @@ -3410,7 +3557,7 @@ void RichTextLabel::set_meta_underline(bool p_underline) { } underline_meta = p_underline; - update(); + queue_redraw(); } bool RichTextLabel::is_meta_underlined() const { @@ -3419,21 +3566,13 @@ bool RichTextLabel::is_meta_underlined() const { void RichTextLabel::set_hint_underline(bool p_underline) { underline_hint = p_underline; - update(); + queue_redraw(); } bool RichTextLabel::is_hint_underlined() const { return underline_hint; } -void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool RichTextLabel::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void RichTextLabel::set_offset(int p_pixel) { vscroll->set_value(p_pixel); } @@ -3445,7 +3584,7 @@ void RichTextLabel::set_scroll_active(bool p_active) { scroll_active = p_active; vscroll->set_drag_node_enabled(p_active); - update(); + queue_redraw(); } bool RichTextLabel::is_scroll_active() const { @@ -3475,13 +3614,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { int pos = 0; List<String> tag_stack; - Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); - Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); - Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); - Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); - Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); - - Color base_color = get_theme_color(SNAME("default_color")); int indent_level = 0; @@ -3499,21 +3631,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { brk_pos = p_bbcode.length(); } - String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; + String txt = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; // Trim the first newline character, it may be added later as needed. if (after_list_close_tag || after_list_open_tag) { - text = text.trim_prefix("\n"); + txt = txt.trim_prefix("\n"); } if (brk_pos == p_bbcode.length()) { // For tags that are not properly closed. - if (text.is_empty() && after_list_open_tag) { - text = "\n"; + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } break; //nothing else to add } @@ -3522,8 +3654,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (brk_end == -1) { //no close, add the rest - text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); - add_text(text); + txt += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); + add_text(txt); break; } @@ -3569,36 +3701,36 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (!tag_ok) { - text += "[" + tag; - add_text(text); + txt += "[" + tag; + add_text(txt); after_list_open_tag = false; after_list_close_tag = false; pos = brk_end; continue; } - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make empty list have at least one item. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make empty list have at least one item. } after_list_open_tag = false; if (tag == "/ol" || tag == "/ul") { - if (!text.is_empty()) { + if (!txt.is_empty()) { // Make sure text ends with a newline character, that is, the last item // will wrap at the end of block. - if (!text.ends_with("\n")) { - text += "\n"; + if (!txt.ends_with("\n")) { + txt += "\n"; } } else if (!after_list_close_tag) { - text = "\n"; // Make the innermost list item wrap at the end of lists. + txt = "\n"; // Make the innermost list item wrap at the end of lists. } after_list_close_tag = true; } else { after_list_close_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } tag_stack.pop_front(); @@ -3610,15 +3742,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) { - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make each list have at least one item at the beginning. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make each list have at least one item at the beginning. } after_list_open_tag = true; } else { after_list_open_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } after_list_close_tag = false; @@ -3626,9 +3758,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use bold font in_bold = true; if (in_italics) { - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + _push_def_font(BOLD_ITALICS_FONT); } else { - push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); + _push_def_font(BOLD_FONT); } pos = brk_end + 1; tag_stack.push_front(tag); @@ -3636,15 +3768,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use italics font in_italics = true; if (in_bold) { - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + _push_def_font(BOLD_ITALICS_FONT); } else { - push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); + _push_def_font(ITALICS_FONT); } pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code") { //use monospace font - push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); + _push_def_font(MONO_FONT); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { @@ -3730,6 +3862,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { Color color2 = Color::from_string(subtag_b[1], fallback_color); set_cell_row_background_color(color1, color2); } + if (subtag_b.size() == 1) { + Color color1 = Color::from_string(subtag_a[1], fallback_color); + set_cell_row_background_color(color1, color1); + } } } } @@ -3860,7 +3996,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; - TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); if (subtag_a.size() == 2) { @@ -3886,24 +4022,24 @@ void RichTextLabel::append_text(const String &p_bbcode) { lang = subtag_a[1]; } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser = TextServer::STRUCTURED_TEXT_URI; + st_parser_type = TextServer::STRUCTURED_TEXT_URI; } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser = TextServer::STRUCTURED_TEXT_FILE; + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser = TextServer::STRUCTURED_TEXT_EMAIL; + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser = TextServer::STRUCTURED_TEXT_LIST; + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; } else if (subtag_a[1] == "n" || subtag_a[1] == "none") { - st_parser = TextServer::STRUCTURED_TEXT_NONE; + st_parser_type = TextServer::STRUCTURED_TEXT_NONE; } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser = TextServer::STRUCTURED_TEXT_CUSTOM; + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; } } } } - push_paragraph(alignment, dir, lang, st_parser); + push_paragraph(alignment, dir, lang, st_parser_type); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -3929,12 +4065,12 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; - Ref<Font> f = get_theme_font(SNAME("normal_font")); - Color color = get_theme_color(SNAME("default_color")); - Color outline_color = get_theme_color(SNAME("outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - Rect2 dropcap_margins = Rect2(); + int fs = theme_cache.normal_font_size * 3; + Ref<Font> f = theme_cache.normal_font; + Color color = theme_cache.default_color; + Color outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; + Rect2 dropcap_margins; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -3969,9 +4105,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { end = p_bbcode.length(); } - String txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); + String dc_txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); - push_dropcap(txt, f, fs, dropcap_margins, color, outline_size, outline_color); + push_dropcap(dc_txt, f, fs, dropcap_margins, color, outline_size, outline_color); pos = end; tag_stack.push_front(bbcode_name); @@ -4016,6 +4152,18 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D"); if (texture.is_valid()) { + Rect2 region; + OptionMap::Iterator region_option = bbcode_options.find("region"); + if (region_option) { + Vector<String> region_values = region_option->value.split(",", false); + if (region_values.size() == 4) { + region.position.x = region_values[0].to_float(); + region.position.y = region_values[1].to_float(); + region.size.x = region_values[2].to_float(); + region.size.y = region_values[3].to_float(); + } + } + Color color = Color(1.0, 1.0, 1.0); OptionMap::Iterator color_option = bbcode_options.find("color"); if (color_option) { @@ -4044,21 +4192,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - add_image(texture, width, height, color, (InlineAlignment)alignment); + add_image(texture, width, height, color, (InlineAlignment)alignment, region); } pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("color=")) { String color_str = tag.substr(6, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("outline_color=")) { String color_str = tag.substr(14, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_outline_color(color); pos = brk_end + 1; tag_stack.push_front("outline_color"); @@ -4069,24 +4217,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("font_size"); - } else if (tag.begins_with("opentype_features=")) { - String fnt_ftr = tag.substr(18, tag.length()); + } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) { + int value_pos = tag.find("="); + String fnt_ftr = tag.substr(value_pos + 1); Vector<String> subtag = fnt_ftr.split(","); if (subtag.size() > 0) { - Ref<Font> font = normal_font; - int font_size = 0; + Ref<Font> font = theme_cache.normal_font; + DefaultFont def_font = NORMAL_FONT; + ItemFont *font_it = _find_font(current); 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; + def_font = font_it->def_font; } } - Ref<FontVariation> fc; - fc.instantiate(); - fc->set_base_font(font); Dictionary features; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -4096,11 +4241,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { features[TS->name_to_tag(subtag_a[0])] = 1; } } + + Ref<FontVariation> fc; + fc.instantiate(); + + fc->set_base_font(font); fc->set_opentype_features(features); - push_font(fc, font_size); + + if (def_font != CUSTOM_FONT) { + _push_def_font_var(def_font, fc); + } else { + push_font(fc); + } } pos = brk_end + 1; - tag_stack.push_front("opentype_features"); + tag_stack.push_front(tag.substr(0, value_pos)); } else if (tag.begins_with("font=")) { String fnt = tag.substr(5, tag.length()); @@ -4116,9 +4271,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("font ")) { Vector<String> subtag = tag.substr(2, tag.length()).split(" "); + Ref<Font> font = theme_cache.normal_font; + DefaultFont def_font = NORMAL_FONT; + + ItemFont *font_it = _find_font(current); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + def_font = font_it->def_font; + } + } + Ref<FontVariation> fc; fc.instantiate(); - int fnt_size = 0; + + int fnt_size = -1; for (int i = 1; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("=", true, 2); if (subtag_a.size() == 2) { @@ -4126,7 +4293,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { String fnt = subtag_a[1]; Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); if (font_data.is_valid()) { - fc->set_base_font(font_data); + font = font_data; + def_font = CUSTOM_FONT; } } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { fnt_size = subtag_a[1].to_int(); @@ -4180,7 +4348,14 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - push_font(fc, fnt_size); + fc->set_base_font(font); + + if (def_font != CUSTOM_FONT) { + _push_def_font_var(def_font, fc, fnt_size); + } else { + push_font(fc, fnt_size); + } + pos = brk_end + 1; tag_stack.push_front("font"); @@ -4221,7 +4396,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { rate = rate_option->value.to_float(); } - push_shake(strength, rate); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_shake(strength, rate, connected); pos = brk_end + 1; tag_stack.push_front("shake"); set_process_internal(true); @@ -4238,7 +4419,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { period = period_option->value.to_float(); } - push_wave(period, amplitude); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_wave(period, amplitude, connected); pos = brk_end + 1; tag_stack.push_front("wave"); set_process_internal(true); @@ -4255,7 +4442,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { frequency = frequency_option->value.to_float(); } - push_tornado(frequency, radius); + bool connected = true; + OptionMap::Iterator connected_option = bbcode_options.find("connected"); + if (connected_option) { + connected = connected_option->value.to_int(); + } + + push_tornado(frequency, radius, connected); pos = brk_end + 1; tag_stack.push_front("tornado"); set_process_internal(true); @@ -4285,7 +4478,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("bgcolor=")) { String color_str = tag.substr(8, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_bgcolor(color); pos = brk_end + 1; @@ -4293,7 +4486,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("fgcolor=")) { String color_str = tag.substr(8, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_fgcolor(color); pos = brk_end + 1; @@ -4335,6 +4528,30 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } +void RichTextLabel::scroll_to_selection() { + if (selection.active && selection.from_frame && selection.from_line >= 0 && selection.from_line < (int)selection.from_frame->lines.size()) { + // Selected frame paragraph offset. + float line_offset = selection.from_frame->lines[selection.from_line].offset.y; + + // Add wrapped line offset. + for (int i = 0; i < selection.from_frame->lines[selection.from_line].text_buf->get_line_count(); i++) { + Vector2i range = selection.from_frame->lines[selection.from_line].text_buf->get_line_range(i); + if (range.x <= selection.from_char && range.y >= selection.from_char) { + break; + } + line_offset += selection.from_frame->lines[selection.from_line].text_buf->get_line_size(i).y + theme_cache.line_separation; + } + + // Add nested frame (e.g. table cell) offset. + ItemFrame *it = selection.from_frame; + while (it->parent_frame != nullptr) { + line_offset += it->parent_frame->lines[it->line].offset.y; + it = it->parent_frame; + } + vscroll->set_value(line_offset); + } +} + void RichTextLabel::scroll_to_paragraph(int p_paragraph) { _validate_line_caches(); @@ -4355,16 +4572,18 @@ int RichTextLabel::get_visible_paragraph_count() const { if (!is_visible()) { return 0; } + + const_cast<RichTextLabel *>(this)->_validate_line_caches(); return visible_paragraph_count; } void RichTextLabel::scroll_to_line(int p_line) { - _validate_line_caches(); - if (p_line <= 0) { vscroll->set_value(0); return; } + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4372,7 +4591,7 @@ void RichTextLabel::scroll_to_line(int p_line) { if ((line_count <= p_line) && (line_count + main->lines[i].text_buf->get_line_count() >= p_line)) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { - line_offset += main->lines[i].text_buf->get_line_size(j).y + get_theme_constant(SNAME("line_separation")); + line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation; } vscroll->set_value(main->lines[i].offset.y + line_offset); return; @@ -4383,6 +4602,8 @@ void RichTextLabel::scroll_to_line(int p_line) { } float RichTextLabel::get_line_offset(int p_line) { + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4390,7 +4611,7 @@ float RichTextLabel::get_line_offset(int p_line) { if ((line_count <= p_line) && (p_line <= line_count + main->lines[i].text_buf->get_line_count())) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { - line_offset += main->lines[i].text_buf->get_line_size(j).y + get_theme_constant(SNAME("line_separation")); + line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation; } return main->lines[i].offset.y + line_offset; } @@ -4400,6 +4621,8 @@ float RichTextLabel::get_line_offset(int p_line) { } float RichTextLabel::get_paragraph_offset(int p_paragraph) { + _validate_line_caches(); + int to_line = main->first_invalid_line.load(); if (0 <= p_paragraph && p_paragraph < to_line) { return main->lines[p_paragraph].offset.y; @@ -4408,6 +4631,8 @@ float RichTextLabel::get_paragraph_offset(int p_paragraph) { } int RichTextLabel::get_line_count() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4421,6 +4646,8 @@ int RichTextLabel::get_visible_line_count() const { if (!is_visible()) { return 0; } + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + return visible_line_count; } @@ -4503,19 +4730,19 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p Line &l = p_frame->lines[p_line]; - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_NEWLINE: { - text += "\n"; + txt += "\n"; } break; case ITEM_TEXT: { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } break; case ITEM_IMAGE: { - text += " "; + txt += " "; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); @@ -4531,9 +4758,9 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p int sp = -1; if (p_reverse_search) { - sp = text.rfindn(p_string, p_char_idx); + sp = txt.rfindn(p_string, p_char_idx); } else { - sp = text.findn(p_string, p_char_idx); + sp = txt.findn(p_string, p_char_idx); } if (sp != -1) { @@ -4569,8 +4796,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char; if (!(p_search_previous && char_idx < 0) && _search_line(selection.from_frame, selection.from_line, p_string, char_idx, p_search_previous)) { - scroll_to_line(selection.from_frame->line + selection.from_line); - update(); + scroll_to_selection(); + queue_redraw(); return true; } char_idx = p_search_previous ? -1 : 0; @@ -4594,8 +4821,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p // Search for next element if (_search_table(parent_table, parent_element, p_string, p_search_previous)) { - scroll_to_line(selection.from_frame->line + selection.from_line); - update(); + scroll_to_selection(); + queue_redraw(); return true; } } @@ -4618,11 +4845,14 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p } if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) { - scroll_to_line(current_line); - update(); + scroll_to_selection(); + queue_redraw(); return true; } - p_search_previous ? current_line-- : current_line++; + + if (current_line != ending_line) { + p_search_previous ? current_line-- : current_line++; + } } if (p_from_selection && selection.active) { @@ -4634,10 +4864,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p } String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const { - String text; + String txt; - ERR_FAIL_COND_V(p_frame == nullptr, text); - ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text); + ERR_FAIL_COND_V(p_frame == nullptr, txt); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), txt); Line &l = p_frame->lines[p_line]; @@ -4657,7 +4887,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E); for (int i = 0; i < (int)frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); + txt += _get_line_text(frame, i, p_selection); } } } @@ -4669,23 +4899,23 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } if (it->type == ITEM_DROPCAP) { const ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { const ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } } if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) { - text = text.substr(0, p_selection.to_char); + txt = txt.substr(0, p_selection.to_char); } if ((l.from != nullptr) && (p_frame == p_selection.from_frame) && (p_selection.from_item != nullptr) && (p_selection.from_item->index >= l.from->index) && (p_selection.from_item->index < end_idx)) { - text = text.substr(p_selection.from_char, -1); + txt = txt.substr(p_selection.from_char, -1); } - return text; + return txt; } void RichTextLabel::set_context_menu_enabled(bool p_enabled) { @@ -4719,24 +4949,24 @@ String RichTextLabel::get_selected_text() const { return ""; } - String text; + String txt; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { - text += _get_line_text(main, i, selection); + txt += _get_line_text(main, i, selection); } - return text; + return txt; } void RichTextLabel::deselect() { selection.active = false; - update(); + queue_redraw(); } void RichTextLabel::selection_copy() { - String text = get_selected_text(); + String txt = get_selected_text(); - if (!text.is_empty()) { - DisplayServer::get_singleton()->clipboard_set(text); + if (!txt.is_empty()) { + DisplayServer::get_singleton()->clipboard_set(txt); } } @@ -4784,7 +5014,7 @@ void RichTextLabel::select_all() { selection.to_char = to_frame->lines[to_line].char_count; selection.to_item = to_item; selection.active = true; - update(); + queue_redraw(); } bool RichTextLabel::is_selection_enabled() const { @@ -4835,7 +5065,14 @@ void RichTextLabel::set_use_bbcode(bool p_enable) { } use_bbcode = p_enable; notify_property_list_changed(); - set_text(text); + + const String current_text = text; + if (use_bbcode) { + parse_bbcode(current_text); + } else { // raw text + clear(); + add_text(current_text); + } } bool RichTextLabel::is_using_bbcode() const { @@ -4843,25 +5080,25 @@ bool RichTextLabel::is_using_bbcode() const { } String RichTextLabel::get_parsed_text() const { - String text = ""; + String txt = ""; Item *it = main; while (it) { if (it->type == ITEM_DROPCAP) { ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } else if (it->type == ITEM_INDENT || it->type == ITEM_LIST) { - text += "\t"; + txt += "\t"; } it = _get_next_item(it, true); } - return text; + return txt; } void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { @@ -4872,7 +5109,7 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) text_direction = p_text_direction; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4883,7 +5120,7 @@ void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredText st_parser = p_parser; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4898,7 +5135,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4917,7 +5154,7 @@ void RichTextLabel::set_language(const String &p_language) { language = p_language; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4932,7 +5169,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { autowrap_mode = p_mode; main->first_invalid_line = 0; //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4944,10 +5181,10 @@ void RichTextLabel::set_visible_ratio(float p_ratio) { if (visible_ratio != p_ratio) { _stop_thread(); - if (visible_ratio >= 1.0) { + if (p_ratio >= 1.0) { visible_characters = -1; visible_ratio = 1.0; - } else if (visible_ratio < 0.0) { + } else if (p_ratio < 0.0) { visible_characters = 0; visible_ratio = 0.0; } else { @@ -4959,7 +5196,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) { main->first_invalid_line.store(0); // Invalidate ALL. _validate_line_caches(); } - update(); + queue_redraw(); } } @@ -4996,7 +5233,12 @@ int RichTextLabel::get_content_height() const { int to_line = main->first_invalid_line.load(); if (to_line) { MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); - total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + if (theme_cache.line_separation < 0) { + // Do not apply to the last line to avoid cutting text. + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + (main->lines[to_line - 1].text_buf->get_line_count() - 1) * theme_cache.line_separation; + } else { + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; + } } return total_height; } @@ -5029,7 +5271,7 @@ 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); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font); @@ -5081,9 +5323,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline); ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active); ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active); @@ -5094,6 +5333,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("scroll_to_line", "line"), &RichTextLabel::scroll_to_line); ClassDB::bind_method(D_METHOD("scroll_to_paragraph", "paragraph"), &RichTextLabel::scroll_to_paragraph); + ClassDB::bind_method(D_METHOD("scroll_to_selection"), &RichTextLabel::scroll_to_selection); ClassDB::bind_method(D_METHOD("set_tab_size", "spaces"), &RichTextLabel::set_tab_size); ClassDB::bind_method(D_METHOD("get_tab_size"), &RichTextLabel::get_tab_size); @@ -5172,31 +5412,35 @@ void RichTextLabel::_bind_methods() { // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_active"), "set_scroll_active", "is_scroll_active"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following"), "set_scroll_follow", "is_scroll_following"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); + 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_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"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_GROUP("Threading", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay"); + + ADD_GROUP("Text Selection", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); + + ADD_GROUP("Displayed Text", ""); // Note: "visible_characters" and "visible_ratio" should be set after "text" to be correctly applied. ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visible_ratio", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_visible_ratio", "get_visible_ratio"); - 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_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); @@ -5254,7 +5498,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter visible_chars_behavior = p_behavior; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -5275,7 +5519,7 @@ void RichTextLabel::set_visible_characters(int p_visible) { main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); } - update(); + queue_redraw(); } } @@ -5284,6 +5528,8 @@ int RichTextLabel::get_visible_characters() const { } int RichTextLabel::get_character_line(int p_char) { + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -5304,6 +5550,8 @@ int RichTextLabel::get_character_line(int p_char) { } int RichTextLabel::get_character_paragraph(int p_char) { + _validate_line_caches(); + int para_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -5335,6 +5583,8 @@ int RichTextLabel::get_total_character_count() const { } int RichTextLabel::get_total_glyph_count() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int tg = 0; Item *it = main; while (it) { @@ -5361,8 +5611,7 @@ void RichTextLabel::set_fixed_size_to_width(int p_width) { } Size2 RichTextLabel::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Size2 size = style->get_minimum_size(); + Size2 size = theme_cache.normal_style->get_minimum_size(); if (fixed_width != -1) { size.x += fixed_width; @@ -5432,6 +5681,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Vector2i fbg_index = Vector2i(end, start); Color last_color = Color(0, 0, 0, 0); bool draw_box = false; + int hpad = get_theme_constant(SNAME("text_highlight_h_padding")); + int vpad = get_theme_constant(SNAME("text_highlight_v_padding")); // Draw a box based on color tags associated with glyphs for (int i = start; i < end; i++) { Item *it = _get_item_at_pos(it_from, it_to, i); @@ -5461,8 +5712,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (draw_box) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y); for (int j = 0; j < sel.size(); j++) { - Vector2 rect_off = line_off + Vector2(sel[j].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[j].y - sel[j].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[j].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } fbg_index = Vector2i(end, start); @@ -5480,8 +5731,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (last_color.a > 0) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end); for (int i = 0; i < sel.size(); i++) { - Vector2 rect_off = line_off + Vector2(sel[i].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[i].y - sel[i].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[i].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } } @@ -5503,11 +5754,11 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id } Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) { - Dictionary d = Dictionary(); + Dictionary d; for (int i = 0; i < p_expressions.size(); i++) { String expression = p_expressions[i]; - Array a = Array(); + Array a; Vector<String> parts = expression.split("=", true); String key = parts[0]; if (parts.size() != 2) { |