summaryrefslogtreecommitdiff
path: root/scene/gui/rich_text_label.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui/rich_text_label.cpp')
-rw-r--r--scene/gui/rich_text_label.cpp161
1 files changed, 129 insertions, 32 deletions
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index b50fad0e42..4865b9770e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -205,6 +205,49 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
return s;
}
+void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
+ ERR_FAIL_COND(p_frame == nullptr);
+ ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+
+ Line &l = p_frame->lines.write[p_line];
+
+ RID t = l.text_buf->get_rid();
+ int spans = TS->shaped_get_span_count(t);
+ for (int i = 0; i < spans; i++) {
+ ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i);
+ if (it) {
+ Ref<Font> font = _find_font(it);
+ if (font.is_null()) {
+ font = p_base_font;
+ }
+ int font_size = _find_font_size(it);
+ if (font_size == -1) {
+ font_size = p_base_font_size;
+ }
+ Dictionary font_ftr = _find_font_features(it);
+ TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr);
+ }
+ }
+
+ Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
+ switch (it->type) {
+ case ITEM_TABLE: {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ for (Item *E : table->subitems) {
+ ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(E);
+ for (int i = 0; i < frame->lines.size(); i++) {
+ _update_line_font(frame, i, p_base_font, p_base_font_size);
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) {
ERR_FAIL_COND(p_frame == nullptr);
ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
@@ -238,7 +281,8 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
for (int i = 0; i < frame->lines.size(); i++) {
- _resize_line(frame, i, p_base_font, p_base_font_size, 1);
+ int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
+ _resize_line(frame, i, p_base_font, p_base_font_size, w);
}
idx++;
}
@@ -254,12 +298,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- // If the column can grow, allow it to grow.
table->columns.write[i].expand = true;
- } else {
- // Otherwise make it shrink as much as possible, so that other columns can grow if needs be.
- // We keep the max width as is to spread the remaining space between the columns later.
- table->columns.write[i].min_width = 0;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -330,7 +369,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
} else {
frame->lines.write[i].offset.y = 0;
}
@@ -370,7 +409,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
if (p_line > 0) {
- l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
} else {
l.offset.y = 0;
}
@@ -382,9 +421,24 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
Line &l = p_frame->lines.write[p_line];
+ uint16_t autowrap_flags = TextServer::BREAK_MANDATORY;
+ switch (autowrap_mode) {
+ case AUTOWRAP_WORD_SMART:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_WORD:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_ARBITRARY:
+ autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_OFF:
+ break;
+ }
+
// Clear cache.
l.text_buf->clear();
- l.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
+ l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
l.char_offset = *r_char_offset;
l.char_count = 0;
@@ -453,7 +507,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
remaining_characters -= tx.length();
- l.text_buf->add_string(tx, font, font_size, font_ftr, lang);
+ l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it);
text += tx;
l.char_count += tx.length();
} break;
@@ -487,7 +541,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int column = idx % col_count;
for (int i = 0; i < frame->lines.size(); i++) {
int char_offset = l.char_offset + l.char_count;
- _shape_line(frame, i, p_base_font, p_base_font_size, 1, &char_offset);
+ int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
+ _shape_line(frame, i, p_base_font, p_base_font_size, w, &char_offset);
int cell_ch = (char_offset - (l.char_offset + l.char_count));
l.char_count += cell_ch;
t_char_count += cell_ch;
@@ -507,12 +562,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- // If the column can grow, allow it to grow.
table->columns.write[i].expand = true;
- } else {
- // Otherwise make it shrink as much as possible, so that other columns can grow if needs be.
- // We keep the max width as is to spread the remaining space between the columns later.
- table->columns.write[i].min_width = 0;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -583,7 +633,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
} else {
frame->lines.write[i].offset.y = 0;
}
@@ -631,7 +681,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
*r_char_offset = l.char_offset + l.char_count;
if (p_line > 0) {
- l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
} else {
l.offset.y = 0;
}
@@ -1198,7 +1248,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
//TODO, change to binary search ?
while (from_line < main->lines.size()) {
- if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y >= vofs) {
+ if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) {
break;
}
from_line++;
@@ -1211,7 +1261,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
while (ofs.y < size.height && from_line < main->lines.size()) {
_find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char);
- ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) {
if (r_outside != nullptr) {
*r_outside = false;
@@ -1330,7 +1380,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (rect.has_point(p_click) && !table_hit) {
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
}
- off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom();
+ off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation"));
}
if (char_pos >= 0) {
@@ -1456,7 +1506,10 @@ void RichTextLabel::_notification(int p_what) {
update();
} break;
- case NOTIFICATION_THEME_CHANGED:
+ case NOTIFICATION_THEME_CHANGED: {
+ main->first_invalid_font_line = 0; //invalidate ALL
+ update();
+ } break;
case NOTIFICATION_ENTER_TREE: {
if (!text.is_empty()) {
set_text(text);
@@ -1494,7 +1547,7 @@ void RichTextLabel::_notification(int p_what) {
//TODO, change to binary search ?
while (from_line < main->lines.size()) {
- if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y >= vofs) {
+ if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")) >= vofs) {
break;
}
from_line++;
@@ -1520,7 +1573,7 @@ void RichTextLabel::_notification(int p_what) {
while (ofs.y < size.height && from_line < main->lines.size()) {
visible_paragraph_count++;
visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs, processed_glyphs);
- ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
+ ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
from_line++;
}
} break;
@@ -1553,6 +1606,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
return get_default_cursor_shape(); //invalid
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return get_default_cursor_shape(); //invalid
+ }
+
if (main->first_resized_line < main->lines.size()) {
return get_default_cursor_shape(); //invalid
}
@@ -1577,6 +1634,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return;
+ }
if (main->first_resized_line < main->lines.size()) {
return;
}
@@ -1740,6 +1800,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return;
+ }
if (main->first_resized_line < main->lines.size()) {
return;
}
@@ -2192,6 +2255,18 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
if (p_frame->first_invalid_line == p_frame->lines.size()) {
+ Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
+ int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+
+ // Update fonts.
+ if (p_frame->first_invalid_font_line != p_frame->lines.size()) {
+ for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) {
+ _update_line_font(p_frame, i, base_font, base_font_size);
+ }
+ p_frame->first_resized_line = p_frame->first_invalid_font_line;
+ p_frame->first_invalid_font_line = p_frame->lines.size();
+ }
+
if (p_frame->first_resized_line == p_frame->lines.size()) {
return;
}
@@ -2199,16 +2274,13 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
// Resize lines without reshaping.
Rect2 text_rect = _get_text_rect();
- Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
- int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
-
for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) {
_resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w);
}
int total_height = 0;
if (p_frame->lines.size()) {
- total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y;
+ total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
p_frame->first_resized_line = p_frame->lines.size();
@@ -2240,11 +2312,12 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
int total_height = 0;
if (p_frame->lines.size()) {
- total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y;
+ total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
p_frame->first_invalid_line = p_frame->lines.size();
p_frame->first_resized_line = p_frame->lines.size();
+ p_frame->first_invalid_font_line = p_frame->lines.size();
updating_scroll = true;
vscroll->set_max(total_height);
@@ -3154,7 +3227,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
indent_level++;
push_list(indent_level, LIST_NUMBERS, false);
pos = brk_end + 1;
- tag_stack.push_front(tag);
+ tag_stack.push_front("ol");
} else if (tag == "ol type=a") {
indent_level++;
push_list(indent_level, LIST_LETTERS, false);
@@ -4019,6 +4092,19 @@ String RichTextLabel::get_language() const {
return language;
}
+void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) {
+ if (autowrap_mode != p_mode) {
+ autowrap_mode = p_mode;
+ main->first_invalid_line = 0; //invalidate ALL
+ _validate_line_caches(main);
+ update();
+ }
+}
+
+RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const {
+ return autowrap_mode;
+}
+
void RichTextLabel::set_percent_visible(float p_percent) {
if (percent_visible != p_percent) {
if (p_percent < 0 || p_percent >= 1) {
@@ -4066,7 +4152,7 @@ void RichTextLabel::install_effect(const Variant effect) {
int RichTextLabel::get_content_height() const {
int total_height = 0;
if (main->lines.size()) {
- total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y;
+ total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y + main->lines[main->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
return total_height;
}
@@ -4130,6 +4216,9 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language);
+ ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode);
+ ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode);
+
ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline);
ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined);
@@ -4222,6 +4311,8 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
+
ADD_GROUP("Structured Text", "structured_text_");
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");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
@@ -4230,6 +4321,11 @@ void RichTextLabel::_bind_methods() {
ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
+ BIND_ENUM_CONSTANT(AUTOWRAP_OFF);
+ BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART);
+
BIND_ENUM_CONSTANT(LIST_NUMBERS);
BIND_ENUM_CONSTANT(LIST_LETTERS);
BIND_ENUM_CONSTANT(LIST_ROMAN);
@@ -4353,7 +4449,7 @@ Size2 RichTextLabel::get_minimum_size() const {
size.x += fixed_width;
}
- if (fixed_width != -1 || fit_content_height) {
+ if (fit_content_height) {
const_cast<RichTextLabel *>(this)->_validate_line_caches(main);
size.y += get_content_height();
}
@@ -4502,6 +4598,7 @@ RichTextLabel::RichTextLabel() {
main->lines.write[0].from = main;
main->first_invalid_line = 0;
main->first_resized_line = 0;
+ main->first_invalid_font_line = 0;
current_frame = main;
vscroll = memnew(VScrollBar);