1/**************************************************************************/
2/* rich_text_label.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "rich_text_label.h"
32
33#include "core/input/input_map.h"
34#include "core/math/math_defs.h"
35#include "core/os/keyboard.h"
36#include "core/os/os.h"
37#include "core/string/translation.h"
38#include "scene/gui/label.h"
39#include "scene/resources/atlas_texture.h"
40#include "scene/scene_string_names.h"
41#include "scene/theme/theme_db.h"
42#include "servers/display_server.h"
43
44#include "modules/modules_enabled.gen.h" // For regex.
45#ifdef MODULE_REGEX_ENABLED
46#include "modules/regex/regex.h"
47#endif
48
49RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) const {
50 if (p_free) {
51 if (p_item->subitems.size()) {
52 return p_item->subitems.front()->get();
53 } else if (!p_item->parent) {
54 return nullptr;
55 } else if (p_item->E->next()) {
56 return p_item->E->next()->get();
57 } else {
58 // Go up until something with a next is found.
59 while (p_item->parent && !p_item->E->next()) {
60 p_item = p_item->parent;
61 }
62
63 if (p_item->parent) {
64 return p_item->E->next()->get();
65 } else {
66 return nullptr;
67 }
68 }
69
70 } else {
71 if (p_item->subitems.size() && p_item->type != ITEM_TABLE) {
72 return p_item->subitems.front()->get();
73 } else if (p_item->type == ITEM_FRAME) {
74 return nullptr;
75 } else if (p_item->E->next()) {
76 return p_item->E->next()->get();
77 } else {
78 // Go up until something with a next is found.
79 while (p_item->type != ITEM_FRAME && !p_item->E->next()) {
80 p_item = p_item->parent;
81 }
82
83 if (p_item->type != ITEM_FRAME) {
84 return p_item->E->next()->get();
85 } else {
86 return nullptr;
87 }
88 }
89 }
90}
91
92RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) const {
93 if (p_free) {
94 if (p_item->subitems.size()) {
95 return p_item->subitems.back()->get();
96 } else if (!p_item->parent) {
97 return nullptr;
98 } else if (p_item->E->prev()) {
99 return p_item->E->prev()->get();
100 } else {
101 // Go back until something with a prev is found.
102 while (p_item->parent && !p_item->E->prev()) {
103 p_item = p_item->parent;
104 }
105
106 if (p_item->parent) {
107 return p_item->E->prev()->get();
108 } else {
109 return nullptr;
110 }
111 }
112
113 } else {
114 if (p_item->subitems.size() && p_item->type != ITEM_TABLE) {
115 return p_item->subitems.back()->get();
116 } else if (p_item->type == ITEM_FRAME) {
117 return nullptr;
118 } else if (p_item->E->prev()) {
119 return p_item->E->prev()->get();
120 } else {
121 // Go back until something with a prev is found.
122 while (p_item->type != ITEM_FRAME && !p_item->E->prev()) {
123 p_item = p_item->parent;
124 }
125
126 if (p_item->type != ITEM_FRAME) {
127 return p_item->E->prev()->get();
128 } else {
129 return nullptr;
130 }
131 }
132 }
133}
134
135Rect2 RichTextLabel::_get_text_rect() {
136 return Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size());
137}
138
139RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item_from, RichTextLabel::Item *p_item_to, int p_position) {
140 int offset = 0;
141 for (Item *it = p_item_from; it && it != p_item_to; it = _get_next_item(it)) {
142 switch (it->type) {
143 case ITEM_TEXT: {
144 ItemText *t = static_cast<ItemText *>(it);
145 offset += t->text.length();
146 if (offset > p_position) {
147 return it;
148 }
149 } break;
150 case ITEM_NEWLINE: {
151 offset += 1;
152 if (offset == p_position) {
153 return it;
154 }
155 } break;
156 case ITEM_IMAGE: {
157 offset += 1;
158 if (offset > p_position) {
159 return it;
160 }
161 } break;
162 case ITEM_TABLE: {
163 offset += 1;
164 } break;
165 default:
166 break;
167 }
168 }
169 return p_item_from;
170}
171
172String RichTextLabel::_roman(int p_num, bool p_capitalize) const {
173 if (p_num > 3999) {
174 return "ERR";
175 };
176 String s;
177 if (p_capitalize) {
178 const String roman_M[] = { "", "M", "MM", "MMM" };
179 const String roman_C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
180 const String roman_X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
181 const String roman_I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
182 s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10];
183 } else {
184 const String roman_M[] = { "", "m", "mm", "mmm" };
185 const String roman_C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
186 const String roman_X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
187 const String roman_I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
188 s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10];
189 }
190 return s;
191}
192
193String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
194 int64_t n = p_num;
195
196 int chars = 0;
197 do {
198 n /= 24;
199 chars++;
200 } while (n);
201
202 String s;
203 s.resize(chars + 1);
204 char32_t *c = s.ptrw();
205 c[chars] = 0;
206 n = p_num;
207 do {
208 int mod = ABS(n % 24);
209 char a = (p_capitalize ? 'A' : 'a');
210 c[--chars] = a + mod - 1;
211
212 n /= 24;
213 } while (n);
214
215 return s;
216}
217
218void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
219 ERR_FAIL_NULL(p_frame);
220 ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
221
222 Line &l = p_frame->lines[p_line];
223 MutexLock lock(l.text_buf->get_mutex());
224
225 RID t = l.text_buf->get_rid();
226 int spans = TS->shaped_get_span_count(t);
227 for (int i = 0; i < spans; i++) {
228 ItemText *it = reinterpret_cast<ItemText *>((uint64_t)TS->shaped_get_span_meta(t, i));
229 if (it) {
230 Ref<Font> font = p_base_font;
231 int font_size = p_base_font_size;
232
233 ItemFont *font_it = _find_font(it);
234 if (font_it) {
235 if (font_it->font.is_valid()) {
236 font = font_it->font;
237 }
238 if (font_it->font_size > 0) {
239 font_size = font_it->font_size;
240 }
241 }
242 ItemFontSize *font_size_it = _find_font_size(it);
243 if (font_size_it && font_size_it->font_size > 0) {
244 font_size = font_size_it->font_size;
245 }
246 TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
247 }
248 }
249
250 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
251 for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
252 switch (it->type) {
253 case ITEM_TABLE: {
254 ItemTable *table = static_cast<ItemTable *>(it);
255 for (Item *E : table->subitems) {
256 ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
257 ItemFrame *frame = static_cast<ItemFrame *>(E);
258 for (int i = 0; i < (int)frame->lines.size(); i++) {
259 _update_line_font(frame, i, p_base_font, p_base_font_size);
260 }
261 }
262 } break;
263 default:
264 break;
265 }
266 }
267}
268
269float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h) {
270 ERR_FAIL_NULL_V(p_frame, p_h);
271 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
272
273 Line &l = p_frame->lines[p_line];
274 MutexLock lock(l.text_buf->get_mutex());
275
276 l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
277 l.text_buf->set_width(p_width - l.offset.x);
278
279 PackedFloat32Array tab_stops = _find_tab_stops(l.from);
280 if (!tab_stops.is_empty()) {
281 l.text_buf->tab_align(tab_stops);
282 } else if (tab_size > 0) { // Align inline tabs.
283 Vector<float> tabs;
284 tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width);
285 l.text_buf->tab_align(tabs);
286 }
287
288 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
289 for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
290 switch (it->type) {
291 case ITEM_TABLE: {
292 ItemTable *table = static_cast<ItemTable *>(it);
293 int col_count = table->columns.size();
294
295 for (int i = 0; i < col_count; i++) {
296 table->columns[i].width = 0;
297 }
298
299 int idx = 0;
300 for (Item *E : table->subitems) {
301 ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
302 ItemFrame *frame = static_cast<ItemFrame *>(E);
303 float prev_h = 0;
304 for (int i = 0; i < (int)frame->lines.size(); i++) {
305 MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
306 int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
307 prev_h = _resize_line(frame, i, p_base_font, p_base_font_size, w, prev_h);
308 }
309 idx++;
310 }
311
312 // Compute minimum width for each cell.
313 const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1);
314
315 // Compute available width and total ratio (for expanders).
316 int total_ratio = 0;
317 int remaining_width = available_width;
318 table->total_width = theme_cache.table_h_separation;
319
320 for (int i = 0; i < col_count; i++) {
321 remaining_width -= table->columns[i].min_width;
322 if (table->columns[i].max_width > table->columns[i].min_width) {
323 table->columns[i].expand = true;
324 }
325 if (table->columns[i].expand) {
326 total_ratio += table->columns[i].expand_ratio;
327 }
328 }
329
330 // Assign actual widths.
331 for (int i = 0; i < col_count; i++) {
332 table->columns[i].width = table->columns[i].min_width;
333 if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
334 table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
335 }
336 if (i != col_count - 1) {
337 table->total_width += table->columns[i].width + theme_cache.table_h_separation;
338 } else {
339 table->total_width += table->columns[i].width;
340 }
341 }
342
343 // Resize to max_width if needed and distribute the remaining space.
344 bool table_need_fit = true;
345 while (table_need_fit) {
346 table_need_fit = false;
347 // Fit slim.
348 for (int i = 0; i < col_count; i++) {
349 if (!table->columns[i].expand) {
350 continue;
351 }
352 int dif = table->columns[i].width - table->columns[i].max_width;
353 if (dif > 0) {
354 table_need_fit = true;
355 table->columns[i].width = table->columns[i].max_width;
356 table->total_width -= dif;
357 total_ratio -= table->columns[i].expand_ratio;
358 }
359 }
360 // Grow.
361 remaining_width = available_width - table->total_width;
362 if (remaining_width > 0 && total_ratio > 0) {
363 for (int i = 0; i < col_count; i++) {
364 if (table->columns[i].expand) {
365 int dif = table->columns[i].max_width - table->columns[i].width;
366 if (dif > 0) {
367 int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
368 int incr = MIN(dif, slice);
369 table->columns[i].width += incr;
370 table->total_width += incr;
371 }
372 }
373 }
374 }
375 }
376
377 // Update line width and get total height.
378 idx = 0;
379 table->total_height = 0;
380 table->rows.clear();
381 table->rows_baseline.clear();
382
383 Vector2 offset;
384 float row_height = 0.0;
385
386 for (Item *E : table->subitems) {
387 ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
388 ItemFrame *frame = static_cast<ItemFrame *>(E);
389
390 int column = idx % col_count;
391
392 offset.x += frame->padding.position.x;
393 float yofs = frame->padding.position.y;
394 float prev_h = 0;
395 float row_baseline = 0.0;
396 for (int i = 0; i < (int)frame->lines.size(); i++) {
397 MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
398 frame->lines[i].text_buf->set_width(table->columns[column].width);
399 table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
400
401 frame->lines[i].offset.y = prev_h;
402
403 float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation;
404 if (i > 0) {
405 h += theme_cache.line_separation;
406 }
407 if (frame->min_size_over.y > 0) {
408 h = MAX(h, frame->min_size_over.y);
409 }
410 if (frame->max_size_over.y > 0) {
411 h = MIN(h, frame->max_size_over.y);
412 }
413 yofs += h;
414 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;
415
416 frame->lines[i].offset += offset;
417 row_baseline = MAX(row_baseline, frame->lines[i].text_buf->get_line_ascent(frame->lines[i].text_buf->get_line_count() - 1));
418 }
419 yofs += frame->padding.size.y;
420 offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x;
421
422 row_height = MAX(yofs, row_height);
423 if (column == col_count - 1) {
424 offset.x = 0;
425 row_height += theme_cache.table_v_separation;
426 table->total_height += row_height;
427 offset.y += row_height;
428 table->rows.push_back(row_height);
429 table->rows_baseline.push_back(table->total_height - row_height + row_baseline);
430 row_height = 0;
431 }
432 idx++;
433 }
434 int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
435 if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) {
436 l.text_buf->resize_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
437 } else {
438 l.text_buf->resize_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align);
439 }
440 } break;
441 default:
442 break;
443 }
444 }
445
446 l.offset.y = p_h;
447 return _calculate_line_vertical_offset(l);
448}
449
450float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset) {
451 ERR_FAIL_NULL_V(p_frame, p_h);
452 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
453
454 Line &l = p_frame->lines[p_line];
455 MutexLock lock(l.text_buf->get_mutex());
456
457 BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY;
458 switch (autowrap_mode) {
459 case TextServer::AUTOWRAP_WORD_SMART:
460 autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY;
461 break;
462 case TextServer::AUTOWRAP_WORD:
463 autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
464 break;
465 case TextServer::AUTOWRAP_ARBITRARY:
466 autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
467 break;
468 case TextServer::AUTOWRAP_OFF:
469 break;
470 }
471 autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
472
473 // Clear cache.
474 l.text_buf->clear();
475 l.text_buf->set_break_flags(autowrap_flags);
476 l.text_buf->set_justification_flags(_find_jst_flags(l.from));
477 l.char_offset = *r_char_offset;
478 l.char_count = 0;
479
480 // Add indent.
481 l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
482 l.text_buf->set_width(p_width - l.offset.x);
483 l.text_buf->set_alignment(_find_alignment(l.from));
484 l.text_buf->set_direction(_find_direction(l.from));
485
486 PackedFloat32Array tab_stops = _find_tab_stops(l.from);
487 if (!tab_stops.is_empty()) {
488 l.text_buf->tab_align(tab_stops);
489 } else if (tab_size > 0) { // Align inline tabs.
490 Vector<float> tabs;
491 tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width);
492 l.text_buf->tab_align(tabs);
493 }
494
495 // Shape current paragraph.
496 String txt;
497 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
498 int remaining_characters = visible_characters - l.char_offset;
499 for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
500 if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) {
501 break;
502 }
503 switch (it->type) {
504 case ITEM_DROPCAP: {
505 // Add dropcap.
506 const ItemDropcap *dc = static_cast<ItemDropcap *>(it);
507 l.text_buf->set_dropcap(dc->text, dc->font, dc->font_size, dc->dropcap_margins);
508 l.dc_color = dc->color;
509 l.dc_ol_size = dc->ol_size;
510 l.dc_ol_color = dc->ol_color;
511 } break;
512 case ITEM_NEWLINE: {
513 Ref<Font> font = p_base_font;
514 int font_size = p_base_font_size;
515
516 ItemFont *font_it = _find_font(it);
517 if (font_it) {
518 if (font_it->font.is_valid()) {
519 font = font_it->font;
520 }
521 if (font_it->font_size > 0) {
522 font_size = font_it->font_size;
523 }
524 }
525 ItemFontSize *font_size_it = _find_font_size(it);
526 if (font_size_it && font_size_it->font_size > 0) {
527 font_size = font_size_it->font_size;
528 }
529 l.text_buf->add_string("\n", font, font_size);
530 txt += "\n";
531 l.char_count++;
532 remaining_characters--;
533 } break;
534 case ITEM_TEXT: {
535 ItemText *t = static_cast<ItemText *>(it);
536 Ref<Font> font = p_base_font;
537 int font_size = p_base_font_size;
538
539 ItemFont *font_it = _find_font(it);
540 if (font_it) {
541 if (font_it->font.is_valid()) {
542 font = font_it->font;
543 }
544 if (font_it->font_size > 0) {
545 font_size = font_it->font_size;
546 }
547 }
548 ItemFontSize *font_size_it = _find_font_size(it);
549 if (font_size_it && font_size_it->font_size > 0) {
550 font_size = font_size_it->font_size;
551 }
552 String lang = _find_language(it);
553 String tx = t->text;
554 if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) {
555 tx = tx.substr(0, remaining_characters);
556 }
557 remaining_characters -= tx.length();
558
559 l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it);
560 txt += tx;
561 l.char_count += tx.length();
562 } break;
563 case ITEM_IMAGE: {
564 ItemImage *img = static_cast<ItemImage *>(it);
565 l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1);
566 txt += String::chr(0xfffc);
567 l.char_count++;
568 remaining_characters--;
569 } break;
570 case ITEM_TABLE: {
571 ItemTable *table = static_cast<ItemTable *>(it);
572 int col_count = table->columns.size();
573 int t_char_count = 0;
574 // Set minimums to zero.
575 for (int i = 0; i < col_count; i++) {
576 table->columns[i].min_width = 0;
577 table->columns[i].max_width = 0;
578 table->columns[i].width = 0;
579 }
580 // Compute minimum width for each cell.
581 const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1);
582
583 int idx = 0;
584 for (Item *E : table->subitems) {
585 ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
586 ItemFrame *frame = static_cast<ItemFrame *>(E);
587
588 int column = idx % col_count;
589 float prev_h = 0;
590 for (int i = 0; i < (int)frame->lines.size(); i++) {
591 MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
592
593 int char_offset = l.char_offset + l.char_count;
594 int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
595 prev_h = _shape_line(frame, i, p_base_font, p_base_font_size, w, prev_h, &char_offset);
596 int cell_ch = (char_offset - (l.char_offset + l.char_count));
597 l.char_count += cell_ch;
598 t_char_count += cell_ch;
599 remaining_characters -= cell_ch;
600
601 table->columns[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
602 table->columns[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x));
603 }
604 idx++;
605 }
606
607 // Compute available width and total ratio (for expanders).
608 int total_ratio = 0;
609 int remaining_width = available_width;
610 table->total_width = theme_cache.table_h_separation;
611
612 for (int i = 0; i < col_count; i++) {
613 remaining_width -= table->columns[i].min_width;
614 if (table->columns[i].max_width > table->columns[i].min_width) {
615 table->columns[i].expand = true;
616 }
617 if (table->columns[i].expand) {
618 total_ratio += table->columns[i].expand_ratio;
619 }
620 }
621
622 // Assign actual widths.
623 for (int i = 0; i < col_count; i++) {
624 table->columns[i].width = table->columns[i].min_width;
625 if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
626 table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
627 }
628 if (i != col_count - 1) {
629 table->total_width += table->columns[i].width + theme_cache.table_h_separation;
630 } else {
631 table->total_width += table->columns[i].width;
632 }
633 }
634
635 // Resize to max_width if needed and distribute the remaining space.
636 bool table_need_fit = true;
637 while (table_need_fit) {
638 table_need_fit = false;
639 // Fit slim.
640 for (int i = 0; i < col_count; i++) {
641 if (!table->columns[i].expand) {
642 continue;
643 }
644 int dif = table->columns[i].width - table->columns[i].max_width;
645 if (dif > 0) {
646 table_need_fit = true;
647 table->columns[i].width = table->columns[i].max_width;
648 table->total_width -= dif;
649 total_ratio -= table->columns[i].expand_ratio;
650 }
651 }
652 // Grow.
653 remaining_width = available_width - table->total_width;
654 if (remaining_width > 0 && total_ratio > 0) {
655 for (int i = 0; i < col_count; i++) {
656 if (table->columns[i].expand) {
657 int dif = table->columns[i].max_width - table->columns[i].width;
658 if (dif > 0) {
659 int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
660 int incr = MIN(dif, slice);
661 table->columns[i].width += incr;
662 table->total_width += incr;
663 }
664 }
665 }
666 }
667 }
668
669 // Update line width and get total height.
670 idx = 0;
671 table->total_height = 0;
672 table->rows.clear();
673 table->rows_baseline.clear();
674
675 Vector2 offset;
676 float row_height = 0.0;
677
678 for (const List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) {
679 ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
680 ItemFrame *frame = static_cast<ItemFrame *>(E->get());
681
682 int column = idx % col_count;
683
684 offset.x += frame->padding.position.x;
685 float yofs = frame->padding.position.y;
686 float prev_h = 0;
687 float row_baseline = 0.0;
688 for (int i = 0; i < (int)frame->lines.size(); i++) {
689 MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
690
691 frame->lines[i].text_buf->set_width(table->columns[column].width);
692 table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
693
694 frame->lines[i].offset.y = prev_h;
695
696 float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation;
697 if (i > 0) {
698 h += theme_cache.line_separation;
699 }
700 if (frame->min_size_over.y > 0) {
701 h = MAX(h, frame->min_size_over.y);
702 }
703 if (frame->max_size_over.y > 0) {
704 h = MIN(h, frame->max_size_over.y);
705 }
706 yofs += h;
707 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;
708
709 frame->lines[i].offset += offset;
710 row_baseline = MAX(row_baseline, frame->lines[i].text_buf->get_line_ascent(frame->lines[i].text_buf->get_line_count() - 1));
711 }
712 yofs += frame->padding.size.y;
713 offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x;
714
715 row_height = MAX(yofs, row_height);
716 // Add row height after last column of the row or last cell of the table.
717 if (column == col_count - 1 || E->next() == nullptr) {
718 offset.x = 0;
719 row_height += theme_cache.table_v_separation;
720 table->total_height += row_height;
721 offset.y += row_height;
722 table->rows.push_back(row_height);
723 table->rows_baseline.push_back(table->total_height - row_height + row_baseline);
724 row_height = 0;
725 }
726 idx++;
727 }
728 int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
729 if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) {
730 l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count, Math::round(table->rows_baseline[row_idx]));
731 } else {
732 l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count);
733 }
734 txt += String::chr(0xfffc).repeat(t_char_count);
735 } break;
736 default:
737 break;
738 }
739 }
740
741 // Apply BiDi override.
742 l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt));
743
744 *r_char_offset = l.char_offset + l.char_count;
745
746 l.offset.y = p_h;
747 return _calculate_line_vertical_offset(l);
748}
749
750int 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, int &r_processed_glyphs) {
751 ERR_FAIL_NULL_V(p_frame, 0);
752 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0);
753
754 Vector2 off;
755
756 Line &l = p_frame->lines[p_line];
757 MutexLock lock(l.text_buf->get_mutex());
758
759 Item *it_from = l.from;
760 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
761
762 if (it_from == nullptr) {
763 return 0;
764 }
765
766 RID ci = get_canvas_item();
767 bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
768 bool lrtl = is_layout_rtl();
769
770 bool trim_chars = (visible_characters >= 0) && (visible_chars_behavior == TextServer::VC_CHARS_AFTER_SHAPING);
771 bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !lrtl));
772 bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && lrtl));
773 int total_glyphs = (trim_glyphs_ltr || trim_glyphs_rtl) ? get_total_glyph_count() : 0;
774 int visible_glyphs = total_glyphs * visible_ratio;
775
776 Vector<int> list_index;
777 Vector<ItemList *> list_items;
778 _find_list(l.from, list_index, list_items);
779
780 String prefix;
781 for (int i = 0; i < list_index.size(); i++) {
782 if (rtl) {
783 prefix = prefix + ".";
784 } else {
785 prefix = "." + prefix;
786 }
787 String segment;
788 if (list_items[i]->list_type == LIST_DOTS) {
789 prefix = list_items[i]->bullet;
790 break;
791 } else if (list_items[i]->list_type == LIST_NUMBERS) {
792 segment = itos(list_index[i]);
793 if (is_localizing_numeral_system()) {
794 segment = TS->format_number(segment, _find_language(l.from));
795 }
796 } else if (list_items[i]->list_type == LIST_LETTERS) {
797 segment = _letters(list_index[i], list_items[i]->capitalize);
798 } else if (list_items[i]->list_type == LIST_ROMAN) {
799 segment = _roman(list_index[i], list_items[i]->capitalize);
800 }
801 if (rtl) {
802 prefix = prefix + segment;
803 } else {
804 prefix = segment + prefix;
805 }
806 }
807 if (!prefix.is_empty()) {
808 Ref<Font> font = theme_cache.normal_font;
809 int font_size = theme_cache.normal_font_size;
810
811 ItemFont *font_it = _find_font(l.from);
812 if (font_it) {
813 if (font_it->font.is_valid()) {
814 font = font_it->font;
815 }
816 if (font_it->font_size > 0) {
817 font_size = font_it->font_size;
818 }
819 }
820 ItemFontSize *font_size_it = _find_font_size(l.from);
821 if (font_size_it && font_size_it->font_size > 0) {
822 font_size = font_size_it->font_size;
823 }
824 if (rtl) {
825 float offx = 0.0f;
826 if (!lrtl && p_frame == main) { // Skip Scrollbar.
827 offx -= scroll_w;
828 }
829 font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
830 } else {
831 float offx = 0.0f;
832 if (lrtl && p_frame == main) { // Skip Scrollbar.
833 offx += scroll_w;
834 }
835 font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
836 }
837 }
838
839 // Draw dropcap.
840 int dc_lines = l.text_buf->get_dropcap_lines();
841 float h_off = l.text_buf->get_dropcap_size().x;
842 if (l.dc_ol_size > 0) {
843 l.text_buf->draw_dropcap_outline(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_ol_size, l.dc_ol_color);
844 }
845 l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color);
846
847 int line_count = 0;
848 Size2 ctrl_size = get_size();
849 // Draw text.
850 for (int line = 0; line < l.text_buf->get_line_count(); line++) {
851 if (line > 0) {
852 off.y += theme_cache.line_separation;
853 }
854
855 if (p_ofs.y + off.y >= ctrl_size.height) {
856 break;
857 }
858
859 const Size2 line_size = l.text_buf->get_line_size(line);
860 if (p_ofs.y + off.y + line_size.y <= 0) {
861 off.y += line_size.y;
862 continue;
863 }
864
865 float width = l.text_buf->get_width();
866 float length = line_size.x;
867
868 // Draw line.
869 line_count++;
870
871 if (rtl) {
872 off.x = p_width - l.offset.x - width;
873 if (!lrtl && p_frame == main) { // Skip Scrollbar.
874 off.x -= scroll_w;
875 }
876 } else {
877 off.x = l.offset.x;
878 if (lrtl && p_frame == main) { // Skip Scrollbar.
879 off.x += scroll_w;
880 }
881 }
882
883 // Draw text.
884 switch (l.text_buf->get_alignment()) {
885 case HORIZONTAL_ALIGNMENT_FILL:
886 case HORIZONTAL_ALIGNMENT_LEFT: {
887 if (rtl) {
888 off.x += width - length;
889 }
890 } break;
891 case HORIZONTAL_ALIGNMENT_CENTER: {
892 off.x += Math::floor((width - length) / 2.0);
893 } break;
894 case HORIZONTAL_ALIGNMENT_RIGHT: {
895 if (!rtl) {
896 off.x += width - length;
897 }
898 } break;
899 }
900
901 if (line <= dc_lines) {
902 if (rtl) {
903 off.x -= h_off;
904 } else {
905 off.x += h_off;
906 }
907 }
908
909 RID rid = l.text_buf->get_line_rid(line);
910 //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS
911
912 off.y += TS->shaped_text_get_ascent(rid);
913 // Draw inlined objects.
914 Array objects = TS->shaped_text_get_objects(rid);
915 for (int i = 0; i < objects.size(); i++) {
916 Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
917 if (it != nullptr) {
918 Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
919 //draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS
920 switch (it->type) {
921 case ITEM_IMAGE: {
922 ItemImage *img = static_cast<ItemImage *>(it);
923 img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color);
924 } break;
925 case ITEM_TABLE: {
926 ItemTable *table = static_cast<ItemTable *>(it);
927 Color odd_row_bg = theme_cache.table_odd_row_bg;
928 Color even_row_bg = theme_cache.table_even_row_bg;
929 Color border = theme_cache.table_border;
930 int h_separation = theme_cache.table_h_separation;
931
932 int col_count = table->columns.size();
933 int row_count = table->rows.size();
934
935 int idx = 0;
936 for (Item *E : table->subitems) {
937 ItemFrame *frame = static_cast<ItemFrame *>(E);
938
939 int col = idx % col_count;
940 int row = idx / col_count;
941
942 if (frame->lines.size() != 0 && row < row_count) {
943 Vector2 coff = frame->lines[0].offset;
944 if (rtl) {
945 coff.x = rect.size.width - table->columns[col].width - coff.x;
946 }
947 if (row % 2 == 0) {
948 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);
949 } else {
950 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);
951 }
952 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);
953 }
954
955 for (int j = 0; j < (int)frame->lines.size(); j++) {
956 _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, r_processed_glyphs);
957 }
958 idx++;
959 }
960 } break;
961 default:
962 break;
963 }
964 }
965 }
966
967 const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
968 int gl_size = TS->shaped_text_get_glyph_count(rid);
969
970 Vector2 gloff = off;
971 // Draw outlines and shadow.
972 int processed_glyphs_ol = r_processed_glyphs;
973 for (int i = 0; i < gl_size; i++) {
974 Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
975 int size = _find_outline_size(it, p_outline_size);
976 Color font_color = _find_color(it, p_base_color);
977 Color font_outline_color = _find_outline_color(it, p_outline_color);
978 Color font_shadow_color = p_font_shadow_color;
979 if ((size <= 0 || font_outline_color.a == 0) && (font_shadow_color.a == 0)) {
980 gloff.x += glyphs[i].advance;
981 continue;
982 }
983
984 // Get FX.
985 ItemFade *fade = nullptr;
986 Item *fade_item = it;
987 while (fade_item) {
988 if (fade_item->type == ITEM_FADE) {
989 fade = static_cast<ItemFade *>(fade_item);
990 break;
991 }
992 fade_item = fade_item->parent;
993 }
994
995 Vector<ItemFX *> fx_stack;
996 _fetch_item_fx_stack(it, fx_stack);
997 bool custom_fx_ok = true;
998
999 Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
1000 RID frid = glyphs[i].font_rid;
1001 uint32_t gl = glyphs[i].index;
1002 uint16_t gl_fl = glyphs[i].flags;
1003 uint8_t gl_cn = glyphs[i].count;
1004 bool cprev_cluster = false;
1005 bool cprev_conn = false;
1006 if (gl_cn == 0) { // Parts of the same cluster, always connected.
1007 cprev_cluster = true;
1008 }
1009 if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
1010 if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
1011 cprev_conn = true;
1012 }
1013 } else {
1014 if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
1015 cprev_conn = true;
1016 }
1017 }
1018
1019 //Apply fx.
1020 if (fade) {
1021 float faded_visibility = 1.0f;
1022 if (glyphs[i].start >= fade->starting_index) {
1023 faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
1024 faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
1025 }
1026 font_outline_color.a = faded_visibility;
1027 font_shadow_color.a = faded_visibility;
1028 }
1029
1030 bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0);
1031
1032 for (int j = 0; j < fx_stack.size(); j++) {
1033 ItemFX *item_fx = fx_stack[j];
1034 bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
1035
1036 if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
1037 ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
1038
1039 Ref<CharFXTransform> charfx = item_custom->char_fx_transform;
1040 Ref<RichTextEffect> custom_effect = item_custom->custom_effect;
1041
1042 if (!custom_effect.is_null()) {
1043 charfx->elapsed_time = item_custom->elapsed_time;
1044 charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
1045 charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
1046 charfx->visibility = txt_visible;
1047 charfx->outline = true;
1048 charfx->font = frid;
1049 charfx->glyph_index = gl;
1050 charfx->glyph_flags = gl_fl;
1051 charfx->glyph_count = gl_cn;
1052 charfx->offset = fx_offset;
1053 charfx->color = font_color;
1054
1055 bool effect_status = custom_effect->_process_effect_impl(charfx);
1056 custom_fx_ok = effect_status;
1057
1058 fx_offset += charfx->offset;
1059 font_color = charfx->color;
1060 frid = charfx->font;
1061 gl = charfx->glyph_index;
1062 txt_visible &= charfx->visibility;
1063 }
1064 } else if (item_fx->type == ITEM_SHAKE) {
1065 ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
1066
1067 if (!cn) {
1068 uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
1069 uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
1070 uint64_t max_rand = 2147483647;
1071 double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
1072 double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
1073 double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
1074 n_time = (n_time > 1.0) ? 1.0 : n_time;
1075 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;
1076 }
1077 fx_offset += item_shake->prev_off;
1078 } else if (item_fx->type == ITEM_WAVE) {
1079 ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
1080
1081 if (!cn) {
1082 double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f);
1083 item_wave->prev_off = Point2(0, 1) * value;
1084 }
1085 fx_offset += item_wave->prev_off;
1086 } else if (item_fx->type == ITEM_TORNADO) {
1087 ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
1088
1089 if (!cn) {
1090 double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
1091 double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
1092 item_tornado->prev_off = Point2(torn_x, torn_y);
1093 }
1094 fx_offset += item_tornado->prev_off;
1095 } else if (item_fx->type == ITEM_RAINBOW) {
1096 ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
1097
1098 font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + gloff.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a);
1099 } else if (item_fx->type == ITEM_PULSE) {
1100 ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx);
1101
1102 const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease));
1103 font_color = font_color.lerp(font_color * item_pulse->color, sined_time);
1104 }
1105 }
1106
1107 if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
1108 fx_offset = fx_offset.round();
1109 }
1110
1111 // Draw glyph outlines.
1112 const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a);
1113 const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a);
1114 for (int j = 0; j < glyphs[i].repeat; j++) {
1115 if (txt_visible) {
1116 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));
1117 if (!skip && frid != RID()) {
1118 if (modulated_shadow_color.a > 0) {
1119 TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, modulated_shadow_color);
1120 }
1121 if (modulated_shadow_color.a > 0 && p_shadow_outline_size > 0) {
1122 TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, modulated_shadow_color);
1123 }
1124 if (modulated_outline_color.a != 0.0 && size > 0) {
1125 TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, modulated_outline_color);
1126 }
1127 }
1128 processed_glyphs_ol++;
1129 }
1130 gloff.x += glyphs[i].advance;
1131 }
1132 }
1133
1134 Vector2 fbg_line_off = off + p_ofs;
1135 // Draw background color box
1136 Vector2i chr_range = TS->shaped_text_get_range(rid);
1137 _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0);
1138
1139 // Draw main text.
1140 Color selection_bg = theme_cache.selection_color;
1141
1142 int sel_start = -1;
1143 int sel_end = -1;
1144
1145 if (selection.active && (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) <= (l.char_offset + TS->shaped_text_get_range(rid).y) && (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) >= (l.char_offset + TS->shaped_text_get_range(rid).x)) {
1146 sel_start = MAX(TS->shaped_text_get_range(rid).x, (selection.from_frame->lines[selection.from_line].char_offset + selection.from_char) - l.char_offset);
1147 sel_end = MIN(TS->shaped_text_get_range(rid).y, (selection.to_frame->lines[selection.to_line].char_offset + selection.to_char) - l.char_offset);
1148
1149 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_start, sel_end);
1150 for (int i = 0; i < sel.size(); i++) {
1151 Rect2 rect = Rect2(sel[i].x + p_ofs.x + off.x, p_ofs.y + off.y - TS->shaped_text_get_ascent(rid), sel[i].y - sel[i].x, TS->shaped_text_get_size(rid).y);
1152 RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, selection_bg);
1153 }
1154 }
1155
1156 Vector2 ul_start;
1157 bool ul_started = false;
1158 Color ul_color;
1159
1160 Vector2 dot_ul_start;
1161 bool dot_ul_started = false;
1162 Color dot_ul_color;
1163
1164 Vector2 st_start;
1165 bool st_started = false;
1166 Color st_color;
1167
1168 for (int i = 0; i < gl_size; i++) {
1169 bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
1170 Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
1171 Color font_color = _find_color(it, p_base_color);
1172 if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) {
1173 if (!ul_started) {
1174 ul_started = true;
1175 ul_start = p_ofs + Vector2(off.x, off.y);
1176 ul_color = font_color;
1177 ul_color.a *= 0.5;
1178 }
1179 } else if (ul_started) {
1180 ul_started = false;
1181 float y_off = TS->shaped_text_get_underline_position(rid);
1182 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1183 draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
1184 }
1185 if (_find_hint(it, nullptr) && underline_hint) {
1186 if (!dot_ul_started) {
1187 dot_ul_started = true;
1188 dot_ul_start = p_ofs + Vector2(off.x, off.y);
1189 dot_ul_color = font_color;
1190 dot_ul_color.a *= 0.5;
1191 }
1192 } else if (dot_ul_started) {
1193 dot_ul_started = false;
1194 float y_off = TS->shaped_text_get_underline_position(rid);
1195 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1196 draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
1197 }
1198 if (_find_strikethrough(it)) {
1199 if (!st_started) {
1200 st_started = true;
1201 st_start = p_ofs + Vector2(off.x, off.y);
1202 st_color = font_color;
1203 st_color.a *= 0.5;
1204 }
1205 } else if (st_started) {
1206 st_started = false;
1207 float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
1208 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1209 draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
1210 }
1211
1212 // Get FX.
1213 ItemFade *fade = nullptr;
1214 Item *fade_item = it;
1215 while (fade_item) {
1216 if (fade_item->type == ITEM_FADE) {
1217 fade = static_cast<ItemFade *>(fade_item);
1218 break;
1219 }
1220 fade_item = fade_item->parent;
1221 }
1222
1223 Vector<ItemFX *> fx_stack;
1224 _fetch_item_fx_stack(it, fx_stack);
1225 bool custom_fx_ok = true;
1226
1227 Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
1228 RID frid = glyphs[i].font_rid;
1229 uint32_t gl = glyphs[i].index;
1230 uint16_t gl_fl = glyphs[i].flags;
1231 uint8_t gl_cn = glyphs[i].count;
1232 bool cprev_cluster = false;
1233 bool cprev_conn = false;
1234 if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
1235 cprev_cluster = true;
1236 }
1237 if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
1238 if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
1239 cprev_conn = true;
1240 }
1241 } else {
1242 if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
1243 cprev_conn = true;
1244 }
1245 }
1246
1247 //Apply fx.
1248 if (fade) {
1249 float faded_visibility = 1.0f;
1250 if (glyphs[i].start >= fade->starting_index) {
1251 faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
1252 faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
1253 }
1254 font_color.a = faded_visibility;
1255 }
1256
1257 bool txt_visible = (font_color.a != 0);
1258
1259 for (int j = 0; j < fx_stack.size(); j++) {
1260 ItemFX *item_fx = fx_stack[j];
1261 bool cn = cprev_cluster || (cprev_conn && item_fx->connected);
1262
1263 if (item_fx->type == ITEM_CUSTOMFX && custom_fx_ok) {
1264 ItemCustomFX *item_custom = static_cast<ItemCustomFX *>(item_fx);
1265
1266 Ref<CharFXTransform> charfx = item_custom->char_fx_transform;
1267 Ref<RichTextEffect> custom_effect = item_custom->custom_effect;
1268
1269 if (!custom_effect.is_null()) {
1270 charfx->elapsed_time = item_custom->elapsed_time;
1271 charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end);
1272 charfx->relative_index = l.char_offset + glyphs[i].start - item_fx->char_ofs;
1273 charfx->visibility = txt_visible;
1274 charfx->outline = false;
1275 charfx->font = frid;
1276 charfx->glyph_index = gl;
1277 charfx->glyph_flags = gl_fl;
1278 charfx->glyph_count = gl_cn;
1279 charfx->offset = fx_offset;
1280 charfx->color = font_color;
1281
1282 bool effect_status = custom_effect->_process_effect_impl(charfx);
1283 custom_fx_ok = effect_status;
1284
1285 fx_offset += charfx->offset;
1286 font_color = charfx->color;
1287 frid = charfx->font;
1288 gl = charfx->glyph_index;
1289 txt_visible &= charfx->visibility;
1290 }
1291 } else if (item_fx->type == ITEM_SHAKE) {
1292 ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
1293
1294 if (!cn) {
1295 uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
1296 uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
1297 uint64_t max_rand = 2147483647;
1298 double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
1299 double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
1300 double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
1301 n_time = (n_time > 1.0) ? 1.0 : n_time;
1302 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;
1303 }
1304 fx_offset += item_shake->prev_off;
1305 } else if (item_fx->type == ITEM_WAVE) {
1306 ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
1307
1308 if (!cn) {
1309 double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
1310 item_wave->prev_off = Point2(0, 1) * value;
1311 }
1312 fx_offset += item_wave->prev_off;
1313 } else if (item_fx->type == ITEM_TORNADO) {
1314 ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
1315
1316 if (!cn) {
1317 double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
1318 double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
1319 item_tornado->prev_off = Point2(torn_x, torn_y);
1320 }
1321 fx_offset += item_tornado->prev_off;
1322 } else if (item_fx->type == ITEM_RAINBOW) {
1323 ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
1324
1325 font_color = font_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + off.x) / 50)), item_rainbow->saturation, item_rainbow->value, font_color.a);
1326 } else if (item_fx->type == ITEM_PULSE) {
1327 ItemPulse *item_pulse = static_cast<ItemPulse *>(item_fx);
1328
1329 const float sined_time = (Math::ease(Math::pingpong(item_pulse->elapsed_time, 1.0 / item_pulse->frequency) * item_pulse->frequency, item_pulse->ease));
1330 font_color = font_color.lerp(font_color * item_pulse->color, sined_time);
1331 }
1332 }
1333
1334 if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
1335 fx_offset = fx_offset.round();
1336 }
1337
1338 if (selected && use_selected_font_color) {
1339 font_color = theme_cache.font_selected_color;
1340 }
1341
1342 // Draw glyphs.
1343 for (int j = 0; j < glyphs[i].repeat; j++) {
1344 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));
1345 if (txt_visible) {
1346 if (!skip) {
1347 if (frid != RID()) {
1348 TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
1349 } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
1350 TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
1351 }
1352 }
1353 r_processed_glyphs++;
1354 }
1355 if (skip) {
1356 // End underline/overline/strikethrough is previous glyph is skipped.
1357 if (ul_started) {
1358 ul_started = false;
1359 float y_off = TS->shaped_text_get_underline_position(rid);
1360 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1361 draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
1362 }
1363 if (dot_ul_started) {
1364 dot_ul_started = false;
1365 float y_off = TS->shaped_text_get_underline_position(rid);
1366 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1367 draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
1368 }
1369 if (st_started) {
1370 st_started = false;
1371 float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
1372 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1373 draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
1374 }
1375 }
1376 off.x += glyphs[i].advance;
1377 }
1378 }
1379 if (ul_started) {
1380 ul_started = false;
1381 float y_off = TS->shaped_text_get_underline_position(rid);
1382 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1383 draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
1384 }
1385 if (dot_ul_started) {
1386 dot_ul_started = false;
1387 float y_off = TS->shaped_text_get_underline_position(rid);
1388 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1389 draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
1390 }
1391 if (st_started) {
1392 st_started = false;
1393 float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
1394 float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
1395 draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
1396 }
1397 // Draw foreground color box
1398 _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1);
1399
1400 off.y += TS->shaped_text_get_descent(rid);
1401 }
1402
1403 return line_count;
1404}
1405
1406void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool *r_outside, bool p_meta) {
1407 if (r_click_item) {
1408 *r_click_item = nullptr;
1409 }
1410 if (r_click_char != nullptr) {
1411 *r_click_char = 0;
1412 }
1413 if (r_outside != nullptr) {
1414 *r_outside = true;
1415 }
1416
1417 Size2 size = get_size();
1418 Rect2 text_rect = _get_text_rect();
1419
1420 int vofs = vscroll->get_value();
1421
1422 // Search for the first line.
1423 int to_line = main->first_invalid_line.load();
1424 int from_line = _find_first_line(0, to_line, vofs);
1425
1426 Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
1427 while (ofs.y < size.height && from_line < to_line) {
1428 MutexLock lock(main->lines[from_line].text_buf->get_mutex());
1429 _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);
1430 ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation;
1431 if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) {
1432 if (r_outside != nullptr) {
1433 *r_outside = false;
1434 }
1435 return;
1436 }
1437 from_line++;
1438 }
1439}
1440
1441float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table, bool p_meta) {
1442 Vector2 off;
1443
1444 bool line_clicked = false;
1445 float text_rect_begin = 0.0;
1446 int char_pos = -1;
1447 Line &l = p_frame->lines[p_line];
1448 MutexLock lock(l.text_buf->get_mutex());
1449
1450 bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
1451 bool lrtl = is_layout_rtl();
1452
1453 // Table hit test results.
1454 bool table_hit = false;
1455 Vector2i table_range;
1456 float table_offy = 0.f;
1457 ItemFrame *table_click_frame = nullptr;
1458 int table_click_line = -1;
1459 Item *table_click_item = nullptr;
1460 int table_click_char = -1;
1461
1462 for (int line = 0; line < l.text_buf->get_line_count(); line++) {
1463 RID rid = l.text_buf->get_line_rid(line);
1464
1465 float width = l.text_buf->get_width();
1466 float length = TS->shaped_text_get_width(rid);
1467
1468 if (rtl) {
1469 off.x = p_width - l.offset.x - width;
1470 if (!lrtl && p_frame == main) { // Skip Scrollbar.
1471 off.x -= scroll_w;
1472 }
1473 } else {
1474 off.x = l.offset.x;
1475 if (lrtl && p_frame == main) { // Skip Scrollbar.
1476 off.x += scroll_w;
1477 }
1478 }
1479
1480 switch (l.text_buf->get_alignment()) {
1481 case HORIZONTAL_ALIGNMENT_FILL:
1482 case HORIZONTAL_ALIGNMENT_LEFT: {
1483 if (rtl) {
1484 off.x += width - length;
1485 }
1486 } break;
1487 case HORIZONTAL_ALIGNMENT_CENTER: {
1488 off.x += Math::floor((width - length) / 2.0);
1489 } break;
1490 case HORIZONTAL_ALIGNMENT_RIGHT: {
1491 if (!rtl) {
1492 off.x += width - length;
1493 }
1494 } break;
1495 }
1496 // Adjust for dropcap.
1497 int dc_lines = l.text_buf->get_dropcap_lines();
1498 float h_off = l.text_buf->get_dropcap_size().x;
1499 if (line <= dc_lines) {
1500 if (rtl) {
1501 off.x -= h_off;
1502 } else {
1503 off.x += h_off;
1504 }
1505 }
1506 off.y += TS->shaped_text_get_ascent(rid);
1507
1508 Array objects = TS->shaped_text_get_objects(rid);
1509 for (int i = 0; i < objects.size(); i++) {
1510 Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
1511 if (it != nullptr) {
1512 Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
1513 rect.position += p_ofs + off;
1514 if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
1515 switch (it->type) {
1516 case ITEM_TABLE: {
1517 ItemTable *table = static_cast<ItemTable *>(it);
1518
1519 int idx = 0;
1520 int col_count = table->columns.size();
1521 int row_count = table->rows.size();
1522
1523 for (Item *E : table->subitems) {
1524 ItemFrame *frame = static_cast<ItemFrame *>(E);
1525
1526 int col = idx % col_count;
1527 int row = idx / col_count;
1528
1529 if (frame->lines.size() != 0 && row < row_count) {
1530 Vector2 coff = frame->lines[0].offset;
1531 if (rtl) {
1532 coff.x = rect.size.width - table->columns[col].width - coff.x;
1533 }
1534 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);
1535 if (col == col_count - 1) {
1536 if (rtl) {
1537 crect.size.x = crect.position.x + crect.size.x;
1538 crect.position.x = 0;
1539 } else {
1540 crect.size.x = get_size().x;
1541 }
1542 }
1543 if (crect.has_point(p_click)) {
1544 for (int j = 0; j < (int)frame->lines.size(); j++) {
1545 _find_click_in_line(frame, j, rect.position + Vector2(frame->padding.position.x, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true, p_meta);
1546 if (table_click_frame && table_click_item) {
1547 // Save cell detected cell hit data.
1548 table_range = Vector2i(INT32_MAX, 0);
1549 for (Item *F : table->subitems) {
1550 ItemFrame *sub_frame = static_cast<ItemFrame *>(F);
1551 for (int k = 0; k < (int)sub_frame->lines.size(); k++) {
1552 table_range.x = MIN(table_range.x, sub_frame->lines[k].char_offset);
1553 table_range.y = MAX(table_range.y, sub_frame->lines[k].char_offset + sub_frame->lines[k].char_count);
1554 }
1555 }
1556 table_offy = off.y;
1557 table_hit = true;
1558 }
1559 }
1560 }
1561 }
1562 idx++;
1563 }
1564 } break;
1565 default:
1566 break;
1567 }
1568 }
1569 }
1570 }
1571 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);
1572 if (p_table) {
1573 rect.size.y += theme_cache.table_v_separation;
1574 }
1575
1576 if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
1577 if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) {
1578 if (p_meta) {
1579 int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x);
1580 if (glyph_idx >= 0) {
1581 const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
1582 char_pos = glyphs[glyph_idx].start;
1583 }
1584 } else {
1585 char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
1586 char_pos = TS->shaped_text_closest_character_pos(rid, char_pos);
1587 }
1588 }
1589 line_clicked = true;
1590 text_rect_begin = rtl ? rect.position.x + rect.size.x : rect.position.x;
1591 }
1592
1593 // If table hit was detected, and line hit is in the table bounds use table hit.
1594 if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) {
1595 if (r_click_frame != nullptr) {
1596 *r_click_frame = table_click_frame;
1597 }
1598
1599 if (r_click_line != nullptr) {
1600 *r_click_line = table_click_line;
1601 }
1602
1603 if (r_click_item != nullptr) {
1604 *r_click_item = table_click_item;
1605 }
1606
1607 if (r_click_char != nullptr) {
1608 *r_click_char = table_click_char;
1609 }
1610 return table_offy;
1611 }
1612
1613 off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation;
1614 }
1615
1616 // Text line hit.
1617 if (line_clicked) {
1618 // Find item.
1619 if (r_click_item != nullptr) {
1620 Item *it = p_frame->lines[p_line].from;
1621 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
1622 if (char_pos >= 0) {
1623 *r_click_item = _get_item_at_pos(it, it_to, char_pos);
1624 } else {
1625 int stop = text_rect_begin;
1626 *r_click_item = _find_indentable(it);
1627 while (*r_click_item) {
1628 Ref<Font> font = theme_cache.normal_font;
1629 int font_size = theme_cache.normal_font_size;
1630 ItemFont *font_it = _find_font(*r_click_item);
1631 if (font_it) {
1632 if (font_it->font.is_valid()) {
1633 font = font_it->font;
1634 }
1635 if (font_it->font_size > 0) {
1636 font_size = font_it->font_size;
1637 }
1638 }
1639 ItemFontSize *font_size_it = _find_font_size(*r_click_item);
1640 if (font_size_it && font_size_it->font_size > 0) {
1641 font_size = font_size_it->font_size;
1642 }
1643 if (rtl) {
1644 stop += tab_size * font->get_char_size(' ', font_size).width;
1645 if (stop > p_click.x) {
1646 break;
1647 }
1648 } else {
1649 stop -= tab_size * font->get_char_size(' ', font_size).width;
1650 if (stop < p_click.x) {
1651 break;
1652 }
1653 }
1654 *r_click_item = _find_indentable((*r_click_item)->parent);
1655 }
1656 }
1657 }
1658
1659 if (r_click_frame != nullptr) {
1660 *r_click_frame = p_frame;
1661 }
1662
1663 if (r_click_line != nullptr) {
1664 *r_click_line = p_line;
1665 }
1666
1667 if (r_click_char != nullptr) {
1668 *r_click_char = char_pos;
1669 }
1670 }
1671
1672 return off.y;
1673}
1674
1675void RichTextLabel::_scroll_changed(double) {
1676 if (updating_scroll) {
1677 return;
1678 }
1679
1680 if (scroll_follow && vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) {
1681 scroll_following = true;
1682 } else {
1683 scroll_following = false;
1684 }
1685
1686 scroll_updated = true;
1687
1688 queue_redraw();
1689}
1690
1691void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) {
1692 Item *it = p_frame;
1693 while (it) {
1694 ItemFX *ifx = nullptr;
1695
1696 if (it->type == ITEM_CUSTOMFX || it->type == ITEM_SHAKE || it->type == ITEM_WAVE || it->type == ITEM_TORNADO || it->type == ITEM_RAINBOW || it->type == ITEM_PULSE) {
1697 ifx = static_cast<ItemFX *>(it);
1698 }
1699
1700 if (!ifx) {
1701 it = _get_next_item(it, true);
1702 continue;
1703 }
1704
1705 ifx->elapsed_time += p_delta_time;
1706
1707 ItemShake *shake = nullptr;
1708
1709 if (it->type == ITEM_SHAKE) {
1710 shake = static_cast<ItemShake *>(it);
1711 }
1712
1713 if (shake) {
1714 bool cycle = (shake->elapsed_time > (1.0f / shake->rate));
1715 if (cycle) {
1716 shake->elapsed_time -= (1.0f / shake->rate);
1717 shake->reroll_random();
1718 }
1719 }
1720
1721 it = _get_next_item(it, true);
1722 }
1723}
1724
1725int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const {
1726 int l = p_from;
1727 int r = p_to;
1728 while (l < r) {
1729 int m = Math::floor(double(l + r) / 2.0);
1730 MutexLock lock(main->lines[m].text_buf->get_mutex());
1731 int ofs = _calculate_line_vertical_offset(main->lines[m]);
1732 if (ofs < p_vofs) {
1733 l = m + 1;
1734 } else {
1735 r = m;
1736 }
1737 }
1738 return MIN(l, (int)main->lines.size() - 1);
1739}
1740
1741_FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const {
1742 return line.get_height(theme_cache.line_separation);
1743}
1744
1745void RichTextLabel::_update_theme_item_cache() {
1746 Control::_update_theme_item_cache();
1747
1748 theme_cache.base_scale = get_theme_default_base_scale();
1749 use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0);
1750}
1751
1752void RichTextLabel::_notification(int p_what) {
1753 switch (p_what) {
1754 case NOTIFICATION_MOUSE_EXIT: {
1755 if (meta_hovering) {
1756 meta_hovering = nullptr;
1757 emit_signal(SNAME("meta_hover_ended"), current_meta);
1758 current_meta = false;
1759 queue_redraw();
1760 }
1761 } break;
1762
1763 case NOTIFICATION_RESIZED: {
1764 _stop_thread();
1765 main->first_resized_line.store(0); //invalidate ALL
1766 queue_redraw();
1767 } break;
1768
1769 case NOTIFICATION_THEME_CHANGED: {
1770 _stop_thread();
1771 main->first_invalid_font_line.store(0); //invalidate ALL
1772 queue_redraw();
1773 } break;
1774
1775 case NOTIFICATION_ENTER_TREE: {
1776 _stop_thread();
1777 if (!text.is_empty()) {
1778 set_text(text);
1779 }
1780
1781 main->first_invalid_line.store(0); //invalidate ALL
1782 queue_redraw();
1783 } break;
1784
1785 case NOTIFICATION_PREDELETE:
1786 case NOTIFICATION_EXIT_TREE: {
1787 _stop_thread();
1788 } break;
1789
1790 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
1791 case NOTIFICATION_TRANSLATION_CHANGED: {
1792 _apply_translation();
1793 queue_redraw();
1794 } break;
1795
1796 case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
1797 if (is_visible_in_tree()) {
1798 queue_redraw();
1799 }
1800 } break;
1801
1802 case NOTIFICATION_DRAW: {
1803 RID ci = get_canvas_item();
1804 Size2 size = get_size();
1805
1806 draw_style_box(theme_cache.normal_style, Rect2(Point2(), size));
1807
1808 if (has_focus()) {
1809 RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
1810 draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
1811 RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
1812 }
1813
1814 // Start text shaping.
1815 if (_validate_line_caches()) {
1816 set_physics_process_internal(false); // Disable auto refresh, if text is fully processed.
1817 } else {
1818 // Draw loading progress bar.
1819 if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) {
1820 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);
1821 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);
1822
1823 draw_style_box(theme_cache.progress_bg_style, Rect2(p_pos, p_size));
1824
1825 bool right_to_left = is_layout_rtl();
1826 double r = loaded.load();
1827 int mp = theme_cache.progress_fg_style->get_minimum_size().width;
1828 int p = round(r * (p_size.width - mp));
1829 if (right_to_left) {
1830 int p_remaining = round((1.0 - r) * (p_size.width - mp));
1831 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)));
1832 } else {
1833 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)));
1834 }
1835 }
1836 }
1837
1838 // Draw main text.
1839 Rect2 text_rect = _get_text_rect();
1840 float vofs = vscroll->get_value();
1841
1842 // Search for the first line.
1843 int to_line = main->first_invalid_line.load();
1844 int from_line = _find_first_line(0, to_line, vofs);
1845
1846 Point2 shadow_ofs(theme_cache.shadow_offset_x, theme_cache.shadow_offset_y);
1847
1848 visible_paragraph_count = 0;
1849 visible_line_count = 0;
1850
1851 // New cache draw.
1852 Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
1853 int processed_glyphs = 0;
1854 while (ofs.y < size.height && from_line < to_line) {
1855 MutexLock lock(main->lines[from_line].text_buf->get_mutex());
1856
1857 visible_paragraph_count++;
1858 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);
1859 ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation;
1860 from_line++;
1861 }
1862 } break;
1863
1864 case NOTIFICATION_INTERNAL_PROCESS: {
1865 if (is_visible_in_tree()) {
1866 if (!is_ready()) {
1867 return;
1868 }
1869 double dt = get_process_delta_time();
1870 _update_fx(main, dt);
1871 queue_redraw();
1872 }
1873 } break;
1874
1875 case NOTIFICATION_FOCUS_EXIT: {
1876 if (deselect_on_focus_loss_enabled) {
1877 deselect();
1878 }
1879 } break;
1880
1881 case NOTIFICATION_DRAG_END: {
1882 selection.drag_attempt = false;
1883 } break;
1884 }
1885}
1886
1887Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const {
1888 if (selection.click_item) {
1889 return CURSOR_IBEAM;
1890 }
1891
1892 Item *item = nullptr;
1893 bool outside = true;
1894 const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside, true);
1895
1896 if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) {
1897 return CURSOR_POINTING_HAND;
1898 }
1899 return get_default_cursor_shape();
1900}
1901
1902void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
1903 ERR_FAIL_COND(p_event.is_null());
1904
1905 Ref<InputEventMouseButton> b = p_event;
1906
1907 if (b.is_valid()) {
1908 if (b->get_button_index() == MouseButton::LEFT) {
1909 if (b->is_pressed() && !b->is_double_click()) {
1910 scroll_updated = false;
1911 ItemFrame *c_frame = nullptr;
1912 int c_line = 0;
1913 Item *c_item = nullptr;
1914 int c_index = 0;
1915 bool outside;
1916
1917 selection.drag_attempt = false;
1918
1919 _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false);
1920 if (c_item != nullptr) {
1921 if (selection.enabled) {
1922 selection.click_frame = c_frame;
1923 selection.click_item = c_item;
1924 selection.click_line = c_line;
1925 selection.click_char = c_index;
1926
1927 // Erase previous selection.
1928 if (selection.active) {
1929 if (drag_and_drop_selection_enabled && _is_click_inside_selection()) {
1930 selection.drag_attempt = true;
1931 selection.click_item = nullptr;
1932 } else {
1933 selection.from_frame = nullptr;
1934 selection.from_line = 0;
1935 selection.from_item = nullptr;
1936 selection.from_char = 0;
1937 selection.to_frame = nullptr;
1938 selection.to_line = 0;
1939 selection.to_item = nullptr;
1940 selection.to_char = 0;
1941 deselect();
1942 }
1943 }
1944 }
1945 }
1946 } else if (b->is_pressed() && b->is_double_click() && selection.enabled) {
1947 //double_click: select word
1948
1949 ItemFrame *c_frame = nullptr;
1950 int c_line = 0;
1951 Item *c_item = nullptr;
1952 int c_index = 0;
1953 bool outside;
1954
1955 selection.drag_attempt = false;
1956
1957 _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false);
1958
1959 if (c_frame) {
1960 const Line &l = c_frame->lines[c_line];
1961 MutexLock lock(l.text_buf->get_mutex());
1962 PackedInt32Array words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid());
1963 for (int i = 0; i < words.size(); i = i + 2) {
1964 if (c_index >= words[i] && c_index < words[i + 1]) {
1965 selection.from_frame = c_frame;
1966 selection.from_line = c_line;
1967 selection.from_item = c_item;
1968 selection.from_char = words[i];
1969
1970 selection.to_frame = c_frame;
1971 selection.to_line = c_line;
1972 selection.to_item = c_item;
1973 selection.to_char = words[i + 1];
1974
1975 selection.active = true;
1976 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
1977 DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
1978 }
1979 queue_redraw();
1980 break;
1981 }
1982 }
1983 }
1984 } else if (!b->is_pressed()) {
1985 if (selection.enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
1986 DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
1987 }
1988 selection.click_item = nullptr;
1989 if (selection.drag_attempt) {
1990 selection.drag_attempt = false;
1991 if (_is_click_inside_selection()) {
1992 selection.from_frame = nullptr;
1993 selection.from_line = 0;
1994 selection.from_item = nullptr;
1995 selection.from_char = 0;
1996 selection.to_frame = nullptr;
1997 selection.to_line = 0;
1998 selection.to_item = nullptr;
1999 selection.to_char = 0;
2000 deselect();
2001 }
2002 }
2003 if (!b->is_double_click() && !scroll_updated && !selection.active) {
2004 Item *c_item = nullptr;
2005
2006 bool outside = true;
2007 _find_click(main, b->get_position(), nullptr, nullptr, &c_item, nullptr, &outside, true);
2008
2009 if (c_item) {
2010 Variant meta;
2011 if (!outside && _find_meta(c_item, &meta)) {
2012 //meta clicked
2013 emit_signal(SNAME("meta_clicked"), meta);
2014 }
2015 }
2016 }
2017 }
2018 }
2019
2020 if (b->get_button_index() == MouseButton::WHEEL_UP) {
2021 if (scroll_active) {
2022 vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8);
2023 }
2024 }
2025 if (b->get_button_index() == MouseButton::WHEEL_DOWN) {
2026 if (scroll_active) {
2027 vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
2028 }
2029 }
2030 if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
2031 _update_context_menu();
2032 menu->set_position(get_screen_position() + b->get_position());
2033 menu->reset_size();
2034 menu->popup();
2035 grab_focus();
2036 }
2037 }
2038
2039 Ref<InputEventPanGesture> pan_gesture = p_event;
2040 if (pan_gesture.is_valid()) {
2041 if (scroll_active) {
2042 vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y * 0.5 / 8);
2043 }
2044
2045 return;
2046 }
2047
2048 Ref<InputEventKey> k = p_event;
2049
2050 if (k.is_valid()) {
2051 if (k->is_pressed()) {
2052 bool handled = false;
2053
2054 if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) {
2055 vscroll->set_value(vscroll->get_value() - vscroll->get_page());
2056 handled = true;
2057 }
2058 if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) {
2059 vscroll->set_value(vscroll->get_value() + vscroll->get_page());
2060 handled = true;
2061 }
2062 if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) {
2063 vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size));
2064 handled = true;
2065 }
2066 if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) {
2067 vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size));
2068 handled = true;
2069 }
2070 if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) {
2071 vscroll->set_value(0);
2072 handled = true;
2073 }
2074 if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) {
2075 vscroll->set_value(vscroll->get_max());
2076 handled = true;
2077 }
2078 if (is_shortcut_keys_enabled()) {
2079 if (k->is_action("ui_text_select_all", true)) {
2080 select_all();
2081 handled = true;
2082 }
2083 if (k->is_action("ui_copy", true)) {
2084 selection_copy();
2085 handled = true;
2086 }
2087 }
2088 if (k->is_action("ui_menu", true)) {
2089 if (context_menu_enabled) {
2090 _update_context_menu();
2091 menu->set_position(get_screen_position());
2092 menu->reset_size();
2093 menu->popup();
2094 menu->grab_focus();
2095 }
2096 handled = true;
2097 }
2098
2099 if (handled) {
2100 accept_event();
2101 }
2102 }
2103 }
2104
2105 Ref<InputEventMouseMotion> m = p_event;
2106 if (m.is_valid()) {
2107 ItemFrame *c_frame = nullptr;
2108 int c_line = 0;
2109 Item *c_item = nullptr;
2110 int c_index = 0;
2111 bool outside;
2112
2113 _find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false);
2114 if (selection.click_item && c_item) {
2115 selection.from_frame = selection.click_frame;
2116 selection.from_line = selection.click_line;
2117 selection.from_item = selection.click_item;
2118 selection.from_char = selection.click_char;
2119
2120 selection.to_frame = c_frame;
2121 selection.to_line = c_line;
2122 selection.to_item = c_item;
2123 selection.to_char = c_index;
2124
2125 bool swap = false;
2126 if (selection.click_frame && c_frame) {
2127 const Line &l1 = c_frame->lines[c_line];
2128 const Line &l2 = selection.click_frame->lines[selection.click_line];
2129 if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
2130 swap = true;
2131 } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char) {
2132 deselect();
2133 return;
2134 }
2135 }
2136
2137 if (swap) {
2138 SWAP(selection.from_frame, selection.to_frame);
2139 SWAP(selection.from_line, selection.to_line);
2140 SWAP(selection.from_item, selection.to_item);
2141 SWAP(selection.from_char, selection.to_char);
2142 }
2143
2144 selection.active = true;
2145 queue_redraw();
2146 }
2147
2148 Variant meta;
2149 ItemMeta *item_meta;
2150 if (c_item && !outside && _find_meta(c_item, &meta, &item_meta)) {
2151 if (meta_hovering != item_meta) {
2152 if (meta_hovering) {
2153 emit_signal(SNAME("meta_hover_ended"), current_meta);
2154 }
2155 meta_hovering = item_meta;
2156 current_meta = meta;
2157 emit_signal(SNAME("meta_hover_started"), meta);
2158 }
2159 } else if (meta_hovering) {
2160 meta_hovering = nullptr;
2161 emit_signal(SNAME("meta_hover_ended"), current_meta);
2162 current_meta = false;
2163 }
2164 }
2165}
2166
2167String RichTextLabel::get_tooltip(const Point2 &p_pos) const {
2168 Item *c_item = nullptr;
2169 bool outside;
2170
2171 const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside, true);
2172
2173 String description;
2174 if (c_item && !outside && const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) {
2175 return description;
2176 } else {
2177 return Control::get_tooltip(p_pos);
2178 }
2179}
2180
2181void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) {
2182 if (r_frame != nullptr) {
2183 *r_frame = nullptr;
2184 }
2185 if (r_line != nullptr) {
2186 *r_line = 0;
2187 }
2188
2189 Item *item = p_item;
2190
2191 while (item) {
2192 if (item->parent != nullptr && item->parent->type == ITEM_FRAME) {
2193 if (r_frame != nullptr) {
2194 *r_frame = static_cast<ItemFrame *>(item->parent);
2195 }
2196 if (r_line != nullptr) {
2197 *r_line = item->line;
2198 }
2199 return;
2200 }
2201
2202 item = item->parent;
2203 }
2204}
2205
2206RichTextLabel::Item *RichTextLabel::_find_indentable(Item *p_item) {
2207 Item *indentable = p_item;
2208
2209 while (indentable) {
2210 if (indentable->type == ITEM_INDENT || indentable->type == ITEM_LIST) {
2211 return indentable;
2212 }
2213 indentable = indentable->parent;
2214 }
2215
2216 return indentable;
2217}
2218
2219RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) {
2220 Item *fontitem = p_item;
2221
2222 while (fontitem) {
2223 if (fontitem->type == ITEM_FONT) {
2224 ItemFont *fi = static_cast<ItemFont *>(fontitem);
2225 switch (fi->def_font) {
2226 case NORMAL_FONT: {
2227 if (fi->variation) {
2228 Ref<FontVariation> fc = fi->font;
2229 if (fc.is_valid()) {
2230 fc->set_base_font(theme_cache.normal_font);
2231 }
2232 } else {
2233 fi->font = theme_cache.normal_font;
2234 }
2235 if (fi->def_size) {
2236 fi->font_size = theme_cache.normal_font_size;
2237 }
2238 } break;
2239 case BOLD_FONT: {
2240 if (fi->variation) {
2241 Ref<FontVariation> fc = fi->font;
2242 if (fc.is_valid()) {
2243 fc->set_base_font(theme_cache.bold_font);
2244 }
2245 } else {
2246 fi->font = theme_cache.bold_font;
2247 }
2248 if (fi->def_size) {
2249 fi->font_size = theme_cache.bold_font_size;
2250 }
2251 } break;
2252 case ITALICS_FONT: {
2253 if (fi->variation) {
2254 Ref<FontVariation> fc = fi->font;
2255 if (fc.is_valid()) {
2256 fc->set_base_font(theme_cache.italics_font);
2257 }
2258 } else {
2259 fi->font = theme_cache.italics_font;
2260 }
2261 if (fi->def_size) {
2262 fi->font_size = theme_cache.italics_font_size;
2263 }
2264 } break;
2265 case BOLD_ITALICS_FONT: {
2266 if (fi->variation) {
2267 Ref<FontVariation> fc = fi->font;
2268 if (fc.is_valid()) {
2269 fc->set_base_font(theme_cache.bold_italics_font);
2270 }
2271 } else {
2272 fi->font = theme_cache.bold_italics_font;
2273 }
2274 if (fi->def_size) {
2275 fi->font_size = theme_cache.bold_italics_font_size;
2276 }
2277 } break;
2278 case MONO_FONT: {
2279 if (fi->variation) {
2280 Ref<FontVariation> fc = fi->font;
2281 if (fc.is_valid()) {
2282 fc->set_base_font(theme_cache.mono_font);
2283 }
2284 } else {
2285 fi->font = theme_cache.mono_font;
2286 }
2287 if (fi->def_size) {
2288 fi->font_size = theme_cache.mono_font_size;
2289 }
2290 } break;
2291 default: {
2292 } break;
2293 }
2294 return fi;
2295 }
2296
2297 fontitem = fontitem->parent;
2298 }
2299
2300 return nullptr;
2301}
2302
2303RichTextLabel::ItemFontSize *RichTextLabel::_find_font_size(Item *p_item) {
2304 Item *sizeitem = p_item;
2305
2306 while (sizeitem) {
2307 if (sizeitem->type == ITEM_FONT_SIZE) {
2308 ItemFontSize *fi = static_cast<ItemFontSize *>(sizeitem);
2309 return fi;
2310 }
2311
2312 sizeitem = sizeitem->parent;
2313 }
2314
2315 return nullptr;
2316}
2317
2318int RichTextLabel::_find_outline_size(Item *p_item, int p_default) {
2319 Item *sizeitem = p_item;
2320
2321 while (sizeitem) {
2322 if (sizeitem->type == ITEM_OUTLINE_SIZE) {
2323 ItemOutlineSize *fi = static_cast<ItemOutlineSize *>(sizeitem);
2324 return fi->outline_size;
2325 }
2326
2327 sizeitem = sizeitem->parent;
2328 }
2329
2330 return p_default;
2331}
2332
2333RichTextLabel::ItemDropcap *RichTextLabel::_find_dc_item(Item *p_item) {
2334 Item *item = p_item;
2335
2336 while (item) {
2337 if (item->type == ITEM_DROPCAP) {
2338 return static_cast<ItemDropcap *>(item);
2339 }
2340 item = item->parent;
2341 }
2342
2343 return nullptr;
2344}
2345
2346RichTextLabel::ItemList *RichTextLabel::_find_list_item(Item *p_item) {
2347 Item *item = p_item;
2348
2349 while (item) {
2350 if (item->type == ITEM_LIST) {
2351 return static_cast<ItemList *>(item);
2352 }
2353 item = item->parent;
2354 }
2355
2356 return nullptr;
2357}
2358
2359int RichTextLabel::_find_list(Item *p_item, Vector<int> &r_index, Vector<ItemList *> &r_list) {
2360 Item *item = p_item;
2361 Item *prev_item = p_item;
2362
2363 int level = 0;
2364
2365 while (item) {
2366 if (item->type == ITEM_LIST) {
2367 ItemList *list = static_cast<ItemList *>(item);
2368
2369 ItemFrame *frame = nullptr;
2370 int line = -1;
2371 _find_frame(list, &frame, &line);
2372
2373 int index = 1;
2374 if (frame != nullptr) {
2375 for (int i = list->line + 1; i <= prev_item->line && i < (int)frame->lines.size(); i++) {
2376 if (_find_list_item(frame->lines[i].from) == list) {
2377 index++;
2378 }
2379 }
2380 }
2381
2382 r_index.push_back(index);
2383 r_list.push_back(list);
2384
2385 prev_item = item;
2386 }
2387 level++;
2388 item = item->parent;
2389 }
2390
2391 return level;
2392}
2393
2394int RichTextLabel::_find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size) {
2395 Item *item = p_item;
2396
2397 float margin = 0.0;
2398
2399 while (item) {
2400 if (item->type == ITEM_INDENT) {
2401 Ref<Font> font = p_base_font;
2402 int font_size = p_base_font_size;
2403
2404 ItemFont *font_it = _find_font(item);
2405 if (font_it) {
2406 if (font_it->font.is_valid()) {
2407 font = font_it->font;
2408 }
2409 if (font_it->font_size > 0) {
2410 font_size = font_it->font_size;
2411 }
2412 }
2413 ItemFontSize *font_size_it = _find_font_size(item);
2414 if (font_size_it && font_size_it->font_size > 0) {
2415 font_size = font_size_it->font_size;
2416 }
2417 margin += tab_size * font->get_char_size(' ', font_size).width;
2418
2419 } else if (item->type == ITEM_LIST) {
2420 Ref<Font> font = p_base_font;
2421 int font_size = p_base_font_size;
2422
2423 ItemFont *font_it = _find_font(item);
2424 if (font_it) {
2425 if (font_it->font.is_valid()) {
2426 font = font_it->font;
2427 }
2428 if (font_it->font_size > 0) {
2429 font_size = font_it->font_size;
2430 }
2431 }
2432 ItemFontSize *font_size_it = _find_font_size(item);
2433 if (font_size_it && font_size_it->font_size > 0) {
2434 font_size = font_size_it->font_size;
2435 }
2436 margin += tab_size * font->get_char_size(' ', font_size).width;
2437 }
2438
2439 item = item->parent;
2440 }
2441
2442 return margin;
2443}
2444
2445BitField<TextServer::JustificationFlag> RichTextLabel::_find_jst_flags(Item *p_item) {
2446 Item *item = p_item;
2447
2448 while (item) {
2449 if (item->type == ITEM_PARAGRAPH) {
2450 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2451 return p->jst_flags;
2452 }
2453
2454 item = item->parent;
2455 }
2456
2457 return default_jst_flags;
2458}
2459
2460PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) {
2461 Item *item = p_item;
2462
2463 while (item) {
2464 if (item->type == ITEM_PARAGRAPH) {
2465 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2466 return p->tab_stops;
2467 }
2468
2469 item = item->parent;
2470 }
2471
2472 return PackedFloat32Array();
2473}
2474
2475HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) {
2476 Item *item = p_item;
2477
2478 while (item) {
2479 if (item->type == ITEM_PARAGRAPH) {
2480 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2481 return p->alignment;
2482 }
2483
2484 item = item->parent;
2485 }
2486
2487 return default_alignment;
2488}
2489
2490TextServer::Direction RichTextLabel::_find_direction(Item *p_item) {
2491 Item *item = p_item;
2492
2493 while (item) {
2494 if (item->type == ITEM_PARAGRAPH) {
2495 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2496 if (p->direction != Control::TEXT_DIRECTION_INHERITED) {
2497 return (TextServer::Direction)p->direction;
2498 }
2499 }
2500
2501 item = item->parent;
2502 }
2503
2504 if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
2505 return is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
2506 } else {
2507 return (TextServer::Direction)text_direction;
2508 }
2509}
2510
2511TextServer::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) {
2512 Item *item = p_item;
2513
2514 while (item) {
2515 if (item->type == ITEM_PARAGRAPH) {
2516 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2517 return p->st_parser;
2518 }
2519
2520 item = item->parent;
2521 }
2522
2523 return st_parser;
2524}
2525
2526String RichTextLabel::_find_language(Item *p_item) {
2527 Item *item = p_item;
2528
2529 while (item) {
2530 if (item->type == ITEM_LANGUAGE) {
2531 ItemLanguage *p = static_cast<ItemLanguage *>(item);
2532 return p->language;
2533 } else if (item->type == ITEM_PARAGRAPH) {
2534 ItemParagraph *p = static_cast<ItemParagraph *>(item);
2535 return p->language;
2536 }
2537
2538 item = item->parent;
2539 }
2540
2541 return language;
2542}
2543
2544Color RichTextLabel::_find_color(Item *p_item, const Color &p_default_color) {
2545 Item *item = p_item;
2546
2547 while (item) {
2548 if (item->type == ITEM_COLOR) {
2549 ItemColor *color = static_cast<ItemColor *>(item);
2550 return color->color;
2551 }
2552
2553 item = item->parent;
2554 }
2555
2556 return p_default_color;
2557}
2558
2559Color RichTextLabel::_find_outline_color(Item *p_item, const Color &p_default_color) {
2560 Item *item = p_item;
2561
2562 while (item) {
2563 if (item->type == ITEM_OUTLINE_COLOR) {
2564 ItemOutlineColor *color = static_cast<ItemOutlineColor *>(item);
2565 return color->color;
2566 }
2567
2568 item = item->parent;
2569 }
2570
2571 return p_default_color;
2572}
2573
2574bool RichTextLabel::_find_underline(Item *p_item) {
2575 Item *item = p_item;
2576
2577 while (item) {
2578 if (item->type == ITEM_UNDERLINE) {
2579 return true;
2580 }
2581
2582 item = item->parent;
2583 }
2584
2585 return false;
2586}
2587
2588bool RichTextLabel::_find_strikethrough(Item *p_item) {
2589 Item *item = p_item;
2590
2591 while (item) {
2592 if (item->type == ITEM_STRIKETHROUGH) {
2593 return true;
2594 }
2595
2596 item = item->parent;
2597 }
2598
2599 return false;
2600}
2601
2602void RichTextLabel::_fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack) {
2603 Item *item = p_item;
2604 while (item) {
2605 if (item->type == ITEM_CUSTOMFX || item->type == ITEM_SHAKE || item->type == ITEM_WAVE || item->type == ITEM_TORNADO || item->type == ITEM_RAINBOW || item->type == ITEM_PULSE) {
2606 r_stack.push_back(static_cast<ItemFX *>(item));
2607 }
2608
2609 item = item->parent;
2610 }
2611}
2612
2613void RichTextLabel::_normalize_subtags(Vector<String> &subtags) {
2614 for (String &subtag : subtags) {
2615 subtag = subtag.unquote();
2616 }
2617}
2618
2619bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) {
2620 Item *item = p_item;
2621
2622 while (item) {
2623 if (item->type == ITEM_META) {
2624 ItemMeta *meta = static_cast<ItemMeta *>(item);
2625 if (r_meta) {
2626 *r_meta = meta->meta;
2627 }
2628 if (r_item) {
2629 *r_item = meta;
2630 }
2631 return true;
2632 }
2633
2634 item = item->parent;
2635 }
2636
2637 return false;
2638}
2639
2640bool RichTextLabel::_find_hint(Item *p_item, String *r_description) {
2641 Item *item = p_item;
2642
2643 while (item) {
2644 if (item->type == ITEM_HINT) {
2645 ItemHint *hint = static_cast<ItemHint *>(item);
2646 if (r_description) {
2647 *r_description = hint->description;
2648 }
2649 return true;
2650 }
2651
2652 item = item->parent;
2653 }
2654
2655 return false;
2656}
2657
2658Color RichTextLabel::_find_bgcolor(Item *p_item) {
2659 Item *item = p_item;
2660
2661 while (item) {
2662 if (item->type == ITEM_BGCOLOR) {
2663 ItemBGColor *color = static_cast<ItemBGColor *>(item);
2664 return color->color;
2665 }
2666
2667 item = item->parent;
2668 }
2669
2670 return Color(0, 0, 0, 0);
2671}
2672
2673Color RichTextLabel::_find_fgcolor(Item *p_item) {
2674 Item *item = p_item;
2675
2676 while (item) {
2677 if (item->type == ITEM_FGCOLOR) {
2678 ItemFGColor *color = static_cast<ItemFGColor *>(item);
2679 return color->color;
2680 }
2681
2682 item = item->parent;
2683 }
2684
2685 return Color(0, 0, 0, 0);
2686}
2687
2688bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
2689 if (from && from != to) {
2690 if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) {
2691 return true;
2692 }
2693
2694 for (Item *E : from->subitems) {
2695 bool layout = _find_layout_subitem(E, to);
2696
2697 if (layout) {
2698 return true;
2699 }
2700 }
2701 }
2702
2703 return false;
2704}
2705
2706void RichTextLabel::_thread_function(void *p_userdata) {
2707 set_current_thread_safe_for_nodes(true);
2708 _process_line_caches();
2709 updating.store(false);
2710 call_deferred(SNAME("thread_end"));
2711}
2712
2713void RichTextLabel::_thread_end() {
2714 set_physics_process_internal(false);
2715 if (!scroll_visible) {
2716 vscroll->hide();
2717 }
2718 if (is_visible_in_tree()) {
2719 queue_redraw();
2720 }
2721}
2722
2723void RichTextLabel::_stop_thread() {
2724 if (threaded) {
2725 stop_thread.store(true);
2726 if (task != WorkerThreadPool::INVALID_TASK_ID) {
2727 WorkerThreadPool::get_singleton()->wait_for_task_completion(task);
2728 task = WorkerThreadPool::INVALID_TASK_ID;
2729 }
2730 }
2731}
2732
2733int RichTextLabel::get_pending_paragraphs() const {
2734 int to_line = main->first_invalid_line.load();
2735 int lines = main->lines.size();
2736
2737 return lines - to_line;
2738}
2739
2740bool RichTextLabel::is_ready() const {
2741 const_cast<RichTextLabel *>(this)->_validate_line_caches();
2742
2743 if (updating.load()) {
2744 return false;
2745 }
2746 return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size());
2747}
2748
2749bool RichTextLabel::is_updating() const {
2750 return updating.load() || validating.load();
2751}
2752
2753void RichTextLabel::set_threaded(bool p_threaded) {
2754 if (threaded != p_threaded) {
2755 _stop_thread();
2756 threaded = p_threaded;
2757 queue_redraw();
2758 }
2759}
2760
2761bool RichTextLabel::is_threaded() const {
2762 return threaded;
2763}
2764
2765void RichTextLabel::set_progress_bar_delay(int p_delay_ms) {
2766 progress_delay = p_delay_ms;
2767}
2768
2769int RichTextLabel::get_progress_bar_delay() const {
2770 return progress_delay;
2771}
2772
2773_FORCE_INLINE_ float RichTextLabel::_update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height) {
2774 updating_scroll = true;
2775
2776 float total_height = p_total_height;
2777 bool exceeds = p_total_height > p_ctrl_height && scroll_active;
2778 if (exceeds != scroll_visible) {
2779 if (exceeds) {
2780 scroll_visible = true;
2781 scroll_w = vscroll->get_combined_minimum_size().width;
2782 vscroll->show();
2783 vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
2784 } else {
2785 scroll_visible = false;
2786 scroll_w = 0;
2787 }
2788
2789 main->first_resized_line.store(0);
2790
2791 total_height = 0;
2792 for (int j = 0; j <= p_idx; j++) {
2793 total_height = _resize_line(main, j, theme_cache.normal_font, theme_cache.normal_font_size, p_width - scroll_w, total_height);
2794
2795 main->first_resized_line.store(j);
2796 }
2797 }
2798 vscroll->set_max(total_height);
2799 vscroll->set_page(p_text_rect_height);
2800 if (scroll_follow && scroll_following) {
2801 vscroll->set_value(total_height);
2802 } else {
2803 vscroll->set_value(p_old_scroll);
2804 }
2805 updating_scroll = false;
2806
2807 return total_height;
2808}
2809
2810bool RichTextLabel::_validate_line_caches() {
2811 if (updating.load()) {
2812 return false;
2813 }
2814 validating.store(true);
2815 if (main->first_invalid_line.load() == (int)main->lines.size()) {
2816 MutexLock data_lock(data_mutex);
2817 Rect2 text_rect = _get_text_rect();
2818
2819 float ctrl_height = get_size().height;
2820
2821 // Update fonts.
2822 float old_scroll = vscroll->get_value();
2823 if (main->first_invalid_font_line.load() != (int)main->lines.size()) {
2824 for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) {
2825 _update_line_font(main, i, theme_cache.normal_font, theme_cache.normal_font_size);
2826 }
2827 main->first_resized_line.store(main->first_invalid_font_line.load());
2828 main->first_invalid_font_line.store(main->lines.size());
2829 }
2830
2831 if (main->first_resized_line.load() == (int)main->lines.size()) {
2832 vscroll->set_value(old_scroll);
2833 validating.store(false);
2834 if (!scroll_visible) {
2835 vscroll->hide();
2836 }
2837 return true;
2838 }
2839
2840 // Resize lines without reshaping.
2841 int fi = main->first_resized_line.load();
2842
2843 float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]);
2844 for (int i = fi; i < (int)main->lines.size(); i++) {
2845 total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height);
2846 total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height);
2847 main->first_resized_line.store(i);
2848 }
2849
2850 main->first_resized_line.store(main->lines.size());
2851
2852 if (fit_content) {
2853 update_minimum_size();
2854 }
2855 validating.store(false);
2856 if (!scroll_visible) {
2857 vscroll->hide();
2858 }
2859 return true;
2860 }
2861 validating.store(false);
2862 stop_thread.store(false);
2863 if (threaded) {
2864 updating.store(true);
2865 loaded.store(true);
2866 task = WorkerThreadPool::get_singleton()->add_template_task(this, &RichTextLabel::_thread_function, nullptr, true, vformat("RichTextLabelShape:%x", (int64_t)get_instance_id()));
2867 set_physics_process_internal(true);
2868 loading_started = OS::get_singleton()->get_ticks_msec();
2869 return false;
2870 } else {
2871 updating.store(true);
2872 _process_line_caches();
2873 updating.store(false);
2874 if (!scroll_visible) {
2875 vscroll->hide();
2876 }
2877 queue_redraw();
2878 return true;
2879 }
2880}
2881
2882void RichTextLabel::_process_line_caches() {
2883 // Shape invalid lines.
2884 if (!is_inside_tree()) {
2885 return;
2886 }
2887
2888 MutexLock data_lock(data_mutex);
2889 Rect2 text_rect = _get_text_rect();
2890
2891 float ctrl_height = get_size().height;
2892 int fi = main->first_invalid_line.load();
2893 int total_chars = main->lines[fi].char_offset;
2894 float old_scroll = vscroll->get_value();
2895
2896 float total_height = 0;
2897 if (fi != 0) {
2898 int sr = MIN(main->first_invalid_font_line.load(), main->first_resized_line.load());
2899
2900 // Update fonts.
2901 for (int i = main->first_invalid_font_line.load(); i < fi; i++) {
2902 _update_line_font(main, i, theme_cache.normal_font, theme_cache.normal_font_size);
2903
2904 main->first_invalid_font_line.store(i);
2905
2906 if (stop_thread.load()) {
2907 return;
2908 }
2909 }
2910
2911 // Resize lines without reshaping.
2912 if (sr != 0) {
2913 total_height = _calculate_line_vertical_offset(main->lines[sr - 1]);
2914 }
2915
2916 for (int i = sr; i < fi; i++) {
2917 total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height);
2918 total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height);
2919
2920 main->first_resized_line.store(i);
2921
2922 if (stop_thread.load()) {
2923 return;
2924 }
2925 }
2926 }
2927
2928 total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]);
2929 for (int i = fi; i < (int)main->lines.size(); i++) {
2930 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);
2931 total_height = _update_scroll_exceeds(total_height, ctrl_height, text_rect.get_size().width, i, old_scroll, text_rect.size.height);
2932
2933 main->first_invalid_line.store(i);
2934 main->first_resized_line.store(i);
2935 main->first_invalid_font_line.store(i);
2936
2937 if (stop_thread.load()) {
2938 return;
2939 }
2940 loaded.store(double(i) / double(main->lines.size()));
2941 }
2942
2943 main->first_invalid_line.store(main->lines.size());
2944 main->first_resized_line.store(main->lines.size());
2945 main->first_invalid_font_line.store(main->lines.size());
2946
2947 if (fit_content) {
2948 update_minimum_size();
2949 }
2950 emit_signal(SNAME("finished"));
2951}
2952
2953void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) {
2954 if ((int)p_frame->lines.size() - 1 <= p_frame->first_invalid_line) {
2955 p_frame->first_invalid_line = (int)p_frame->lines.size() - 1;
2956 }
2957}
2958
2959void RichTextLabel::add_text(const String &p_text) {
2960 _stop_thread();
2961 MutexLock data_lock(data_mutex);
2962
2963 if (current->type == ITEM_TABLE) {
2964 return; //can't add anything here
2965 }
2966
2967 int pos = 0;
2968
2969 while (pos < p_text.length()) {
2970 int end = p_text.find("\n", pos);
2971 String line;
2972 bool eol = false;
2973 if (end == -1) {
2974 end = p_text.length();
2975 } else {
2976 eol = true;
2977 }
2978
2979 if (pos == 0 && end == p_text.length()) {
2980 line = p_text;
2981 } else {
2982 line = p_text.substr(pos, end - pos);
2983 }
2984
2985 if (line.length() > 0) {
2986 if (current->subitems.size() && current->subitems.back()->get()->type == ITEM_TEXT) {
2987 //append text condition!
2988 ItemText *ti = static_cast<ItemText *>(current->subitems.back()->get());
2989 ti->text += line;
2990 _invalidate_current_line(main);
2991
2992 } else {
2993 //append item condition
2994 ItemText *item = memnew(ItemText);
2995 item->text = line;
2996 _add_item(item, false);
2997 }
2998 }
2999
3000 if (eol) {
3001 ItemNewline *item = memnew(ItemNewline);
3002 item->line = current_frame->lines.size();
3003 _add_item(item, false);
3004 current_frame->lines.resize(current_frame->lines.size() + 1);
3005 if (item->type != ITEM_NEWLINE) {
3006 current_frame->lines[current_frame->lines.size() - 1].from = item;
3007 }
3008 _invalidate_current_line(current_frame);
3009 }
3010
3011 pos = end + 1;
3012 }
3013 queue_redraw();
3014}
3015
3016void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) {
3017 p_item->parent = current;
3018 p_item->E = current->subitems.push_back(p_item);
3019 p_item->index = current_idx++;
3020 p_item->char_ofs = current_char_ofs;
3021 if (p_item->type == ITEM_TEXT) {
3022 ItemText *t = static_cast<ItemText *>(p_item);
3023 current_char_ofs += t->text.length();
3024 } else if (p_item->type == ITEM_IMAGE) {
3025 current_char_ofs++;
3026 }
3027
3028 if (p_enter) {
3029 current = p_item;
3030 }
3031
3032 if (p_ensure_newline) {
3033 Item *from = current_frame->lines[current_frame->lines.size() - 1].from;
3034 // only create a new line for Item types that generate content/layout, ignore those that represent formatting/styling
3035 if (_find_layout_subitem(from, p_item)) {
3036 _invalidate_current_line(current_frame);
3037 current_frame->lines.resize(current_frame->lines.size() + 1);
3038 }
3039 }
3040
3041 if (current_frame->lines[current_frame->lines.size() - 1].from == nullptr) {
3042 current_frame->lines[current_frame->lines.size() - 1].from = p_item;
3043 }
3044 p_item->line = current_frame->lines.size() - 1;
3045
3046 _invalidate_current_line(current_frame);
3047
3048 if (fit_content) {
3049 update_minimum_size();
3050 }
3051 queue_redraw();
3052}
3053
3054void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) {
3055 int size = p_item->subitems.size();
3056 if (size == 0) {
3057 p_item->parent->subitems.erase(p_item);
3058 // If a newline was erased, all lines AFTER the newline need to be decremented.
3059 if (p_item->type == ITEM_NEWLINE) {
3060 current_frame->lines.remove_at(p_line);
3061 if (p_line < (int)current_frame->lines.size() && current_frame->lines[p_line].from) {
3062 for (List<Item *>::Element *E = current_frame->lines[p_line].from->E; E; E = E->next()) {
3063 if (E->get()->line > p_subitem_line) {
3064 E->get()->line--;
3065 }
3066 }
3067 }
3068 }
3069 } else {
3070 // First, remove all child items for the provided item.
3071 while (p_item->subitems.size()) {
3072 _remove_item(p_item->subitems.front()->get(), p_line, p_subitem_line);
3073 }
3074 // Then remove the provided item itself.
3075 p_item->parent->subitems.erase(p_item);
3076 }
3077 memdelete(p_item);
3078}
3079
3080void 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) {
3081 _stop_thread();
3082 MutexLock data_lock(data_mutex);
3083
3084 if (current->type == ITEM_TABLE) {
3085 return;
3086 }
3087
3088 ERR_FAIL_COND(p_image.is_null());
3089 ERR_FAIL_COND(p_image->get_width() == 0);
3090 ERR_FAIL_COND(p_image->get_height() == 0);
3091 ItemImage *item = memnew(ItemImage);
3092
3093 if (p_region.has_area()) {
3094 Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture);
3095 atlas_tex->set_atlas(p_image);
3096 atlas_tex->set_region(p_region);
3097 item->image = atlas_tex;
3098 } else {
3099 item->image = p_image;
3100 }
3101
3102 item->color = p_color;
3103 item->inline_align = p_alignment;
3104
3105 if (p_width > 0) {
3106 // custom width
3107 item->size.width = p_width;
3108 if (p_height > 0) {
3109 // custom height
3110 item->size.height = p_height;
3111 } else {
3112 // calculate height to keep aspect ratio
3113 if (p_region.has_area()) {
3114 item->size.height = p_region.get_size().height * p_width / p_region.get_size().width;
3115 } else {
3116 item->size.height = p_image->get_height() * p_width / p_image->get_width();
3117 }
3118 }
3119 } else {
3120 if (p_height > 0) {
3121 // custom height
3122 item->size.height = p_height;
3123 // calculate width to keep aspect ratio
3124 if (p_region.has_area()) {
3125 item->size.width = p_region.get_size().width * p_height / p_region.get_size().height;
3126 } else {
3127 item->size.width = p_image->get_width() * p_height / p_image->get_height();
3128 }
3129 } else {
3130 if (p_region.has_area()) {
3131 // if the image has a region, keep the region size
3132 item->size = p_region.get_size();
3133 } else {
3134 // keep original width and height
3135 item->size = p_image->get_size();
3136 }
3137 }
3138 }
3139
3140 _add_item(item, false);
3141}
3142
3143void RichTextLabel::add_newline() {
3144 _stop_thread();
3145 MutexLock data_lock(data_mutex);
3146
3147 if (current->type == ITEM_TABLE) {
3148 return;
3149 }
3150 ItemNewline *item = memnew(ItemNewline);
3151 item->line = current_frame->lines.size();
3152 _add_item(item, false);
3153 current_frame->lines.resize(current_frame->lines.size() + 1);
3154 _invalidate_current_line(current_frame);
3155 queue_redraw();
3156}
3157
3158bool RichTextLabel::remove_paragraph(const int p_paragraph) {
3159 _stop_thread();
3160 MutexLock data_lock(data_mutex);
3161
3162 if (p_paragraph >= (int)current_frame->lines.size() || p_paragraph < 0) {
3163 return false;
3164 }
3165
3166 // Remove all subitems with the same line as that provided.
3167 Vector<List<Item *>::Element *> subitem_to_remove;
3168 if (current_frame->lines[p_paragraph].from) {
3169 for (List<Item *>::Element *E = current_frame->lines[p_paragraph].from->E; E; E = E->next()) {
3170 if (E->get()->line == p_paragraph) {
3171 subitem_to_remove.push_back(E);
3172 } else {
3173 break;
3174 }
3175 }
3176 }
3177
3178 bool had_newline = false;
3179 // Reverse for loop to remove items from the end first.
3180 for (int i = subitem_to_remove.size() - 1; i >= 0; i--) {
3181 List<Item *>::Element *subitem = subitem_to_remove[i];
3182 had_newline = had_newline || subitem->get()->type == ITEM_NEWLINE;
3183 _remove_item(subitem->get(), subitem->get()->line, p_paragraph);
3184 }
3185
3186 if (!had_newline) {
3187 current_frame->lines.remove_at(p_paragraph);
3188 }
3189
3190 if (current_frame->lines.is_empty()) {
3191 current_frame->lines.resize(1);
3192 }
3193
3194 if (p_paragraph == 0 && current->subitems.size() > 0) {
3195 main->lines[0].from = main;
3196 }
3197
3198 main->first_invalid_line.store(MIN(main->first_invalid_line.load(), p_paragraph));
3199 main->first_resized_line.store(MIN(main->first_resized_line.load(), p_paragraph));
3200 main->first_invalid_font_line.store(MIN(main->first_invalid_font_line.load(), p_paragraph));
3201 queue_redraw();
3202
3203 return true;
3204}
3205
3206void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins, const Color &p_color, int p_ol_size, const Color &p_ol_color) {
3207 _stop_thread();
3208 MutexLock data_lock(data_mutex);
3209
3210 ERR_FAIL_COND(current->type == ITEM_TABLE);
3211 ERR_FAIL_COND(p_string.is_empty());
3212 ERR_FAIL_COND(p_font.is_null());
3213 ERR_FAIL_COND(p_size <= 0);
3214
3215 ItemDropcap *item = memnew(ItemDropcap);
3216
3217 item->text = p_string;
3218 item->font = p_font;
3219 item->font_size = p_size;
3220 item->color = p_color;
3221 item->ol_size = p_ol_size;
3222 item->ol_color = p_ol_color;
3223 item->dropcap_margins = p_dropcap_margins;
3224 _add_item(item, false);
3225}
3226
3227void RichTextLabel::_push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size) {
3228 _stop_thread();
3229 MutexLock data_lock(data_mutex);
3230
3231 ERR_FAIL_COND(current->type == ITEM_TABLE);
3232 ItemFont *item = memnew(ItemFont);
3233
3234 item->def_font = p_def_font;
3235 item->variation = true;
3236 item->font = p_font;
3237 item->font_size = p_size;
3238 item->def_size = (p_size <= 0);
3239 _add_item(item, true);
3240}
3241
3242void RichTextLabel::_push_def_font(DefaultFont p_def_font) {
3243 _stop_thread();
3244 MutexLock data_lock(data_mutex);
3245
3246 ERR_FAIL_COND(current->type == ITEM_TABLE);
3247 ItemFont *item = memnew(ItemFont);
3248
3249 item->def_font = p_def_font;
3250 item->def_size = true;
3251 _add_item(item, true);
3252}
3253
3254void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) {
3255 _stop_thread();
3256 MutexLock data_lock(data_mutex);
3257
3258 ERR_FAIL_COND(current->type == ITEM_TABLE);
3259 ERR_FAIL_COND(p_font.is_null());
3260 ItemFont *item = memnew(ItemFont);
3261
3262 item->font = p_font;
3263 item->font_size = p_size;
3264 _add_item(item, true);
3265}
3266
3267void RichTextLabel::push_normal() {
3268 ERR_FAIL_COND(theme_cache.normal_font.is_null());
3269
3270 _push_def_font(NORMAL_FONT);
3271}
3272
3273void RichTextLabel::push_bold() {
3274 ERR_FAIL_COND(theme_cache.bold_font.is_null());
3275
3276 ItemFont *item_font = _find_font(current);
3277 _push_def_font((item_font && item_font->def_font == ITALICS_FONT) ? BOLD_ITALICS_FONT : BOLD_FONT);
3278}
3279
3280void RichTextLabel::push_bold_italics() {
3281 ERR_FAIL_COND(theme_cache.bold_italics_font.is_null());
3282
3283 _push_def_font(BOLD_ITALICS_FONT);
3284}
3285
3286void RichTextLabel::push_italics() {
3287 ERR_FAIL_COND(theme_cache.italics_font.is_null());
3288
3289 ItemFont *item_font = _find_font(current);
3290 _push_def_font((item_font && item_font->def_font == BOLD_FONT) ? BOLD_ITALICS_FONT : ITALICS_FONT);
3291}
3292
3293void RichTextLabel::push_mono() {
3294 ERR_FAIL_COND(theme_cache.mono_font.is_null());
3295
3296 _push_def_font(MONO_FONT);
3297}
3298
3299void RichTextLabel::push_font_size(int p_font_size) {
3300 _stop_thread();
3301 MutexLock data_lock(data_mutex);
3302
3303 ERR_FAIL_COND(current->type == ITEM_TABLE);
3304 ItemFontSize *item = memnew(ItemFontSize);
3305
3306 item->font_size = p_font_size;
3307 _add_item(item, true);
3308}
3309
3310void RichTextLabel::push_outline_size(int p_ol_size) {
3311 _stop_thread();
3312 MutexLock data_lock(data_mutex);
3313
3314 ERR_FAIL_COND(current->type == ITEM_TABLE);
3315 ItemOutlineSize *item = memnew(ItemOutlineSize);
3316
3317 item->outline_size = p_ol_size;
3318 _add_item(item, true);
3319}
3320
3321void RichTextLabel::push_color(const Color &p_color) {
3322 _stop_thread();
3323 MutexLock data_lock(data_mutex);
3324
3325 ERR_FAIL_COND(current->type == ITEM_TABLE);
3326 ItemColor *item = memnew(ItemColor);
3327
3328 item->color = p_color;
3329 _add_item(item, true);
3330}
3331
3332void RichTextLabel::push_outline_color(const Color &p_color) {
3333 _stop_thread();
3334 MutexLock data_lock(data_mutex);
3335
3336 ERR_FAIL_COND(current->type == ITEM_TABLE);
3337 ItemOutlineColor *item = memnew(ItemOutlineColor);
3338
3339 item->color = p_color;
3340 _add_item(item, true);
3341}
3342
3343void RichTextLabel::push_underline() {
3344 _stop_thread();
3345 MutexLock data_lock(data_mutex);
3346
3347 ERR_FAIL_COND(current->type == ITEM_TABLE);
3348 ItemUnderline *item = memnew(ItemUnderline);
3349
3350 _add_item(item, true);
3351}
3352
3353void RichTextLabel::push_strikethrough() {
3354 _stop_thread();
3355 MutexLock data_lock(data_mutex);
3356
3357 ERR_FAIL_COND(current->type == ITEM_TABLE);
3358 ItemStrikethrough *item = memnew(ItemStrikethrough);
3359
3360 _add_item(item, true);
3361}
3362
3363void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser, BitField<TextServer::JustificationFlag> p_jst_flags, const PackedFloat32Array &p_tab_stops) {
3364 _stop_thread();
3365 MutexLock data_lock(data_mutex);
3366
3367 ERR_FAIL_COND(current->type == ITEM_TABLE);
3368
3369 ItemParagraph *item = memnew(ItemParagraph);
3370 item->alignment = p_alignment;
3371 item->direction = p_direction;
3372 item->language = p_language;
3373 item->st_parser = p_st_parser;
3374 item->jst_flags = p_jst_flags;
3375 item->tab_stops = p_tab_stops;
3376 _add_item(item, true, true);
3377}
3378
3379void RichTextLabel::push_indent(int p_level) {
3380 _stop_thread();
3381 MutexLock data_lock(data_mutex);
3382
3383 ERR_FAIL_COND(current->type == ITEM_TABLE);
3384 ERR_FAIL_COND(p_level < 0);
3385
3386 ItemIndent *item = memnew(ItemIndent);
3387 item->level = p_level;
3388 _add_item(item, true, true);
3389}
3390
3391void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet) {
3392 _stop_thread();
3393 MutexLock data_lock(data_mutex);
3394
3395 ERR_FAIL_COND(current->type == ITEM_TABLE);
3396 ERR_FAIL_COND(p_level < 0);
3397
3398 ItemList *item = memnew(ItemList);
3399
3400 item->list_type = p_list;
3401 item->level = p_level;
3402 item->capitalize = p_capitalize;
3403 item->bullet = p_bullet;
3404 _add_item(item, true, true);
3405}
3406
3407void RichTextLabel::push_meta(const Variant &p_meta) {
3408 _stop_thread();
3409 MutexLock data_lock(data_mutex);
3410
3411 ERR_FAIL_COND(current->type == ITEM_TABLE);
3412 ItemMeta *item = memnew(ItemMeta);
3413
3414 item->meta = p_meta;
3415 _add_item(item, true);
3416}
3417
3418void RichTextLabel::push_language(const String &p_language) {
3419 _stop_thread();
3420 MutexLock data_lock(data_mutex);
3421
3422 ERR_FAIL_COND(current->type == ITEM_TABLE);
3423 ItemLanguage *item = memnew(ItemLanguage);
3424
3425 item->language = p_language;
3426 _add_item(item, true);
3427}
3428
3429void RichTextLabel::push_hint(const String &p_string) {
3430 _stop_thread();
3431 MutexLock data_lock(data_mutex);
3432
3433 ERR_FAIL_COND(current->type == ITEM_TABLE);
3434 ItemHint *item = memnew(ItemHint);
3435
3436 item->description = p_string;
3437 _add_item(item, true);
3438}
3439
3440void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment, int p_align_to_row) {
3441 _stop_thread();
3442 MutexLock data_lock(data_mutex);
3443
3444 ERR_FAIL_COND(current->type == ITEM_TABLE);
3445 ERR_FAIL_COND(p_columns < 1);
3446 ItemTable *item = memnew(ItemTable);
3447
3448 item->columns.resize(p_columns);
3449 item->total_width = 0;
3450 item->inline_align = p_alignment;
3451 item->align_to_row = p_align_to_row;
3452 for (int i = 0; i < (int)item->columns.size(); i++) {
3453 item->columns[i].expand = false;
3454 item->columns[i].expand_ratio = 1;
3455 }
3456 _add_item(item, true, false);
3457}
3458
3459void RichTextLabel::push_fade(int p_start_index, int p_length) {
3460 _stop_thread();
3461 MutexLock data_lock(data_mutex);
3462
3463 ERR_FAIL_COND(current->type == ITEM_TABLE);
3464 ItemFade *item = memnew(ItemFade);
3465 item->starting_index = p_start_index;
3466 item->length = p_length;
3467 _add_item(item, true);
3468}
3469
3470void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f, bool p_connected = true) {
3471 _stop_thread();
3472 MutexLock data_lock(data_mutex);
3473
3474 ERR_FAIL_COND(current->type == ITEM_TABLE);
3475 ItemShake *item = memnew(ItemShake);
3476 item->strength = p_strength;
3477 item->rate = p_rate;
3478 item->connected = p_connected;
3479 _add_item(item, true);
3480}
3481
3482void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f, bool p_connected = true) {
3483 _stop_thread();
3484 MutexLock data_lock(data_mutex);
3485
3486 ERR_FAIL_COND(current->type == ITEM_TABLE);
3487 ItemWave *item = memnew(ItemWave);
3488 item->frequency = p_frequency;
3489 item->amplitude = p_amplitude;
3490 item->connected = p_connected;
3491 _add_item(item, true);
3492}
3493
3494void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f, bool p_connected = true) {
3495 _stop_thread();
3496 MutexLock data_lock(data_mutex);
3497
3498 ERR_FAIL_COND(current->type == ITEM_TABLE);
3499 ItemTornado *item = memnew(ItemTornado);
3500 item->frequency = p_frequency;
3501 item->radius = p_radius;
3502 item->connected = p_connected;
3503 _add_item(item, true);
3504}
3505
3506void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_frequency) {
3507 _stop_thread();
3508 MutexLock data_lock(data_mutex);
3509
3510 ERR_FAIL_COND(current->type == ITEM_TABLE);
3511 ItemRainbow *item = memnew(ItemRainbow);
3512 item->frequency = p_frequency;
3513 item->saturation = p_saturation;
3514 item->value = p_value;
3515 _add_item(item, true);
3516}
3517
3518void RichTextLabel::push_pulse(const Color &p_color, float p_frequency, float p_ease) {
3519 _stop_thread();
3520 MutexLock data_lock(data_mutex);
3521
3522 ItemPulse *item = memnew(ItemPulse);
3523 item->color = p_color;
3524 item->frequency = p_frequency;
3525 item->ease = p_ease;
3526 _add_item(item, true);
3527}
3528
3529void RichTextLabel::push_bgcolor(const Color &p_color) {
3530 _stop_thread();
3531 MutexLock data_lock(data_mutex);
3532
3533 ERR_FAIL_COND(current->type == ITEM_TABLE);
3534 ItemBGColor *item = memnew(ItemBGColor);
3535
3536 item->color = p_color;
3537 _add_item(item, true);
3538}
3539
3540void RichTextLabel::push_fgcolor(const Color &p_color) {
3541 _stop_thread();
3542 MutexLock data_lock(data_mutex);
3543
3544 ERR_FAIL_COND(current->type == ITEM_TABLE);
3545 ItemFGColor *item = memnew(ItemFGColor);
3546
3547 item->color = p_color;
3548 _add_item(item, true);
3549}
3550
3551void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) {
3552 _stop_thread();
3553 MutexLock data_lock(data_mutex);
3554
3555 ERR_FAIL_COND(current->type == ITEM_TABLE);
3556 ItemCustomFX *item = memnew(ItemCustomFX);
3557 item->custom_effect = p_custom_effect;
3558 item->char_fx_transform->environment = p_environment;
3559 _add_item(item, true);
3560
3561 set_process_internal(true);
3562}
3563
3564void RichTextLabel::push_context() {
3565 _stop_thread();
3566 MutexLock data_lock(data_mutex);
3567
3568 ERR_FAIL_COND(current->type == ITEM_TABLE);
3569 ItemContext *item = memnew(ItemContext);
3570 _add_item(item, true);
3571}
3572
3573void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) {
3574 _stop_thread();
3575 MutexLock data_lock(data_mutex);
3576
3577 ERR_FAIL_COND(current->type != ITEM_TABLE);
3578
3579 ItemTable *table = static_cast<ItemTable *>(current);
3580 ERR_FAIL_INDEX(p_column, (int)table->columns.size());
3581 table->columns[p_column].expand = p_expand;
3582 table->columns[p_column].expand_ratio = p_ratio;
3583}
3584
3585void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg) {
3586 _stop_thread();
3587 MutexLock data_lock(data_mutex);
3588
3589 ERR_FAIL_COND(current->type != ITEM_FRAME);
3590
3591 ItemFrame *cell = static_cast<ItemFrame *>(current);
3592 ERR_FAIL_COND(!cell->cell);
3593 cell->odd_row_bg = p_odd_row_bg;
3594 cell->even_row_bg = p_even_row_bg;
3595}
3596
3597void RichTextLabel::set_cell_border_color(const Color &p_color) {
3598 _stop_thread();
3599 MutexLock data_lock(data_mutex);
3600
3601 ERR_FAIL_COND(current->type != ITEM_FRAME);
3602
3603 ItemFrame *cell = static_cast<ItemFrame *>(current);
3604 ERR_FAIL_COND(!cell->cell);
3605 cell->border = p_color;
3606}
3607
3608void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size) {
3609 _stop_thread();
3610 MutexLock data_lock(data_mutex);
3611
3612 ERR_FAIL_COND(current->type != ITEM_FRAME);
3613
3614 ItemFrame *cell = static_cast<ItemFrame *>(current);
3615 ERR_FAIL_COND(!cell->cell);
3616 cell->min_size_over = p_min_size;
3617 cell->max_size_over = p_max_size;
3618}
3619
3620void RichTextLabel::set_cell_padding(const Rect2 &p_padding) {
3621 _stop_thread();
3622 MutexLock data_lock(data_mutex);
3623
3624 ERR_FAIL_COND(current->type != ITEM_FRAME);
3625
3626 ItemFrame *cell = static_cast<ItemFrame *>(current);
3627 ERR_FAIL_COND(!cell->cell);
3628 cell->padding = p_padding;
3629}
3630
3631void RichTextLabel::push_cell() {
3632 _stop_thread();
3633 MutexLock data_lock(data_mutex);
3634
3635 ERR_FAIL_COND(current->type != ITEM_TABLE);
3636
3637 ItemFrame *item = memnew(ItemFrame);
3638 item->parent_frame = current_frame;
3639 _add_item(item, true);
3640 current_frame = item;
3641 item->cell = true;
3642 item->lines.resize(1);
3643 item->lines[0].from = nullptr;
3644 item->first_invalid_line.store(0); // parent frame last line ???
3645}
3646
3647int RichTextLabel::get_current_table_column() const {
3648 ERR_FAIL_COND_V(current->type != ITEM_TABLE, -1);
3649
3650 ItemTable *table = static_cast<ItemTable *>(current);
3651 return table->subitems.size() % table->columns.size();
3652}
3653
3654void RichTextLabel::pop() {
3655 _stop_thread();
3656 MutexLock data_lock(data_mutex);
3657
3658 ERR_FAIL_NULL(current->parent);
3659
3660 if (current->type == ITEM_FRAME) {
3661 current_frame = static_cast<ItemFrame *>(current)->parent_frame;
3662 }
3663 current = current->parent;
3664}
3665
3666void RichTextLabel::pop_context() {
3667 _stop_thread();
3668 MutexLock data_lock(data_mutex);
3669
3670 ERR_FAIL_NULL(current->parent);
3671
3672 while (current->parent && current != main) {
3673 if (current->type == ITEM_FRAME) {
3674 current_frame = static_cast<ItemFrame *>(current)->parent_frame;
3675 } else if (current->type == ITEM_CONTEXT) {
3676 current = current->parent;
3677 return;
3678 }
3679 current = current->parent;
3680 }
3681}
3682
3683void RichTextLabel::pop_all() {
3684 _stop_thread();
3685 MutexLock data_lock(data_mutex);
3686
3687 current = main;
3688 current_frame = main;
3689}
3690
3691void RichTextLabel::clear() {
3692 _stop_thread();
3693 MutexLock data_lock(data_mutex);
3694
3695 main->_clear_children();
3696 current = main;
3697 current_frame = main;
3698 main->lines.clear();
3699 main->lines.resize(1);
3700 main->first_invalid_line.store(0);
3701
3702 selection.click_frame = nullptr;
3703 selection.click_item = nullptr;
3704 deselect();
3705
3706 current_idx = 1;
3707 current_char_ofs = 0;
3708 if (scroll_follow) {
3709 scroll_following = true;
3710 }
3711
3712 if (fit_content) {
3713 update_minimum_size();
3714 }
3715}
3716
3717void RichTextLabel::set_tab_size(int p_spaces) {
3718 if (tab_size == p_spaces) {
3719 return;
3720 }
3721
3722 _stop_thread();
3723
3724 tab_size = p_spaces;
3725 main->first_resized_line.store(0);
3726 queue_redraw();
3727}
3728
3729int RichTextLabel::get_tab_size() const {
3730 return tab_size;
3731}
3732
3733void RichTextLabel::set_fit_content(bool p_enabled) {
3734 if (p_enabled == fit_content) {
3735 return;
3736 }
3737
3738 fit_content = p_enabled;
3739 update_minimum_size();
3740}
3741
3742bool RichTextLabel::is_fit_content_enabled() const {
3743 return fit_content;
3744}
3745
3746void RichTextLabel::set_meta_underline(bool p_underline) {
3747 if (underline_meta == p_underline) {
3748 return;
3749 }
3750
3751 underline_meta = p_underline;
3752 queue_redraw();
3753}
3754
3755bool RichTextLabel::is_meta_underlined() const {
3756 return underline_meta;
3757}
3758
3759void RichTextLabel::set_hint_underline(bool p_underline) {
3760 underline_hint = p_underline;
3761 queue_redraw();
3762}
3763
3764bool RichTextLabel::is_hint_underlined() const {
3765 return underline_hint;
3766}
3767
3768void RichTextLabel::set_offset(int p_pixel) {
3769 vscroll->set_value(p_pixel);
3770}
3771
3772void RichTextLabel::set_scroll_active(bool p_active) {
3773 if (scroll_active == p_active) {
3774 return;
3775 }
3776
3777 scroll_active = p_active;
3778 vscroll->set_drag_node_enabled(p_active);
3779 queue_redraw();
3780}
3781
3782bool RichTextLabel::is_scroll_active() const {
3783 return scroll_active;
3784}
3785
3786void RichTextLabel::set_scroll_follow(bool p_follow) {
3787 scroll_follow = p_follow;
3788 if (!vscroll->is_visible_in_tree() || vscroll->get_value() >= (vscroll->get_max() - vscroll->get_page())) {
3789 scroll_following = true;
3790 }
3791}
3792
3793bool RichTextLabel::is_scroll_following() const {
3794 return scroll_follow;
3795}
3796
3797void RichTextLabel::parse_bbcode(const String &p_bbcode) {
3798 clear();
3799 append_text(p_bbcode);
3800}
3801
3802void RichTextLabel::append_text(const String &p_bbcode) {
3803 _stop_thread();
3804 MutexLock data_lock(data_mutex);
3805
3806 int pos = 0;
3807
3808 List<String> tag_stack;
3809
3810 int indent_level = 0;
3811
3812 bool in_bold = false;
3813 bool in_italics = false;
3814 bool after_list_open_tag = false;
3815 bool after_list_close_tag = false;
3816
3817 set_process_internal(false);
3818
3819 while (pos <= p_bbcode.length()) {
3820 int brk_pos = p_bbcode.find("[", pos);
3821
3822 if (brk_pos < 0) {
3823 brk_pos = p_bbcode.length();
3824 }
3825
3826 String txt = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : "";
3827
3828 // Trim the first newline character, it may be added later as needed.
3829 if (after_list_close_tag || after_list_open_tag) {
3830 txt = txt.trim_prefix("\n");
3831 }
3832
3833 if (brk_pos == p_bbcode.length()) {
3834 // For tags that are not properly closed.
3835 if (txt.is_empty() && after_list_open_tag) {
3836 txt = "\n";
3837 }
3838
3839 if (!txt.is_empty()) {
3840 add_text(txt);
3841 }
3842 break; //nothing else to add
3843 }
3844
3845 int brk_end = p_bbcode.find("]", brk_pos + 1);
3846
3847 if (brk_end == -1) {
3848 //no close, add the rest
3849 txt += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos);
3850 add_text(txt);
3851 break;
3852 }
3853
3854 String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
3855 Vector<String> split_tag_block = tag.split(" ", false);
3856
3857 // Find optional parameters.
3858 String bbcode_name;
3859 typedef HashMap<String, String> OptionMap;
3860 OptionMap bbcode_options;
3861 if (!split_tag_block.is_empty()) {
3862 bbcode_name = split_tag_block[0];
3863 for (int i = 1; i < split_tag_block.size(); i++) {
3864 const String &expr = split_tag_block[i];
3865 int value_pos = expr.find("=");
3866 if (value_pos > -1) {
3867 bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote();
3868 }
3869 }
3870 } else {
3871 bbcode_name = tag;
3872 }
3873
3874 // Find main parameter.
3875 String bbcode_value;
3876 int main_value_pos = bbcode_name.find("=");
3877 if (main_value_pos > -1) {
3878 bbcode_value = bbcode_name.substr(main_value_pos + 1);
3879 bbcode_name = bbcode_name.substr(0, main_value_pos);
3880 }
3881
3882 if (tag.begins_with("/") && tag_stack.size()) {
3883 bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length());
3884
3885 if (tag_stack.front()->get() == "b") {
3886 in_bold = false;
3887 }
3888 if (tag_stack.front()->get() == "i") {
3889 in_italics = false;
3890 }
3891 if ((tag_stack.front()->get() == "indent") || (tag_stack.front()->get() == "ol") || (tag_stack.front()->get() == "ul")) {
3892 indent_level--;
3893 }
3894
3895 if (!tag_ok) {
3896 txt += "[" + tag;
3897 add_text(txt);
3898 after_list_open_tag = false;
3899 after_list_close_tag = false;
3900 pos = brk_end;
3901 continue;
3902 }
3903
3904 if (txt.is_empty() && after_list_open_tag) {
3905 txt = "\n"; // Make empty list have at least one item.
3906 }
3907 after_list_open_tag = false;
3908
3909 if (tag == "/ol" || tag == "/ul") {
3910 if (!txt.is_empty()) {
3911 // Make sure text ends with a newline character, that is, the last item
3912 // will wrap at the end of block.
3913 if (!txt.ends_with("\n")) {
3914 txt += "\n";
3915 }
3916 } else if (!after_list_close_tag) {
3917 txt = "\n"; // Make the innermost list item wrap at the end of lists.
3918 }
3919 after_list_close_tag = true;
3920 } else {
3921 after_list_close_tag = false;
3922 }
3923
3924 if (!txt.is_empty()) {
3925 add_text(txt);
3926 }
3927
3928 tag_stack.pop_front();
3929 pos = brk_end + 1;
3930 if (tag != "/img" && tag != "/dropcap") {
3931 pop();
3932 }
3933 continue;
3934 }
3935
3936 if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) {
3937 if (txt.is_empty() && after_list_open_tag) {
3938 txt = "\n"; // Make each list have at least one item at the beginning.
3939 }
3940 after_list_open_tag = true;
3941 } else {
3942 after_list_open_tag = false;
3943 }
3944 if (!txt.is_empty()) {
3945 add_text(txt);
3946 }
3947 after_list_close_tag = false;
3948
3949 if (tag == "b") {
3950 //use bold font
3951 in_bold = true;
3952 if (in_italics) {
3953 _push_def_font(BOLD_ITALICS_FONT);
3954 } else {
3955 _push_def_font(BOLD_FONT);
3956 }
3957 pos = brk_end + 1;
3958 tag_stack.push_front(tag);
3959 } else if (tag == "i") {
3960 //use italics font
3961 in_italics = true;
3962 if (in_bold) {
3963 _push_def_font(BOLD_ITALICS_FONT);
3964 } else {
3965 _push_def_font(ITALICS_FONT);
3966 }
3967 pos = brk_end + 1;
3968 tag_stack.push_front(tag);
3969 } else if (tag == "code") {
3970 //use monospace font
3971 _push_def_font(MONO_FONT);
3972 pos = brk_end + 1;
3973 tag_stack.push_front(tag);
3974 } else if (tag.begins_with("table=")) {
3975 Vector<String> subtag = tag.substr(6, tag.length()).split(",");
3976 _normalize_subtags(subtag);
3977
3978 int columns = subtag[0].to_int();
3979 if (columns < 1) {
3980 columns = 1;
3981 }
3982
3983 int alignment = INLINE_ALIGNMENT_TOP;
3984 if (subtag.size() > 2) {
3985 if (subtag[1] == "top" || subtag[1] == "t") {
3986 alignment = INLINE_ALIGNMENT_TOP_TO;
3987 } else if (subtag[1] == "center" || subtag[1] == "c") {
3988 alignment = INLINE_ALIGNMENT_CENTER_TO;
3989 } else if (subtag[1] == "baseline" || subtag[1] == "l") {
3990 alignment = INLINE_ALIGNMENT_BASELINE_TO;
3991 } else if (subtag[1] == "bottom" || subtag[1] == "b") {
3992 alignment = INLINE_ALIGNMENT_BOTTOM_TO;
3993 }
3994 if (subtag[2] == "top" || subtag[2] == "t") {
3995 alignment |= INLINE_ALIGNMENT_TO_TOP;
3996 } else if (subtag[2] == "center" || subtag[2] == "c") {
3997 alignment |= INLINE_ALIGNMENT_TO_CENTER;
3998 } else if (subtag[2] == "baseline" || subtag[2] == "l") {
3999 alignment |= INLINE_ALIGNMENT_TO_BASELINE;
4000 } else if (subtag[2] == "bottom" || subtag[2] == "b") {
4001 alignment |= INLINE_ALIGNMENT_TO_BOTTOM;
4002 }
4003 } else if (subtag.size() > 1) {
4004 if (subtag[1] == "top" || subtag[1] == "t") {
4005 alignment = INLINE_ALIGNMENT_TOP;
4006 } else if (subtag[1] == "center" || subtag[1] == "c") {
4007 alignment = INLINE_ALIGNMENT_CENTER;
4008 } else if (subtag[1] == "bottom" || subtag[1] == "b") {
4009 alignment = INLINE_ALIGNMENT_BOTTOM;
4010 }
4011 }
4012 int row = -1;
4013 if (subtag.size() > 3) {
4014 row = subtag[3].to_int();
4015 }
4016
4017 push_table(columns, (InlineAlignment)alignment, row);
4018 pos = brk_end + 1;
4019 tag_stack.push_front("table");
4020 } else if (tag == "cell") {
4021 push_cell();
4022 pos = brk_end + 1;
4023 tag_stack.push_front(tag);
4024 } else if (tag.begins_with("cell=")) {
4025 int ratio = tag.substr(5, tag.length()).to_int();
4026 if (ratio < 1) {
4027 ratio = 1;
4028 }
4029
4030 set_table_column_expand(get_current_table_column(), true, ratio);
4031 push_cell();
4032
4033 pos = brk_end + 1;
4034 tag_stack.push_front("cell");
4035 } else if (tag.begins_with("cell ")) {
4036 Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
4037 _normalize_subtags(subtag);
4038
4039 for (int i = 0; i < subtag.size(); i++) {
4040 Vector<String> subtag_a = subtag[i].split("=");
4041 _normalize_subtags(subtag_a);
4042
4043 if (subtag_a.size() == 2) {
4044 if (subtag_a[0] == "expand") {
4045 int ratio = subtag_a[1].to_int();
4046 if (ratio < 1) {
4047 ratio = 1;
4048 }
4049 set_table_column_expand(get_current_table_column(), true, ratio);
4050 }
4051 }
4052 }
4053 push_cell();
4054 const Color fallback_color = Color(0, 0, 0, 0);
4055 for (int i = 0; i < subtag.size(); i++) {
4056 Vector<String> subtag_a = subtag[i].split("=");
4057 _normalize_subtags(subtag_a);
4058
4059 if (subtag_a.size() == 2) {
4060 if (subtag_a[0] == "border") {
4061 Color color = Color::from_string(subtag_a[1], fallback_color);
4062 set_cell_border_color(color);
4063 } else if (subtag_a[0] == "bg") {
4064 Vector<String> subtag_b = subtag_a[1].split(",");
4065 _normalize_subtags(subtag_b);
4066
4067 if (subtag_b.size() == 2) {
4068 Color color1 = Color::from_string(subtag_b[0], fallback_color);
4069 Color color2 = Color::from_string(subtag_b[1], fallback_color);
4070 set_cell_row_background_color(color1, color2);
4071 }
4072 if (subtag_b.size() == 1) {
4073 Color color1 = Color::from_string(subtag_a[1], fallback_color);
4074 set_cell_row_background_color(color1, color1);
4075 }
4076 } else if (subtag_a[0] == "padding") {
4077 Vector<String> subtag_b = subtag_a[1].split(",");
4078 _normalize_subtags(subtag_b);
4079
4080 if (subtag_b.size() == 4) {
4081 set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float()));
4082 }
4083 }
4084 }
4085 }
4086
4087 pos = brk_end + 1;
4088 tag_stack.push_front("cell");
4089 } else if (tag == "u") {
4090 //use underline
4091 push_underline();
4092 pos = brk_end + 1;
4093 tag_stack.push_front(tag);
4094 } else if (tag == "s") {
4095 //use strikethrough
4096 push_strikethrough();
4097 pos = brk_end + 1;
4098 tag_stack.push_front(tag);
4099 } else if (tag == "lb") {
4100 add_text("[");
4101 pos = brk_end + 1;
4102 } else if (tag == "rb") {
4103 add_text("]");
4104 pos = brk_end + 1;
4105 } else if (tag == "lrm") {
4106 add_text(String::chr(0x200E));
4107 pos = brk_end + 1;
4108 } else if (tag == "rlm") {
4109 add_text(String::chr(0x200F));
4110 pos = brk_end + 1;
4111 } else if (tag == "lre") {
4112 add_text(String::chr(0x202A));
4113 pos = brk_end + 1;
4114 } else if (tag == "rle") {
4115 add_text(String::chr(0x202B));
4116 pos = brk_end + 1;
4117 } else if (tag == "lro") {
4118 add_text(String::chr(0x202D));
4119 pos = brk_end + 1;
4120 } else if (tag == "rlo") {
4121 add_text(String::chr(0x202E));
4122 pos = brk_end + 1;
4123 } else if (tag == "pdf") {
4124 add_text(String::chr(0x202C));
4125 pos = brk_end + 1;
4126 } else if (tag == "alm") {
4127 add_text(String::chr(0x061c));
4128 pos = brk_end + 1;
4129 } else if (tag == "lri") {
4130 add_text(String::chr(0x2066));
4131 pos = brk_end + 1;
4132 } else if (tag == "rli") {
4133 add_text(String::chr(0x2027));
4134 pos = brk_end + 1;
4135 } else if (tag == "fsi") {
4136 add_text(String::chr(0x2068));
4137 pos = brk_end + 1;
4138 } else if (tag == "pdi") {
4139 add_text(String::chr(0x2069));
4140 pos = brk_end + 1;
4141 } else if (tag == "zwj") {
4142 add_text(String::chr(0x200D));
4143 pos = brk_end + 1;
4144 } else if (tag == "zwnj") {
4145 add_text(String::chr(0x200C));
4146 pos = brk_end + 1;
4147 } else if (tag == "wj") {
4148 add_text(String::chr(0x2060));
4149 pos = brk_end + 1;
4150 } else if (tag == "shy") {
4151 add_text(String::chr(0x00AD));
4152 pos = brk_end + 1;
4153 } else if (tag == "center") {
4154 push_paragraph(HORIZONTAL_ALIGNMENT_CENTER);
4155 pos = brk_end + 1;
4156 tag_stack.push_front(tag);
4157 } else if (tag == "fill") {
4158 push_paragraph(HORIZONTAL_ALIGNMENT_FILL);
4159 pos = brk_end + 1;
4160 tag_stack.push_front(tag);
4161 } else if (tag == "left") {
4162 push_paragraph(HORIZONTAL_ALIGNMENT_LEFT);
4163 pos = brk_end + 1;
4164 tag_stack.push_front(tag);
4165 } else if (tag == "right") {
4166 push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT);
4167 pos = brk_end + 1;
4168 tag_stack.push_front(tag);
4169 } else if (tag == "ul") {
4170 indent_level++;
4171 push_list(indent_level, LIST_DOTS, false);
4172 pos = brk_end + 1;
4173 tag_stack.push_front(tag);
4174 } else if (tag.begins_with("ul bullet=")) {
4175 String bullet = tag.substr(10, 1);
4176 indent_level++;
4177 push_list(indent_level, LIST_DOTS, false, bullet);
4178 pos = brk_end + 1;
4179 tag_stack.push_front("ul");
4180 } else if ((tag == "ol") || (tag == "ol type=1")) {
4181 indent_level++;
4182 push_list(indent_level, LIST_NUMBERS, false);
4183 pos = brk_end + 1;
4184 tag_stack.push_front("ol");
4185 } else if (tag == "ol type=a") {
4186 indent_level++;
4187 push_list(indent_level, LIST_LETTERS, false);
4188 pos = brk_end + 1;
4189 tag_stack.push_front("ol");
4190 } else if (tag == "ol type=A") {
4191 indent_level++;
4192 push_list(indent_level, LIST_LETTERS, true);
4193 pos = brk_end + 1;
4194 tag_stack.push_front("ol");
4195 } else if (tag == "ol type=i") {
4196 indent_level++;
4197 push_list(indent_level, LIST_ROMAN, false);
4198 pos = brk_end + 1;
4199 tag_stack.push_front("ol");
4200 } else if (tag == "ol type=I") {
4201 indent_level++;
4202 push_list(indent_level, LIST_ROMAN, true);
4203 pos = brk_end + 1;
4204 tag_stack.push_front("ol");
4205 } else if (tag == "indent") {
4206 indent_level++;
4207 push_indent(indent_level);
4208 pos = brk_end + 1;
4209 tag_stack.push_front(tag);
4210 } else if (tag.begins_with("lang=")) {
4211 String lang = tag.substr(5, tag.length()).unquote();
4212 push_language(lang);
4213 pos = brk_end + 1;
4214 tag_stack.push_front("lang");
4215 } else if (tag == "p") {
4216 push_paragraph(HORIZONTAL_ALIGNMENT_LEFT);
4217 pos = brk_end + 1;
4218 tag_stack.push_front("p");
4219 } else if (tag.begins_with("p ")) {
4220 Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
4221 _normalize_subtags(subtag);
4222
4223 HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
4224 Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
4225 String lang;
4226 PackedFloat32Array tab_stops;
4227 TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
4228 BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags;
4229 for (int i = 0; i < subtag.size(); i++) {
4230 Vector<String> subtag_a = subtag[i].split("=");
4231 _normalize_subtags(subtag_a);
4232
4233 if (subtag_a.size() == 2) {
4234 if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") {
4235 Vector<String> subtag_b = subtag_a[1].split(",");
4236 for (const String &E : subtag_b) {
4237 if (E == "kashida" || E == "k") {
4238 jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA);
4239 } else if (E == "word" || E == "w") {
4240 jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND);
4241 } else if (E == "trim" || E == "tr") {
4242 jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
4243 } else if (E == "after_last_tab" || E == "lt") {
4244 jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
4245 } else if (E == "skip_last" || E == "sl") {
4246 jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE);
4247 } else if (E == "skip_last_with_chars" || E == "sv") {
4248 jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS);
4249 } else if (E == "do_not_skip_singe" || E == "ns") {
4250 jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE);
4251 }
4252 }
4253 } else if (subtag_a[0] == "tab_stops") {
4254 Vector<String> splitters;
4255 splitters.push_back(",");
4256 splitters.push_back(";");
4257 tab_stops = subtag_a[1].split_floats_mk(splitters);
4258 } else if (subtag_a[0] == "align") {
4259 if (subtag_a[1] == "l" || subtag_a[1] == "left") {
4260 alignment = HORIZONTAL_ALIGNMENT_LEFT;
4261 } else if (subtag_a[1] == "c" || subtag_a[1] == "center") {
4262 alignment = HORIZONTAL_ALIGNMENT_CENTER;
4263 } else if (subtag_a[1] == "r" || subtag_a[1] == "right") {
4264 alignment = HORIZONTAL_ALIGNMENT_RIGHT;
4265 } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") {
4266 alignment = HORIZONTAL_ALIGNMENT_FILL;
4267 }
4268 } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") {
4269 if (subtag_a[1] == "a" || subtag_a[1] == "auto") {
4270 dir = Control::TEXT_DIRECTION_AUTO;
4271 } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") {
4272 dir = Control::TEXT_DIRECTION_LTR;
4273 } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") {
4274 dir = Control::TEXT_DIRECTION_RTL;
4275 }
4276 } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") {
4277 lang = subtag_a[1];
4278 } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") {
4279 if (subtag_a[1] == "d" || subtag_a[1] == "default") {
4280 st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT;
4281 } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") {
4282 st_parser_type = TextServer::STRUCTURED_TEXT_URI;
4283 } else if (subtag_a[1] == "f" || subtag_a[1] == "file") {
4284 st_parser_type = TextServer::STRUCTURED_TEXT_FILE;
4285 } else if (subtag_a[1] == "e" || subtag_a[1] == "email") {
4286 st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL;
4287 } else if (subtag_a[1] == "l" || subtag_a[1] == "list") {
4288 st_parser_type = TextServer::STRUCTURED_TEXT_LIST;
4289 } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") {
4290 st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT;
4291 } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") {
4292 st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM;
4293 }
4294 }
4295 }
4296 }
4297 push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops);
4298 pos = brk_end + 1;
4299 tag_stack.push_front("p");
4300 } else if (tag == "url") {
4301 int end = p_bbcode.find("[", brk_end);
4302 if (end == -1) {
4303 end = p_bbcode.length();
4304 }
4305 String url = p_bbcode.substr(brk_end + 1, end - brk_end - 1).unquote();
4306 push_meta(url);
4307
4308 pos = brk_end + 1;
4309 tag_stack.push_front(tag);
4310
4311 } else if (tag.begins_with("url=")) {
4312 String url = tag.substr(4, tag.length()).unquote();
4313 push_meta(url);
4314 pos = brk_end + 1;
4315 tag_stack.push_front("url");
4316 } else if (tag.begins_with("hint=")) {
4317 String description = tag.substr(5, tag.length()).unquote();
4318 push_hint(description);
4319 pos = brk_end + 1;
4320 tag_stack.push_front("hint");
4321 } else if (tag.begins_with("dropcap")) {
4322 Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
4323 _normalize_subtags(subtag);
4324
4325 int fs = theme_cache.normal_font_size * 3;
4326 Ref<Font> f = theme_cache.normal_font;
4327 Color color = theme_cache.default_color;
4328 Color outline_color = theme_cache.font_outline_color;
4329 int outline_size = theme_cache.outline_size;
4330 Rect2 dropcap_margins;
4331
4332 for (int i = 0; i < subtag.size(); i++) {
4333 Vector<String> subtag_a = subtag[i].split("=");
4334 _normalize_subtags(subtag_a);
4335
4336 if (subtag_a.size() == 2) {
4337 if (subtag_a[0] == "font" || subtag_a[0] == "f") {
4338 String fnt = subtag_a[1];
4339 Ref<Font> font = ResourceLoader::load(fnt, "Font");
4340 if (font.is_valid()) {
4341 f = font;
4342 }
4343 } else if (subtag_a[0] == "font_size") {
4344 fs = subtag_a[1].to_int();
4345 } else if (subtag_a[0] == "margins") {
4346 Vector<String> subtag_b = subtag_a[1].split(",");
4347 _normalize_subtags(subtag_b);
4348
4349 if (subtag_b.size() == 4) {
4350 dropcap_margins.position.x = subtag_b[0].to_float();
4351 dropcap_margins.position.y = subtag_b[1].to_float();
4352 dropcap_margins.size.x = subtag_b[2].to_float();
4353 dropcap_margins.size.y = subtag_b[3].to_float();
4354 }
4355 } else if (subtag_a[0] == "outline_size") {
4356 outline_size = subtag_a[1].to_int();
4357 } else if (subtag_a[0] == "color") {
4358 color = Color::from_string(subtag_a[1], color);
4359 } else if (subtag_a[0] == "outline_color") {
4360 outline_color = Color::from_string(subtag_a[1], outline_color);
4361 }
4362 }
4363 }
4364 int end = p_bbcode.find("[", brk_end);
4365 if (end == -1) {
4366 end = p_bbcode.length();
4367 }
4368
4369 String dc_txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1);
4370
4371 push_dropcap(dc_txt, f, fs, dropcap_margins, color, outline_size, outline_color);
4372
4373 pos = end;
4374 tag_stack.push_front(bbcode_name);
4375 } else if (tag.begins_with("img")) {
4376 int alignment = INLINE_ALIGNMENT_CENTER;
4377 if (tag.begins_with("img=")) {
4378 Vector<String> subtag = tag.substr(4, tag.length()).split(",");
4379 _normalize_subtags(subtag);
4380
4381 if (subtag.size() > 1) {
4382 if (subtag[0] == "top" || subtag[0] == "t") {
4383 alignment = INLINE_ALIGNMENT_TOP_TO;
4384 } else if (subtag[0] == "center" || subtag[0] == "c") {
4385 alignment = INLINE_ALIGNMENT_CENTER_TO;
4386 } else if (subtag[0] == "bottom" || subtag[0] == "b") {
4387 alignment = INLINE_ALIGNMENT_BOTTOM_TO;
4388 }
4389 if (subtag[1] == "top" || subtag[1] == "t") {
4390 alignment |= INLINE_ALIGNMENT_TO_TOP;
4391 } else if (subtag[1] == "center" || subtag[1] == "c") {
4392 alignment |= INLINE_ALIGNMENT_TO_CENTER;
4393 } else if (subtag[1] == "baseline" || subtag[1] == "l") {
4394 alignment |= INLINE_ALIGNMENT_TO_BASELINE;
4395 } else if (subtag[1] == "bottom" || subtag[1] == "b") {
4396 alignment |= INLINE_ALIGNMENT_TO_BOTTOM;
4397 }
4398 } else if (subtag.size() > 0) {
4399 if (subtag[0] == "top" || subtag[0] == "t") {
4400 alignment = INLINE_ALIGNMENT_TOP;
4401 } else if (subtag[0] == "center" || subtag[0] == "c") {
4402 alignment = INLINE_ALIGNMENT_CENTER;
4403 } else if (subtag[0] == "bottom" || subtag[0] == "b") {
4404 alignment = INLINE_ALIGNMENT_BOTTOM;
4405 }
4406 }
4407 }
4408
4409 int end = p_bbcode.find("[", brk_end);
4410 if (end == -1) {
4411 end = p_bbcode.length();
4412 }
4413
4414 String image = p_bbcode.substr(brk_end + 1, end - brk_end - 1);
4415
4416 Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D");
4417 if (texture.is_valid()) {
4418 Rect2 region;
4419 OptionMap::Iterator region_option = bbcode_options.find("region");
4420 if (region_option) {
4421 Vector<String> region_values = region_option->value.split(",", false);
4422 if (region_values.size() == 4) {
4423 region.position.x = region_values[0].to_float();
4424 region.position.y = region_values[1].to_float();
4425 region.size.x = region_values[2].to_float();
4426 region.size.y = region_values[3].to_float();
4427 }
4428 }
4429
4430 Color color = Color(1.0, 1.0, 1.0);
4431 OptionMap::Iterator color_option = bbcode_options.find("color");
4432 if (color_option) {
4433 color = Color::from_string(color_option->value, color);
4434 }
4435
4436 int width = 0;
4437 int height = 0;
4438 if (!bbcode_value.is_empty()) {
4439 int sep = bbcode_value.find("x");
4440 if (sep == -1) {
4441 width = bbcode_value.to_int();
4442 } else {
4443 width = bbcode_value.substr(0, sep).to_int();
4444 height = bbcode_value.substr(sep + 1).to_int();
4445 }
4446 } else {
4447 OptionMap::Iterator width_option = bbcode_options.find("width");
4448 if (width_option) {
4449 width = width_option->value.to_int();
4450 }
4451
4452 OptionMap::Iterator height_option = bbcode_options.find("height");
4453 if (height_option) {
4454 height = height_option->value.to_int();
4455 }
4456 }
4457
4458 add_image(texture, width, height, color, (InlineAlignment)alignment, region);
4459 }
4460
4461 pos = end;
4462 tag_stack.push_front(bbcode_name);
4463 } else if (tag.begins_with("color=")) {
4464 String color_str = tag.substr(6, tag.length()).unquote();
4465 Color color = Color::from_string(color_str, theme_cache.default_color);
4466 push_color(color);
4467 pos = brk_end + 1;
4468 tag_stack.push_front("color");
4469
4470 } else if (tag.begins_with("outline_color=")) {
4471 String color_str = tag.substr(14, tag.length()).unquote();
4472 Color color = Color::from_string(color_str, theme_cache.default_color);
4473 push_outline_color(color);
4474 pos = brk_end + 1;
4475 tag_stack.push_front("outline_color");
4476
4477 } else if (tag.begins_with("font_size=")) {
4478 int fnt_size = tag.substr(10, tag.length()).to_int();
4479 push_font_size(fnt_size);
4480 pos = brk_end + 1;
4481 tag_stack.push_front("font_size");
4482
4483 } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) {
4484 int value_pos = tag.find("=");
4485 String fnt_ftr = tag.substr(value_pos + 1);
4486 Vector<String> subtag = fnt_ftr.split(",");
4487 _normalize_subtags(subtag);
4488
4489 if (subtag.size() > 0) {
4490 Ref<Font> font = theme_cache.normal_font;
4491 DefaultFont def_font = NORMAL_FONT;
4492
4493 ItemFont *font_it = _find_font(current);
4494 if (font_it) {
4495 if (font_it->font.is_valid()) {
4496 font = font_it->font;
4497 def_font = font_it->def_font;
4498 }
4499 }
4500 Dictionary features;
4501 for (int i = 0; i < subtag.size(); i++) {
4502 Vector<String> subtag_a = subtag[i].split("=");
4503 _normalize_subtags(subtag_a);
4504
4505 if (subtag_a.size() == 2) {
4506 features[TS->name_to_tag(subtag_a[0])] = subtag_a[1].to_int();
4507 } else if (subtag_a.size() == 1) {
4508 features[TS->name_to_tag(subtag_a[0])] = 1;
4509 }
4510 }
4511
4512 Ref<FontVariation> fc;
4513 fc.instantiate();
4514
4515 fc->set_base_font(font);
4516 fc->set_opentype_features(features);
4517
4518 if (def_font != CUSTOM_FONT) {
4519 _push_def_font_var(def_font, fc);
4520 } else {
4521 push_font(fc);
4522 }
4523 }
4524 pos = brk_end + 1;
4525 tag_stack.push_front(tag.substr(0, value_pos));
4526
4527 } else if (tag.begins_with("font=")) {
4528 String fnt = tag.substr(5, tag.length()).unquote();
4529
4530 Ref<Font> fc = ResourceLoader::load(fnt, "Font");
4531 if (fc.is_valid()) {
4532 push_font(fc);
4533 }
4534
4535 pos = brk_end + 1;
4536 tag_stack.push_front("font");
4537
4538 } else if (tag.begins_with("font ")) {
4539 Vector<String> subtag = tag.substr(2, tag.length()).split(" ");
4540 _normalize_subtags(subtag);
4541
4542 Ref<Font> font = theme_cache.normal_font;
4543 DefaultFont def_font = NORMAL_FONT;
4544
4545 ItemFont *font_it = _find_font(current);
4546 if (font_it) {
4547 if (font_it->font.is_valid()) {
4548 font = font_it->font;
4549 def_font = font_it->def_font;
4550 }
4551 }
4552
4553 Ref<FontVariation> fc;
4554 fc.instantiate();
4555
4556 int fnt_size = -1;
4557 for (int i = 1; i < subtag.size(); i++) {
4558 Vector<String> subtag_a = subtag[i].split("=", true, 1);
4559 _normalize_subtags(subtag_a);
4560
4561 if (subtag_a.size() == 2) {
4562 if (subtag_a[0] == "name" || subtag_a[0] == "n") {
4563 String fnt = subtag_a[1];
4564 Ref<Font> font_data = ResourceLoader::load(fnt, "Font");
4565 if (font_data.is_valid()) {
4566 font = font_data;
4567 def_font = CUSTOM_FONT;
4568 }
4569 } else if (subtag_a[0] == "size" || subtag_a[0] == "s") {
4570 fnt_size = subtag_a[1].to_int();
4571 } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") {
4572 int spacing = subtag_a[1].to_int();
4573 fc->set_spacing(TextServer::SPACING_GLYPH, spacing);
4574 } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") {
4575 int spacing = subtag_a[1].to_int();
4576 fc->set_spacing(TextServer::SPACING_SPACE, spacing);
4577 } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") {
4578 int spacing = subtag_a[1].to_int();
4579 fc->set_spacing(TextServer::SPACING_TOP, spacing);
4580 } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") {
4581 int spacing = subtag_a[1].to_int();
4582 fc->set_spacing(TextServer::SPACING_BOTTOM, spacing);
4583 } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") {
4584 float emb = subtag_a[1].to_float();
4585 fc->set_variation_embolden(emb);
4586 } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") {
4587 int fi = subtag_a[1].to_int();
4588 fc->set_variation_face_index(fi);
4589 } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") {
4590 float slant = subtag_a[1].to_float();
4591 fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0));
4592 } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") {
4593 Dictionary variations;
4594 if (!subtag_a[1].is_empty()) {
4595 Vector<String> variation_tags = subtag_a[1].split(",");
4596 for (int j = 0; j < variation_tags.size(); j++) {
4597 Vector<String> subtag_b = variation_tags[j].split("=");
4598 _normalize_subtags(subtag_b);
4599
4600 if (subtag_b.size() == 2) {
4601 variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
4602 }
4603 }
4604 fc->set_variation_opentype(variations);
4605 }
4606 } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") {
4607 Dictionary features;
4608 if (!subtag_a[1].is_empty()) {
4609 Vector<String> feature_tags = subtag_a[1].split(",");
4610 for (int j = 0; j < feature_tags.size(); j++) {
4611 Vector<String> subtag_b = feature_tags[j].split("=");
4612 _normalize_subtags(subtag_b);
4613
4614 if (subtag_b.size() == 2) {
4615 features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float();
4616 } else if (subtag_b.size() == 1) {
4617 features[TS->name_to_tag(subtag_b[0])] = 1;
4618 }
4619 }
4620 fc->set_opentype_features(features);
4621 }
4622 }
4623 }
4624 }
4625 fc->set_base_font(font);
4626
4627 if (def_font != CUSTOM_FONT) {
4628 _push_def_font_var(def_font, fc, fnt_size);
4629 } else {
4630 push_font(fc, fnt_size);
4631 }
4632
4633 pos = brk_end + 1;
4634 tag_stack.push_front("font");
4635
4636 } else if (tag.begins_with("outline_size=")) {
4637 int fnt_size = tag.substr(13, tag.length()).to_int();
4638 if (fnt_size > 0) {
4639 push_outline_size(fnt_size);
4640 }
4641 pos = brk_end + 1;
4642 tag_stack.push_front("outline_size");
4643
4644 } else if (bbcode_name == "fade") {
4645 int start_index = 0;
4646 OptionMap::Iterator start_option = bbcode_options.find("start");
4647 if (start_option) {
4648 start_index = start_option->value.to_int();
4649 }
4650
4651 int length = 10;
4652 OptionMap::Iterator length_option = bbcode_options.find("length");
4653 if (length_option) {
4654 length = length_option->value.to_int();
4655 }
4656
4657 push_fade(start_index, length);
4658 pos = brk_end + 1;
4659 tag_stack.push_front("fade");
4660 } else if (bbcode_name == "shake") {
4661 int strength = 5;
4662 OptionMap::Iterator strength_option = bbcode_options.find("level");
4663 if (strength_option) {
4664 strength = strength_option->value.to_int();
4665 }
4666
4667 float rate = 20.0f;
4668 OptionMap::Iterator rate_option = bbcode_options.find("rate");
4669 if (rate_option) {
4670 rate = rate_option->value.to_float();
4671 }
4672
4673 bool connected = true;
4674 OptionMap::Iterator connected_option = bbcode_options.find("connected");
4675 if (connected_option) {
4676 connected = connected_option->value.to_int();
4677 }
4678
4679 push_shake(strength, rate, connected);
4680 pos = brk_end + 1;
4681 tag_stack.push_front("shake");
4682 set_process_internal(true);
4683 } else if (bbcode_name == "wave") {
4684 float amplitude = 20.0f;
4685 OptionMap::Iterator amplitude_option = bbcode_options.find("amp");
4686 if (amplitude_option) {
4687 amplitude = amplitude_option->value.to_float();
4688 }
4689
4690 float period = 5.0f;
4691 OptionMap::Iterator period_option = bbcode_options.find("freq");
4692 if (period_option) {
4693 period = period_option->value.to_float();
4694 }
4695
4696 bool connected = true;
4697 OptionMap::Iterator connected_option = bbcode_options.find("connected");
4698 if (connected_option) {
4699 connected = connected_option->value.to_int();
4700 }
4701
4702 push_wave(period, amplitude, connected);
4703 pos = brk_end + 1;
4704 tag_stack.push_front("wave");
4705 set_process_internal(true);
4706 } else if (bbcode_name == "tornado") {
4707 float radius = 10.0f;
4708 OptionMap::Iterator radius_option = bbcode_options.find("radius");
4709 if (radius_option) {
4710 radius = radius_option->value.to_float();
4711 }
4712
4713 float frequency = 1.0f;
4714 OptionMap::Iterator frequency_option = bbcode_options.find("freq");
4715 if (frequency_option) {
4716 frequency = frequency_option->value.to_float();
4717 }
4718
4719 bool connected = true;
4720 OptionMap::Iterator connected_option = bbcode_options.find("connected");
4721 if (connected_option) {
4722 connected = connected_option->value.to_int();
4723 }
4724
4725 push_tornado(frequency, radius, connected);
4726 pos = brk_end + 1;
4727 tag_stack.push_front("tornado");
4728 set_process_internal(true);
4729 } else if (bbcode_name == "rainbow") {
4730 float saturation = 0.8f;
4731 OptionMap::Iterator saturation_option = bbcode_options.find("sat");
4732 if (saturation_option) {
4733 saturation = saturation_option->value.to_float();
4734 }
4735
4736 float value = 0.8f;
4737 OptionMap::Iterator value_option = bbcode_options.find("val");
4738 if (value_option) {
4739 value = value_option->value.to_float();
4740 }
4741
4742 float frequency = 1.0f;
4743 OptionMap::Iterator frequency_option = bbcode_options.find("freq");
4744 if (frequency_option) {
4745 frequency = frequency_option->value.to_float();
4746 }
4747
4748 push_rainbow(saturation, value, frequency);
4749 pos = brk_end + 1;
4750 tag_stack.push_front("rainbow");
4751 set_process_internal(true);
4752 } else if (bbcode_name == "pulse") {
4753 Color color = Color(1, 1, 1, 0.25);
4754 OptionMap::Iterator color_option = bbcode_options.find("color");
4755 if (color_option) {
4756 color = Color::from_string(color_option->value, color);
4757 }
4758
4759 float frequency = 1.0;
4760 OptionMap::Iterator freq_option = bbcode_options.find("freq");
4761 if (freq_option) {
4762 frequency = freq_option->value.to_float();
4763 }
4764
4765 float ease = -2.0;
4766 OptionMap::Iterator ease_option = bbcode_options.find("ease");
4767 if (ease_option) {
4768 ease = ease_option->value.to_float();
4769 }
4770
4771 push_pulse(color, frequency, ease);
4772 pos = brk_end + 1;
4773 tag_stack.push_front("pulse");
4774 set_process_internal(true);
4775 } else if (tag.begins_with("bgcolor=")) {
4776 String color_str = tag.substr(8, tag.length()).unquote();
4777 Color color = Color::from_string(color_str, theme_cache.default_color);
4778
4779 push_bgcolor(color);
4780 pos = brk_end + 1;
4781 tag_stack.push_front("bgcolor");
4782
4783 } else if (tag.begins_with("fgcolor=")) {
4784 String color_str = tag.substr(8, tag.length()).unquote();
4785 Color color = Color::from_string(color_str, theme_cache.default_color);
4786
4787 push_fgcolor(color);
4788 pos = brk_end + 1;
4789 tag_stack.push_front("fgcolor");
4790
4791 } else {
4792 Vector<String> &expr = split_tag_block;
4793 if (expr.size() < 1) {
4794 add_text("[");
4795 pos = brk_pos + 1;
4796 } else {
4797 String identifier = expr[0];
4798 expr.remove_at(0);
4799 Dictionary properties = parse_expressions_for_values(expr);
4800 Ref<RichTextEffect> effect = _get_custom_effect_by_code(identifier);
4801
4802 if (!effect.is_null()) {
4803 push_customfx(effect, properties);
4804 pos = brk_end + 1;
4805 tag_stack.push_front(identifier);
4806 } else {
4807 add_text("["); //ignore
4808 pos = brk_pos + 1;
4809 }
4810 }
4811 }
4812 }
4813
4814 Vector<ItemFX *> fx_items;
4815 for (Item *E : main->subitems) {
4816 Item *subitem = static_cast<Item *>(E);
4817 _fetch_item_fx_stack(subitem, fx_items);
4818
4819 if (fx_items.size()) {
4820 set_process_internal(true);
4821 break;
4822 }
4823 }
4824}
4825
4826void RichTextLabel::scroll_to_selection() {
4827 if (selection.active && selection.from_frame && selection.from_line >= 0 && selection.from_line < (int)selection.from_frame->lines.size()) {
4828 // Selected frame paragraph offset.
4829 float line_offset = selection.from_frame->lines[selection.from_line].offset.y;
4830
4831 // Add wrapped line offset.
4832 for (int i = 0; i < selection.from_frame->lines[selection.from_line].text_buf->get_line_count(); i++) {
4833 Vector2i range = selection.from_frame->lines[selection.from_line].text_buf->get_line_range(i);
4834 if (range.x <= selection.from_char && range.y >= selection.from_char) {
4835 break;
4836 }
4837 line_offset += selection.from_frame->lines[selection.from_line].text_buf->get_line_size(i).y + theme_cache.line_separation;
4838 }
4839
4840 // Add nested frame (e.g. table cell) offset.
4841 ItemFrame *it = selection.from_frame;
4842 while (it->parent_frame != nullptr) {
4843 line_offset += it->parent_frame->lines[it->line].offset.y;
4844 it = it->parent_frame;
4845 }
4846 vscroll->set_value(line_offset);
4847 }
4848}
4849
4850void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
4851 _validate_line_caches();
4852
4853 if (p_paragraph <= 0) {
4854 vscroll->set_value(0);
4855 } else if (p_paragraph >= main->first_invalid_line.load()) {
4856 vscroll->set_value(vscroll->get_max());
4857 } else {
4858 vscroll->set_value(main->lines[p_paragraph].offset.y);
4859 }
4860}
4861
4862int RichTextLabel::get_paragraph_count() const {
4863 return current_frame->lines.size();
4864}
4865
4866int RichTextLabel::get_visible_paragraph_count() const {
4867 if (!is_visible()) {
4868 return 0;
4869 }
4870
4871 const_cast<RichTextLabel *>(this)->_validate_line_caches();
4872 return visible_paragraph_count;
4873}
4874
4875void RichTextLabel::scroll_to_line(int p_line) {
4876 if (p_line <= 0) {
4877 vscroll->set_value(0);
4878 return;
4879 }
4880 _validate_line_caches();
4881
4882 int line_count = 0;
4883 int to_line = main->first_invalid_line.load();
4884 for (int i = 0; i < to_line; i++) {
4885 MutexLock lock(main->lines[i].text_buf->get_mutex());
4886 if ((line_count <= p_line) && (line_count + main->lines[i].text_buf->get_line_count() >= p_line)) {
4887 float line_offset = 0.f;
4888 for (int j = 0; j < p_line - line_count; j++) {
4889 line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation;
4890 }
4891 vscroll->set_value(main->lines[i].offset.y + line_offset);
4892 return;
4893 }
4894 line_count += main->lines[i].text_buf->get_line_count();
4895 }
4896 vscroll->set_value(vscroll->get_max());
4897}
4898
4899float RichTextLabel::get_line_offset(int p_line) {
4900 _validate_line_caches();
4901
4902 int line_count = 0;
4903 int to_line = main->first_invalid_line.load();
4904 for (int i = 0; i < to_line; i++) {
4905 MutexLock lock(main->lines[i].text_buf->get_mutex());
4906 if ((line_count <= p_line) && (p_line <= line_count + main->lines[i].text_buf->get_line_count())) {
4907 float line_offset = 0.f;
4908 for (int j = 0; j < p_line - line_count; j++) {
4909 line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation;
4910 }
4911 return main->lines[i].offset.y + line_offset;
4912 }
4913 line_count += main->lines[i].text_buf->get_line_count();
4914 }
4915 return 0;
4916}
4917
4918float RichTextLabel::get_paragraph_offset(int p_paragraph) {
4919 _validate_line_caches();
4920
4921 int to_line = main->first_invalid_line.load();
4922 if (0 <= p_paragraph && p_paragraph < to_line) {
4923 return main->lines[p_paragraph].offset.y;
4924 }
4925 return 0;
4926}
4927
4928int RichTextLabel::get_line_count() const {
4929 const_cast<RichTextLabel *>(this)->_validate_line_caches();
4930
4931 int line_count = 0;
4932 int to_line = main->first_invalid_line.load();
4933 for (int i = 0; i < to_line; i++) {
4934 MutexLock lock(main->lines[i].text_buf->get_mutex());
4935 line_count += main->lines[i].text_buf->get_line_count();
4936 }
4937 return line_count;
4938}
4939
4940int RichTextLabel::get_visible_line_count() const {
4941 if (!is_visible()) {
4942 return 0;
4943 }
4944 const_cast<RichTextLabel *>(this)->_validate_line_caches();
4945
4946 return visible_line_count;
4947}
4948
4949void RichTextLabel::set_selection_enabled(bool p_enabled) {
4950 if (selection.enabled == p_enabled) {
4951 return;
4952 }
4953
4954 selection.enabled = p_enabled;
4955 if (!p_enabled) {
4956 if (selection.active) {
4957 deselect();
4958 }
4959 set_focus_mode(FOCUS_NONE);
4960 } else {
4961 set_focus_mode(FOCUS_ALL);
4962 }
4963}
4964
4965void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
4966 if (deselect_on_focus_loss_enabled == p_enabled) {
4967 return;
4968 }
4969
4970 deselect_on_focus_loss_enabled = p_enabled;
4971 if (p_enabled && selection.active && !has_focus()) {
4972 deselect();
4973 }
4974}
4975
4976Variant RichTextLabel::get_drag_data(const Point2 &p_point) {
4977 Variant ret = Control::get_drag_data(p_point);
4978 if (ret != Variant()) {
4979 return ret;
4980 }
4981
4982 if (selection.drag_attempt && selection.enabled) {
4983 String t = get_selected_text();
4984 Label *l = memnew(Label);
4985 l->set_text(t);
4986 set_drag_preview(l);
4987 return t;
4988 }
4989
4990 return Variant();
4991}
4992
4993bool RichTextLabel::_is_click_inside_selection() const {
4994 if (selection.active && selection.enabled && selection.click_frame && selection.from_frame && selection.to_frame) {
4995 const Line &l_click = selection.click_frame->lines[selection.click_line];
4996 const Line &l_from = selection.from_frame->lines[selection.from_line];
4997 const Line &l_to = selection.to_frame->lines[selection.to_line];
4998 return (l_click.char_offset + selection.click_char >= l_from.char_offset + selection.from_char) && (l_click.char_offset + selection.click_char <= l_to.char_offset + selection.to_char);
4999 } else {
5000 return false;
5001 }
5002}
5003
5004bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) {
5005 List<Item *>::Element *E = p_from;
5006 while (E != nullptr) {
5007 ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
5008 ItemFrame *frame = static_cast<ItemFrame *>(E->get());
5009 if (p_reverse_search) {
5010 for (int i = (int)frame->lines.size() - 1; i >= 0; i--) {
5011 if (_search_line(frame, i, p_string, -1, p_reverse_search)) {
5012 return true;
5013 }
5014 }
5015 } else {
5016 for (int i = 0; i < (int)frame->lines.size(); i++) {
5017 if (_search_line(frame, i, p_string, 0, p_reverse_search)) {
5018 return true;
5019 }
5020 }
5021 }
5022 E = p_reverse_search ? E->prev() : E->next();
5023 }
5024 return false;
5025}
5026
5027bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) {
5028 ERR_FAIL_NULL_V(p_frame, false);
5029 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), false);
5030
5031 Line &l = p_frame->lines[p_line];
5032
5033 String txt;
5034 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
5035 for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
5036 switch (it->type) {
5037 case ITEM_NEWLINE: {
5038 txt += "\n";
5039 } break;
5040 case ITEM_TEXT: {
5041 ItemText *t = static_cast<ItemText *>(it);
5042 txt += t->text;
5043 } break;
5044 case ITEM_IMAGE: {
5045 txt += " ";
5046 } break;
5047 case ITEM_TABLE: {
5048 ItemTable *table = static_cast<ItemTable *>(it);
5049 List<Item *>::Element *E = p_reverse_search ? table->subitems.back() : table->subitems.front();
5050 if (_search_table(table, E, p_string, p_reverse_search)) {
5051 return true;
5052 }
5053 } break;
5054 default:
5055 break;
5056 }
5057 }
5058
5059 int sp = -1;
5060 if (p_reverse_search) {
5061 sp = txt.rfindn(p_string, p_char_idx);
5062 } else {
5063 sp = txt.findn(p_string, p_char_idx);
5064 }
5065
5066 if (sp != -1) {
5067 selection.from_frame = p_frame;
5068 selection.from_line = p_line;
5069 selection.from_item = _get_item_at_pos(l.from, it_to, sp);
5070 selection.from_char = sp;
5071 selection.to_frame = p_frame;
5072 selection.to_line = p_line;
5073 selection.to_item = _get_item_at_pos(l.from, it_to, sp + p_string.length());
5074 selection.to_char = sp + p_string.length();
5075 selection.active = true;
5076 return true;
5077 }
5078
5079 return false;
5080}
5081
5082bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p_search_previous) {
5083 ERR_FAIL_COND_V(!selection.enabled, false);
5084
5085 if (p_string.size() == 0) {
5086 selection.active = false;
5087 return false;
5088 }
5089
5090 int char_idx = p_search_previous ? -1 : 0;
5091 int current_line = 0;
5092 int to_line = main->first_invalid_line.load();
5093 int ending_line = to_line - 1;
5094 if (p_from_selection && selection.active) {
5095 // First check to see if other results exist in current line
5096 char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char;
5097 if (!(p_search_previous && char_idx < 0) &&
5098 _search_line(selection.from_frame, selection.from_line, p_string, char_idx, p_search_previous)) {
5099 scroll_to_selection();
5100 queue_redraw();
5101 return true;
5102 }
5103 char_idx = p_search_previous ? -1 : 0;
5104
5105 // Next, check to see if the current search result is in a table
5106 if (selection.from_frame->parent != nullptr && selection.from_frame->parent->type == ITEM_TABLE) {
5107 // Find last search result in table
5108 ItemTable *parent_table = static_cast<ItemTable *>(selection.from_frame->parent);
5109 List<Item *>::Element *parent_element = p_search_previous ? parent_table->subitems.back() : parent_table->subitems.front();
5110
5111 while (parent_element->get() != selection.from_frame) {
5112 parent_element = p_search_previous ? parent_element->prev() : parent_element->next();
5113 ERR_FAIL_NULL_V(parent_element, false);
5114 }
5115
5116 // Search remainder of table
5117 if (!(p_search_previous && parent_element == parent_table->subitems.front()) &&
5118 parent_element != parent_table->subitems.back()) {
5119 parent_element = p_search_previous ? parent_element->prev() : parent_element->next(); // Don't want to search current item
5120 ERR_FAIL_NULL_V(parent_element, false);
5121
5122 // Search for next element
5123 if (_search_table(parent_table, parent_element, p_string, p_search_previous)) {
5124 scroll_to_selection();
5125 queue_redraw();
5126 return true;
5127 }
5128 }
5129 }
5130
5131 ending_line = selection.from_frame->line + selection.from_line;
5132 current_line = p_search_previous ? ending_line - 1 : ending_line + 1;
5133 } else if (p_search_previous) {
5134 current_line = ending_line;
5135 ending_line = 0;
5136 }
5137
5138 // Search remainder of the file
5139 while (current_line != ending_line) {
5140 // Wrap around
5141 if (current_line < 0) {
5142 current_line = to_line - 1;
5143 } else if (current_line >= to_line) {
5144 current_line = 0;
5145 }
5146
5147 if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) {
5148 scroll_to_selection();
5149 queue_redraw();
5150 return true;
5151 }
5152
5153 if (current_line != ending_line) {
5154 p_search_previous ? current_line-- : current_line++;
5155 }
5156 }
5157
5158 if (p_from_selection && selection.active) {
5159 // Check contents of selection
5160 return _search_line(main, current_line, p_string, char_idx, p_search_previous);
5161 } else {
5162 return false;
5163 }
5164}
5165
5166String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const {
5167 String txt;
5168
5169 ERR_FAIL_NULL_V(p_frame, txt);
5170 ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), txt);
5171
5172 Line &l = p_frame->lines[p_line];
5173
5174 Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
5175 int end_idx = 0;
5176 if (it_to != nullptr) {
5177 end_idx = it_to->index;
5178 } else {
5179 for (Item *it = l.from; it; it = _get_next_item(it)) {
5180 end_idx = it->index + 1;
5181 }
5182 }
5183 for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
5184 if (it->type == ITEM_TABLE) {
5185 ItemTable *table = static_cast<ItemTable *>(it);
5186 for (Item *E : table->subitems) {
5187 ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
5188 ItemFrame *frame = static_cast<ItemFrame *>(E);
5189 for (int i = 0; i < (int)frame->lines.size(); i++) {
5190 txt += _get_line_text(frame, i, p_selection);
5191 }
5192 }
5193 }
5194 if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) {
5195 continue;
5196 }
5197 if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) {
5198 continue;
5199 }
5200 if (it->type == ITEM_DROPCAP) {
5201 const ItemDropcap *dc = static_cast<ItemDropcap *>(it);
5202 txt += dc->text;
5203 } else if (it->type == ITEM_TEXT) {
5204 const ItemText *t = static_cast<ItemText *>(it);
5205 txt += t->text;
5206 } else if (it->type == ITEM_NEWLINE) {
5207 txt += "\n";
5208 } else if (it->type == ITEM_IMAGE) {
5209 txt += " ";
5210 }
5211 }
5212 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)) {
5213 txt = txt.substr(0, p_selection.to_char);
5214 }
5215 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)) {
5216 txt = txt.substr(p_selection.from_char, -1);
5217 }
5218 return txt;
5219}
5220
5221void RichTextLabel::set_context_menu_enabled(bool p_enabled) {
5222 context_menu_enabled = p_enabled;
5223}
5224
5225bool RichTextLabel::is_context_menu_enabled() const {
5226 return context_menu_enabled;
5227}
5228
5229void RichTextLabel::set_shortcut_keys_enabled(bool p_enabled) {
5230 shortcut_keys_enabled = p_enabled;
5231}
5232
5233bool RichTextLabel::is_shortcut_keys_enabled() const {
5234 return shortcut_keys_enabled;
5235}
5236
5237// Context menu.
5238PopupMenu *RichTextLabel::get_menu() const {
5239 if (!menu) {
5240 const_cast<RichTextLabel *>(this)->_generate_context_menu();
5241 }
5242 return menu;
5243}
5244
5245bool RichTextLabel::is_menu_visible() const {
5246 return menu && menu->is_visible();
5247}
5248
5249String RichTextLabel::get_selected_text() const {
5250 if (!selection.active || !selection.enabled) {
5251 return "";
5252 }
5253
5254 String txt;
5255 int to_line = main->first_invalid_line.load();
5256 for (int i = 0; i < to_line; i++) {
5257 txt += _get_line_text(main, i, selection);
5258 }
5259 return txt;
5260}
5261
5262void RichTextLabel::deselect() {
5263 selection.active = false;
5264 queue_redraw();
5265}
5266
5267void RichTextLabel::selection_copy() {
5268 String txt = get_selected_text();
5269
5270 if (!txt.is_empty()) {
5271 DisplayServer::get_singleton()->clipboard_set(txt);
5272 }
5273}
5274
5275void RichTextLabel::select_all() {
5276 _validate_line_caches();
5277
5278 if (!selection.enabled) {
5279 return;
5280 }
5281
5282 Item *it = main;
5283 Item *from_item = nullptr;
5284 Item *to_item = nullptr;
5285
5286 while (it) {
5287 if (it->type != ITEM_FRAME) {
5288 if (!from_item) {
5289 from_item = it;
5290 }
5291 to_item = it;
5292 }
5293 it = _get_next_item(it, true);
5294 }
5295 if (!from_item) {
5296 return;
5297 }
5298
5299 ItemFrame *from_frame = nullptr;
5300 int from_line = 0;
5301 _find_frame(from_item, &from_frame, &from_line);
5302 if (!from_frame) {
5303 return;
5304 }
5305 ItemFrame *to_frame = nullptr;
5306 int to_line = 0;
5307 _find_frame(to_item, &to_frame, &to_line);
5308 if (!to_frame) {
5309 return;
5310 }
5311 selection.from_line = from_line;
5312 selection.from_frame = from_frame;
5313 selection.from_char = 0;
5314 selection.from_item = from_item;
5315 selection.to_line = to_line;
5316 selection.to_frame = to_frame;
5317 selection.to_char = to_frame->lines[to_line].char_count;
5318 selection.to_item = to_item;
5319 selection.active = true;
5320 queue_redraw();
5321}
5322
5323bool RichTextLabel::is_selection_enabled() const {
5324 return selection.enabled;
5325}
5326
5327bool RichTextLabel::is_deselect_on_focus_loss_enabled() const {
5328 return deselect_on_focus_loss_enabled;
5329}
5330
5331void RichTextLabel::set_drag_and_drop_selection_enabled(const bool p_enabled) {
5332 drag_and_drop_selection_enabled = p_enabled;
5333}
5334
5335bool RichTextLabel::is_drag_and_drop_selection_enabled() const {
5336 return drag_and_drop_selection_enabled;
5337}
5338
5339int RichTextLabel::get_selection_from() const {
5340 if (!selection.active || !selection.enabled) {
5341 return -1;
5342 }
5343
5344 return selection.from_frame->lines[selection.from_line].char_offset + selection.from_char;
5345}
5346
5347int RichTextLabel::get_selection_to() const {
5348 if (!selection.active || !selection.enabled) {
5349 return -1;
5350 }
5351
5352 return selection.to_frame->lines[selection.to_line].char_offset + selection.to_char - 1;
5353}
5354
5355void RichTextLabel::set_text(const String &p_bbcode) {
5356 if (text == p_bbcode) {
5357 return;
5358 }
5359 text = p_bbcode;
5360 _apply_translation();
5361}
5362
5363void RichTextLabel::_apply_translation() {
5364 String xl_text = atr(text);
5365 if (use_bbcode) {
5366 parse_bbcode(xl_text);
5367 } else { // raw text
5368 clear();
5369 add_text(xl_text);
5370 }
5371}
5372
5373String RichTextLabel::get_text() const {
5374 return text;
5375}
5376
5377void RichTextLabel::set_use_bbcode(bool p_enable) {
5378 if (use_bbcode == p_enable) {
5379 return;
5380 }
5381 use_bbcode = p_enable;
5382 notify_property_list_changed();
5383
5384 _apply_translation();
5385}
5386
5387bool RichTextLabel::is_using_bbcode() const {
5388 return use_bbcode;
5389}
5390
5391String RichTextLabel::get_parsed_text() const {
5392 String txt = "";
5393 Item *it = main;
5394 while (it) {
5395 if (it->type == ITEM_DROPCAP) {
5396 ItemDropcap *dc = static_cast<ItemDropcap *>(it);
5397 txt += dc->text;
5398 } else if (it->type == ITEM_TEXT) {
5399 ItemText *t = static_cast<ItemText *>(it);
5400 txt += t->text;
5401 } else if (it->type == ITEM_NEWLINE) {
5402 txt += "\n";
5403 } else if (it->type == ITEM_IMAGE) {
5404 txt += " ";
5405 } else if (it->type == ITEM_INDENT || it->type == ITEM_LIST) {
5406 txt += "\t";
5407 }
5408 it = _get_next_item(it, true);
5409 }
5410 return txt;
5411}
5412
5413void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) {
5414 ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
5415 _stop_thread();
5416
5417 if (text_direction != p_text_direction) {
5418 text_direction = p_text_direction;
5419 main->first_invalid_line.store(0); //invalidate ALL
5420 _validate_line_caches();
5421 queue_redraw();
5422 }
5423}
5424
5425void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
5426 if (st_parser != p_parser) {
5427 _stop_thread();
5428
5429 st_parser = p_parser;
5430 main->first_invalid_line.store(0); //invalidate ALL
5431 _validate_line_caches();
5432 queue_redraw();
5433 }
5434}
5435
5436TextServer::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const {
5437 return st_parser;
5438}
5439
5440void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) {
5441 if (st_args != p_args) {
5442 _stop_thread();
5443
5444 st_args = p_args;
5445 main->first_invalid_line.store(0); //invalidate ALL
5446 _validate_line_caches();
5447 queue_redraw();
5448 }
5449}
5450
5451Array RichTextLabel::get_structured_text_bidi_override_options() const {
5452 return st_args;
5453}
5454
5455Control::TextDirection RichTextLabel::get_text_direction() const {
5456 return text_direction;
5457}
5458
5459void RichTextLabel::set_language(const String &p_language) {
5460 if (language != p_language) {
5461 _stop_thread();
5462
5463 language = p_language;
5464 main->first_invalid_line.store(0); //invalidate ALL
5465 _validate_line_caches();
5466 queue_redraw();
5467 }
5468}
5469
5470String RichTextLabel::get_language() const {
5471 return language;
5472}
5473
5474void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
5475 if (autowrap_mode != p_mode) {
5476 _stop_thread();
5477
5478 autowrap_mode = p_mode;
5479 main->first_invalid_line = 0; //invalidate ALL
5480 _validate_line_caches();
5481 queue_redraw();
5482 }
5483}
5484
5485TextServer::AutowrapMode RichTextLabel::get_autowrap_mode() const {
5486 return autowrap_mode;
5487}
5488
5489void RichTextLabel::set_visible_ratio(float p_ratio) {
5490 if (visible_ratio != p_ratio) {
5491 _stop_thread();
5492
5493 if (p_ratio >= 1.0) {
5494 visible_characters = -1;
5495 visible_ratio = 1.0;
5496 } else if (p_ratio < 0.0) {
5497 visible_characters = 0;
5498 visible_ratio = 0.0;
5499 } else {
5500 visible_characters = get_total_character_count() * p_ratio;
5501 visible_ratio = p_ratio;
5502 }
5503
5504 if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
5505 main->first_invalid_line.store(0); // Invalidate ALL.
5506 _validate_line_caches();
5507 }
5508 queue_redraw();
5509 }
5510}
5511
5512float RichTextLabel::get_visible_ratio() const {
5513 return visible_ratio;
5514}
5515
5516void RichTextLabel::set_effects(Array p_effects) {
5517 custom_effects = p_effects;
5518 if ((!text.is_empty()) && use_bbcode) {
5519 parse_bbcode(atr(text));
5520 }
5521}
5522
5523Array RichTextLabel::get_effects() {
5524 return custom_effects;
5525}
5526
5527void RichTextLabel::install_effect(const Variant effect) {
5528 Ref<RichTextEffect> rteffect;
5529 rteffect = effect;
5530
5531 ERR_FAIL_COND_MSG(rteffect.is_null(), "Invalid RichTextEffect resource.");
5532 custom_effects.push_back(effect);
5533 if ((!text.is_empty()) && use_bbcode) {
5534 parse_bbcode(atr(text));
5535 }
5536}
5537
5538int RichTextLabel::get_content_height() const {
5539 const_cast<RichTextLabel *>(this)->_validate_line_caches();
5540
5541 int total_height = 0;
5542 int to_line = main->first_invalid_line.load();
5543 if (to_line) {
5544 MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
5545 if (theme_cache.line_separation < 0) {
5546 // Do not apply to the last line to avoid cutting text.
5547 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;
5548 } else {
5549 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;
5550 }
5551 }
5552 return total_height;
5553}
5554
5555int RichTextLabel::get_content_width() const {
5556 const_cast<RichTextLabel *>(this)->_validate_line_caches();
5557
5558 int total_width = 0;
5559 int to_line = main->first_invalid_line.load();
5560 for (int i = 0; i < to_line; i++) {
5561 MutexLock lock(main->lines[i].text_buf->get_mutex());
5562 total_width = MAX(total_width, main->lines[i].offset.x + main->lines[i].text_buf->get_size().x);
5563 }
5564 return total_width;
5565}
5566
5567#ifndef DISABLE_DEPRECATED
5568// People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0)
5569// Although some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty.
5570bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) {
5571 if (p_name == "bbcode_text" && !((String)p_value).is_empty()) {
5572 set_text(p_value);
5573 return true;
5574 }
5575 return false;
5576}
5577#endif
5578
5579void RichTextLabel::_bind_methods() {
5580 ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text);
5581 ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text);
5582 ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text);
5583 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)));
5584 ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline);
5585 ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph);
5586 ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0));
5587 ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size);
5588 ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal);
5589 ClassDB::bind_method(D_METHOD("push_bold"), &RichTextLabel::push_bold);
5590 ClassDB::bind_method(D_METHOD("push_bold_italics"), &RichTextLabel::push_bold_italics);
5591 ClassDB::bind_method(D_METHOD("push_italics"), &RichTextLabel::push_italics);
5592 ClassDB::bind_method(D_METHOD("push_mono"), &RichTextLabel::push_mono);
5593 ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color);
5594 ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size);
5595 ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color);
5596 ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser", "justification_flags", "tab_stops"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT), DEFVAL(TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE), DEFVAL(PackedFloat32Array()));
5597 ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent);
5598 ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize", "bullet"), &RichTextLabel::push_list, DEFVAL(String::utf8("•")));
5599 ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta);
5600 ClassDB::bind_method(D_METHOD("push_hint", "description"), &RichTextLabel::push_hint);
5601 ClassDB::bind_method(D_METHOD("push_language", "language"), &RichTextLabel::push_language);
5602 ClassDB::bind_method(D_METHOD("push_underline"), &RichTextLabel::push_underline);
5603 ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough);
5604 ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align", "align_to_row"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGNMENT_TOP), DEFVAL(-1));
5605 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)));
5606 ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand, DEFVAL(1));
5607 ClassDB::bind_method(D_METHOD("set_cell_row_background_color", "odd_row_bg", "even_row_bg"), &RichTextLabel::set_cell_row_background_color);
5608 ClassDB::bind_method(D_METHOD("set_cell_border_color", "color"), &RichTextLabel::set_cell_border_color);
5609 ClassDB::bind_method(D_METHOD("set_cell_size_override", "min_size", "max_size"), &RichTextLabel::set_cell_size_override);
5610 ClassDB::bind_method(D_METHOD("set_cell_padding", "padding"), &RichTextLabel::set_cell_padding);
5611 ClassDB::bind_method(D_METHOD("push_cell"), &RichTextLabel::push_cell);
5612 ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor);
5613 ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor);
5614 ClassDB::bind_method(D_METHOD("push_customfx", "effect", "env"), &RichTextLabel::push_customfx);
5615 ClassDB::bind_method(D_METHOD("push_context"), &RichTextLabel::push_context);
5616 ClassDB::bind_method(D_METHOD("pop_context"), &RichTextLabel::pop_context);
5617 ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop);
5618 ClassDB::bind_method(D_METHOD("pop_all"), &RichTextLabel::pop_all);
5619
5620 ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear);
5621
5622 ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &RichTextLabel::set_structured_text_bidi_override);
5623 ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &RichTextLabel::get_structured_text_bidi_override);
5624 ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &RichTextLabel::set_structured_text_bidi_override_options);
5625 ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &RichTextLabel::get_structured_text_bidi_override_options);
5626 ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &RichTextLabel::set_text_direction);
5627 ClassDB::bind_method(D_METHOD("get_text_direction"), &RichTextLabel::get_text_direction);
5628 ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language);
5629 ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language);
5630
5631 ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode);
5632 ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode);
5633
5634 ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline);
5635 ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined);
5636
5637 ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline);
5638 ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined);
5639
5640 ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active);
5641 ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active);
5642
5643 ClassDB::bind_method(D_METHOD("set_scroll_follow", "follow"), &RichTextLabel::set_scroll_follow);
5644 ClassDB::bind_method(D_METHOD("is_scroll_following"), &RichTextLabel::is_scroll_following);
5645
5646 ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &RichTextLabel::get_v_scroll_bar);
5647
5648 ClassDB::bind_method(D_METHOD("scroll_to_line", "line"), &RichTextLabel::scroll_to_line);
5649 ClassDB::bind_method(D_METHOD("scroll_to_paragraph", "paragraph"), &RichTextLabel::scroll_to_paragraph);
5650 ClassDB::bind_method(D_METHOD("scroll_to_selection"), &RichTextLabel::scroll_to_selection);
5651
5652 ClassDB::bind_method(D_METHOD("set_tab_size", "spaces"), &RichTextLabel::set_tab_size);
5653 ClassDB::bind_method(D_METHOD("get_tab_size"), &RichTextLabel::get_tab_size);
5654
5655 ClassDB::bind_method(D_METHOD("set_fit_content", "enabled"), &RichTextLabel::set_fit_content);
5656 ClassDB::bind_method(D_METHOD("is_fit_content_enabled"), &RichTextLabel::is_fit_content_enabled);
5657
5658 ClassDB::bind_method(D_METHOD("set_selection_enabled", "enabled"), &RichTextLabel::set_selection_enabled);
5659 ClassDB::bind_method(D_METHOD("is_selection_enabled"), &RichTextLabel::is_selection_enabled);
5660
5661 ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enabled"), &RichTextLabel::set_context_menu_enabled);
5662 ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &RichTextLabel::is_context_menu_enabled);
5663
5664 ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enabled"), &RichTextLabel::set_shortcut_keys_enabled);
5665 ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &RichTextLabel::is_shortcut_keys_enabled);
5666
5667 ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &RichTextLabel::set_deselect_on_focus_loss_enabled);
5668 ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &RichTextLabel::is_deselect_on_focus_loss_enabled);
5669
5670 ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &RichTextLabel::set_drag_and_drop_selection_enabled);
5671 ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &RichTextLabel::is_drag_and_drop_selection_enabled);
5672
5673 ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from);
5674 ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to);
5675
5676 ClassDB::bind_method(D_METHOD("select_all"), &RichTextLabel::select_all);
5677 ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text);
5678 ClassDB::bind_method(D_METHOD("deselect"), &RichTextLabel::deselect);
5679
5680 ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode);
5681 ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text);
5682
5683 ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text);
5684
5685 ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready);
5686
5687 ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded);
5688 ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded);
5689
5690 ClassDB::bind_method(D_METHOD("set_progress_bar_delay", "delay_ms"), &RichTextLabel::set_progress_bar_delay);
5691 ClassDB::bind_method(D_METHOD("get_progress_bar_delay"), &RichTextLabel::get_progress_bar_delay);
5692
5693 ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters);
5694 ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters);
5695
5696 ClassDB::bind_method(D_METHOD("get_visible_characters_behavior"), &RichTextLabel::get_visible_characters_behavior);
5697 ClassDB::bind_method(D_METHOD("set_visible_characters_behavior", "behavior"), &RichTextLabel::set_visible_characters_behavior);
5698
5699 ClassDB::bind_method(D_METHOD("set_visible_ratio", "ratio"), &RichTextLabel::set_visible_ratio);
5700 ClassDB::bind_method(D_METHOD("get_visible_ratio"), &RichTextLabel::get_visible_ratio);
5701
5702 ClassDB::bind_method(D_METHOD("get_character_line", "character"), &RichTextLabel::get_character_line);
5703 ClassDB::bind_method(D_METHOD("get_character_paragraph", "character"), &RichTextLabel::get_character_paragraph);
5704 ClassDB::bind_method(D_METHOD("get_total_character_count"), &RichTextLabel::get_total_character_count);
5705
5706 ClassDB::bind_method(D_METHOD("set_use_bbcode", "enable"), &RichTextLabel::set_use_bbcode);
5707 ClassDB::bind_method(D_METHOD("is_using_bbcode"), &RichTextLabel::is_using_bbcode);
5708
5709 ClassDB::bind_method(D_METHOD("get_line_count"), &RichTextLabel::get_line_count);
5710 ClassDB::bind_method(D_METHOD("get_visible_line_count"), &RichTextLabel::get_visible_line_count);
5711
5712 ClassDB::bind_method(D_METHOD("get_paragraph_count"), &RichTextLabel::get_paragraph_count);
5713 ClassDB::bind_method(D_METHOD("get_visible_paragraph_count"), &RichTextLabel::get_visible_paragraph_count);
5714
5715 ClassDB::bind_method(D_METHOD("get_content_height"), &RichTextLabel::get_content_height);
5716 ClassDB::bind_method(D_METHOD("get_content_width"), &RichTextLabel::get_content_width);
5717
5718 ClassDB::bind_method(D_METHOD("get_line_offset", "line"), &RichTextLabel::get_line_offset);
5719 ClassDB::bind_method(D_METHOD("get_paragraph_offset", "paragraph"), &RichTextLabel::get_paragraph_offset);
5720
5721 ClassDB::bind_method(D_METHOD("parse_expressions_for_values", "expressions"), &RichTextLabel::parse_expressions_for_values);
5722
5723 ClassDB::bind_method(D_METHOD("set_effects", "effects"), &RichTextLabel::set_effects);
5724 ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects);
5725 ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect);
5726
5727 ClassDB::bind_method(D_METHOD("get_menu"), &RichTextLabel::get_menu);
5728 ClassDB::bind_method(D_METHOD("is_menu_visible"), &RichTextLabel::is_menu_visible);
5729 ClassDB::bind_method(D_METHOD("menu_option", "option"), &RichTextLabel::menu_option);
5730
5731 ClassDB::bind_method(D_METHOD("_thread_end"), &RichTextLabel::_thread_end);
5732
5733#ifndef DISABLE_DEPRECATED
5734 ClassDB::bind_compatibility_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font);
5735 ClassDB::bind_compatibility_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand);
5736#endif // DISABLE_DEPRECATED
5737
5738 // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets.
5739 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
5740 ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
5741
5742 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content"), "set_fit_content", "is_fit_content_enabled");
5743 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_active"), "set_scroll_active", "is_scroll_active");
5744 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following"), "set_scroll_follow", "is_scroll_following");
5745 ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
5746 ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
5747 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
5748 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
5749
5750 ADD_GROUP("Markup", "");
5751 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");
5752 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
5753 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined");
5754
5755 ADD_GROUP("Threading", "");
5756 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded");
5757 ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay");
5758
5759 ADD_GROUP("Text Selection", "");
5760 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
5761 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
5762 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled");
5763
5764 ADD_GROUP("Displayed Text", "");
5765 // Note: "visible_characters" and "visible_ratio" should be set after "text" to be correctly applied.
5766 ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
5767 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");
5768 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visible_ratio", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_visible_ratio", "get_visible_ratio");
5769
5770 ADD_GROUP("BiDi", "");
5771 ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
5772 ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
5773 ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
5774 ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
5775
5776 ADD_SIGNAL(MethodInfo("meta_clicked", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
5777 ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
5778 ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
5779
5780 ADD_SIGNAL(MethodInfo("finished"));
5781
5782 BIND_ENUM_CONSTANT(LIST_NUMBERS);
5783 BIND_ENUM_CONSTANT(LIST_LETTERS);
5784 BIND_ENUM_CONSTANT(LIST_ROMAN);
5785 BIND_ENUM_CONSTANT(LIST_DOTS);
5786
5787 BIND_ENUM_CONSTANT(MENU_COPY);
5788 BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
5789 BIND_ENUM_CONSTANT(MENU_MAX);
5790
5791 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, normal_style, "normal");
5792 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, focus_style, "focus");
5793 BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_bg_style, "background", "ProgressBar");
5794 BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_fg_style, "fill", "ProgressBar");
5795
5796 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, line_separation);
5797
5798 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, normal_font);
5799 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, RichTextLabel, normal_font_size);
5800
5801 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, default_color);
5802 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, font_selected_color);
5803 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, selection_color);
5804 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, font_outline_color);
5805 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, font_shadow_color);
5806 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, shadow_outline_size);
5807 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, shadow_offset_x);
5808 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, shadow_offset_y);
5809 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, outline_size);
5810
5811 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, bold_font);
5812 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, RichTextLabel, bold_font_size);
5813 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, bold_italics_font);
5814 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, RichTextLabel, bold_italics_font_size);
5815 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, italics_font);
5816 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, RichTextLabel, italics_font_size);
5817 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, mono_font);
5818 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, RichTextLabel, mono_font_size);
5819
5820 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, text_highlight_h_padding);
5821 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, text_highlight_v_padding);
5822
5823 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, table_h_separation);
5824 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, table_v_separation);
5825 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, table_odd_row_bg);
5826 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, table_even_row_bg);
5827 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, RichTextLabel, table_border);
5828}
5829
5830TextServer::VisibleCharactersBehavior RichTextLabel::get_visible_characters_behavior() const {
5831 return visible_chars_behavior;
5832}
5833
5834void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior) {
5835 if (visible_chars_behavior != p_behavior) {
5836 _stop_thread();
5837
5838 visible_chars_behavior = p_behavior;
5839 main->first_invalid_line.store(0); //invalidate ALL
5840 _validate_line_caches();
5841 queue_redraw();
5842 }
5843}
5844
5845void RichTextLabel::set_visible_characters(int p_visible) {
5846 if (visible_characters != p_visible) {
5847 _stop_thread();
5848
5849 visible_characters = p_visible;
5850 if (p_visible == -1) {
5851 visible_ratio = 1;
5852 } else {
5853 int total_char_count = get_total_character_count();
5854 if (total_char_count > 0) {
5855 visible_ratio = (float)p_visible / (float)total_char_count;
5856 }
5857 }
5858 if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
5859 main->first_invalid_line.store(0); //invalidate ALL
5860 _validate_line_caches();
5861 }
5862 queue_redraw();
5863 }
5864}
5865
5866int RichTextLabel::get_visible_characters() const {
5867 return visible_characters;
5868}
5869
5870int RichTextLabel::get_character_line(int p_char) {
5871 _validate_line_caches();
5872
5873 int line_count = 0;
5874 int to_line = main->first_invalid_line.load();
5875 for (int i = 0; i < to_line; i++) {
5876 MutexLock lock(main->lines[i].text_buf->get_mutex());
5877 int char_offset = main->lines[i].char_offset;
5878 int char_count = main->lines[i].char_count;
5879 if (char_offset <= p_char && p_char < char_offset + char_count) {
5880 int lc = main->lines[i].text_buf->get_line_count();
5881 for (int j = 0; j < lc; j++) {
5882 Vector2i range = main->lines[i].text_buf->get_line_range(j);
5883 if (char_offset + range.x <= p_char && p_char < char_offset + range.y) {
5884 break;
5885 }
5886 if (char_offset + range.x > p_char && line_count > 0) {
5887 line_count--; // Character is not rendered and is between the lines (e.g., edge space).
5888 break;
5889 }
5890 if (j != lc - 1) {
5891 line_count++;
5892 }
5893 }
5894 return line_count;
5895 } else {
5896 line_count += main->lines[i].text_buf->get_line_count();
5897 }
5898 }
5899 return -1;
5900}
5901
5902int RichTextLabel::get_character_paragraph(int p_char) {
5903 _validate_line_caches();
5904
5905 int to_line = main->first_invalid_line.load();
5906 for (int i = 0; i < to_line; i++) {
5907 int char_offset = main->lines[i].char_offset;
5908 if (char_offset <= p_char && p_char < char_offset + main->lines[i].char_count) {
5909 return i;
5910 }
5911 }
5912 return -1;
5913}
5914
5915int RichTextLabel::get_total_character_count() const {
5916 // Note: Do not use line buffer "char_count", it includes only visible characters.
5917 int tc = 0;
5918 Item *it = main;
5919 while (it) {
5920 if (it->type == ITEM_TEXT) {
5921 ItemText *t = static_cast<ItemText *>(it);
5922 tc += t->text.length();
5923 } else if (it->type == ITEM_NEWLINE) {
5924 tc++;
5925 } else if (it->type == ITEM_IMAGE) {
5926 tc++;
5927 }
5928 it = _get_next_item(it, true);
5929 }
5930 return tc;
5931}
5932
5933int RichTextLabel::get_total_glyph_count() const {
5934 const_cast<RichTextLabel *>(this)->_validate_line_caches();
5935
5936 int tg = 0;
5937 Item *it = main;
5938 while (it) {
5939 if (it->type == ITEM_FRAME) {
5940 ItemFrame *f = static_cast<ItemFrame *>(it);
5941 for (int i = 0; i < (int)f->lines.size(); i++) {
5942 MutexLock lock(f->lines[i].text_buf->get_mutex());
5943 tg += TS->shaped_text_get_glyph_count(f->lines[i].text_buf->get_rid());
5944 }
5945 }
5946 it = _get_next_item(it, true);
5947 }
5948
5949 return tg;
5950}
5951
5952Size2 RichTextLabel::get_minimum_size() const {
5953 Size2 sb_min_size = theme_cache.normal_style->get_minimum_size();
5954 Size2 min_size;
5955
5956 if (fit_content) {
5957 min_size.x = get_content_width();
5958 min_size.y = get_content_height();
5959 }
5960
5961 return sb_min_size +
5962 ((autowrap_mode != TextServer::AUTOWRAP_OFF) ? Size2(1, min_size.height) : min_size);
5963}
5964
5965// Context menu.
5966void RichTextLabel::_generate_context_menu() {
5967 menu = memnew(PopupMenu);
5968 add_child(menu, false, INTERNAL_MODE_FRONT);
5969 menu->connect("id_pressed", callable_mp(this, &RichTextLabel::menu_option));
5970
5971 menu->add_item(RTR("Copy"), MENU_COPY);
5972 menu->add_item(RTR("Select All"), MENU_SELECT_ALL);
5973}
5974
5975void RichTextLabel::_update_context_menu() {
5976 if (!menu) {
5977 _generate_context_menu();
5978 }
5979
5980 int idx = -1;
5981
5982#define MENU_ITEM_ACTION_DISABLED(m_menu, m_id, m_action, m_disabled) \
5983 idx = m_menu->get_item_index(m_id); \
5984 if (idx >= 0) { \
5985 m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
5986 m_menu->set_item_disabled(idx, m_disabled); \
5987 }
5988
5989 MENU_ITEM_ACTION_DISABLED(menu, MENU_COPY, "ui_copy", !selection.enabled)
5990 MENU_ITEM_ACTION_DISABLED(menu, MENU_SELECT_ALL, "ui_text_select_all", !selection.enabled)
5991
5992#undef MENU_ITEM_ACTION_DISABLED
5993}
5994
5995Key RichTextLabel::_get_menu_action_accelerator(const String &p_action) {
5996 const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
5997 if (!events) {
5998 return Key::NONE;
5999 }
6000
6001 // Use first event in the list for the accelerator.
6002 const List<Ref<InputEvent>>::Element *first_event = events->front();
6003 if (!first_event) {
6004 return Key::NONE;
6005 }
6006
6007 const Ref<InputEventKey> event = first_event->get();
6008 if (event.is_null()) {
6009 return Key::NONE;
6010 }
6011
6012 // Use physical keycode if non-zero
6013 if (event->get_physical_keycode() != Key::NONE) {
6014 return event->get_physical_keycode_with_modifiers();
6015 } else {
6016 return event->get_keycode_with_modifiers();
6017 }
6018}
6019
6020void RichTextLabel::menu_option(int p_option) {
6021 switch (p_option) {
6022 case MENU_COPY: {
6023 selection_copy();
6024 } break;
6025 case MENU_SELECT_ALL: {
6026 select_all();
6027 } break;
6028 }
6029}
6030
6031void 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) {
6032 Vector2i fbg_index = Vector2i(end, start);
6033 Color last_color = Color(0, 0, 0, 0);
6034 bool draw_box = false;
6035 // Draw a box based on color tags associated with glyphs
6036 for (int i = start; i < end; i++) {
6037 Item *it = _get_item_at_pos(it_from, it_to, i);
6038 Color color;
6039
6040 if (fbg_flag == 0) {
6041 color = _find_bgcolor(it);
6042 } else {
6043 color = _find_fgcolor(it);
6044 }
6045
6046 bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01));
6047 bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0));
6048 bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color));
6049
6050 if (change_to_color) {
6051 fbg_index.x = MIN(i, fbg_index.x);
6052 fbg_index.y = MAX(i, fbg_index.y);
6053 }
6054
6055 if (change_from_color || change_color) {
6056 fbg_index.x = MIN(i, fbg_index.x);
6057 fbg_index.y = MAX(i, fbg_index.y);
6058 draw_box = true;
6059 }
6060
6061 if (draw_box) {
6062 Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y);
6063 for (int j = 0; j < sel.size(); j++) {
6064 Vector2 rect_off = line_off + Vector2(sel[j].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding);
6065 Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding);
6066 RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
6067 }
6068 fbg_index = Vector2i(end, start);
6069 draw_box = false;
6070 }
6071
6072 if (change_color) {
6073 fbg_index.x = MIN(i, fbg_index.x);
6074 fbg_index.y = MAX(i, fbg_index.y);
6075 }
6076
6077 last_color = color;
6078 }
6079
6080 if (last_color.a > 0) {
6081 Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end);
6082 for (int i = 0; i < sel.size(); i++) {
6083 Vector2 rect_off = line_off + Vector2(sel[i].x - theme_cache.text_highlight_h_padding, -TS->shaped_text_get_ascent(p_rid) - theme_cache.text_highlight_v_padding);
6084 Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * theme_cache.text_highlight_h_padding, TS->shaped_text_get_size(p_rid).y + 2 * theme_cache.text_highlight_v_padding);
6085 RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
6086 }
6087 }
6088}
6089
6090Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) {
6091 for (int i = 0; i < custom_effects.size(); i++) {
6092 Ref<RichTextEffect> effect = custom_effects[i];
6093 if (!effect.is_valid()) {
6094 continue;
6095 }
6096
6097 if (effect->get_bbcode() == p_bbcode_identifier) {
6098 return effect;
6099 }
6100 }
6101
6102 return Ref<RichTextEffect>();
6103}
6104
6105Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) {
6106 Dictionary d;
6107 for (int i = 0; i < p_expressions.size(); i++) {
6108 String expression = p_expressions[i];
6109
6110 Array a;
6111 Vector<String> parts = expression.split("=", true);
6112 String key = parts[0];
6113 if (parts.size() != 2) {
6114 return d;
6115 }
6116
6117 Vector<String> values = parts[1].split(",", false);
6118
6119#ifdef MODULE_REGEX_ENABLED
6120 RegEx color = RegEx();
6121 color.compile("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
6122 RegEx nodepath = RegEx();
6123 nodepath.compile("^\\$");
6124 RegEx boolean = RegEx();
6125 boolean.compile("^(true|false)$");
6126 RegEx decimal = RegEx();
6127 decimal.compile("^-?^.?\\d+(\\.\\d+?)?$");
6128 RegEx numerical = RegEx();
6129 numerical.compile("^\\d+$");
6130
6131 for (int j = 0; j < values.size(); j++) {
6132 if (!color.search(values[j]).is_null()) {
6133 a.append(Color::html(values[j]));
6134 } else if (!nodepath.search(values[j]).is_null()) {
6135 if (values[j].begins_with("$")) {
6136 String v = values[j].substr(1, values[j].length());
6137 a.append(NodePath(v));
6138 }
6139 } else if (!boolean.search(values[j]).is_null()) {
6140 if (values[j] == "true") {
6141 a.append(true);
6142 } else if (values[j] == "false") {
6143 a.append(false);
6144 }
6145 } else if (!decimal.search(values[j]).is_null()) {
6146 a.append(values[j].to_float());
6147 } else if (!numerical.search(values[j]).is_null()) {
6148 a.append(values[j].to_int());
6149 } else {
6150 a.append(values[j]);
6151 }
6152 }
6153#endif
6154
6155 if (values.size() > 1) {
6156 d[key] = a;
6157 } else if (values.size() == 1) {
6158 d[key] = a[0];
6159 }
6160 }
6161 return d;
6162}
6163
6164RichTextLabel::RichTextLabel(const String &p_text) {
6165 main = memnew(ItemFrame);
6166 main->index = 0;
6167 current = main;
6168 main->lines.resize(1);
6169 main->lines[0].from = main;
6170 main->first_invalid_line.store(0);
6171 main->first_resized_line.store(0);
6172 main->first_invalid_font_line.store(0);
6173 current_frame = main;
6174
6175 vscroll = memnew(VScrollBar);
6176 add_child(vscroll, false, INTERNAL_MODE_FRONT);
6177 vscroll->set_drag_node(String(".."));
6178 vscroll->set_step(1);
6179 vscroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0);
6180 vscroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
6181 vscroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);
6182 vscroll->connect("value_changed", callable_mp(this, &RichTextLabel::_scroll_changed));
6183 vscroll->set_step(1);
6184 vscroll->hide();
6185
6186 set_text(p_text);
6187 updating.store(false);
6188 validating.store(false);
6189 stop_thread.store(false);
6190
6191 set_clip_contents(true);
6192}
6193
6194RichTextLabel::~RichTextLabel() {
6195 _stop_thread();
6196 memdelete(main);
6197}
6198