mirror of
https://github.com/godotengine/godot.git
synced 2025-01-22 10:32:54 -05:00
[Label] Handle text as multiple independent paragraphs.
This commit is contained in:
parent
76fa7b2914
commit
b329b4ab06
6 changed files with 581 additions and 340 deletions
|
@ -74,6 +74,9 @@
|
|||
Limits the lines of text the node shows on screen.
|
||||
</member>
|
||||
<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="2" />
|
||||
<member name="paragraph_separator" type="String" setter="set_paragraph_separator" getter="get_paragraph_separator" default=""\\n"">
|
||||
String used as a paragraph separator. Each paragraph is processed independently, in its own BiDi context.
|
||||
</member>
|
||||
<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" enum="Control.SizeFlags" is_bitfield="true" default="4" />
|
||||
<member name="structured_text_bidi_override" type="int" setter="set_structured_text_bidi_override" getter="get_structured_text_bidi_override" enum="TextServer.StructuredTextParser" default="0">
|
||||
Set BiDi algorithm override for the structured text.
|
||||
|
@ -129,6 +132,9 @@
|
|||
[b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended.
|
||||
[b]Note:[/b] Using a value that is larger than half the font size is not recommended, as the font outline may fail to be fully closed in this case.
|
||||
</theme_item>
|
||||
<theme_item name="paragraph_spacing" data_type="constant" type="int" default="0">
|
||||
Vertical space between paragraphs. Added on top of [theme_item line_spacing].
|
||||
</theme_item>
|
||||
<theme_item name="shadow_offset_x" data_type="constant" type="int" default="1">
|
||||
The horizontal offset of the text's shadow.
|
||||
</theme_item>
|
||||
|
|
|
@ -27,6 +27,9 @@
|
|||
<member name="outline_size" type="int" setter="set_outline_size" getter="get_outline_size" default="0">
|
||||
Text outline size.
|
||||
</member>
|
||||
<member name="paragraph_spacing" type="float" setter="set_paragraph_spacing" getter="get_paragraph_spacing" default="0.0">
|
||||
Vertical space between paragraphs. Added on top of [member line_spacing].
|
||||
</member>
|
||||
<member name="shadow_color" type="Color" setter="set_shadow_color" getter="get_shadow_color" default="Color(0, 0, 0, 0)">
|
||||
Color of the shadow effect. If alpha is [code]0[/code], no shadow will be drawn.
|
||||
</member>
|
||||
|
|
|
@ -43,7 +43,9 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
|
|||
}
|
||||
|
||||
autowrap_mode = p_mode;
|
||||
lines_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
update_configuration_warnings();
|
||||
|
||||
|
@ -62,7 +64,9 @@ void Label::set_justification_flags(BitField<TextServer::JustificationFlag> p_fl
|
|||
}
|
||||
|
||||
jst_flags = p_flags;
|
||||
lines_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
|
@ -76,7 +80,7 @@ void Label::set_uppercase(bool p_uppercase) {
|
|||
}
|
||||
|
||||
uppercase = p_uppercase;
|
||||
dirty = true;
|
||||
text_dirty = true;
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
@ -87,12 +91,14 @@ bool Label::is_uppercase() const {
|
|||
|
||||
int Label::get_line_height(int p_line) const {
|
||||
Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
|
||||
if (p_line >= 0 && p_line < lines_rid.size()) {
|
||||
return TS->shaped_text_get_size(lines_rid[p_line]).y;
|
||||
} else if (lines_rid.size() > 0) {
|
||||
if (p_line >= 0 && p_line < total_line_count) {
|
||||
return TS->shaped_text_get_size(get_line_rid(p_line)).y;
|
||||
} else if (total_line_count > 0) {
|
||||
int h = 0;
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y);
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
h = MAX(h, TS->shaped_text_get_size(line_rid).y);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
} else {
|
||||
|
@ -105,44 +111,70 @@ void Label::_shape() {
|
|||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int width = (get_size().width - style->get_minimum_size().width);
|
||||
|
||||
if (dirty || font_dirty) {
|
||||
if (dirty) {
|
||||
TS->shaped_text_clear(text_rid);
|
||||
if (text_dirty) {
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
TS->free_rid(line_rid);
|
||||
}
|
||||
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
|
||||
TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
|
||||
} else {
|
||||
TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction);
|
||||
para.lines_rid.clear();
|
||||
TS->free_rid(para.text_rid);
|
||||
}
|
||||
const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
|
||||
int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size;
|
||||
ERR_FAIL_COND(font.is_null());
|
||||
paragraphs.clear();
|
||||
|
||||
String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text;
|
||||
if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
|
||||
txt = txt.substr(0, visible_chars);
|
||||
}
|
||||
if (dirty) {
|
||||
TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language);
|
||||
} else {
|
||||
int spans = TS->shaped_get_span_count(text_rid);
|
||||
for (int i = 0; i < spans; i++) {
|
||||
TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features());
|
||||
String ps = paragraph_separator.c_unescape();
|
||||
Vector<String> para_text = txt.split(ps);
|
||||
int start = 0;
|
||||
for (const String &str : para_text) {
|
||||
Paragraph para;
|
||||
para.text_rid = TS->create_shaped_text();
|
||||
para.text = str;
|
||||
para.start = start;
|
||||
start += str.length() + ps.length();
|
||||
paragraphs.push_back(para);
|
||||
}
|
||||
}
|
||||
TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, txt));
|
||||
if (!tab_stops.is_empty()) {
|
||||
TS->shaped_text_tab_align(text_rid, tab_stops);
|
||||
}
|
||||
dirty = false;
|
||||
font_dirty = false;
|
||||
lines_dirty = true;
|
||||
text_dirty = false;
|
||||
}
|
||||
|
||||
if (lines_dirty) {
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
TS->free_rid(lines_rid[i]);
|
||||
total_line_count = 0;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
if (para.dirty || font_dirty) {
|
||||
if (para.dirty) {
|
||||
TS->shaped_text_clear(para.text_rid);
|
||||
}
|
||||
lines_rid.clear();
|
||||
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
|
||||
TS->shaped_text_set_direction(para.text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
|
||||
} else {
|
||||
TS->shaped_text_set_direction(para.text_rid, (TextServer::Direction)text_direction);
|
||||
}
|
||||
const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font;
|
||||
int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size;
|
||||
ERR_FAIL_COND(font.is_null());
|
||||
|
||||
if (para.dirty) {
|
||||
TS->shaped_text_add_string(para.text_rid, para.text, font->get_rids(), font_size, font->get_opentype_features(), language);
|
||||
} else {
|
||||
int spans = TS->shaped_get_span_count(para.text_rid);
|
||||
for (int i = 0; i < spans; i++) {
|
||||
TS->shaped_set_span_update_font(para.text_rid, i, font->get_rids(), font_size, font->get_opentype_features());
|
||||
}
|
||||
}
|
||||
TS->shaped_text_set_bidi_override(para.text_rid, structured_text_parser(st_parser, st_args, para.text));
|
||||
if (!tab_stops.is_empty()) {
|
||||
TS->shaped_text_tab_align(para.text_rid, tab_stops);
|
||||
}
|
||||
para.dirty = false;
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
|
||||
if (para.lines_dirty) {
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
TS->free_rid(line_rid);
|
||||
}
|
||||
para.lines_rid.clear();
|
||||
|
||||
BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY;
|
||||
switch (autowrap_mode) {
|
||||
|
@ -160,31 +192,40 @@ void Label::_shape() {
|
|||
}
|
||||
autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
|
||||
|
||||
PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags);
|
||||
PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(para.text_rid, width, 0, autowrap_flags);
|
||||
for (int i = 0; i < line_breaks.size(); i = i + 2) {
|
||||
RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
|
||||
RID line = TS->shaped_text_substr(para.text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]);
|
||||
if (!tab_stops.is_empty()) {
|
||||
TS->shaped_text_tab_align(line, tab_stops);
|
||||
}
|
||||
lines_rid.push_back(line);
|
||||
para.lines_rid.push_back(line);
|
||||
}
|
||||
}
|
||||
total_line_count += para.lines_rid.size();
|
||||
}
|
||||
dirty = false;
|
||||
font_dirty = false;
|
||||
|
||||
if (xl_text.length() == 0) {
|
||||
minsize = Size2(1, get_line_height());
|
||||
return;
|
||||
}
|
||||
|
||||
int visible_lines = get_visible_line_count();
|
||||
bool lines_hidden = visible_lines > 0 && visible_lines < total_line_count;
|
||||
|
||||
int line_index = 0;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
if (autowrap_mode == TextServer::AUTOWRAP_OFF) {
|
||||
minsize.width = 0.0f;
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
if (minsize.width < TS->shaped_text_get_size(lines_rid[i]).x) {
|
||||
minsize.width = TS->shaped_text_get_size(lines_rid[i]).x;
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
if (minsize.width < TS->shaped_text_get_size(line_rid).x) {
|
||||
minsize.width = TS->shaped_text_get_size(line_rid).x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lines_dirty) {
|
||||
if (para.lines_dirty) {
|
||||
BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM;
|
||||
switch (overrun_behavior) {
|
||||
case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS:
|
||||
|
@ -214,72 +255,72 @@ void Label::_shape() {
|
|||
line_jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB);
|
||||
}
|
||||
if (autowrap_mode != TextServer::AUTOWRAP_OFF) {
|
||||
int visible_lines = get_visible_line_count();
|
||||
bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size();
|
||||
if (lines_hidden) {
|
||||
overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS);
|
||||
}
|
||||
if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) {
|
||||
int jst_to_line = visible_lines;
|
||||
if (lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
|
||||
jst_to_line = lines_rid.size();
|
||||
int jst_to_line = para.lines_rid.size();
|
||||
if (para.lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
|
||||
jst_to_line = para.lines_rid.size();
|
||||
} else {
|
||||
if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) {
|
||||
jst_to_line = visible_lines - 1;
|
||||
jst_to_line = para.lines_rid.size() - 1;
|
||||
}
|
||||
if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) {
|
||||
for (int i = visible_lines - 1; i >= 0; i--) {
|
||||
if (TS->shaped_text_has_visible_chars(lines_rid[i])) {
|
||||
for (int i = para.lines_rid.size() - 1; i >= 0; i--) {
|
||||
if (TS->shaped_text_has_visible_chars(para.lines_rid[i])) {
|
||||
jst_to_line = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
for (int i = 0; i < para.lines_rid.size(); i++) {
|
||||
if (i < jst_to_line) {
|
||||
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
|
||||
} else if (i == (visible_lines - 1)) {
|
||||
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
|
||||
TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags);
|
||||
} else if (i == (visible_lines - line_index - 1)) {
|
||||
TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags);
|
||||
}
|
||||
}
|
||||
} else if (lines_hidden) {
|
||||
TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
|
||||
} else if (lines_hidden && (visible_lines - line_index - 1 >= 0) && (visible_lines - line_index - 1) < para.lines_rid.size()) {
|
||||
TS->shaped_text_set_custom_ellipsis(para.lines_rid[visible_lines - line_index - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(para.lines_rid[visible_lines - line_index - 1], width, overrun_flags);
|
||||
}
|
||||
} else {
|
||||
// Autowrap disabled.
|
||||
int jst_to_line = lines_rid.size();
|
||||
if (lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
|
||||
jst_to_line = lines_rid.size();
|
||||
int jst_to_line = para.lines_rid.size();
|
||||
if (para.lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) {
|
||||
jst_to_line = para.lines_rid.size();
|
||||
} else {
|
||||
if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) {
|
||||
jst_to_line = lines_rid.size() - 1;
|
||||
jst_to_line = para.lines_rid.size() - 1;
|
||||
}
|
||||
if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) {
|
||||
for (int i = lines_rid.size() - 1; i >= 0; i--) {
|
||||
if (TS->shaped_text_has_visible_chars(lines_rid[i])) {
|
||||
for (int i = para.lines_rid.size() - 1; i >= 0; i--) {
|
||||
if (TS->shaped_text_has_visible_chars(para.lines_rid[i])) {
|
||||
jst_to_line = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
for (int i = 0; i < para.lines_rid.size(); i++) {
|
||||
if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) {
|
||||
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
|
||||
TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags);
|
||||
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
|
||||
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
|
||||
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
|
||||
TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags);
|
||||
TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
|
||||
} else {
|
||||
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
|
||||
TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
|
||||
TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
lines_dirty = false;
|
||||
para.lines_dirty = false;
|
||||
}
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
|
||||
_update_visible();
|
||||
|
@ -291,20 +332,37 @@ void Label::_shape() {
|
|||
|
||||
void Label::_update_visible() {
|
||||
int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
int paragraph_spacing = settings.is_valid() ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing;
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int lines_visible = lines_rid.size();
|
||||
int lines_visible = total_line_count;
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
lines_visible = max_lines_visible;
|
||||
}
|
||||
|
||||
minsize.height = 0;
|
||||
int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
|
||||
for (int64_t i = lines_skipped; i < last_line; i++) {
|
||||
minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
int last_line = MIN(total_line_count, lines_visible + lines_skipped);
|
||||
|
||||
int line_index = 0;
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
int end = (line_index + para.lines_rid.size() < last_line) ? para.lines_rid.size() : last_line - line_index;
|
||||
if (end <= 0) {
|
||||
break;
|
||||
}
|
||||
for (int i = start; i < end; i++) {
|
||||
minsize.height += TS->shaped_text_get_size(para.lines_rid[i]).y + line_spacing;
|
||||
}
|
||||
minsize.height += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (minsize.height > 0) {
|
||||
minsize.height -= line_spacing;
|
||||
minsize.height -= (line_spacing + paragraph_spacing);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,25 +394,61 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
|
|||
}
|
||||
|
||||
void Label::_ensure_shaped() const {
|
||||
if (dirty || font_dirty || lines_dirty) {
|
||||
if (dirty || font_dirty || text_dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
} else
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (para.lines_dirty || para.dirty) {
|
||||
const_cast<Label *>(this)->_shape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RID Label::get_line_rid(int p_line) const {
|
||||
return lines_rid[p_line];
|
||||
if (p_line < 0 || p_line >= total_line_count) {
|
||||
return RID();
|
||||
}
|
||||
|
||||
int line_index = 0;
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (line_index + para.lines_rid.size() <= p_line) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
return para.lines_rid[p_line - line_index];
|
||||
}
|
||||
}
|
||||
|
||||
return RID();
|
||||
}
|
||||
|
||||
Rect2 Label::get_line_rect(int p_line) const {
|
||||
if (p_line < 0 || p_line >= total_line_count) {
|
||||
return Rect2();
|
||||
}
|
||||
|
||||
int line_index = 0;
|
||||
for (int p = 0; p < paragraphs.size(); p++) {
|
||||
const Paragraph ¶ = paragraphs[p];
|
||||
if (line_index + para.lines_rid.size() <= p_line) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
return _get_line_rect(p, p_line - line_index);
|
||||
}
|
||||
}
|
||||
|
||||
return Rect2();
|
||||
}
|
||||
|
||||
Rect2 Label::_get_line_rect(int p_para, int p_line) const {
|
||||
// Returns a rect providing the line's horizontal offset and total size. To determine the vertical
|
||||
// offset, use r_offset and r_line_spacing from get_layout_data.
|
||||
bool rtl = TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL;
|
||||
bool rtl = TS->shaped_text_get_inferred_direction(paragraphs[p_para].text_rid) == TextServer::DIRECTION_RTL;
|
||||
bool rtl_layout = is_layout_rtl();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
Size2 size = get_size();
|
||||
Size2 line_size = TS->shaped_text_get_size(lines_rid[p_line]);
|
||||
Size2 line_size = TS->shaped_text_get_size(paragraphs[p_para].lines_rid[p_line]);
|
||||
Vector2 offset;
|
||||
|
||||
switch (horizontal_alignment) {
|
||||
case HORIZONTAL_ALIGNMENT_FILL:
|
||||
if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
|
||||
|
@ -381,43 +475,67 @@ Rect2 Label::get_line_rect(int p_line) const {
|
|||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return Rect2(offset, line_size);
|
||||
}
|
||||
|
||||
void Label::get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const {
|
||||
int Label::get_layout_data(Vector2 &r_offset, int &r_last_line, int &r_line_spacing) const {
|
||||
// Computes several common parameters involved in laying out and rendering text set to this label.
|
||||
// Only vertical margin is considered in r_offset: use get_line_rect to get the horizontal offset
|
||||
// for a given line of text.
|
||||
Size2 size = get_size();
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
|
||||
int paragraph_spacing = settings.is_valid() ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing;
|
||||
float total_h = 0.0;
|
||||
int lines_visible = 0;
|
||||
|
||||
// Get number of lines to fit to the height.
|
||||
for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
int line_index = 0;
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
for (int i = start; i < para.lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(para.lines_rid[i]).y + line_spacing;
|
||||
if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
|
||||
break;
|
||||
}
|
||||
lines_visible++;
|
||||
}
|
||||
total_h += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
lines_visible = max_lines_visible;
|
||||
}
|
||||
|
||||
r_line_limit = MIN(lines_rid.size(), lines_visible + lines_skipped);
|
||||
r_last_line = MIN(total_line_count, lines_visible + lines_skipped);
|
||||
|
||||
// Get real total height.
|
||||
int total_glyphs = 0;
|
||||
total_h = 0;
|
||||
for (int64_t i = lines_skipped; i < r_line_limit; i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
line_index = 0;
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
int end = (line_index + para.lines_rid.size() < r_last_line) ? para.lines_rid.size() : r_last_line - line_index;
|
||||
if (end <= 0) {
|
||||
break;
|
||||
}
|
||||
for (int i = start; i < end; i++) {
|
||||
total_h += TS->shaped_text_get_size(para.lines_rid[i]).y + line_spacing;
|
||||
total_glyphs += TS->shaped_text_get_glyph_count(para.lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(para.lines_rid[i]);
|
||||
}
|
||||
total_h += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
|
||||
|
||||
int vbegin = 0, vsep = 0;
|
||||
if (lines_visible > 0) {
|
||||
switch (vertical_alignment) {
|
||||
|
@ -425,29 +543,27 @@ void Label::get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_sp
|
|||
// Nothing.
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_CENTER: {
|
||||
vbegin = (size.y - (total_h - line_spacing)) / 2;
|
||||
vbegin = (size.y - (total_h - line_spacing - paragraph_spacing)) / 2;
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_BOTTOM: {
|
||||
vbegin = size.y - (total_h - line_spacing);
|
||||
vbegin = size.y - (total_h - line_spacing - paragraph_spacing);
|
||||
vsep = 0;
|
||||
|
||||
} break;
|
||||
case VERTICAL_ALIGNMENT_FILL: {
|
||||
vbegin = 0;
|
||||
if (lines_visible > 1) {
|
||||
vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
|
||||
vsep = (size.y - (total_h - line_spacing - paragraph_spacing)) / (lines_visible - 1);
|
||||
} else {
|
||||
vsep = 0;
|
||||
}
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
r_offset = { 0, style->get_offset().y + vbegin };
|
||||
r_line_spacing = line_spacing + vsep;
|
||||
|
||||
return total_glyphs;
|
||||
}
|
||||
|
||||
PackedStringArray Label::get_configuration_warnings() const {
|
||||
|
@ -477,8 +593,10 @@ PackedStringArray Label::get_configuration_warnings() const {
|
|||
|
||||
if (font.is_valid()) {
|
||||
_ensure_shaped();
|
||||
const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid);
|
||||
int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid);
|
||||
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
const Glyph *glyph = TS->shaped_text_get_glyphs(para.text_rid);
|
||||
int64_t glyph_count = TS->shaped_text_get_glyph_count(para.text_rid);
|
||||
for (int64_t i = 0; i < glyph_count; i++) {
|
||||
if (glyph[i].font_rid == RID()) {
|
||||
warnings.push_back(RTR("The current font does not support rendering one or more characters used in this Label's text."));
|
||||
|
@ -486,6 +604,7 @@ PackedStringArray Label::get_configuration_warnings() const {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
@ -501,7 +620,7 @@ void Label::_notification(int p_what) {
|
|||
if (visible_ratio < 1) {
|
||||
visible_chars = get_total_character_count() * visible_ratio;
|
||||
}
|
||||
dirty = true;
|
||||
text_dirty = true;
|
||||
|
||||
queue_redraw();
|
||||
update_configuration_warnings();
|
||||
|
@ -517,16 +636,18 @@ void Label::_notification(int p_what) {
|
|||
}
|
||||
|
||||
// When a shaped text is invalidated by an external source, we want to reshape it.
|
||||
if (!TS->shaped_text_is_ready(text_rid)) {
|
||||
dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
if (!TS->shaped_text_is_ready(para.text_rid)) {
|
||||
para.dirty = true;
|
||||
}
|
||||
|
||||
for (const RID &line_rid : lines_rid) {
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
if (!TS->shaped_text_is_ready(line_rid)) {
|
||||
lines_dirty = true;
|
||||
para.lines_dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ensure_shaped();
|
||||
|
||||
|
@ -539,10 +660,10 @@ void Label::_notification(int p_what) {
|
|||
Color font_color = has_settings ? settings->get_font_color() : theme_cache.font_color;
|
||||
Color font_shadow_color = has_settings ? settings->get_shadow_color() : theme_cache.font_shadow_color;
|
||||
Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : theme_cache.font_shadow_offset;
|
||||
int paragraph_spacing = has_settings ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing;
|
||||
Color font_outline_color = has_settings ? settings->get_outline_color() : theme_cache.font_outline_color;
|
||||
int outline_size = has_settings ? settings->get_outline_size() : theme_cache.font_outline_size;
|
||||
int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size;
|
||||
bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL);
|
||||
bool rtl_layout = is_layout_rtl();
|
||||
|
||||
style->draw(ci, Rect2(Point2(0, 0), get_size()));
|
||||
|
@ -552,34 +673,42 @@ void Label::_notification(int p_what) {
|
|||
bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && rtl_layout));
|
||||
|
||||
Vector2 ofs;
|
||||
int line_limit;
|
||||
int line_spacing;
|
||||
get_layout_data(ofs, line_limit, line_spacing);
|
||||
|
||||
int last_line;
|
||||
int total_glyphs = get_layout_data(ofs, last_line, line_spacing);
|
||||
int processed_glyphs = 0;
|
||||
int total_glyphs = 0;
|
||||
|
||||
for (int64_t i = lines_skipped; i < line_limit; i++) {
|
||||
total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
|
||||
}
|
||||
|
||||
int visible_glyphs = total_glyphs * visible_ratio;
|
||||
|
||||
for (int i = lines_skipped; i < line_limit; i++) {
|
||||
Vector2 line_offset = get_line_rect(i).position;
|
||||
int line_index = 0;
|
||||
for (int p = 0; p < paragraphs.size(); p++) {
|
||||
const Paragraph ¶ = paragraphs[p];
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
int end = (line_index + para.lines_rid.size() < last_line) ? para.lines_rid.size() : last_line - line_index;
|
||||
if (end <= 0) {
|
||||
break;
|
||||
}
|
||||
bool rtl = (TS->shaped_text_get_inferred_direction(para.text_rid) == TextServer::DIRECTION_RTL);
|
||||
for (int i = start; i < end; i++) {
|
||||
RID line_rid = para.lines_rid[i];
|
||||
Vector2 line_offset = _get_line_rect(p, i).position;
|
||||
ofs.x = line_offset.x;
|
||||
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]);
|
||||
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
|
||||
int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(line_rid);
|
||||
int gl_size = TS->shaped_text_get_glyph_count(line_rid);
|
||||
|
||||
int ellipsis_pos = TS->shaped_text_get_ellipsis_pos(lines_rid[i]);
|
||||
int trim_pos = TS->shaped_text_get_trim_pos(lines_rid[i]);
|
||||
int ellipsis_pos = TS->shaped_text_get_ellipsis_pos(line_rid);
|
||||
int trim_pos = TS->shaped_text_get_trim_pos(line_rid);
|
||||
|
||||
const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]);
|
||||
int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
|
||||
const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(line_rid);
|
||||
int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(line_rid);
|
||||
|
||||
ofs.y += TS->shaped_text_get_ascent(line_rid);
|
||||
|
||||
// Draw shadow, outline and text. Note: Do not merge this into the single loop iteration, to prevent overlaps.
|
||||
int processed_glyphs_step = 0;
|
||||
for (int step = DRAW_STEP_SHADOW; step < DRAW_STEP_MAX; step++) {
|
||||
if (step == DRAW_STEP_SHADOW && (font_shadow_color.a == 0)) {
|
||||
continue;
|
||||
|
@ -588,13 +717,13 @@ void Label::_notification(int p_what) {
|
|||
continue;
|
||||
}
|
||||
|
||||
int processed_glyphs_step = processed_glyphs;
|
||||
processed_glyphs_step = processed_glyphs;
|
||||
Vector2 offset_step = ofs;
|
||||
// Draw RTL ellipsis string when necessary.
|
||||
if (rtl && ellipsis_pos >= 0) {
|
||||
for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
|
||||
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
|
||||
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
if (!skip) {
|
||||
if (step == DRAW_STEP_SHADOW) {
|
||||
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
|
||||
|
@ -624,7 +753,7 @@ void Label::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
for (int k = 0; k < glyphs[j].repeat; k++) {
|
||||
bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
bool skip = (trim_chars && glyphs[j].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
if (!skip) {
|
||||
if (step == DRAW_STEP_SHADOW) {
|
||||
draw_glyph_shadow(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
|
||||
|
@ -642,7 +771,7 @@ void Label::_notification(int p_what) {
|
|||
if (!rtl && ellipsis_pos >= 0) {
|
||||
for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
|
||||
for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
|
||||
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end + para.start > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs));
|
||||
if (!skip) {
|
||||
if (step == DRAW_STEP_SHADOW) {
|
||||
draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs);
|
||||
|
@ -658,7 +787,12 @@ void Label::_notification(int p_what) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
|
||||
processed_glyphs = processed_glyphs_step;
|
||||
ofs.y += TS->shaped_text_get_descent(line_rid) + line_spacing;
|
||||
}
|
||||
ofs.y += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -668,7 +802,9 @@ void Label::_notification(int p_what) {
|
|||
} break;
|
||||
|
||||
case NOTIFICATION_RESIZED: {
|
||||
lines_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
@ -676,21 +812,36 @@ void Label::_notification(int p_what) {
|
|||
Rect2 Label::get_character_bounds(int p_pos) const {
|
||||
_ensure_shaped();
|
||||
|
||||
Vector2 ofs;
|
||||
int line_limit;
|
||||
int line_spacing;
|
||||
get_layout_data(ofs, line_limit, line_spacing);
|
||||
int paragraph_spacing = settings.is_valid() ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing;
|
||||
|
||||
for (int i = lines_skipped; i < line_limit; i++) {
|
||||
Rect2 line_rect = get_line_rect(i);
|
||||
Vector2 ofs;
|
||||
int line_spacing;
|
||||
int last_line;
|
||||
get_layout_data(ofs, last_line, line_spacing);
|
||||
|
||||
int line_index = 0;
|
||||
for (int p = 0; p < paragraphs.size(); p++) {
|
||||
const Paragraph ¶ = paragraphs[p];
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
int end = (line_index + para.lines_rid.size() < last_line) ? para.lines_rid.size() : last_line - line_index;
|
||||
if (end <= 0) {
|
||||
break;
|
||||
}
|
||||
for (int i = start; i < end; i++) {
|
||||
RID line_rid = para.lines_rid[i];
|
||||
Rect2 line_rect = _get_line_rect(p, i);
|
||||
ofs.x = line_rect.position.x;
|
||||
int v_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
|
||||
|
||||
int v_size = TS->shaped_text_get_glyph_count(line_rid);
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(line_rid);
|
||||
|
||||
float gl_off = 0.0f;
|
||||
for (int j = 0; j < v_size; j++) {
|
||||
if ((glyphs[j].count > 0) && ((glyphs[j].index != 0) || ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE))) {
|
||||
if (p_pos >= glyphs[j].start && p_pos < glyphs[j].end) {
|
||||
if (p_pos >= glyphs[j].start + para.start && p_pos < glyphs[j].end + para.start) {
|
||||
float advance = 0.f;
|
||||
for (int k = 0; k < glyphs[j].count; k++) {
|
||||
advance += glyphs[j + k].advance;
|
||||
|
@ -703,7 +854,11 @@ Rect2 Label::get_character_bounds(int p_pos) const {
|
|||
}
|
||||
gl_off += glyphs[j].advance * glyphs[j].repeat;
|
||||
}
|
||||
ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + line_spacing;
|
||||
ofs.y += TS->shaped_text_get_ascent(line_rid) + TS->shaped_text_get_descent(line_rid) + line_spacing;
|
||||
}
|
||||
ofs.y += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
return Rect2();
|
||||
}
|
||||
|
@ -747,24 +902,33 @@ int Label::get_line_count() const {
|
|||
return 1;
|
||||
}
|
||||
_ensure_shaped();
|
||||
return lines_rid.size();
|
||||
|
||||
return total_line_count;
|
||||
}
|
||||
|
||||
int Label::get_visible_line_count() const {
|
||||
Ref<StyleBox> style = theme_cache.normal_style;
|
||||
int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing;
|
||||
int paragraph_spacing = settings.is_valid() ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing;
|
||||
int lines_visible = 0;
|
||||
float total_h = 0.0;
|
||||
for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
|
||||
|
||||
int line_index = 0;
|
||||
for (const Paragraph ¶ : paragraphs) {
|
||||
if (line_index + para.lines_rid.size() <= lines_skipped) {
|
||||
line_index += para.lines_rid.size();
|
||||
} else {
|
||||
int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0;
|
||||
for (int i = start; i < para.lines_rid.size(); i++) {
|
||||
total_h += TS->shaped_text_get_size(para.lines_rid[i]).y + line_spacing;
|
||||
if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
|
||||
break;
|
||||
}
|
||||
lines_visible++;
|
||||
}
|
||||
|
||||
if (lines_visible > lines_rid.size()) {
|
||||
lines_visible = lines_rid.size();
|
||||
total_h += paragraph_spacing;
|
||||
line_index += para.lines_rid.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
|
||||
|
@ -781,7 +945,9 @@ void Label::set_horizontal_alignment(HorizontalAlignment p_alignment) {
|
|||
}
|
||||
|
||||
if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) {
|
||||
lines_dirty = true; // Reshape lines.
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true; // Reshape lines.
|
||||
}
|
||||
}
|
||||
horizontal_alignment = p_alignment;
|
||||
|
||||
|
@ -813,7 +979,7 @@ void Label::set_text(const String &p_string) {
|
|||
}
|
||||
text = p_string;
|
||||
xl_text = atr(p_string);
|
||||
dirty = true;
|
||||
text_dirty = true;
|
||||
if (visible_ratio < 1) {
|
||||
visible_chars = get_total_character_count() * visible_ratio;
|
||||
}
|
||||
|
@ -848,7 +1014,9 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
|
|||
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
|
||||
if (text_direction != p_text_direction) {
|
||||
text_direction = p_text_direction;
|
||||
font_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -856,7 +1024,9 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
|
|||
void Label::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
|
||||
if (st_parser != p_parser) {
|
||||
st_parser = p_parser;
|
||||
dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -871,7 +1041,9 @@ void Label::set_structured_text_bidi_override_options(Array p_args) {
|
|||
}
|
||||
|
||||
st_args = p_args;
|
||||
dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
|
@ -886,7 +1058,9 @@ Control::TextDirection Label::get_text_direction() const {
|
|||
void Label::set_language(const String &p_language) {
|
||||
if (language != p_language) {
|
||||
language = p_language;
|
||||
dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -895,6 +1069,18 @@ String Label::get_language() const {
|
|||
return language;
|
||||
}
|
||||
|
||||
void Label::set_paragraph_separator(const String &p_paragraph_separator) {
|
||||
if (paragraph_separator != p_paragraph_separator) {
|
||||
paragraph_separator = p_paragraph_separator;
|
||||
text_dirty = true;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
String Label::get_paragraph_separator() const {
|
||||
return paragraph_separator;
|
||||
}
|
||||
|
||||
void Label::set_clip_text(bool p_clip) {
|
||||
if (clip == p_clip) {
|
||||
return;
|
||||
|
@ -912,7 +1098,9 @@ bool Label::is_clipping_text() const {
|
|||
void Label::set_tab_stops(const PackedFloat32Array &p_tab_stops) {
|
||||
if (tab_stops != p_tab_stops) {
|
||||
tab_stops = p_tab_stops;
|
||||
dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -927,7 +1115,9 @@ void Label::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) {
|
|||
}
|
||||
|
||||
overrun_behavior = p_behavior;
|
||||
lines_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
|
||||
update_minimum_size();
|
||||
|
@ -948,7 +1138,9 @@ void Label::set_ellipsis_char(const String &p_char) {
|
|||
return;
|
||||
}
|
||||
el_char = c;
|
||||
lines_dirty = true;
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
para.lines_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
|
||||
update_minimum_size();
|
||||
|
@ -972,7 +1164,7 @@ void Label::set_visible_characters(int p_amount) {
|
|||
visible_ratio = 1.0;
|
||||
}
|
||||
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
|
||||
dirty = true;
|
||||
text_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
|
@ -996,7 +1188,7 @@ void Label::set_visible_ratio(float p_ratio) {
|
|||
}
|
||||
|
||||
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
|
||||
dirty = true;
|
||||
text_dirty = true;
|
||||
}
|
||||
queue_redraw();
|
||||
}
|
||||
|
@ -1012,8 +1204,10 @@ TextServer::VisibleCharactersBehavior Label::get_visible_characters_behavior() c
|
|||
|
||||
void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior) {
|
||||
if (visible_chars_behavior != p_behavior) {
|
||||
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING || p_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
|
||||
text_dirty = true;
|
||||
}
|
||||
visible_chars_behavior = p_behavior;
|
||||
dirty = true;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
@ -1049,7 +1243,6 @@ int Label::get_max_lines_visible() const {
|
|||
}
|
||||
|
||||
int Label::get_total_character_count() const {
|
||||
_ensure_shaped();
|
||||
return xl_text.length();
|
||||
}
|
||||
|
||||
|
@ -1066,6 +1259,8 @@ void Label::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_text_direction"), &Label::get_text_direction);
|
||||
ClassDB::bind_method(D_METHOD("set_language", "language"), &Label::set_language);
|
||||
ClassDB::bind_method(D_METHOD("get_language"), &Label::get_language);
|
||||
ClassDB::bind_method(D_METHOD("set_paragraph_separator", "paragraph_separator"), &Label::set_paragraph_separator);
|
||||
ClassDB::bind_method(D_METHOD("get_paragraph_separator"), &Label::get_paragraph_separator);
|
||||
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label::get_autowrap_mode);
|
||||
ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &Label::set_justification_flags);
|
||||
|
@ -1107,6 +1302,7 @@ void Label::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "paragraph_separator"), "set_paragraph_separator", "get_paragraph_separator");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
|
||||
|
@ -1130,6 +1326,7 @@ void Label::_bind_methods() {
|
|||
|
||||
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Label, normal_style, "normal");
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, line_spacing);
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Label, paragraph_spacing);
|
||||
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, Label, font);
|
||||
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, Label, font_size);
|
||||
|
@ -1143,17 +1340,18 @@ void Label::_bind_methods() {
|
|||
}
|
||||
|
||||
Label::Label(const String &p_text) {
|
||||
text_rid = TS->create_shaped_text();
|
||||
|
||||
set_mouse_filter(MOUSE_FILTER_IGNORE);
|
||||
set_text(p_text);
|
||||
set_v_size_flags(SIZE_SHRINK_CENTER);
|
||||
}
|
||||
|
||||
Label::~Label() {
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
TS->free_rid(lines_rid[i]);
|
||||
for (Paragraph ¶ : paragraphs) {
|
||||
for (const RID &line_rid : para.lines_rid) {
|
||||
TS->free_rid(line_rid);
|
||||
}
|
||||
lines_rid.clear();
|
||||
TS->free_rid(text_rid);
|
||||
para.lines_rid.clear();
|
||||
TS->free_rid(para.text_rid);
|
||||
}
|
||||
paragraphs.clear();
|
||||
}
|
||||
|
|
|
@ -57,11 +57,21 @@ private:
|
|||
Size2 minsize;
|
||||
bool uppercase = false;
|
||||
|
||||
struct Paragraph {
|
||||
bool lines_dirty = true;
|
||||
bool dirty = true;
|
||||
bool font_dirty = true;
|
||||
int start = 0;
|
||||
|
||||
String text;
|
||||
RID text_rid;
|
||||
Vector<RID> lines_rid;
|
||||
};
|
||||
bool dirty = true;
|
||||
bool font_dirty = true;
|
||||
bool text_dirty = true;
|
||||
Vector<Paragraph> paragraphs;
|
||||
int total_line_count = 0;
|
||||
String paragraph_separator = "\\n";
|
||||
|
||||
String language;
|
||||
TextDirection text_direction = TEXT_DIRECTION_AUTO;
|
||||
|
@ -83,6 +93,7 @@ private:
|
|||
|
||||
int font_size = 0;
|
||||
int line_spacing = 0;
|
||||
int paragraph_spacing = 0;
|
||||
Color font_color;
|
||||
Color font_shadow_color;
|
||||
Point2 font_shadow_offset;
|
||||
|
@ -91,6 +102,7 @@ private:
|
|||
int font_shadow_outline_size;
|
||||
} theme_cache;
|
||||
|
||||
Rect2 _get_line_rect(int p_para, int p_line) const;
|
||||
void _ensure_shaped() const;
|
||||
void _update_visible();
|
||||
void _shape();
|
||||
|
@ -99,7 +111,7 @@ private:
|
|||
protected:
|
||||
RID get_line_rid(int p_line) const;
|
||||
Rect2 get_line_rect(int p_line) const;
|
||||
void get_layout_data(Vector2 &r_offset, int &r_line_limit, int &r_line_spacing) const;
|
||||
int get_layout_data(Vector2 &r_offset, int &r_last_line, int &r_line_spacing) const;
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
@ -129,6 +141,9 @@ public:
|
|||
void set_language(const String &p_language);
|
||||
String get_language() const;
|
||||
|
||||
void set_paragraph_separator(const String &p_paragraph_separator);
|
||||
String get_paragraph_separator() const;
|
||||
|
||||
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
|
||||
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ void LabelSettings::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_line_spacing", "spacing"), &LabelSettings::set_line_spacing);
|
||||
ClassDB::bind_method(D_METHOD("get_line_spacing"), &LabelSettings::get_line_spacing);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_paragraph_spacing", "spacing"), &LabelSettings::set_paragraph_spacing);
|
||||
ClassDB::bind_method(D_METHOD("get_paragraph_spacing"), &LabelSettings::get_paragraph_spacing);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_font", "font"), &LabelSettings::set_font);
|
||||
ClassDB::bind_method(D_METHOD("get_font"), &LabelSettings::get_font);
|
||||
|
||||
|
@ -63,6 +66,7 @@ void LabelSettings::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_shadow_offset"), &LabelSettings::get_shadow_offset);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "paragraph_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_paragraph_spacing", "get_paragraph_spacing");
|
||||
|
||||
ADD_GROUP("Font", "font_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font");
|
||||
|
@ -90,6 +94,17 @@ real_t LabelSettings::get_line_spacing() const {
|
|||
return line_spacing;
|
||||
}
|
||||
|
||||
void LabelSettings::set_paragraph_spacing(real_t p_spacing) {
|
||||
if (paragraph_spacing != p_spacing) {
|
||||
paragraph_spacing = p_spacing;
|
||||
emit_changed();
|
||||
}
|
||||
}
|
||||
|
||||
real_t LabelSettings::get_paragraph_spacing() const {
|
||||
return paragraph_spacing;
|
||||
}
|
||||
|
||||
void LabelSettings::set_font(const Ref<Font> &p_font) {
|
||||
if (font != p_font) {
|
||||
if (font.is_valid()) {
|
||||
|
|
|
@ -40,6 +40,7 @@ class LabelSettings : public Resource {
|
|||
GDCLASS(LabelSettings, Resource);
|
||||
|
||||
real_t line_spacing = 3;
|
||||
real_t paragraph_spacing = 0;
|
||||
|
||||
Ref<Font> font;
|
||||
int font_size = Font::DEFAULT_FONT_SIZE;
|
||||
|
@ -61,6 +62,9 @@ public:
|
|||
void set_line_spacing(real_t p_spacing);
|
||||
real_t get_line_spacing() const;
|
||||
|
||||
void set_paragraph_spacing(real_t p_spacing);
|
||||
real_t get_paragraph_spacing() const;
|
||||
|
||||
void set_font(const Ref<Font> &p_font);
|
||||
Ref<Font> get_font() const;
|
||||
|
||||
|
|
Loading…
Reference in a new issue