diff options
Diffstat (limited to 'scene/gui/rich_text_label.cpp')
-rw-r--r-- | scene/gui/rich_text_label.cpp | 972 |
1 files changed, 645 insertions, 327 deletions
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 2800ab0442..fd19fad667 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -36,15 +36,11 @@ #include "scene/scene_string_names.h" #include "servers/display_server.h" -#include "modules/modules_enabled.gen.h" +#include "modules/modules_enabled.gen.h" // For regex. #ifdef MODULE_REGEX_ENABLED #include "modules/regex/regex.h" #endif -#ifdef TOOLS_ENABLED -#include "editor/editor_scale.h" -#endif - RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) const { if (p_free) { if (p_item->subitems.size()) { @@ -136,7 +132,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } Rect2 RichTextLabel::_get_text_rect() { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); return Rect2(style->get_offset(), get_size() - style->get_minimum_size()); } @@ -229,8 +225,8 @@ void 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("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); int col_count = table->columns.size(); for (int i = 0; i < col_count; i++) { @@ -238,9 +234,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); for (int i = 0; i < frame->lines.size(); i++) { _resize_line(frame, i, p_base_font, p_base_font_size, 1); } @@ -316,9 +312,9 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> Vector2 offset; float row_height = 0.0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; @@ -333,7 +329,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { @@ -366,7 +362,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -399,8 +395,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Shape current paragraph. String text; Item *it_to = (p_line + 1 < 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)) { - if (visible_characters >= 0 && l.char_offset + l.char_count > visible_characters) { + if (visible_characters >= 0 && remaining_characters <= 0) { break; } switch (it->type) { @@ -427,7 +424,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } l.text_buf->add_string("\n", font, font_size, Dictionary(), ""); text += "\n"; - l.char_count += 1; + l.char_count++; + remaining_characters--; } break; case ITEM_TEXT: { ItemText *t = (ItemText *)it; @@ -442,9 +440,10 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Dictionary font_ftr = _find_font_features(it); String lang = _find_language(it); String tx = t->text; - if (visible_characters >= 0 && l.char_offset + l.char_count + tx.length() > visible_characters) { - tx = tx.substr(0, l.char_offset + l.char_count + tx.length() - visible_characters); + if (visible_characters >= 0 && remaining_characters >= 0) { + tx = tx.substr(0, remaining_characters); } + remaining_characters -= tx.length(); l.text_buf->add_string(tx, font, font_size, font_ftr, lang); text += tx; @@ -454,12 +453,13 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> ItemImage *img = (ItemImage *)it; l.text_buf->add_object((uint64_t)it, img->image->get_size(), img->inline_align, 1); text += String::chr(0xfffc); - l.char_count += 1; + l.char_count++; + remaining_characters--; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); int col_count = table->columns.size(); int t_char_count = 0; // Set minimums to zero. @@ -472,9 +472,9 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> const int available_width = p_width - hseparation * (col_count - 1); int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); int column = idx % col_count; for (int i = 0; i < frame->lines.size(); i++) { @@ -483,9 +483,10 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> int cell_ch = (char_offset - (l.char_offset + l.char_count)); l.char_count += cell_ch; t_char_count += cell_ch; + remaining_characters -= cell_ch; table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x)); - table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wraped_size().x)); + table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x)); } idx++; } @@ -556,7 +557,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Vector2 offset; float row_height = 0.0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { + for (const List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E->get()); @@ -573,7 +574,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } else { frame->lines.write[i].offset.y = 0; } - frame->lines.write[i].offset += Vector2(offset.x, offset.y); + frame->lines.write[i].offset += offset; float h = frame->lines[i].text_buf->get_size().y; if (frame->min_size_over.y > 0) { @@ -614,13 +615,13 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> *r_char_offset = l.char_offset + l.char_count; if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y; + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } } -int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &p_shadow_ofs) { +int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs) { Vector2 off; ERR_FAIL_COND_V(p_frame == nullptr, 0); @@ -672,11 +673,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (prefix != "") { Ref<Font> font = _find_font(l.from); if (font.is_null()) { - font = get_theme_font("normal_font"); + font = get_theme_font(SNAME("normal_font")); } int font_size = _find_font_size(l.from); if (font_size == -1) { - font_size = get_theme_font_size("normal_font_size"); + font_size = get_theme_font_size(SNAME("normal_font_size")); } if (rtl) { float offx = 0.0f; @@ -775,16 +776,16 @@ 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("table_odd_row_bg"); - Color even_row_bg = get_theme_color("table_even_row_bg"); - Color border = get_theme_color("table_border"); - int hseparation = get_theme_constant("table_hseparation"); + 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_hseparation")); int col_count = table->columns.size(); int row_count = table->rows.size(); int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); int col = idx % col_count; int row = idx / col_count; @@ -803,7 +804,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } for (int j = 0; j < frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_as_outline, p_shadow_ofs); + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs); } idx++; } @@ -814,9 +815,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(rid); - const TextServer::Glyph *glyphs = visual.ptr(); - int gl_size = visual.size(); + const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); + int gl_size = TS->shaped_text_get_glyph_count(rid); Vector2 gloff = off; // Draw oulines and shadow. @@ -824,7 +824,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); int size = _find_outline_size(it, p_outline_size); Color font_color = _find_outline_color(it, p_outline_color); - if (size <= 0) { + Color font_shadow_color = p_font_shadow_color; + if ((size <= 0 || font_color.a == 0) && (font_shadow_color.a == 0)) { gloff.x += glyphs[i].advance; continue; } @@ -847,6 +848,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); RID frid = glyphs[i].font_rid; uint32_t gl = glyphs[i].index; + uint16_t gl_fl = glyphs[i].flags; + uint8_t gl_cn = glyphs[i].count; + bool cprev = false; + if (gl_cn == 0) { // Parts of the same cluster, always connected. + cprev = 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; + } + } else { + if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { + cprev = true; + } + } //Apply fx. float faded_visibility = 1.0f; @@ -856,9 +872,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; } font_color.a = faded_visibility; + font_shadow_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool visible = (font_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -874,7 +891,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = true; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; + charfx->glyph_flags = gl_fl; + charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; @@ -884,31 +903,40 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - 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 n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - fx_offset += 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; + if (!cprev) { + 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 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; + } + fx_offset += item_shake->prev_off; } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); - fx_offset += Point2(0, 1) * value; + if (!cprev) { + 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; + } + fx_offset += item_wave->prev_off; } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - 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); - fx_offset += Point2(torn_x, torn_y); + if (!cprev) { + 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); + } + fx_offset += item_tornado->prev_off; } else if (item_fx->type == ITEM_RAINBOW) { ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); @@ -916,27 +944,33 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); - // Draw glyph outlines. for (int j = 0; j < glyphs[i].repeat; j++) { if (visible) { if (frid != RID()) { - if (p_shadow_as_outline) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); + if (font_shadow_color.a > 0) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_shadow_color.a > 0 && p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_color.a != 0.0 && size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } } gloff.x += glyphs[i].advance; } } + Vector2 fbg_line_off = off + p_ofs; + // Draw background color box + Vector2i chr_range = TS->shaped_text_get_range(rid); + _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("font_selected_color"); - Color selection_bg = get_theme_color("selection_color"); + Color selection_fg = get_theme_color(SNAME("font_selected_color")); + Color selection_bg = get_theme_color(SNAME("selection_color")); int sel_start = -1; int sel_end = -1; @@ -960,19 +994,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Color uc = font_color; uc.a *= 0.5; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid); -#ifdef TOOLS_ENABLED - underline_width *= EDSCALE; -#endif + float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); draw_line(p_ofs + Vector2(off.x, off.y + y_off), p_ofs + Vector2(off.x + glyphs[i].advance * glyphs[i].repeat, off.y + y_off), uc, underline_width); } else if (_find_strikethrough(it)) { Color uc = font_color; uc.a *= 0.5; 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); -#ifdef TOOLS_ENABLED - underline_width *= EDSCALE; -#endif + float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); draw_line(p_ofs + Vector2(off.x, off.y + y_off), p_ofs + Vector2(off.x + glyphs[i].advance * glyphs[i].repeat, off.y + y_off), uc, underline_width); } @@ -994,6 +1022,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off); RID frid = glyphs[i].font_rid; uint32_t gl = glyphs[i].index; + uint16_t gl_fl = glyphs[i].flags; + uint8_t gl_cn = glyphs[i].count; + bool cprev = false; + if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected. + cprev = 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; + } + } else { + if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) { + cprev = true; + } + } //Apply fx. float faded_visibility = 1.0f; @@ -1021,7 +1064,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->visibility = visible; charfx->outline = false; charfx->font = frid; - charfx->glpyh_index = gl; + charfx->glyph_index = gl; + charfx->glyph_flags = gl_fl; + charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; @@ -1031,31 +1076,40 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; - gl = charfx->glpyh_index; + gl = charfx->glyph_index; visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); - 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 n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); - n_time = (n_time > 1.0) ? 1.0 : n_time; - fx_offset += 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; + if (!cprev) { + 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 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; + } + fx_offset += item_shake->prev_off; } else if (item_fx->type == ITEM_WAVE) { ItemWave *item_wave = static_cast<ItemWave *>(item_fx); - double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f); - fx_offset += Point2(0, 1) * value; + if (!cprev) { + 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; + } + fx_offset += item_wave->prev_off; } else if (item_fx->type == ITEM_TORNADO) { ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx); - 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); - fx_offset += Point2(torn_x, torn_y); + if (!cprev) { + 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); + } + fx_offset += item_tornado->prev_off; } else if (item_fx->type == ITEM_RAINBOW) { ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx); @@ -1079,6 +1133,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.x += glyphs[i].advance; } } + // Draw foreground color box + _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1); + off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); } @@ -1119,7 +1176,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < main->lines.size()) { _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); - ofs.y += main->lines[from_line].text_buf->get_size().y; + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("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; @@ -1184,8 +1241,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rect.has_point(p_click - p_ofs - off)) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant("table_hseparation"); - int vseparation = get_theme_constant("table_vseparation"); + int hseparation = get_theme_constant(SNAME("table_hseparation")); + int vseparation = get_theme_constant(SNAME("table_vseparation")); ItemTable *table = static_cast<ItemTable *>(it); @@ -1195,8 +1252,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V int col_count = table->columns.size(); int row_count = table->rows.size(); - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); int col = idx % col_count; int row = idx / col_count; @@ -1315,7 +1372,7 @@ void RichTextLabel::_update_scroll() { } } -void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, float p_delta_time) { +void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) { Item *it = p_frame; while (it) { ItemFX *ifx = nullptr; @@ -1354,7 +1411,7 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { if (meta_hovering) { meta_hovering = nullptr; - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; update(); } @@ -1366,8 +1423,8 @@ void RichTextLabel::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { - if (bbcode != "") { - set_bbcode(bbcode); + if (text != "") { + set_text(text); } main->first_invalid_line = 0; //invalidate ALL @@ -1387,11 +1444,11 @@ void RichTextLabel::_notification(int p_what) { Size2 size = get_size(); Rect2 text_rect = _get_text_rect(); - draw_style_box(get_theme_stylebox("normal"), Rect2(Point2(), size)); + draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size)); if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - draw_style_box(get_theme_stylebox("focus"), Rect2(Point2(), size)); + draw_style_box(get_theme_stylebox(SNAME("focus")), Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } @@ -1411,13 +1468,13 @@ void RichTextLabel::_notification(int p_what) { if (from_line >= main->lines.size()) { break; //nothing to draw } - Ref<Font> base_font = get_theme_font("normal_font"); - Color base_color = get_theme_color("default_color"); - Color outline_color = get_theme_color("font_outline_color"); - int outline_size = get_theme_constant("outline_size"); - Color font_shadow_color = get_theme_color("font_shadow_color"); - bool use_outline = get_theme_constant("shadow_as_outline"); - Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); + 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"))); visible_paragraph_count = 0; visible_line_count = 0; @@ -1426,24 +1483,30 @@ void RichTextLabel::_notification(int p_what) { Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < main->lines.size()) { 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, use_outline, shadow_ofs); - ofs.y += main->lines[from_line].text_buf->get_size().y; + 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); + ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); from_line++; } } break; case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { - float dt = get_process_delta_time(); + double dt = get_process_delta_time(); _update_fx(main, dt); update(); } - } + } break; + case NOTIFICATION_FOCUS_EXIT: { + if (deselect_on_focus_loss_enabled) { + selection.active = false; + update(); + } + } break; } } Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const { if (!underline_meta) { - return CURSOR_ARROW; + return get_default_cursor_shape(); } if (selection.click_item) { @@ -1451,11 +1514,11 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const } if (main->first_invalid_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } if (main->first_resized_line < main->lines.size()) { - return CURSOR_ARROW; //invalid + return get_default_cursor_shape(); //invalid } Item *item = nullptr; @@ -1466,10 +1529,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return CURSOR_POINTING_HAND; } - return CURSOR_ARROW; + return get_default_cursor_shape(); } -void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { +void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseButton> b = p_event; @@ -1482,7 +1545,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { return; } - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { if (b->is_pressed() && !b->is_double_click()) { scroll_updated = false; ItemFrame *c_frame = nullptr; @@ -1528,26 +1591,32 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { if (c_frame) { const Line &l = c_frame->lines[c_line]; - Vector<Vector2i> words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid()); - for (int i = 0; i < words.size(); i++) { - if (c_index >= words[i].x && c_index < words[i].y) { + PackedInt32Array words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid()); + for (int i = 0; i < words.size(); i = i + 2) { + if (c_index >= words[i] && c_index < words[i + 1]) { selection.from_frame = c_frame; selection.from_line = c_line; selection.from_item = c_item; - selection.from_char = words[i].x; + selection.from_char = words[i]; selection.to_frame = c_frame; selection.to_line = c_line; selection.to_item = c_item; - selection.to_char = words[i].y; + selection.to_char = words[i + 1]; selection.active = true; + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); + } update(); break; } } } } else if (!b->is_pressed()) { + if (selection.enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); + } selection.click_item = nullptr; if (!b->is_double_click() && !scroll_updated) { @@ -1560,19 +1629,19 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { Variant meta; if (!outside && _find_meta(c_item, &meta)) { //meta clicked - emit_signal("meta_clicked", meta); + emit_signal(SNAME("meta_clicked"), meta); } } } } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); } @@ -1603,11 +1672,11 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { handled = true; } if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - get_theme_font("normal_font")->get_height(get_theme_font_size("normal_font_size"))); + vscroll->set_value(vscroll->get_value() - get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("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("normal_font")->get_height(get_theme_font_size("normal_font_size"))); + vscroll->set_value(vscroll->get_value() + get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); handled = true; } if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { @@ -1665,6 +1734,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { swap = true; } else if (selection.from_char == selection.to_char) { selection.active = false; + update(); return; } } @@ -1685,15 +1755,15 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) { if (meta_hovering != item_meta) { if (meta_hovering) { - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); } meta_hovering = item_meta; current_meta = meta; - emit_signal("meta_hover_started", meta); + emit_signal(SNAME("meta_hover_started"), meta); } } else if (meta_hovering) { meta_hovering = nullptr; - emit_signal("meta_hover_ended", current_meta); + emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; } } @@ -2036,14 +2106,44 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) return false; } +Color RichTextLabel::_find_bgcolor(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_BGCOLOR) { + ItemBGColor *color = static_cast<ItemBGColor *>(item); + return color->color; + } + + item = item->parent; + } + + return Color(0, 0, 0, 0); +} + +Color RichTextLabel::_find_fgcolor(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_FGCOLOR) { + ItemFGColor *color = static_cast<ItemFGColor *>(item); + return color->color; + } + + item = item->parent; + } + + return Color(0, 0, 0, 0); +} + bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { if (from && from != to) { if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) { return true; } - for (List<Item *>::Element *E = from->subitems.front(); E; E = E->next()) { - bool layout = _find_layout_subitem(E->get(), to); + for (Item *E : from->subitems) { + bool layout = _find_layout_subitem(E, to); if (layout) { return true; @@ -2067,8 +2167,8 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { } Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font("normal_font"); - int base_font_size = get_theme_font_size("normal_font_size"); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) { _resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w); @@ -2102,8 +2202,8 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { } Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font("normal_font"); - int base_font_size = get_theme_font_size("normal_font_size"); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); int total_chars = (p_frame->first_invalid_line == 0) ? 0 : (p_frame->lines[p_frame->first_invalid_line].char_offset + p_frame->lines[p_frame->first_invalid_line].char_count); for (int i = p_frame->first_invalid_line; i < p_frame->lines.size(); i++) { @@ -2232,22 +2332,26 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub int size = p_item->subitems.size(); if (size == 0) { p_item->parent->subitems.erase(p_item); + // If a newline was erased, all lines AFTER the newline need to be decremented. if (p_item->type == ITEM_NEWLINE) { - current_frame->lines.remove(p_line); - for (int i = p_subitem_line; i < current->subitems.size(); i++) { - if (current->subitems[i]->line > 0) { + current_frame->lines.remove_at(p_line); + for (int i = 0; i < current->subitems.size(); i++) { + if (current->subitems[i]->line > p_subitem_line) { current->subitems[i]->line--; } } } } else { + // First, remove all child items for the provided item. for (int i = 0; i < size; i++) { _remove_item(p_item->subitems.front()->get(), p_line, p_subitem_line); } + // Then remove the provided item itself. + p_item->parent->subitems.erase(p_item); } } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, VAlign p_align) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlign p_align) { if (current->type == ITEM_TABLE) { return; } @@ -2279,8 +2383,7 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.width = p_image->get_width() * p_height / p_image->get_height(); } else { // keep original width and height - item->size.height = p_image->get_height(); - item->size.width = p_image->get_width(); + item->size = p_image->get_size(); } } @@ -2303,22 +2406,24 @@ bool RichTextLabel::remove_line(const int p_line) { return false; } - int i = 0; - while (i < current->subitems.size() && current->subitems[i]->line < p_line) { - i++; + // Remove all subitems with the same line as that provided. + Vector<int> subitem_indices_to_remove; + for (int i = 0; i < current->subitems.size(); i++) { + if (current->subitems[i]->line == p_line) { + subitem_indices_to_remove.push_back(i); + } } - bool was_newline = false; - while (i < current->subitems.size()) { - was_newline = current->subitems[i]->type == ITEM_NEWLINE; - _remove_item(current->subitems[i], current->subitems[i]->line, p_line); - if (was_newline) { - break; - } + bool had_newline = false; + // Reverse for loop to remove items from the end first. + for (int i = subitem_indices_to_remove.size() - 1; i >= 0; i--) { + int subitem_idx = subitem_indices_to_remove[i]; + had_newline = had_newline || current->subitems[subitem_idx]->type == ITEM_NEWLINE; + _remove_item(current->subitems[subitem_idx], current->subitems[subitem_idx]->line, p_line); } - if (!was_newline) { - current_frame->lines.remove(p_line); + if (!had_newline) { + current_frame->lines.remove_at(p_line); if (current_frame->lines.size() == 0) { current_frame->lines.resize(1); } @@ -2329,6 +2434,7 @@ bool RichTextLabel::remove_line(const int p_line) { } main->first_invalid_line = 0; // p_line ??? + update(); return true; } @@ -2361,35 +2467,35 @@ void RichTextLabel::push_font(const Ref<Font> &p_font) { } void RichTextLabel::push_normal() { - Ref<Font> normal_font = get_theme_font("normal_font"); + Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); ERR_FAIL_COND(normal_font.is_null()); push_font(normal_font); } void RichTextLabel::push_bold() { - Ref<Font> bold_font = get_theme_font("bold_font"); + Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); ERR_FAIL_COND(bold_font.is_null()); push_font(bold_font); } void RichTextLabel::push_bold_italics() { - Ref<Font> bold_italics_font = get_theme_font("bold_italics_font"); + Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); ERR_FAIL_COND(bold_italics_font.is_null()); push_font(bold_italics_font); } void RichTextLabel::push_italics() { - Ref<Font> italics_font = get_theme_font("italics_font"); + Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); ERR_FAIL_COND(italics_font.is_null()); push_font(italics_font); } void RichTextLabel::push_mono() { - Ref<Font> mono_font = get_theme_font("mono_font"); + Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); ERR_FAIL_COND(mono_font.is_null()); push_font(mono_font); @@ -2489,7 +2595,7 @@ void RichTextLabel::push_meta(const Variant &p_meta) { _add_item(item, true); } -void RichTextLabel::push_table(int p_columns, VAlign p_align) { +void RichTextLabel::push_table(int p_columns, InlineAlign p_align) { ERR_FAIL_COND(p_columns < 1); ItemTable *item = memnew(ItemTable); @@ -2539,6 +2645,22 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq _add_item(item, true); } +void RichTextLabel::push_bgcolor(const Color &p_color) { + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemBGColor *item = memnew(ItemBGColor); + + item->color = p_color; + _add_item(item, true); +} + +void RichTextLabel::push_fgcolor(const Color &p_color) { + ERR_FAIL_COND(current->type == ITEM_TABLE); + ItemFGColor *item = memnew(ItemFGColor); + + item->color = p_color; + _add_item(item, true); +} + void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) { ItemCustomFX *item = memnew(ItemCustomFX); item->custom_effect = p_custom_effect; @@ -2613,14 +2735,6 @@ void RichTextLabel::pop() { current = current->parent; } -// Creates a new line without adding an ItemNewline to the previous line. -// Useful when wanting to calling remove_line and add a new line immediately after. -void RichTextLabel::increment_line_count() { - _validate_line_caches(main); - current_frame->lines.resize(current_frame->lines.size() + 1); - _invalidate_current_line(current_frame); -} - void RichTextLabel::clear() { main->_clear_children(); current = main; @@ -2712,22 +2826,22 @@ bool RichTextLabel::is_scroll_following() const { return scroll_follow; } -Error RichTextLabel::parse_bbcode(const String &p_bbcode) { +void RichTextLabel::parse_bbcode(const String &p_bbcode) { clear(); - return append_bbcode(p_bbcode); + append_text(p_bbcode); } -Error RichTextLabel::append_bbcode(const String &p_bbcode) { +void RichTextLabel::append_text(const String &p_bbcode) { int pos = 0; List<String> tag_stack; - Ref<Font> normal_font = get_theme_font("normal_font"); - Ref<Font> bold_font = get_theme_font("bold_font"); - Ref<Font> italics_font = get_theme_font("italics_font"); - Ref<Font> bold_italics_font = get_theme_font("bold_italics_font"); - Ref<Font> mono_font = get_theme_font("mono_font"); + 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("default_color"); + Color base_color = get_theme_color(SNAME("default_color")); int indent_level = 0; @@ -2844,18 +2958,35 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { columns = 1; } - VAlign align = VALIGN_TOP; - if (subtag.size() > 1) { + int align = INLINE_ALIGN_TOP; + if (subtag.size() > 2) { + if (subtag[1] == "top" || subtag[1] == "t") { + align = INLINE_ALIGN_TOP_TO; + } else if (subtag[1] == "center" || subtag[1] == "c") { + align = INLINE_ALIGN_CENTER_TO; + } else if (subtag[1] == "bottom" || subtag[1] == "b") { + align = INLINE_ALIGN_BOTTOM_TO; + } + if (subtag[2] == "top" || subtag[2] == "t") { + align |= INLINE_ALIGN_TO_TOP; + } else if (subtag[2] == "center" || subtag[2] == "c") { + align |= INLINE_ALIGN_TO_CENTER; + } else if (subtag[2] == "baseline" || subtag[2] == "l") { + align |= INLINE_ALIGN_TO_BASELINE; + } else if (subtag[2] == "bottom" || subtag[2] == "b") { + align |= INLINE_ALIGN_TO_BOTTOM; + } + } else if (subtag.size() > 1) { if (subtag[1] == "top" || subtag[1] == "t") { - align = VALIGN_TOP; + align = INLINE_ALIGN_TOP; } else if (subtag[1] == "center" || subtag[1] == "c") { - align = VALIGN_CENTER; + align = INLINE_ALIGN_CENTER; } else if (subtag[1] == "bottom" || subtag[1] == "b") { - align = VALIGN_BOTTOM; + align = INLINE_ALIGN_BOTTOM; } } - push_table(columns, align); + push_table(columns, (InlineAlign)align); pos = brk_end + 1; tag_stack.push_front("table"); } else if (tag == "cell") { @@ -3087,11 +3218,11 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { tag_stack.push_front("url"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - Ref<Font> f = get_theme_font("normal_font"); - int fs = get_theme_font_size("normal_font_size") * 3; - Color color = get_theme_color("default_color"); - Color outline_color = get_theme_color("outline_color"); - int outline_size = get_theme_constant("outline_size"); + Ref<Font> f = get_theme_font(SNAME("normal_font")); + int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; + 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(); for (int i = 0; i < subtag.size(); i++) { @@ -3134,15 +3265,34 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("img")) { - VAlign align = VALIGN_TOP; + int align = INLINE_ALIGN_CENTER; if (tag.begins_with("img=")) { - String al = tag.substr(4, tag.length()); - if (al == "top" || al == "t") { - align = VALIGN_TOP; - } else if (al == "center" || al == "c") { - align = VALIGN_CENTER; - } else if (al == "bottom" || al == "b") { - align = VALIGN_BOTTOM; + Vector<String> subtag = tag.substr(4, tag.length()).split(","); + if (subtag.size() > 1) { + if (subtag[0] == "top" || subtag[0] == "t") { + align = INLINE_ALIGN_TOP_TO; + } else if (subtag[0] == "center" || subtag[0] == "c") { + align = INLINE_ALIGN_CENTER_TO; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + align = INLINE_ALIGN_BOTTOM_TO; + } + if (subtag[1] == "top" || subtag[1] == "t") { + align |= INLINE_ALIGN_TO_TOP; + } else if (subtag[1] == "center" || subtag[1] == "c") { + align |= INLINE_ALIGN_TO_CENTER; + } else if (subtag[1] == "baseline" || subtag[1] == "l") { + align |= INLINE_ALIGN_TO_BASELINE; + } else if (subtag[1] == "bottom" || subtag[1] == "b") { + align |= INLINE_ALIGN_TO_BOTTOM; + } + } else if (subtag.size() > 0) { + if (subtag[0] == "top" || subtag[0] == "t") { + align = INLINE_ALIGN_TOP; + } else if (subtag[0] == "center" || subtag[0] == "c") { + align = INLINE_ALIGN_CENTER; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + align = INLINE_ALIGN_BOTTOM; + } } } @@ -3183,7 +3333,7 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { } } - add_image(texture, width, height, color, align); + add_image(texture, width, height, color, (InlineAlign)align); } pos = end; @@ -3353,6 +3503,23 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("rainbow"); set_process_internal(true); + + } else if (tag.begins_with("bgcolor=")) { + String color_str = tag.substr(8, tag.length()); + Color color = Color::from_string(color_str, base_color); + + push_bgcolor(color); + pos = brk_end + 1; + tag_stack.push_front("bgcolor"); + + } else if (tag.begins_with("fgcolor=")) { + String color_str = tag.substr(8, tag.length()); + Color color = Color::from_string(color_str, base_color); + + push_fgcolor(color); + pos = brk_end + 1; + tag_stack.push_front("fgcolor"); + } else { Vector<String> &expr = split_tag_block; if (expr.size() < 1) { @@ -3360,7 +3527,7 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { pos = brk_pos + 1; } else { String identifier = expr[0]; - expr.remove(0); + expr.remove_at(0); Dictionary properties = parse_expressions_for_values(expr); Ref<RichTextEffect> effect = _get_custom_effect_by_code(identifier); @@ -3378,8 +3545,8 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { } Vector<ItemFX *> fx_items; - for (List<Item *>::Element *E = main->subitems.front(); E; E = E->next()) { - Item *subitem = static_cast<Item *>(E->get()); + for (Item *E : main->subitems) { + Item *subitem = static_cast<Item *>(E); _fetch_item_fx_stack(subitem, fx_items); if (fx_items.size()) { @@ -3387,8 +3554,6 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { break; } } - - return OK; } void RichTextLabel::scroll_to_paragraph(int p_paragraph) { @@ -3453,7 +3618,38 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) { } } -bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, Item *p_from, Item *p_to) { +void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) { + deselect_on_focus_loss_enabled = p_enabled; + if (p_enabled && selection.active && !has_focus()) { + selection.active = false; + update(); + } +} + +bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) { + List<Item *>::Element *E = p_from; + while (E != nullptr) { + ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E->get()); + if (p_reverse_search) { + for (int i = frame->lines.size() - 1; i >= 0; i--) { + if (_search_line(frame, i, p_string, -1, p_reverse_search)) { + return true; + } + } + } else { + for (int i = 0; i < frame->lines.size(); i++) { + if (_search_line(frame, i, p_string, 0, p_reverse_search)) { + return true; + } + } + } + E = p_reverse_search ? E->prev() : E->next(); + } + return false; +} + +bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) { ERR_FAIL_COND_V(p_frame == nullptr, false); ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), false); @@ -3475,24 +3671,23 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int idx = 0; - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); - - for (int i = 0; i < frame->lines.size(); i++) { - if (_search_line(frame, i, p_string, p_from, p_to)) { - return true; - } - } - idx++; + List<Item *>::Element *E = p_reverse_search ? table->subitems.back() : table->subitems.front(); + if (_search_table(table, E, p_string, p_reverse_search)) { + return true; } } break; default: break; } } - int sp = text.findn(p_string, 0); + + int sp = -1; + if (p_reverse_search) { + sp = text.rfindn(p_string, p_char_idx); + } else { + sp = text.findn(p_string, p_char_idx); + } + if (sp != -1) { selection.from_frame = p_frame; selection.from_line = p_line; @@ -3500,8 +3695,8 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p selection.from_char = sp; selection.to_frame = p_frame; selection.to_line = p_line; - selection.to_item = _get_item_at_pos(l.from, it_to, sp + p_string.length() - 1); - selection.to_char = sp + p_string.length() - 1; + selection.to_item = _get_item_at_pos(l.from, it_to, sp + p_string.length()); + selection.to_char = sp + p_string.length(); selection.active = true; return true; } @@ -3512,23 +3707,81 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p_search_previous) { ERR_FAIL_COND_V(!selection.enabled, false); + if (p_string.size() == 0) { + selection.active = false; + return false; + } + + int char_idx = p_search_previous ? -1 : 0; + int current_line = 0; + int ending_line = main->lines.size() - 1; if (p_from_selection && selection.active) { - for (int i = 0; i < main->lines.size(); i++) { - if (_search_line(main, i, p_string, selection.from_item, selection.to_item)) { - update(); - return true; - } + // First check to see if other results exist in current line + 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(); + return true; } - } else { - for (int i = 0; i < main->lines.size(); i++) { - if (_search_line(main, i, p_string, nullptr, nullptr)) { - update(); - return true; + char_idx = p_search_previous ? -1 : 0; + + // Next, check to see if the current search result is in a table + if (selection.from_frame->parent != nullptr && selection.from_frame->parent->type == ITEM_TABLE) { + // Find last search result in table + ItemTable *parent_table = static_cast<ItemTable *>(selection.from_frame->parent); + List<Item *>::Element *parent_element = p_search_previous ? parent_table->subitems.back() : parent_table->subitems.front(); + + while (parent_element->get() != selection.from_frame) { + parent_element = p_search_previous ? parent_element->prev() : parent_element->next(); + ERR_FAIL_COND_V(parent_element == nullptr, false); + } + + // Search remainder of table + if (!(p_search_previous && parent_element == parent_table->subitems.front()) && + parent_element != parent_table->subitems.back()) { + parent_element = p_search_previous ? parent_element->prev() : parent_element->next(); // Don't want to search current item + ERR_FAIL_COND_V(parent_element == nullptr, false); + + // 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(); + return true; + } } } + + ending_line = selection.from_frame->line + selection.from_line; + current_line = p_search_previous ? ending_line - 1 : ending_line + 1; + } else if (p_search_previous) { + current_line = ending_line; + ending_line = 0; } - return false; + // Search remainder of the file + while (current_line != ending_line) { + // Wrap around + if (current_line < 0) { + current_line = main->lines.size() - 1; + } else if (current_line >= main->lines.size()) { + current_line = 0; + } + + if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) { + scroll_to_line(current_line); + update(); + return true; + } + p_search_previous ? current_line-- : current_line++; + } + + if (p_from_selection && selection.active) { + // Check contents of selection + return _search_line(main, current_line, p_string, char_idx, p_search_previous); + } else { + return false; + } } String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const { @@ -3548,45 +3801,32 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } } for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { + if (it->type == ITEM_TABLE) { + ItemTable *table = static_cast<ItemTable *>(it); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); + for (int i = 0; i < frame->lines.size(); i++) { + text += _get_line_text(frame, i, p_selection); + } + } + } if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) { - break; + continue; } if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) { - break; + continue; } - switch (it->type) { - case ITEM_NEWLINE: { - text += "\n"; - } break; - case ITEM_TEXT: { - ItemText *t = (ItemText *)it; - text += t->text; - } break; - case ITEM_IMAGE: { - text += " "; - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - int idx = 0; - int col_count = table->columns.size(); - for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) { - ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E->get()); - int column = idx % col_count; - - for (int i = 0; i < frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); - } - if (column == col_count - 1) { - text += "\n"; - } else { - text += " "; - } - idx++; - } - } break; - default: - break; + if (it->type == ITEM_DROPCAP) { + const ItemDropcap *dc = static_cast<ItemDropcap *>(it); + text += dc->text; + } else if (it->type == ITEM_TEXT) { + const ItemText *t = static_cast<ItemText *>(it); + text += t->text; + } else if (it->type == ITEM_NEWLINE) { + text += "\n"; + } else if (it->type == ITEM_IMAGE) { + text += " "; } } 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)) { @@ -3622,6 +3862,10 @@ bool RichTextLabel::is_selection_enabled() const { return selection.enabled; } +bool RichTextLabel::is_deselect_on_focus_loss_enabled() const { + return deselect_on_focus_loss_enabled; +} + int RichTextLabel::get_selection_from() const { if (!selection.active || !selection.enabled) { return -1; @@ -3638,8 +3882,8 @@ int RichTextLabel::get_selection_to() const { return selection.to_frame->lines[selection.to_line].char_offset + selection.to_char - 1; } -void RichTextLabel::set_bbcode(const String &p_bbcode) { - bbcode = p_bbcode; +void RichTextLabel::set_text(const String &p_bbcode) { + text = p_bbcode; if (is_inside_tree() && use_bbcode) { parse_bbcode(p_bbcode); } else { // raw text @@ -3648,8 +3892,8 @@ void RichTextLabel::set_bbcode(const String &p_bbcode) { } } -String RichTextLabel::get_bbcode() const { - return bbcode; +String RichTextLabel::get_text() const { + return text; } void RichTextLabel::set_use_bbcode(bool p_enable) { @@ -3657,19 +3901,24 @@ void RichTextLabel::set_use_bbcode(bool p_enable) { return; } use_bbcode = p_enable; - set_bbcode(bbcode); notify_property_list_changed(); + set_text(text); } bool RichTextLabel::is_using_bbcode() const { return use_bbcode; } -String RichTextLabel::get_text() { +String RichTextLabel::get_parsed_text() const { String text = ""; Item *it = main; while (it) { - if (it->type == ITEM_TEXT) { + if (it->type == ITEM_DROPCAP) { + const ItemDropcap *dc = (ItemDropcap *)it; + if (dc != nullptr) { + text += dc->text; + } + } else if (it->type == ITEM_TEXT) { ItemText *t = static_cast<ItemText *>(it); text += t->text; } else if (it->type == ITEM_NEWLINE) { @@ -3684,11 +3933,6 @@ String RichTextLabel::get_text() { return text; } -void RichTextLabel::set_text(const String &p_string) { - clear(); - add_text(p_string); -} - void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { @@ -3745,7 +3989,6 @@ void RichTextLabel::set_percent_visible(float p_percent) { if (p_percent < 0 || p_percent >= 1) { visible_characters = -1; percent_visible = 1; - } else { visible_characters = get_total_character_count() * p_percent; percent_visible = p_percent; @@ -3760,24 +4003,15 @@ float RichTextLabel::get_percent_visible() const { return percent_visible; } -void RichTextLabel::set_effects(const Vector<Variant> &effects) { - custom_effects.clear(); - for (int i = 0; i < effects.size(); i++) { - Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]); - custom_effects.push_back(effect); - } - - if ((bbcode != "") && use_bbcode) { - parse_bbcode(bbcode); +void RichTextLabel::set_effects(Array p_effects) { + custom_effects = p_effects; + if ((text != "") && use_bbcode) { + parse_bbcode(text); } } -Vector<Variant> RichTextLabel::get_effects() { - Vector<Variant> r; - for (int i = 0; i < custom_effects.size(); i++) { - r.push_back(custom_effects[i]); - } - return r; +Array RichTextLabel::get_effects() { + return custom_effects; } void RichTextLabel::install_effect(const Variant effect) { @@ -3786,8 +4020,8 @@ void RichTextLabel::install_effect(const Variant effect) { if (rteffect.is_valid()) { custom_effects.push_back(effect); - if ((bbcode != "") && use_bbcode) { - parse_bbcode(bbcode); + if ((text != "") && use_bbcode) { + parse_bbcode(text); } } } @@ -3800,18 +4034,23 @@ int RichTextLabel::get_content_height() const { return total_height; } -void RichTextLabel::_validate_property(PropertyInfo &property) const { - if (!use_bbcode && property.name == "bbcode_text") { - property.usage = PROPERTY_USAGE_NOEDITOR; +#ifndef DISABLE_DEPRECATED +// People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0) +// Altough some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty +bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "bbcode_text" && !((String)p_value).is_empty()) { + set_text(p_value); + return true; } + return false; } +#endif void RichTextLabel::_bind_methods() { - ClassDB::bind_method(D_METHOD("_gui_input"), &RichTextLabel::_gui_input); - ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); + 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(VALIGN_TOP)); + 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_ALIGN_CENTER)); 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"), &RichTextLabel::push_font); @@ -3831,7 +4070,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); ClassDB::bind_method(D_METHOD("push_underline"), &RichTextLabel::push_underline); ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough); - ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(VALIGN_TOP)); + ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGN_TOP)); ClassDB::bind_method(D_METHOD("push_dropcap", "string", "font", "size", "dropcap_margins", "color", "outline_size", "outline_color"), &RichTextLabel::push_dropcap, DEFVAL(Rect2()), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand); ClassDB::bind_method(D_METHOD("set_cell_row_background_color", "odd_row_bg", "even_row_bg"), &RichTextLabel::set_cell_row_background_color); @@ -3839,6 +4078,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_size_override", "min_size", "max_size"), &RichTextLabel::set_cell_size_override); ClassDB::bind_method(D_METHOD("set_cell_padding", "padding"), &RichTextLabel::set_cell_padding); ClassDB::bind_method(D_METHOD("push_cell"), &RichTextLabel::push_cell); + ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor); + ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor); ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop); ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear); @@ -3878,16 +4119,18 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selection_enabled", "enabled"), &RichTextLabel::set_selection_enabled); ClassDB::bind_method(D_METHOD("is_selection_enabled"), &RichTextLabel::is_selection_enabled); + ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &RichTextLabel::set_deselect_on_focus_loss_enabled); + ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &RichTextLabel::is_deselect_on_focus_loss_enabled); + ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from); ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to); ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text); ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode); - ClassDB::bind_method(D_METHOD("append_bbcode", "bbcode"), &RichTextLabel::append_bbcode); + ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text); - ClassDB::bind_method(D_METHOD("set_bbcode", "text"), &RichTextLabel::set_bbcode); - ClassDB::bind_method(D_METHOD("get_bbcode"), &RichTextLabel::get_bbcode); + ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters); ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters); @@ -3914,16 +4157,13 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects); ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect); - ADD_GROUP("BBCode", "bbcode_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "bbcode_text", PROPERTY_HINT_MULTILINE_TEXT), "set_bbcode", "get_bbcode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); 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, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled"); @@ -3933,9 +4173,11 @@ void RichTextLabel::_bind_methods() { 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::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); + 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, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + 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"), "set_language", "get_language"); ADD_GROUP("Structured Text", "structured_text_"); @@ -3977,22 +4219,28 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(ITEM_WAVE); BIND_ENUM_CONSTANT(ITEM_TORNADO); BIND_ENUM_CONSTANT(ITEM_RAINBOW); + BIND_ENUM_CONSTANT(ITEM_BGCOLOR); + BIND_ENUM_CONSTANT(ITEM_FGCOLOR); BIND_ENUM_CONSTANT(ITEM_META); BIND_ENUM_CONSTANT(ITEM_DROPCAP); BIND_ENUM_CONSTANT(ITEM_CUSTOMFX); } void RichTextLabel::set_visible_characters(int p_visible) { - visible_characters = p_visible; - if (p_visible == -1) { - percent_visible = 1; - } else { - int total_char_count = get_total_character_count(); - if (total_char_count > 0) { - percent_visible = (float)p_visible / (float)total_char_count; + if (visible_characters != p_visible) { + visible_characters = p_visible; + if (p_visible == -1) { + percent_visible = 1; + } else { + int total_char_count = get_total_character_count(); + if (total_char_count > 0) { + percent_visible = (float)p_visible / (float)total_char_count; + } } + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + update(); } - update(); } int RichTextLabel::get_visible_characters() const { @@ -4000,9 +4248,19 @@ int RichTextLabel::get_visible_characters() const { } int RichTextLabel::get_total_character_count() const { + // Note: Do not use line buffer "char_count", it includes only visible characters. int tc = 0; - for (int i = 0; i < current_frame->lines.size(); i++) { - tc += current_frame->lines[i].char_count; + Item *it = main; + while (it) { + if (it->type == ITEM_TEXT) { + ItemText *t = static_cast<ItemText *>(it); + tc += t->text.length(); + } else if (it->type == ITEM_NEWLINE) { + tc++; + } else if (it->type == ITEM_IMAGE) { + tc++; + } + it = _get_next_item(it, true); } return tc; @@ -4014,7 +4272,7 @@ void RichTextLabel::set_fixed_size_to_width(int p_width) { } Size2 RichTextLabel::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox("normal"); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); Size2 size = style->get_minimum_size(); if (fixed_width != -1) { @@ -4029,14 +4287,74 @@ Size2 RichTextLabel::get_minimum_size() const { return size; } +void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) { + Vector2i fbg_index = Vector2i(end, start); + Color last_color = Color(0, 0, 0, 0); + bool draw_box = false; + // 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); + Color color = Color(0, 0, 0, 0); + + if (fbg_flag == 0) { + color = _find_bgcolor(it); + } else { + color = _find_fgcolor(it); + } + + bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01)); + bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0)); + bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color)); + + if (change_to_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + } + + if (change_from_color || change_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + draw_box = true; + } + + 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); + RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); + } + fbg_index = Vector2i(end, start); + draw_box = false; + } + + if (change_color) { + fbg_index.x = MIN(i, fbg_index.x); + fbg_index.y = MAX(i, fbg_index.y); + } + + last_color = color; + } + + 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); + RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); + } + } +} + Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) { for (int i = 0; i < custom_effects.size(); i++) { - if (!custom_effects[i].is_valid()) { + Ref<RichTextEffect> effect = custom_effects[i]; + if (!effect.is_valid()) { continue; } - if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) { - return custom_effects[i]; + if (effect->get_bbcode() == p_bbcode_identifier) { + return effect; } } @@ -4113,7 +4431,7 @@ RichTextLabel::RichTextLabel() { current_frame = main; vscroll = memnew(VScrollBar); - add_child(vscroll); + add_child(vscroll, false, INTERNAL_MODE_FRONT); vscroll->set_drag_node(String("..")); vscroll->set_step(1); vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); |