mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 04:06:14 +00:00
Improvements to Label's layout options
- Added options to trim the text in case it overruns - Added more autowrap modes - Improved line breaking, which ignores trailing spaces
This commit is contained in:
parent
cb4e42155d
commit
56a8d3f30c
@ -75,8 +75,8 @@
|
||||
<member name="align" type="int" setter="set_align" getter="get_align" enum="Label.Align" default="0">
|
||||
Controls the text's horizontal align. Supports left, center, right, and fill, or justify. Set it to one of the [enum Align] constants.
|
||||
</member>
|
||||
<member name="autowrap" type="bool" setter="set_autowrap" getter="has_autowrap" default="false">
|
||||
If [code]true[/code], wraps the text inside the node's bounding rectangle. If you resize the node, it will change its height automatically to show all the text.
|
||||
<member name="autowrap_mode" type="int" setter="set_autowrap_mode" getter="get_autowrap_mode" enum="Label.AutowrapMode" default="0">
|
||||
If set to something other than [constant AUTOWRAP_OFF], the text gets wrapped inside the node's bounding rectangle. If you resize the node, it will change its height automatically to show all the text. To see how each mode behaves, see [enum AutowrapMode].
|
||||
</member>
|
||||
<member name="clip_text" type="bool" setter="set_clip_text" getter="is_clipping_text" default="false">
|
||||
If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally.
|
||||
@ -107,6 +107,9 @@
|
||||
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
|
||||
Base text writing direction.
|
||||
</member>
|
||||
<member name="text_overrun_behavior" type="int" setter="set_text_overrun_behavior" getter="get_text_overrun_behavior" enum="Label.OverrunBehavior" default="0">
|
||||
Sets the clipping behavior when the text exceeds the node's bounding rectangle. See [enum OverrunBehavior] for a description of all modes.
|
||||
</member>
|
||||
<member name="uppercase" type="bool" setter="set_uppercase" getter="is_uppercase" default="false">
|
||||
If [code]true[/code], all the text displays as UPPERCASE.
|
||||
</member>
|
||||
@ -142,6 +145,33 @@
|
||||
<constant name="VALIGN_FILL" value="3" enum="VAlign">
|
||||
Align the whole text by spreading the rows.
|
||||
</constant>
|
||||
<constant name="AUTOWRAP_OFF" value="0" enum="AutowrapMode">
|
||||
Autowrap is disabled.
|
||||
</constant>
|
||||
<constant name="AUTOWRAP_ARBITRARY" value="1" enum="AutowrapMode">
|
||||
Wraps the text inside the node's bounding rectangle by allowing to break lines at arbitrary positions, which is useful when very limited space is available.
|
||||
</constant>
|
||||
<constant name="AUTOWRAP_WORD" value="2" enum="AutowrapMode">
|
||||
Wraps the text inside the node's bounding rectangle by soft-breaking between words.
|
||||
</constant>
|
||||
<constant name="AUTOWRAP_WORD_SMART" value="3" enum="AutowrapMode">
|
||||
Behaves similarly to [constant AUTOWRAP_WORD], but force-breaks a word if that single word does not fit in one line.
|
||||
</constant>
|
||||
<constant name="OVERRUN_NO_TRIMMING" value="0" enum="OverrunBehavior">
|
||||
No text trimming is performed.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM_CHAR" value="1" enum="OverrunBehavior">
|
||||
Trims the text per character.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM_WORD" value="2" enum="OverrunBehavior">
|
||||
Trims the text per word.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM_ELLIPSIS" value="3" enum="OverrunBehavior">
|
||||
Trims the text per character and adds an ellipsis to indicate that parts are hidden.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM_WORD_ELLIPSIS" value="4" enum="OverrunBehavior">
|
||||
Trims the text per word and adds an ellipsis to indicate that parts are hidden.
|
||||
</constant>
|
||||
</constants>
|
||||
<theme_items>
|
||||
<theme_item name="font" type="Font">
|
||||
|
@ -1088,6 +1088,19 @@
|
||||
Returns composite character end position closest to the [code]pos[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_overrun_trim_to_width">
|
||||
<return type="void">
|
||||
</return>
|
||||
<argument index="0" name="shaped" type="RID">
|
||||
</argument>
|
||||
<argument index="1" name="width" type="float" default="0">
|
||||
</argument>
|
||||
<argument index="2" name="overrun_trim_flags" type="int" default="0">
|
||||
</argument>
|
||||
<description>
|
||||
Trims text if it exceeds the given width.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_prev_grapheme_pos">
|
||||
<return type="int">
|
||||
</return>
|
||||
@ -1260,6 +1273,21 @@
|
||||
<constant name="BREAK_GRAPHEME_BOUND" value="64" enum="LineBreakFlag">
|
||||
Break the line between any unconnected graphemes.
|
||||
</constant>
|
||||
<constant name="OVERRUN_NO_TRIMMING" value="0" enum="TextOverrunFlag">
|
||||
No trimming is performed.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM" value="1" enum="TextOverrunFlag">
|
||||
Trims the text when it exceeds the given width.
|
||||
</constant>
|
||||
<constant name="OVERRUN_TRIM_WORD_ONLY" value="2" enum="TextOverrunFlag">
|
||||
Trims the text per word instead of per grapheme.
|
||||
</constant>
|
||||
<constant name="OVERRUN_ADD_ELLIPSIS" value="4" enum="TextOverrunFlag">
|
||||
Determines whether an ellipsis should be added at the end of the text.
|
||||
</constant>
|
||||
<constant name="OVERRUN_ENFORCE_ELLIPSIS" value="8" enum="TextOverrunFlag">
|
||||
Determines whether the ellipsis at the end of the text is enforced and may not be hidden.
|
||||
</constant>
|
||||
<constant name="GRAPHEME_IS_RTL" value="2" enum="GraphemeFlag">
|
||||
Grapheme is part of right-to-left or bottom-to-top run.
|
||||
</constant>
|
||||
|
@ -5784,7 +5784,7 @@ AnimationTrackEditor::AnimationTrackEditor() {
|
||||
info_message->set_text(TTR("Select an AnimationPlayer node to create and edit animations."));
|
||||
info_message->set_valign(Label::VALIGN_CENTER);
|
||||
info_message->set_align(Label::ALIGN_CENTER);
|
||||
info_message->set_autowrap(true);
|
||||
info_message->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
|
||||
main_panel->add_child(info_message);
|
||||
|
@ -380,7 +380,7 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() {
|
||||
info_message->set_text(TTR("Pick one or more items from the list to display the graph."));
|
||||
info_message->set_valign(Label::VALIGN_CENTER);
|
||||
info_message->set_align(Label::ALIGN_CENTER);
|
||||
info_message->set_autowrap(true);
|
||||
info_message->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
|
||||
monitor_draw->add_child(info_message);
|
||||
|
@ -1535,7 +1535,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) {
|
||||
reason->set_text("");
|
||||
hbc->add_child(reason);
|
||||
reason->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
reason->set_autowrap(true);
|
||||
reason->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
reason->set_max_lines_visible(3);
|
||||
reason->set_mouse_filter(Control::MOUSE_FILTER_PASS);
|
||||
|
||||
|
@ -212,7 +212,7 @@ EditorAbout::EditorAbout() {
|
||||
|
||||
Label *tpl_label = memnew(Label);
|
||||
tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tpl_label->set_autowrap(true);
|
||||
tpl_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
tpl_label->set_text(TTR("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms."));
|
||||
tpl_label->set_size(Size2(630, 1) * EDSCALE);
|
||||
license_thirdparty->add_child(tpl_label);
|
||||
|
@ -531,7 +531,7 @@ GroupDialog::GroupDialog() {
|
||||
group_empty->set_text(TTR("Empty groups will be automatically removed."));
|
||||
group_empty->set_valign(Label::VALIGN_CENTER);
|
||||
group_empty->set_align(Label::ALIGN_CENTER);
|
||||
group_empty->set_autowrap(true);
|
||||
group_empty->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
group_empty->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
nodes_to_remove->add_child(group_empty);
|
||||
group_empty->set_anchors_and_offsets_preset(Control::PRESET_WIDE, Control::PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
|
||||
|
@ -130,6 +130,6 @@ NodeDock::NodeDock() {
|
||||
select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
select_a_node->set_valign(Label::VALIGN_CENTER);
|
||||
select_a_node->set_align(Label::ALIGN_CENTER);
|
||||
select_a_node->set_autowrap(true);
|
||||
select_a_node->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
add_child(select_a_node);
|
||||
}
|
||||
|
@ -1141,7 +1141,7 @@ ThemeItemImportTree::ThemeItemImportTree() {
|
||||
|
||||
select_icons_warning = memnew(Label);
|
||||
select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource."));
|
||||
select_icons_warning->set_autowrap(true);
|
||||
select_icons_warning->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
select_icons_warning_hb->add_child(select_icons_warning);
|
||||
}
|
||||
|
@ -3855,7 +3855,7 @@ VisualShaderEditor::VisualShaderEditor() {
|
||||
|
||||
error_label = memnew(Label);
|
||||
error_panel->add_child(error_label);
|
||||
error_label->set_autowrap(true);
|
||||
error_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
|
||||
///////////////////////////////////////
|
||||
// POPUP MENU
|
||||
@ -3941,7 +3941,7 @@ VisualShaderEditor::VisualShaderEditor() {
|
||||
add_child(members_dialog);
|
||||
|
||||
alert = memnew(AcceptDialog);
|
||||
alert->get_label()->set_autowrap(true);
|
||||
alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD);
|
||||
alert->get_label()->set_align(Label::ALIGN_CENTER);
|
||||
alert->get_label()->set_valign(Label::VALIGN_CENTER);
|
||||
alert->get_label()->set_custom_minimum_size(Size2(400, 60) * EDSCALE);
|
||||
|
@ -771,7 +771,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
|
||||
builtin_warning_label->set_text(
|
||||
TTR("Note: Built-in scripts have some limitations and can't be edited using an external editor."));
|
||||
vb->add_child(builtin_warning_label);
|
||||
builtin_warning_label->set_autowrap(true);
|
||||
builtin_warning_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
builtin_warning_label->hide();
|
||||
|
||||
script_name_warning_label = memnew(Label);
|
||||
@ -779,7 +779,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
|
||||
TTR("Warning: Having the script name be the same as a built-in type is usually not desired."));
|
||||
vb->add_child(script_name_warning_label);
|
||||
script_name_warning_label->add_theme_color_override("font_color", Color(1, 0.85, 0.4));
|
||||
script_name_warning_label->set_autowrap(true);
|
||||
script_name_warning_label->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
script_name_warning_label->hide();
|
||||
|
||||
status_panel = memnew(PanelContainer);
|
||||
@ -892,7 +892,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
|
||||
add_child(file_browse);
|
||||
get_ok_button()->set_text(TTR("Create"));
|
||||
alert = memnew(AcceptDialog);
|
||||
alert->get_label()->set_autowrap(true);
|
||||
alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
alert->get_label()->set_align(Label::ALIGN_CENTER);
|
||||
alert->get_label()->set_valign(Label::VALIGN_CENTER);
|
||||
alert->get_label()->set_custom_minimum_size(Size2(325, 60) * EDSCALE);
|
||||
|
@ -143,6 +143,7 @@ typedef struct {
|
||||
bool (*shaped_text_shape)(void *, godot_rid *);
|
||||
bool (*shaped_text_update_breaks)(void *, godot_rid *);
|
||||
bool (*shaped_text_update_justification_ops)(void *, godot_rid *);
|
||||
void (*shaped_text_overrun_trim_to_width)(void *, godot_rid *, float, uint8_t);
|
||||
bool (*shaped_text_is_ready)(void *, godot_rid *);
|
||||
godot_packed_glyph_array (*shaped_text_get_glyphs)(void *, godot_rid *);
|
||||
godot_vector2i (*shaped_text_get_range)(void *, godot_rid *);
|
||||
|
@ -498,6 +498,11 @@ bool TextServerGDNative::shaped_text_update_justification_ops(RID p_shaped) {
|
||||
return interface->shaped_text_update_justification_ops(data, (godot_rid *)&p_shaped);
|
||||
}
|
||||
|
||||
void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) {
|
||||
ERR_FAIL_COND(interface == nullptr);
|
||||
interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_clip_flags);
|
||||
};
|
||||
|
||||
bool TextServerGDNative::shaped_text_is_ready(RID p_shaped) const {
|
||||
ERR_FAIL_COND_V(interface == nullptr, false);
|
||||
return interface->shaped_text_is_ready(data, (godot_rid *)&p_shaped);
|
||||
|
@ -167,6 +167,8 @@ public:
|
||||
virtual bool shaped_text_update_breaks(RID p_shaped) override;
|
||||
virtual bool shaped_text_update_justification_ops(RID p_shaped) override;
|
||||
|
||||
virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override;
|
||||
|
||||
virtual bool shaped_text_is_ready(RID p_shaped) const override;
|
||||
|
||||
virtual Vector<Glyph> shaped_text_get_glyphs(RID p_shaped) const override;
|
||||
|
@ -1271,7 +1271,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) {
|
||||
info_message->set_text(TTR("Give a MeshLibrary resource to this GridMap to use its meshes."));
|
||||
info_message->set_valign(Label::VALIGN_CENTER);
|
||||
info_message->set_align(Label::ALIGN_CENTER);
|
||||
info_message->set_autowrap(true);
|
||||
info_message->set_autowrap_mode(Label::AUTOWRAP_WORD_SMART);
|
||||
info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
|
||||
mesh_library_palette->add_child(info_message);
|
||||
|
@ -1658,6 +1658,161 @@ float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const Vector<float
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped_line);
|
||||
ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid.");
|
||||
if (!sd->valid) {
|
||||
shaped_text_shape(p_shaped_line);
|
||||
}
|
||||
|
||||
bool add_ellipsis = (p_clip_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS;
|
||||
bool cut_per_word = (p_clip_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY;
|
||||
bool enforce_ellipsis = (p_clip_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS;
|
||||
|
||||
Glyph *sd_glyphs = sd->glyphs.ptrw();
|
||||
|
||||
if ((p_clip_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int sd_size = sd->glyphs.size();
|
||||
RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
|
||||
uint32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.');
|
||||
Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, dot_gl_idx, last_gl_font_size);
|
||||
uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' ');
|
||||
Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size);
|
||||
|
||||
int ellipsis_advance = 0;
|
||||
if (add_ellipsis) {
|
||||
ellipsis_advance = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0);
|
||||
}
|
||||
|
||||
int ell_min_characters = 6;
|
||||
float width = sd->width;
|
||||
|
||||
bool is_rtl = sd->direction == DIRECTION_RTL || (sd->direction == DIRECTION_AUTO && sd->para_direction == DIRECTION_RTL);
|
||||
|
||||
int trim_pos = (is_rtl) ? sd_size : 0;
|
||||
int ellipsis_pos = (enforce_ellipsis) ? 0 : -1;
|
||||
|
||||
int last_valid_cut = 0;
|
||||
bool found = false;
|
||||
|
||||
int glyphs_from = (is_rtl) ? 0 : sd_size - 1;
|
||||
int glyphs_to = (is_rtl) ? sd_size - 1 : -1;
|
||||
int glyphs_delta = (is_rtl) ? +1 : -1;
|
||||
|
||||
for (int i = glyphs_from; i != glyphs_to; i += glyphs_delta) {
|
||||
if (!is_rtl) {
|
||||
width -= sd_glyphs[i].advance;
|
||||
}
|
||||
if (sd_glyphs[i].count > 0) {
|
||||
bool above_min_char_treshold = ((is_rtl) ? sd_size - 1 - i : i) >= ell_min_characters;
|
||||
|
||||
if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_advance : 0) <= p_width) {
|
||||
if (cut_per_word && above_min_char_treshold) {
|
||||
if ((sd_glyphs[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
|
||||
last_valid_cut = i;
|
||||
found = true;
|
||||
}
|
||||
} else {
|
||||
last_valid_cut = i;
|
||||
found = true;
|
||||
}
|
||||
if (found) {
|
||||
trim_pos = last_valid_cut;
|
||||
|
||||
if (above_min_char_treshold && width - ellipsis_advance <= p_width) {
|
||||
ellipsis_pos = trim_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_rtl) {
|
||||
width -= sd_glyphs[i].advance;
|
||||
}
|
||||
}
|
||||
|
||||
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
|
||||
int added_glyphs = 0;
|
||||
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
|
||||
// Insert an additional space when cutting word bound for aesthetics.
|
||||
if (cut_per_word && (ellipsis_pos > 0)) {
|
||||
TextServer::Glyph gl;
|
||||
gl.start = sd_glyphs[ellipsis_pos].start;
|
||||
gl.end = sd_glyphs[ellipsis_pos].end;
|
||||
gl.count = 1;
|
||||
gl.advance = whitespace_adv.x;
|
||||
gl.index = whitespace_gl_idx;
|
||||
gl.font_rid = last_gl_font_rid;
|
||||
gl.font_size = last_gl_font_size;
|
||||
gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
|
||||
|
||||
// Optimized glyph insertion by replacing a glyph whenever possible.
|
||||
int glyph_idx = trim_pos + ((is_rtl) ? (-added_glyphs - 1) : added_glyphs);
|
||||
if (is_rtl) {
|
||||
if (glyph_idx < 0) {
|
||||
sd->glyphs.insert(0, gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
} else {
|
||||
if (glyph_idx > (sd_size - 1)) {
|
||||
sd->glyphs.append(gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
}
|
||||
added_glyphs++;
|
||||
}
|
||||
// Add ellipsis dots.
|
||||
for (int d = 0; d < 3; d++) {
|
||||
TextServer::Glyph gl;
|
||||
gl.start = sd_glyphs[ellipsis_pos].start;
|
||||
gl.end = sd_glyphs[ellipsis_pos].end;
|
||||
gl.count = 1;
|
||||
gl.advance = dot_adv.x;
|
||||
gl.index = dot_gl_idx;
|
||||
gl.font_rid = last_gl_font_rid;
|
||||
gl.font_size = last_gl_font_size;
|
||||
gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
|
||||
|
||||
// Optimized glyph insertion by replacing a glyph whenever possible.
|
||||
int glyph_idx = trim_pos + ((is_rtl) ? (-added_glyphs - 1) : added_glyphs);
|
||||
if (is_rtl) {
|
||||
if (glyph_idx < 0) {
|
||||
sd->glyphs.insert(0, gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
} else {
|
||||
if (glyph_idx > (sd_size - 1)) {
|
||||
sd->glyphs.append(gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
}
|
||||
added_glyphs++;
|
||||
}
|
||||
}
|
||||
|
||||
// Cut the remaining glyphs off.
|
||||
if (!is_rtl) {
|
||||
sd->glyphs.resize(trim_pos + added_glyphs);
|
||||
} else {
|
||||
if (trim_pos - added_glyphs >= 0) {
|
||||
sd->glyphs = sd->glyphs.subarray(trim_pos - added_glyphs, sd->glyphs.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update to correct width.
|
||||
sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped);
|
||||
|
@ -229,6 +229,8 @@ public:
|
||||
virtual bool shaped_text_update_breaks(RID p_shaped) override;
|
||||
virtual bool shaped_text_update_justification_ops(RID p_shaped) override;
|
||||
|
||||
virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override;
|
||||
|
||||
virtual bool shaped_text_is_ready(RID p_shaped) const override;
|
||||
|
||||
virtual Vector<Glyph> shaped_text_get_glyphs(RID p_shaped) const override;
|
||||
|
@ -1141,6 +1141,161 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_clip_flags) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
ShapedTextData *sd = shaped_owner.getornull(p_shaped_line);
|
||||
ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid.");
|
||||
if (!sd->valid) {
|
||||
shaped_text_shape(p_shaped_line);
|
||||
}
|
||||
|
||||
bool add_ellipsis = (p_clip_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS;
|
||||
bool cut_per_word = (p_clip_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY;
|
||||
bool enforce_ellipsis = (p_clip_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS;
|
||||
|
||||
Glyph *sd_glyphs = sd->glyphs.ptrw();
|
||||
|
||||
if ((p_clip_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int sd_size = sd->glyphs.size();
|
||||
RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
|
||||
uint32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.');
|
||||
Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, dot_gl_idx, last_gl_font_size);
|
||||
uint32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' ');
|
||||
Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, whitespace_gl_idx, last_gl_font_size);
|
||||
|
||||
int ellipsis_advance = 0;
|
||||
if (add_ellipsis) {
|
||||
ellipsis_advance = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0);
|
||||
}
|
||||
|
||||
int ell_min_characters = 6;
|
||||
float width = sd->width;
|
||||
|
||||
bool is_rtl = sd->direction == DIRECTION_RTL || (sd->direction == DIRECTION_AUTO && sd->para_direction == DIRECTION_RTL);
|
||||
|
||||
int trim_pos = (is_rtl) ? sd_size : 0;
|
||||
int ellipsis_pos = (enforce_ellipsis) ? 0 : -1;
|
||||
|
||||
int last_valid_cut = 0;
|
||||
bool found = false;
|
||||
|
||||
int glyphs_from = (is_rtl) ? 0 : sd_size - 1;
|
||||
int glyphs_to = (is_rtl) ? sd_size - 1 : -1;
|
||||
int glyphs_delta = (is_rtl) ? +1 : -1;
|
||||
|
||||
for (int i = glyphs_from; i != glyphs_to; i += glyphs_delta) {
|
||||
if (!is_rtl) {
|
||||
width -= sd_glyphs[i].advance;
|
||||
}
|
||||
if (sd_glyphs[i].count > 0) {
|
||||
bool above_min_char_treshold = ((is_rtl) ? sd_size - 1 - i : i) >= ell_min_characters;
|
||||
|
||||
if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_advance : 0) <= p_width) {
|
||||
if (cut_per_word && above_min_char_treshold) {
|
||||
if ((sd_glyphs[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
|
||||
last_valid_cut = i;
|
||||
found = true;
|
||||
}
|
||||
} else {
|
||||
last_valid_cut = i;
|
||||
found = true;
|
||||
}
|
||||
if (found) {
|
||||
trim_pos = last_valid_cut;
|
||||
|
||||
if (above_min_char_treshold && width - ellipsis_advance <= p_width) {
|
||||
ellipsis_pos = trim_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_rtl) {
|
||||
width -= sd_glyphs[i].advance;
|
||||
}
|
||||
}
|
||||
|
||||
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
|
||||
int added_glyphs = 0;
|
||||
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
|
||||
// Insert an additional space when cutting word bound for aesthetics.
|
||||
if (cut_per_word && (ellipsis_pos > 0)) {
|
||||
TextServer::Glyph gl;
|
||||
gl.start = sd_glyphs[ellipsis_pos].start;
|
||||
gl.end = sd_glyphs[ellipsis_pos].end;
|
||||
gl.count = 1;
|
||||
gl.advance = whitespace_adv.x;
|
||||
gl.index = whitespace_gl_idx;
|
||||
gl.font_rid = last_gl_font_rid;
|
||||
gl.font_size = last_gl_font_size;
|
||||
gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
|
||||
|
||||
// Optimized glyph insertion by replacing a glyph whenever possible.
|
||||
int glyph_idx = trim_pos + ((is_rtl) ? -added_glyphs : added_glyphs);
|
||||
if (is_rtl) {
|
||||
if (glyph_idx < 0) {
|
||||
sd->glyphs.insert(0, gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
} else {
|
||||
if (glyph_idx > (sd_size - 1)) {
|
||||
sd->glyphs.append(gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
}
|
||||
added_glyphs++;
|
||||
}
|
||||
// Add ellipsis dots.
|
||||
for (int d = 0; d < 3; d++) {
|
||||
TextServer::Glyph gl;
|
||||
gl.start = sd_glyphs[ellipsis_pos].start;
|
||||
gl.end = sd_glyphs[ellipsis_pos].end;
|
||||
gl.count = 1;
|
||||
gl.advance = dot_adv.x;
|
||||
gl.index = dot_gl_idx;
|
||||
gl.font_rid = last_gl_font_rid;
|
||||
gl.font_size = last_gl_font_size;
|
||||
gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
|
||||
|
||||
// Optimized glyph insertion by replacing a glyph whenever possible.
|
||||
int glyph_idx = trim_pos + ((is_rtl) ? -added_glyphs : added_glyphs);
|
||||
if (is_rtl) {
|
||||
if (glyph_idx < 0) {
|
||||
sd->glyphs.insert(0, gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
} else {
|
||||
if (glyph_idx > (sd_size - 1)) {
|
||||
sd->glyphs.append(gl);
|
||||
} else {
|
||||
sd->glyphs.set(glyph_idx, gl);
|
||||
}
|
||||
}
|
||||
added_glyphs++;
|
||||
}
|
||||
}
|
||||
|
||||
// Cut the remaining glyphs off.
|
||||
if (!is_rtl) {
|
||||
sd->glyphs.resize(trim_pos + added_glyphs);
|
||||
} else {
|
||||
for (int ridx = 0; ridx <= trim_pos - added_glyphs; ridx++) {
|
||||
sd->glyphs.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Update to correct width.
|
||||
sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool TextServerFallback::shaped_text_shape(RID p_shaped) {
|
||||
_THREAD_SAFE_METHOD_
|
||||
ShapedTextData *sd = shaped_owner.getornull(p_shaped);
|
||||
|
@ -178,6 +178,8 @@ public:
|
||||
virtual bool shaped_text_update_breaks(RID p_shaped) override;
|
||||
virtual bool shaped_text_update_justification_ops(RID p_shaped) override;
|
||||
|
||||
virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) override;
|
||||
|
||||
virtual bool shaped_text_is_ready(RID p_shaped) const override;
|
||||
|
||||
virtual Vector<Glyph> shaped_text_get_glyphs(RID p_shaped) const override;
|
||||
|
@ -148,11 +148,11 @@ bool AcceptDialog::get_hide_on_ok() const {
|
||||
}
|
||||
|
||||
void AcceptDialog::set_autowrap(bool p_autowrap) {
|
||||
label->set_autowrap(p_autowrap);
|
||||
label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF);
|
||||
}
|
||||
|
||||
bool AcceptDialog::has_autowrap() {
|
||||
return label->has_autowrap();
|
||||
return label->get_autowrap_mode() != Label::AUTOWRAP_OFF;
|
||||
}
|
||||
|
||||
void AcceptDialog::register_text_enter(Control *p_line_edit) {
|
||||
|
@ -36,20 +36,20 @@
|
||||
|
||||
#include "servers/text_server.h"
|
||||
|
||||
void Label::set_autowrap(bool p_autowrap) {
|
||||
if (autowrap != p_autowrap) {
|
||||
autowrap = p_autowrap;
|
||||
void Label::set_autowrap_mode(Label::AutowrapMode p_mode) {
|
||||
if (autowrap_mode != p_mode) {
|
||||
autowrap_mode = p_mode;
|
||||
lines_dirty = true;
|
||||
}
|
||||
update();
|
||||
|
||||
if (clip) {
|
||||
if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
|
||||
minimum_size_changed();
|
||||
}
|
||||
}
|
||||
|
||||
bool Label::has_autowrap() const {
|
||||
return autowrap;
|
||||
Label::AutowrapMode Label::get_autowrap_mode() const {
|
||||
return autowrap_mode;
|
||||
}
|
||||
|
||||
void Label::set_uppercase(bool p_uppercase) {
|
||||
@ -94,24 +94,76 @@ void Label::_shape() {
|
||||
dirty = false;
|
||||
lines_dirty = true;
|
||||
}
|
||||
|
||||
uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING;
|
||||
if (lines_dirty) {
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
TS->free(lines_rid[i]);
|
||||
}
|
||||
lines_rid.clear();
|
||||
|
||||
Vector<Vector2i> lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, (autowrap) ? (TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) : TextServer::BREAK_MANDATORY);
|
||||
uint8_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;
|
||||
}
|
||||
Vector<Vector2i> lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags);
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
RID line = TS->shaped_text_substr(text_rid, lines[i].x, lines[i].y - lines[i].x);
|
||||
|
||||
switch (overrun_behavior) {
|
||||
case OVERRUN_TRIM_WORD_ELLIPSIS:
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM;
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY;
|
||||
overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS;
|
||||
break;
|
||||
case OVERRUN_TRIM_ELLIPSIS:
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM;
|
||||
overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS;
|
||||
break;
|
||||
case OVERRUN_TRIM_WORD:
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM;
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY;
|
||||
break;
|
||||
case OVERRUN_TRIM_CHAR:
|
||||
overrun_flags |= TextServer::OVERRUN_TRIM;
|
||||
break;
|
||||
case OVERRUN_NO_TRIMMING:
|
||||
break;
|
||||
}
|
||||
|
||||
if (autowrap_mode == AUTOWRAP_OFF && align != ALIGN_FILL && overrun_behavior != OVERRUN_NO_TRIMMING) {
|
||||
TS->shaped_text_overrun_trim_to_width(line, width, overrun_flags);
|
||||
}
|
||||
|
||||
lines_rid.push_back(line);
|
||||
}
|
||||
|
||||
if (autowrap_mode != AUTOWRAP_OFF && overrun_behavior != OVERRUN_NO_TRIMMING) {
|
||||
int visible_lines = get_visible_line_count();
|
||||
|
||||
if (visible_lines < lines_rid.size() && visible_lines > 0) {
|
||||
overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS;
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xl_text.length() == 0) {
|
||||
minsize = Size2(1, get_line_height());
|
||||
return;
|
||||
}
|
||||
if (!autowrap) {
|
||||
if (autowrap_mode == 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) {
|
||||
@ -120,10 +172,21 @@ void Label::_shape() {
|
||||
}
|
||||
}
|
||||
|
||||
if (lines_dirty) { // Fill after min_size calculation.
|
||||
if (lines_dirty) {
|
||||
// Fill after min_size calculation.
|
||||
if (align == ALIGN_FILL) {
|
||||
for (int i = 0; i < lines_rid.size(); i++) {
|
||||
TS->shaped_text_fit_to_width(lines_rid.write[i], width);
|
||||
if (overrun_behavior != OVERRUN_NO_TRIMMING && autowrap_mode == AUTOWRAP_OFF) {
|
||||
float line_unaltered_width = TS->shaped_text_get_width(lines_rid[i]);
|
||||
TS->shaped_text_fit_to_width(lines_rid[i], width);
|
||||
float new_line_width = TS->shaped_text_get_width(lines_rid[i]);
|
||||
// Begin trimming when there is no space between words available anymore.
|
||||
if (new_line_width < line_unaltered_width) {
|
||||
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
|
||||
}
|
||||
} else {
|
||||
TS->shaped_text_fit_to_width(lines_rid[i], width);
|
||||
}
|
||||
}
|
||||
}
|
||||
lines_dirty = false;
|
||||
@ -131,7 +194,7 @@ void Label::_shape() {
|
||||
|
||||
_update_visible();
|
||||
|
||||
if (!autowrap || !clip) {
|
||||
if (autowrap_mode == AUTOWRAP_OFF || !clip || overrun_behavior == OVERRUN_NO_TRIMMING) {
|
||||
minimum_size_changed();
|
||||
}
|
||||
}
|
||||
@ -370,13 +433,12 @@ Size2 Label::get_minimum_size() const {
|
||||
min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size("font_size")) + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM));
|
||||
|
||||
Size2 min_style = get_theme_stylebox("normal")->get_minimum_size();
|
||||
if (autowrap) {
|
||||
return Size2(1, clip ? 1 : min_size.height) + min_style;
|
||||
if (autowrap_mode != AUTOWRAP_OFF) {
|
||||
return Size2(1, (clip || overrun_behavior != OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style;
|
||||
} else {
|
||||
if (clip) {
|
||||
if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
|
||||
min_size.width = 1;
|
||||
}
|
||||
|
||||
return min_size + min_style;
|
||||
}
|
||||
}
|
||||
@ -536,6 +598,21 @@ bool Label::is_clipping_text() const {
|
||||
return clip;
|
||||
}
|
||||
|
||||
void Label::set_text_overrun_behavior(Label::OverrunBehavior p_behavior) {
|
||||
if (overrun_behavior != p_behavior) {
|
||||
overrun_behavior = p_behavior;
|
||||
lines_dirty = true;
|
||||
}
|
||||
update();
|
||||
if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
|
||||
minimum_size_changed();
|
||||
}
|
||||
}
|
||||
|
||||
Label::OverrunBehavior Label::get_text_overrun_behavior() const {
|
||||
return overrun_behavior;
|
||||
}
|
||||
|
||||
String Label::get_text() const {
|
||||
return text;
|
||||
}
|
||||
@ -663,10 +740,12 @@ void Label::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Label::clear_opentype_features);
|
||||
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_autowrap", "enable"), &Label::set_autowrap);
|
||||
ClassDB::bind_method(D_METHOD("has_autowrap"), &Label::has_autowrap);
|
||||
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_clip_text", "enable"), &Label::set_clip_text);
|
||||
ClassDB::bind_method(D_METHOD("is_clipping_text"), &Label::is_clipping_text);
|
||||
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior);
|
||||
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior);
|
||||
ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase);
|
||||
ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase);
|
||||
ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1));
|
||||
@ -696,13 +775,25 @@ void Label::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(VALIGN_BOTTOM);
|
||||
BIND_ENUM_CONSTANT(VALIGN_FILL);
|
||||
|
||||
BIND_ENUM_CONSTANT(AUTOWRAP_OFF);
|
||||
BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY);
|
||||
BIND_ENUM_CONSTANT(AUTOWRAP_WORD);
|
||||
BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART);
|
||||
|
||||
BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM_CHAR);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autowrap"), "set_autowrap", "has_autowrap");
|
||||
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::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");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1", PROPERTY_USAGE_EDITOR), "set_visible_characters", "get_visible_characters");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
|
||||
|
@ -51,13 +51,29 @@ public:
|
||||
VALIGN_FILL
|
||||
};
|
||||
|
||||
enum AutowrapMode {
|
||||
AUTOWRAP_OFF,
|
||||
AUTOWRAP_ARBITRARY,
|
||||
AUTOWRAP_WORD,
|
||||
AUTOWRAP_WORD_SMART
|
||||
};
|
||||
|
||||
enum OverrunBehavior {
|
||||
OVERRUN_NO_TRIMMING,
|
||||
OVERRUN_TRIM_CHAR,
|
||||
OVERRUN_TRIM_WORD,
|
||||
OVERRUN_TRIM_ELLIPSIS,
|
||||
OVERRUN_TRIM_WORD_ELLIPSIS,
|
||||
};
|
||||
|
||||
private:
|
||||
Align align = ALIGN_LEFT;
|
||||
VAlign valign = VALIGN_TOP;
|
||||
String text;
|
||||
String xl_text;
|
||||
bool autowrap = false;
|
||||
AutowrapMode autowrap_mode = AUTOWRAP_OFF;
|
||||
bool clip = false;
|
||||
OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING;
|
||||
Size2 minsize;
|
||||
bool uppercase = false;
|
||||
|
||||
@ -118,8 +134,8 @@ public:
|
||||
void set_structured_text_bidi_override_options(Array p_args);
|
||||
Array get_structured_text_bidi_override_options() const;
|
||||
|
||||
void set_autowrap(bool p_autowrap);
|
||||
bool has_autowrap() const;
|
||||
void set_autowrap_mode(AutowrapMode p_mode);
|
||||
AutowrapMode get_autowrap_mode() const;
|
||||
|
||||
void set_uppercase(bool p_uppercase);
|
||||
bool is_uppercase() const;
|
||||
@ -131,6 +147,9 @@ public:
|
||||
void set_clip_text(bool p_clip);
|
||||
bool is_clipping_text() const;
|
||||
|
||||
void set_text_overrun_behavior(OverrunBehavior p_behavior);
|
||||
OverrunBehavior get_text_overrun_behavior() const;
|
||||
|
||||
void set_percent_visible(float p_percent);
|
||||
float get_percent_visible() const;
|
||||
|
||||
@ -150,5 +169,7 @@ public:
|
||||
|
||||
VARIANT_ENUM_CAST(Label::Align);
|
||||
VARIANT_ENUM_CAST(Label::VAlign);
|
||||
VARIANT_ENUM_CAST(Label::AutowrapMode);
|
||||
VARIANT_ENUM_CAST(Label::OverrunBehavior);
|
||||
|
||||
#endif
|
||||
|
@ -331,6 +331,9 @@ void TextServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks_adv", "shaped", "width", "start", "once", "break_flags"), &TextServer::_shaped_text_get_line_breaks_adv, DEFVAL(0), DEFVAL(true), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND));
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_line_breaks", "shaped", "width", "start", "break_flags"), &TextServer::_shaped_text_get_line_breaks, DEFVAL(0), DEFVAL(BREAK_MANDATORY | BREAK_WORD_BOUND));
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_word_breaks", "shaped"), &TextServer::_shaped_text_get_word_breaks);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_overrun_trim_to_width", "shaped", "width", "overrun_trim_flags"), &TextServer::shaped_text_overrun_trim_to_width, DEFVAL(0), DEFVAL(OVERRUN_NO_TRIMMING));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_objects", "shaped"), &TextServer::shaped_text_get_objects);
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_object_rect", "shaped", "key"), &TextServer::shaped_text_get_object_rect);
|
||||
|
||||
@ -381,6 +384,13 @@ void TextServer::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(BREAK_WORD_BOUND);
|
||||
BIND_ENUM_CONSTANT(BREAK_GRAPHEME_BOUND);
|
||||
|
||||
/* TextOverrunFlag */
|
||||
BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ONLY);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_ADD_ELLIPSIS);
|
||||
BIND_ENUM_CONSTANT(OVERRUN_ENFORCE_ELLIPSIS);
|
||||
|
||||
/* GraphemeFlag */
|
||||
BIND_ENUM_CONSTANT(GRAPHEME_IS_RTL);
|
||||
BIND_ENUM_CONSTANT(GRAPHEME_IS_VIRTUAL);
|
||||
@ -646,7 +656,7 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_w
|
||||
float width = 0.f;
|
||||
int line_start = MAX(p_start, range.x);
|
||||
int last_safe_break = -1;
|
||||
|
||||
int word_count = 0;
|
||||
int l_size = logical.size();
|
||||
const Glyph *l_gl = logical.ptr();
|
||||
|
||||
@ -655,12 +665,15 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_w
|
||||
continue;
|
||||
}
|
||||
if (l_gl[i].count > 0) {
|
||||
if ((p_width > 0) && (width + l_gl[i].advance > p_width) && (last_safe_break >= 0)) {
|
||||
//Ignore trailing spaces.
|
||||
bool is_space = (l_gl[i].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE;
|
||||
if ((p_width > 0) && (width + (is_space ? 0 : l_gl[i].advance) > p_width) && (last_safe_break >= 0)) {
|
||||
lines.push_back(Vector2i(line_start, l_gl[last_safe_break].end));
|
||||
line_start = l_gl[last_safe_break].end;
|
||||
i = last_safe_break;
|
||||
last_safe_break = -1;
|
||||
width = 0;
|
||||
word_count = 0;
|
||||
continue;
|
||||
}
|
||||
if ((p_break_flags & BREAK_MANDATORY) == BREAK_MANDATORY) {
|
||||
@ -675,8 +688,12 @@ Vector<Vector2i> TextServer::shaped_text_get_line_breaks(RID p_shaped, float p_w
|
||||
if ((p_break_flags & BREAK_WORD_BOUND) == BREAK_WORD_BOUND) {
|
||||
if ((l_gl[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
|
||||
last_safe_break = i;
|
||||
word_count++;
|
||||
}
|
||||
}
|
||||
if (((p_break_flags & BREAK_WORD_BOUND_ADAPTIVE) == BREAK_WORD_BOUND_ADAPTIVE) && word_count == 0) {
|
||||
last_safe_break = i;
|
||||
}
|
||||
if ((p_break_flags & BREAK_GRAPHEME_BOUND) == BREAK_GRAPHEME_BOUND) {
|
||||
last_safe_break = i;
|
||||
}
|
||||
|
@ -66,8 +66,16 @@ public:
|
||||
BREAK_NONE = 0,
|
||||
BREAK_MANDATORY = 1 << 4,
|
||||
BREAK_WORD_BOUND = 1 << 5,
|
||||
BREAK_GRAPHEME_BOUND = 1 << 6
|
||||
//RESERVED = 1 << 7
|
||||
BREAK_GRAPHEME_BOUND = 1 << 6,
|
||||
BREAK_WORD_BOUND_ADAPTIVE = 1 << 5 | 1 << 7
|
||||
};
|
||||
|
||||
enum TextOverrunFlag {
|
||||
OVERRUN_NO_TRIMMING = 0,
|
||||
OVERRUN_TRIM = 1 << 0,
|
||||
OVERRUN_TRIM_WORD_ONLY = 1 << 1,
|
||||
OVERRUN_ADD_ELLIPSIS = 1 << 2,
|
||||
OVERRUN_ENFORCE_ELLIPSIS = 1 << 3
|
||||
};
|
||||
|
||||
enum GraphemeFlag {
|
||||
@ -138,7 +146,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return l.count > r.count; // Sort first glyoh with count & flags, order of the rest are irrelevant.
|
||||
return l.count > r.count; // Sort first glyph with count & flags, order of the rest are irrelevant.
|
||||
} else {
|
||||
return l.start < r.start;
|
||||
}
|
||||
@ -347,6 +355,9 @@ public:
|
||||
virtual Vector<Vector2i> shaped_text_get_line_breaks_adv(RID p_shaped, const Vector<float> &p_width, int p_start = 0, bool p_once = true, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const;
|
||||
virtual Vector<Vector2i> shaped_text_get_line_breaks(RID p_shaped, float p_width, int p_start = 0, uint8_t /*TextBreakFlag*/ p_break_flags = BREAK_MANDATORY | BREAK_WORD_BOUND) const;
|
||||
virtual Vector<Vector2i> shaped_text_get_word_breaks(RID p_shaped) const;
|
||||
|
||||
virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) = 0;
|
||||
|
||||
virtual Array shaped_text_get_objects(RID p_shaped) const = 0;
|
||||
virtual Rect2 shaped_text_get_object_rect(RID p_shaped, Variant p_key) const = 0;
|
||||
|
||||
@ -461,6 +472,7 @@ VARIANT_ENUM_CAST(TextServer::Direction);
|
||||
VARIANT_ENUM_CAST(TextServer::Orientation);
|
||||
VARIANT_ENUM_CAST(TextServer::JustificationFlag);
|
||||
VARIANT_ENUM_CAST(TextServer::LineBreakFlag);
|
||||
VARIANT_ENUM_CAST(TextServer::TextOverrunFlag);
|
||||
VARIANT_ENUM_CAST(TextServer::GraphemeFlag);
|
||||
VARIANT_ENUM_CAST(TextServer::Hinting);
|
||||
VARIANT_ENUM_CAST(TextServer::Feature);
|
||||
|
Loading…
Reference in New Issue
Block a user