Fix RichTextLabel table overlapping with next line

When calculating table height, RichTextLabel::_shape_line would increase the height when it reached the end of a complete row, or the final cell of the table.  RichTextLabel::_resize_line would only increase the height at the end of a complete row, causing the height to ignore the final row if not all cells were populated.  This would cause the final row to overlap with the following line in the RichTextLabel if it was the last visible line in the RichTextLabel, as _resize_line is called when updating scrolling properties.

This change moves the common table size calculations to a separate function to reuse the code between _shape_line and _resize_line, keeping the final cell check that was used in _shape_line.

Fixes #92603
This commit is contained in:
aaronp64 2024-05-31 14:32:11 -04:00
parent 705b7a0b0b
commit 6ecccd6085
2 changed files with 130 additions and 242 deletions

View file

@ -318,7 +318,6 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
table->columns[i].width = 0;
}
int idx = 0;
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
@ -328,131 +327,11 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
prev_h = _resize_line(frame, i, p_base_font, p_base_font_size, w, prev_h);
}
idx++;
}
// Compute minimum width for each cell.
const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1);
_set_table_size(table, available_width);
// Compute available width and total ratio (for expanders).
int total_ratio = 0;
int remaining_width = available_width;
table->total_width = theme_cache.table_h_separation;
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) {
table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
}
}
// Assign actual widths.
for (int i = 0; i < col_count; i++) {
table->columns[i].width = table->columns[i].min_width;
if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
if (i != col_count - 1) {
table->total_width += table->columns[i].width + theme_cache.table_h_separation;
} else {
table->total_width += table->columns[i].width;
}
}
// Resize to max_width if needed and distribute the remaining space.
bool table_need_fit = true;
while (table_need_fit) {
table_need_fit = false;
// Fit slim.
for (int i = 0; i < col_count; i++) {
if (!table->columns[i].expand) {
continue;
}
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
}
// Grow.
remaining_width = available_width - table->total_width;
if (remaining_width > 0 && total_ratio > 0) {
for (int i = 0; i < col_count; i++) {
if (table->columns[i].expand) {
int dif = table->columns[i].max_width - table->columns[i].width;
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
table->columns[i].width += incr;
table->total_width += incr;
}
}
}
}
}
// Update line width and get total height.
idx = 0;
table->total_height = 0;
table->rows.clear();
table->rows_baseline.clear();
Vector2 offset;
float row_height = 0.0;
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
int column = idx % col_count;
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
float prev_h = 0;
float row_baseline = 0.0;
for (int i = 0; i < (int)frame->lines.size(); i++) {
MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
frame->lines[i].text_buf->set_width(table->columns[column].width);
table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
frame->lines[i].offset.y = prev_h;
float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation;
if (i > 0) {
h += theme_cache.line_separation;
}
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
if (frame->max_size_over.y > 0) {
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation;
frame->lines[i].offset += offset;
row_baseline = MAX(row_baseline, frame->lines[i].text_buf->get_line_ascent(frame->lines[i].text_buf->get_line_count() - 1));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x;
row_height = MAX(yofs, row_height);
if (column == col_count - 1) {
offset.x = 0;
row_height += theme_cache.table_v_separation;
table->total_height += row_height;
offset.y += row_height;
table->rows.push_back(row_height);
table->rows_baseline.push_back(table->total_height - row_height + row_baseline);
row_height = 0;
}
idx++;
}
int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) {
l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
@ -630,127 +509,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
idx++;
}
// Compute available width and total ratio (for expanders).
int total_ratio = 0;
int remaining_width = available_width;
table->total_width = theme_cache.table_h_separation;
_set_table_size(table, available_width);
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) {
table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
}
}
// Assign actual widths.
for (int i = 0; i < col_count; i++) {
table->columns[i].width = table->columns[i].min_width;
if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
if (i != col_count - 1) {
table->total_width += table->columns[i].width + theme_cache.table_h_separation;
} else {
table->total_width += table->columns[i].width;
}
}
// Resize to max_width if needed and distribute the remaining space.
bool table_need_fit = true;
while (table_need_fit) {
table_need_fit = false;
// Fit slim.
for (int i = 0; i < col_count; i++) {
if (!table->columns[i].expand) {
continue;
}
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
}
// Grow.
remaining_width = available_width - table->total_width;
if (remaining_width > 0 && total_ratio > 0) {
for (int i = 0; i < col_count; i++) {
if (table->columns[i].expand) {
int dif = table->columns[i].max_width - table->columns[i].width;
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
table->columns[i].width += incr;
table->total_width += incr;
}
}
}
}
}
// Update line width and get total height.
idx = 0;
table->total_height = 0;
table->rows.clear();
table->rows_baseline.clear();
Vector2 offset;
float row_height = 0.0;
for (const List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) {
ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E->get());
int column = idx % col_count;
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
float prev_h = 0;
float row_baseline = 0.0;
for (int i = 0; i < (int)frame->lines.size(); i++) {
MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
frame->lines[i].text_buf->set_width(table->columns[column].width);
table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
frame->lines[i].offset.y = prev_h;
float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation;
if (i > 0) {
h += theme_cache.line_separation;
}
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
if (frame->max_size_over.y > 0) {
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation;
frame->lines[i].offset += offset;
row_baseline = MAX(row_baseline, frame->lines[i].text_buf->get_line_ascent(frame->lines[i].text_buf->get_line_count() - 1));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x;
row_height = MAX(yofs, row_height);
// Add row height after last column of the row or last cell of the table.
if (column == col_count - 1 || E->next() == nullptr) {
offset.x = 0;
row_height += theme_cache.table_v_separation;
table->total_height += row_height;
offset.y += row_height;
table->rows.push_back(row_height);
table->rows_baseline.push_back(table->total_height - row_height + row_baseline);
row_height = 0;
}
idx++;
}
int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size() - 1) {
l.text_buf->add_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, t_char_count, Math::round(table->rows_baseline[row_idx]));
@ -773,6 +533,132 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
return _calculate_line_vertical_offset(l);
}
void RichTextLabel::_set_table_size(ItemTable *p_table, int p_available_width) {
int col_count = p_table->columns.size();
// Compute available width and total ratio (for expanders).
int total_ratio = 0;
int remaining_width = p_available_width;
p_table->total_width = theme_cache.table_h_separation;
for (int i = 0; i < col_count; i++) {
remaining_width -= p_table->columns[i].min_width;
if (p_table->columns[i].max_width > p_table->columns[i].min_width) {
p_table->columns[i].expand = true;
}
if (p_table->columns[i].expand) {
total_ratio += p_table->columns[i].expand_ratio;
}
}
// Assign actual widths.
for (int i = 0; i < col_count; i++) {
p_table->columns[i].width = p_table->columns[i].min_width;
if (p_table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
p_table->columns[i].width += p_table->columns[i].expand_ratio * remaining_width / total_ratio;
}
if (i != col_count - 1) {
p_table->total_width += p_table->columns[i].width + theme_cache.table_h_separation;
} else {
p_table->total_width += p_table->columns[i].width;
}
}
// Resize to max_width if needed and distribute the remaining space.
bool table_need_fit = true;
while (table_need_fit) {
table_need_fit = false;
// Fit slim.
for (int i = 0; i < col_count; i++) {
if (!p_table->columns[i].expand) {
continue;
}
int dif = p_table->columns[i].width - p_table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
p_table->columns[i].width = p_table->columns[i].max_width;
p_table->total_width -= dif;
total_ratio -= p_table->columns[i].expand_ratio;
}
}
// Grow.
remaining_width = p_available_width - p_table->total_width;
if (remaining_width > 0 && total_ratio > 0) {
for (int i = 0; i < col_count; i++) {
if (p_table->columns[i].expand) {
int dif = p_table->columns[i].max_width - p_table->columns[i].width;
if (dif > 0) {
int slice = p_table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
p_table->columns[i].width += incr;
p_table->total_width += incr;
}
}
}
}
}
// Update line width and get total height.
int idx = 0;
p_table->total_height = 0;
p_table->rows.clear();
p_table->rows_baseline.clear();
Vector2 offset;
float row_height = 0.0;
for (const List<Item *>::Element *E = p_table->subitems.front(); E; E = E->next()) {
ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E->get());
int column = idx % col_count;
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
float prev_h = 0;
float row_baseline = 0.0;
for (int i = 0; i < (int)frame->lines.size(); i++) {
MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
frame->lines[i].text_buf->set_width(p_table->columns[column].width);
p_table->columns[column].width = MAX(p_table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
frame->lines[i].offset.y = prev_h;
float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation;
if (i > 0) {
h += theme_cache.line_separation;
}
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
if (frame->max_size_over.y > 0) {
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation;
frame->lines[i].offset += offset;
row_baseline = MAX(row_baseline, frame->lines[i].text_buf->get_line_ascent(frame->lines[i].text_buf->get_line_count() - 1));
}
yofs += frame->padding.size.y;
offset.x += p_table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x;
row_height = MAX(yofs, row_height);
// Add row height after last column of the row or last cell of the table.
if (column == col_count - 1 || E->next() == nullptr) {
offset.x = 0;
row_height += theme_cache.table_v_separation;
p_table->total_height += row_height;
offset.y += row_height;
p_table->rows.push_back(row_height);
p_table->rows_baseline.push_back(p_table->total_height - row_height + row_baseline);
row_height = 0;
}
idx++;
}
}
int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) {
ERR_FAIL_NULL_V(p_frame, 0);
ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0);

View file

@ -554,6 +554,8 @@ private:
float _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);
float _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);
void _set_table_size(ItemTable *p_table, int p_available_width);
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _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);
float _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 = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false);