diff options
28 files changed, 1365 insertions, 483 deletions
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 2e5d2e6497..a65a88a4f2 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -32,6 +32,12 @@ Makes possible to disconnect nodes when dragging from the slot at the right if it has the specified type. </description> </method> + <method name="arrange_nodes"> + <return type="void" /> + <description> + Rearranges selected nodes in a layout with minimum crossings between connections and uniform horizontal and vertical gap between nodes. + </description> + </method> <method name="clear_connections"> <return type="void" /> <description> @@ -283,6 +289,8 @@ <theme_item name="grid_minor" data_type="color" type="Color" default="Color(1, 1, 1, 0.05)"> Color of minor grid lines. </theme_item> + <theme_item name="layout" data_type="icon" type="Texture2D"> + </theme_item> <theme_item name="minimap" data_type="icon" type="Texture2D"> </theme_item> <theme_item name="minus" data_type="icon" type="Texture2D"> diff --git a/doc/classes/StyleBoxFlat.xml b/doc/classes/StyleBoxFlat.xml index 540b007341..59ab724f48 100644 --- a/doc/classes/StyleBoxFlat.xml +++ b/doc/classes/StyleBoxFlat.xml @@ -121,7 +121,7 @@ <member name="anti_aliasing" type="bool" setter="set_anti_aliased" getter="is_anti_aliased" default="true"> Antialiasing draws a small ring around the edges, which fades to transparency. As a result, edges look much smoother. This is only noticeable when using rounded corners. </member> - <member name="anti_aliasing_size" type="int" setter="set_aa_size" getter="get_aa_size" default="1"> + <member name="anti_aliasing_size" type="float" setter="set_aa_size" getter="get_aa_size" default="0.625"> This changes the size of the faded ring. Higher values can be used to achieve a "blurry" effect. </member> <member name="bg_color" type="Color" setter="set_bg_color" getter="get_bg_color" default="Color(0.6, 0.6, 0.6, 1)"> diff --git a/doc/classes/TextLine.xml b/doc/classes/TextLine.xml index daa7298418..598d0fb859 100644 --- a/doc/classes/TextLine.xml +++ b/doc/classes/TextLine.xml @@ -162,10 +162,28 @@ <member name="preserve_invalid" type="bool" setter="set_preserve_invalid" getter="get_preserve_invalid" default="true"> If set to [code]true[/code] text will display invalid characters. </member> + <member name="text_overrun_behavior" type="int" setter="set_text_overrun_behavior" getter="get_text_overrun_behavior" enum="TextLine.OverrunBehavior" default="3"> + Sets the clipping behavior when the text exceeds the text line's set width. See [enum OverrunBehavior] for a description of all modes. + </member> <member name="width" type="float" setter="set_width" getter="get_width" default="-1.0"> Text line width. </member> </members> <constants> + <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> </class> diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index 811fe32eb2..fb94e14c8d 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -284,6 +284,9 @@ <member name="flags" type="int" setter="set_flags" getter="get_flags" default="51"> Line breaking and alignment rules. For more info see [TextServer]. </member> + <member name="max_lines_visible" type="int" setter="set_max_lines_visible" getter="get_max_lines_visible" default="-1"> + Limits the lines of text shown. + </member> <member name="orientation" type="int" setter="set_orientation" getter="get_orientation" enum="TextServer.Orientation" default="0"> Text orientation. </member> @@ -293,10 +296,28 @@ <member name="preserve_invalid" type="bool" setter="set_preserve_invalid" getter="get_preserve_invalid" default="true"> If set to [code]true[/code] text will display invalid characters. </member> + <member name="text_overrun_behavior" type="int" setter="set_text_overrun_behavior" getter="get_text_overrun_behavior" enum="TextParagraph.OverrunBehavior" default="0"> + Sets the clipping behavior when the text exceeds the paragraph's set width. See [enum OverrunBehavior] for a description of all modes. + </member> <member name="width" type="float" setter="set_width" getter="get_width" default="-1.0"> Paragraph width. </member> </members> <constants> + <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> </class> diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 08dd37bc73..5adce2951d 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1232,6 +1232,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("reset", "GraphEdit", theme->get_icon("ZoomReset", "EditorIcons")); theme->set_icon("snap", "GraphEdit", theme->get_icon("SnapGrid", "EditorIcons")); theme->set_icon("minimap", "GraphEdit", theme->get_icon("GridMinimap", "EditorIcons")); + theme->set_icon("layout", "GraphEdit", theme->get_icon("GridLayout", "EditorIcons")); theme->set_constant("bezier_len_pos", "GraphEdit", 80 * EDSCALE); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * EDSCALE); diff --git a/editor/icons/GridLayout.svg b/editor/icons/GridLayout.svg new file mode 100644 index 0000000000..71ad504477 --- /dev/null +++ b/editor/icons/GridLayout.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m14 2.1992188v2.6152343l-2.625 1.3125v-2.6152343zm-12 4.0644531 2.625 1.3125v2.5507811l-2.625-1.3124999zm12 0v2.5507812l-2.625 1.3124999v-2.5507811zm-8 1.4550781h4v2.640625h-4zm-4 2.560547 2.625 1.3125v2.521484l-2.625-1.3125zm12 0v2.521484l-2.625 1.3125v-2.521484zm-8 1.455078h4v2.640625h-4zm1.7014535-8.109375h2.2985465v2.734375h-4.15625s-.7487346.647119-.8746377.640625c-.1310411-.0067594-1.5097373-1.4558594-1.5097373-1.4558594l-1.459375-.7296875v-2.6152343l.068419.034223s.026411-.4573464.062111-.6760553c.0346282-.2121439.1970747-.59225724.1970747-.59225724l-1.0483078-.52372301c-.0795772-.04012218-.1668141-.06276382-.2558594-.06640625-.35427845-.01325803-.64865004.27047362-.6484375.625v12c.00021484.236623.13402736.45284.34570312.558594l3.99999998 2c.086686.043505.1823067.06624.2792969.066406h6c.09699-.000166.192611-.0229.279297-.06641l4-2c.211676-.10575.345488-.321967.345703-.55859v-12c-.000468-.46423753-.488958-.76598317-.904297-.55859375l-3.869141 1.93359375h-2.9709527s.033448.4166167.015891.625c-.029188.3464401-.1950466.625-.1950468.625z" fill="#b05b5b"/><path d="m5 6s-2.21875-2.1616704-2.21875-3.2425057c0-1.0808352 0-2.6072392 2.21875-2.6072392s2.21875 1.526404 2.21875 2.6072392c0 1.0808353-2.21875 3.2425057-2.21875 3.2425057z" fill="#fff" fill-opacity=".68627"/></svg> diff --git a/modules/gdnative/text/text_server_gdnative.cpp b/modules/gdnative/text/text_server_gdnative.cpp index d54b1a47df..3ed3f5449e 100644 --- a/modules/gdnative/text/text_server_gdnative.cpp +++ b/modules/gdnative/text/text_server_gdnative.cpp @@ -498,9 +498,9 @@ 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) { +void TextServerGDNative::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { ERR_FAIL_COND(interface == nullptr); - interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_clip_flags); + interface->shaped_text_overrun_trim_to_width(data, (godot_rid *)&p_shaped_line, p_width, p_trim_flags); }; bool TextServerGDNative::shaped_text_is_ready(RID p_shaped) const { diff --git a/modules/gdnative/text/text_server_gdnative.h b/modules/gdnative/text/text_server_gdnative.h index a2eb944499..e1b36f4264 100644 --- a/modules/gdnative/text/text_server_gdnative.h +++ b/modules/gdnative/text/text_server_gdnative.h @@ -167,7 +167,7 @@ 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 void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 66816f32d1..9ecb0de5b8 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -977,6 +977,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; + p_shaped->text_trimmed = false; p_shaped->ascent = 0.f; p_shaped->descent = 0.f; p_shaped->width = 0.f; @@ -984,6 +985,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced * p_shaped->uthk = 0.f; p_shaped->glyphs.clear(); p_shaped->glyphs_logical.clear(); + p_shaped->overrun_trim_data = TrimData(); p_shaped->utf16 = Char16String(); if (p_shaped->script_iter != nullptr) { memdelete(p_shaped->script_iter); @@ -1375,7 +1377,7 @@ RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_leng ERR_FAIL_COND_V_MSG((start < 0 || end - start > new_sd->utf16.length()), RID(), "Invalid BiDi override range."); - //Create temporary line bidi & shape + // Create temporary line bidi & shape. UBiDi *bidi_iter = ubidi_openSized(end - start, 0, &err); ERR_FAIL_COND_V_MSG(U_FAILURE(err), RID(), u_errorName(err)); ubidi_setLine(sd->bidi_iter[ov], start, end, bidi_iter, &err); @@ -1541,6 +1543,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, const_cast<TextServerAdvanced *>(this)->shaped_text_update_justification_ops(p_shaped); } + sd->fit_width_minimum_reached = false; int start_pos = 0; int end_pos = sd->glyphs.size() - 1; @@ -1569,14 +1572,26 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } } + float justification_width; + if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) { + if (sd->overrun_trim_data.trim_pos >= 0) { + start_pos = sd->overrun_trim_data.trim_pos; + justification_width = sd->width_trimmed; + } else { + return sd->width; + } + } else { + justification_width = sd->width; + } + if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) { while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { - sd->width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat; + justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat; sd->glyphs.write[start_pos].advance = 0; start_pos += sd->glyphs[start_pos].count; } while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { - sd->width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat; + justification_width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat; sd->glyphs.write[end_pos].advance = 0; end_pos -= sd->glyphs[end_pos].count; } @@ -1597,7 +1612,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, } if ((elongation_count > 0) && ((p_jst_flags & JUSTIFICATION_KASHIDA) == JUSTIFICATION_KASHIDA)) { - float delta_width_per_kashida = (p_width - sd->width) / elongation_count; + float delta_width_per_kashida = (p_width - justification_width) / elongation_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { @@ -1605,34 +1620,50 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width, int count = delta_width_per_kashida / gl.advance; int prev_count = gl.repeat; if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) { - gl.repeat = count; - } else { - gl.repeat = count + 1; + gl.repeat = MAX(count, 0); } - sd->width += (gl.repeat - prev_count) * gl.advance; + justification_width += (gl.repeat - prev_count) * gl.advance; } } } } - + float adv_remain = 0; if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) { - float delta_width_per_space = (p_width - sd->width) / space_count; + float delta_width_per_space = (p_width - justification_width) / space_count; for (int i = start_pos; i <= end_pos; i++) { Glyph &gl = sd->glyphs.write[i]; if (gl.count > 0) { if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) { float old_adv = gl.advance; + float new_advance; if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) { - gl.advance = Math::round(MAX(gl.advance + delta_width_per_space, 0.f)); + new_advance = MAX(gl.advance + delta_width_per_space, 0.f); } else { - gl.advance = Math::round(MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size)); + new_advance = MAX(gl.advance + delta_width_per_space, 0.05 * gl.font_size); + } + gl.advance = new_advance; + adv_remain += (new_advance - gl.advance); + if (adv_remain >= 1.0) { + gl.advance++; + adv_remain -= 1.0; + } else if (adv_remain <= -1.0) { + gl.advance = MAX(gl.advance - 1, 0); + adv_remain -= 1.0; } - sd->width += (gl.advance - old_adv); + justification_width += (gl.advance - old_adv); } } } } + if (Math::floor(p_width) < Math::floor(justification_width)) { + sd->fit_width_minimum_reached = true; + } + + if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) != JUSTIFICATION_CONSTRAIN_ELLIPSIS) { + sd->width = justification_width; + } + return sd->width; } @@ -1685,7 +1716,7 @@ 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) { +void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); @@ -1693,13 +1724,22 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl 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; + sd->overrun_trim_data.ellipsis_glyph_buf.clear(); + + bool add_ellipsis = (p_trim_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; + bool cut_per_word = (p_trim_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; + bool enforce_ellipsis = (p_trim_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + bool justification_aware = (p_trim_flags & OVERRUN_JUSTIFICATION_AWARE) == OVERRUN_JUSTIFICATION_AWARE; 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)) { + if ((p_trim_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + sd->overrun_trim_data.trim_pos = -1; + sd->overrun_trim_data.ellipsis_pos = -1; + return; + } + + if (justification_aware && !sd->fit_width_minimum_reached) { return; } @@ -1711,9 +1751,9 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl 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; + int ellipsis_width = 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); + ellipsis_width = 3 * dot_adv.x + font_get_spacing_glyph(last_gl_font_rid) + (cut_per_word ? whitespace_adv.x : 0); } int ell_min_characters = 6; @@ -1733,12 +1773,12 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl for (int i = glyphs_from; i != glyphs_to; i += glyphs_delta) { if (!is_rtl) { - width -= sd_glyphs[i].advance; + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; } 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 (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_width : 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; @@ -1751,7 +1791,7 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl if (found) { trim_pos = last_valid_cut; - if (above_min_char_treshold && width - ellipsis_advance <= p_width) { + if (add_ellipsis && (above_min_char_treshold || enforce_ellipsis) && width - ellipsis_width <= p_width) { ellipsis_pos = trim_pos; } break; @@ -1759,18 +1799,21 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl } } if (is_rtl) { - width -= sd_glyphs[i].advance; + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; } } + sd->overrun_trim_data.trim_pos = trim_pos; + sd->overrun_trim_data.ellipsis_pos = ellipsis_pos; + if (trim_pos == 0 && enforce_ellipsis && add_ellipsis) { + sd->overrun_trim_data.ellipsis_pos = 0; + } + 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; @@ -1778,68 +1821,33 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl 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++; + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } // 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); - } + TextServer::Glyph gl; + gl.count = 1; + gl.repeat = 3; + 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); + + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } - // Update to correct width. - sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0); + sd->text_trimmed = true; + sd->width_trimmed = width + ((ellipsis_pos != -1) ? ellipsis_width : 0); } } +TextServer::TrimData TextServerAdvanced::shaped_text_get_trim_data(RID p_shaped) const { + _THREAD_SAFE_METHOD_ + ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + return sd->overrun_trim_data; +} + bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextDataAdvanced *sd = shaped_owner.getornull(p_shaped); @@ -1866,7 +1874,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { int r_end = sd->spans[i].end; UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err); if (U_FAILURE(err)) { - //No data loaded - use fallback. + // No data loaded - use fallback. for (int j = r_start; j < r_end; j++) { char32_t c = sd->text[j - sd->start]; if (is_whitespace(c)) { @@ -1930,7 +1938,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) { gl.font_rid = sd_glyphs[i].font_rid; gl.font_size = sd_glyphs[i].font_size; gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL; - sd->glyphs.insert(i + sd_glyphs[i].count, gl); // insert after + sd->glyphs.insert(i + sd_glyphs[i].count, gl); // Insert after. // Update write pointer and size. sd_size = sd->glyphs.size(); @@ -2050,7 +2058,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { UErrorCode err = U_ZERO_ERROR; UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err); if (U_FAILURE(err)) { - // No data - use fallback + // No data - use fallback. int limit = 0; for (int i = 0; i < sd->text.length(); i++) { if (is_whitespace(data[i])) { @@ -2123,7 +2131,7 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) { gl.font_rid = sd->glyphs[i].font_rid; gl.font_size = sd->glyphs[i].font_size; gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL; - sd->glyphs.insert(i + sd->glyphs[i].count, gl); // insert after + sd->glyphs.insert(i + sd->glyphs[i].count, gl); // Insert after. i += sd->glyphs[i].count; continue; } @@ -2189,7 +2197,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star int fs = p_sd->spans[p_span].font_size; if (fd == nullptr) { - // Add fallback glyphs + // Add fallback glyphs. for (int i = p_start; i < p_end; i++) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { TextServer::Glyph gl; @@ -2332,7 +2340,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star w[last_cluster_index].flags |= GRAPHEME_IS_VALID; } - //Fallback. + // Fallback. int failed_subrun_start = p_end + 1; int failed_subrun_end = p_start; @@ -2677,9 +2685,9 @@ Size2 TextServerAdvanced::shaped_text_get_size(RID p_shaped) const { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(sd->width, sd->ascent + sd->descent); + return Size2((sd->text_trimmed ? sd->width_trimmed : sd->width), sd->ascent + sd->descent); } else { - return Size2(sd->ascent + sd->descent, sd->width); + return Size2(sd->ascent + sd->descent, (sd->text_trimmed ? sd->width_trimmed : sd->width)); } } @@ -2710,7 +2718,7 @@ float TextServerAdvanced::shaped_text_get_width(RID p_shaped) const { if (!sd->valid) { const_cast<TextServerAdvanced *>(this)->shaped_text_shape(p_shaped); } - return sd->width; + return (sd->text_trimmed ? sd->width_trimmed : sd->width); } float TextServerAdvanced::shaped_text_get_underline_position(RID p_shaped) const { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index c54686c114..d19ba41a1a 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -229,7 +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 void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index b60bf8e8d3..110194c373 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -570,7 +570,7 @@ void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::O } void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Vector<Vector2i> &p_override) { - //No BiDi support, ignore. + // No BiDi support, ignore. } TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const { @@ -1173,7 +1173,7 @@ 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) { +void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint8_t p_trim_flags) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped_line); ERR_FAIL_COND_MSG(!sd, "ShapedTextDataAdvanced invalid."); @@ -1181,13 +1181,22 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl 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; + sd->overrun_trim_data.ellipsis_glyph_buf.clear(); + + bool add_ellipsis = (p_trim_flags & OVERRUN_ADD_ELLIPSIS) == OVERRUN_ADD_ELLIPSIS; + bool cut_per_word = (p_trim_flags & OVERRUN_TRIM_WORD_ONLY) == OVERRUN_TRIM_WORD_ONLY; + bool enforce_ellipsis = (p_trim_flags & OVERRUN_ENFORCE_ELLIPSIS) == OVERRUN_ENFORCE_ELLIPSIS; + bool justification_aware = (p_trim_flags & OVERRUN_JUSTIFICATION_AWARE) == OVERRUN_JUSTIFICATION_AWARE; 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)) { + if ((p_trim_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIMMING || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) { + sd->overrun_trim_data.trim_pos = -1; + sd->overrun_trim_data.ellipsis_pos = -1; + return; + } + + if (justification_aware && !sd->fit_width_minimum_reached) { return; } @@ -1199,34 +1208,27 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl 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; + int ellipsis_width = 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); + ellipsis_width = 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 trim_pos = 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 = sd_size - 1; i != -1; i--) { + width -= sd_glyphs[i].advance * sd_glyphs[i].repeat; - 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; + bool above_min_char_treshold = (i >= ell_min_characters); - if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_advance : 0) <= p_width) { + if (width + (((above_min_char_treshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_width : 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; @@ -1239,95 +1241,60 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl if (found) { trim_pos = last_valid_cut; - if (above_min_char_treshold && width - ellipsis_advance <= p_width) { + if (add_ellipsis && (above_min_char_treshold || enforce_ellipsis) && width - ellipsis_width <= p_width) { ellipsis_pos = trim_pos; } break; } } } - if (is_rtl) { - width -= sd_glyphs[i].advance; - } + } + + sd->overrun_trim_data.trim_pos = trim_pos; + sd->overrun_trim_data.ellipsis_pos = ellipsis_pos; + if (trim_pos == 0 && enforce_ellipsis && add_ellipsis) { + sd->overrun_trim_data.ellipsis_pos = 0; } 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); + gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL; - // 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++; + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } // 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); - } + TextServer::Glyph gl; + gl.count = 1; + gl.repeat = 3; + 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; + + sd->overrun_trim_data.ellipsis_glyph_buf.append(gl); } - // Update to correct width. - sd->width = width + ((ellipsis_pos != -1) ? ellipsis_advance : 0); + sd->text_trimmed = true; + sd->width_trimmed = width + ((ellipsis_pos != -1) ? ellipsis_width : 0); } } +TextServer::TrimData TextServerFallback::shaped_text_get_trim_data(RID p_shaped) const { + _THREAD_SAFE_METHOD_ + ShapedTextData *sd = shaped_owner.getornull(p_shaped); + ERR_FAIL_COND_V_MSG(!sd, TrimData(), "ShapedTextDataAdvanced invalid."); + return sd->overrun_trim_data; +} + bool TextServerFallback::shaped_text_shape(RID p_shaped) { _THREAD_SAFE_METHOD_ ShapedTextData *sd = shaped_owner.getornull(p_shaped); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index e3bbde76d4..d4cab2409a 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -178,7 +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 void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_flags) override; + virtual TrimData shaped_text_get_trim_data(RID p_shaped) const override; virtual bool shaped_text_is_ready(RID p_shaped) const override; diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h index 3b7ed3dba6..962ea380aa 100644 --- a/modules/visual_script/visual_script_editor.h +++ b/modules/visual_script/visual_script_editor.h @@ -60,7 +60,7 @@ class VisualScriptEditor : public ScriptEditorBase { EDIT_CUT_NODES, EDIT_PASTE_NODES, EDIT_CREATE_FUNCTION, - REFRESH_GRAPH + REFRESH_GRAPH, }; enum PortAction { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 1fac2b9129..cdb8f7046b 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -450,6 +450,7 @@ void GraphEdit::_notification(int p_what) { zoom_plus->set_icon(get_theme_icon(SNAME("more"))); snap_button->set_icon(get_theme_icon(SNAME("snap"))); minimap_button->set_icon(get_theme_icon(SNAME("minimap"))); + layout_button->set_icon(get_theme_icon(SNAME("layout"))); } if (p_what == NOTIFICATION_READY) { Size2 hmin = h_scroll->get_combined_minimum_size(); @@ -1646,6 +1647,500 @@ HBoxContainer *GraphEdit::get_zoom_hbox() { return zoom_hb; } +int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) { + switch (p_operation) { + case GraphEdit::IS_EQUAL: { + for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + if (!r_v.has(E->get())) + return 0; + } + return r_u.size() == r_v.size(); + } break; + case GraphEdit::IS_SUBSET: { + if (r_u.size() == r_v.size() && !r_u.size()) { + return 1; + } + for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + if (!r_v.has(E->get())) + return 0; + } + return 1; + } break; + case GraphEdit::DIFFERENCE: { + for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + if (r_v.has(E->get())) { + r_u.erase(E->get()); + } + } + return r_u.size(); + } break; + case GraphEdit::UNION: { + for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) { + if (!r_u.has(E->get())) { + r_u.insert(E->get()); + } + } + return r_v.size(); + } break; + default: + break; + } + return -1; +} + +HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { + HashMap<int, Vector<StringName>> l; + + Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; + int current_layer = 0; + bool selected = false; + + while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) { + _set_operations(GraphEdit::DIFFERENCE, p, u); + for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) { + Set<StringName> n = r_upper_neighbours[E->get()]; + if (_set_operations(GraphEdit::IS_SUBSET, n, z)) { + Vector<StringName> t; + t.push_back(E->get()); + if (!l.has(current_layer)) { + l.set(current_layer, Vector<StringName>{}); + } + selected = true; + t.append_array(l[current_layer]); + l.set(current_layer, t); + Set<StringName> V; + V.insert(E->get()); + _set_operations(GraphEdit::UNION, u, V); + } + } + if (!selected) { + current_layer++; + _set_operations(GraphEdit::UNION, z, u); + } + selected = false; + } + + return l; +} + +Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) { + if (!r_layer.size()) { + return Vector<StringName>(); + } + + StringName p = r_layer[Math::random(0, r_layer.size() - 1)]; + Vector<StringName> left; + Vector<StringName> right; + + for (int i = 0; i < r_layer.size(); i++) { + if (p != r_layer[i]) { + StringName q = r_layer[i]; + int cross_pq = r_crossings[p][q]; + int cross_qp = r_crossings[q][p]; + if (cross_pq > cross_qp) { + left.push_back(q); + } else { + right.push_back(q); + } + } + } + + left.push_back(p); + left.append_array(right); + return left; +} + +void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) { + for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) { + r_root[E->get()] = E->get(); + r_align[E->get()] = E->get(); + } + + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> lower_layer = r_layers[i]; + Vector<StringName> upper_layer = r_layers[i - 1]; + int r = -1; + + for (int j = 0; j < lower_layer.size(); j++) { + Vector<Pair<int, StringName>> up; + StringName current_node = lower_layer[j]; + for (int k = 0; k < upper_layer.size(); k++) { + StringName adjacent_neighbour = upper_layer[k]; + if (r_upper_neighbours[current_node].has(adjacent_neighbour)) { + up.push_back(Pair<int, StringName>(k, adjacent_neighbour)); + } + } + + int start = up.size() / 2; + int end = up.size() % 2 ? start : start + 1; + for (int p = start; p <= end; p++) { + StringName Align = r_align[current_node]; + if (Align == current_node && r < up[p].first) { + r_align[up[p].second] = lower_layer[j]; + r_root[current_node] = r_root[up[p].second]; + r_align[current_node] = r_root[up[p].second]; + r = up[p].first; + } + } + } + } +} + +void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { + if (r_layers.size() == 1) { + return; + } + + for (unsigned int i = 1; i < r_layers.size(); i++) { + Vector<StringName> upper_layer = r_layers[i - 1]; + Vector<StringName> lower_layer = r_layers[i]; + HashMap<StringName, Dictionary> c; + + for (int j = 0; j < lower_layer.size(); j++) { + StringName p = lower_layer[j]; + Dictionary d; + + for (int k = 0; k < lower_layer.size(); k++) { + unsigned int crossings = 0; + StringName q = lower_layer[k]; + + if (j != k) { + for (int h = 1; h < upper_layer.size(); h++) { + if (r_upper_neighbours[p].has(upper_layer[h])) { + for (int g = 0; g < h; g++) { + if (r_upper_neighbours[q].has(upper_layer[g])) { + crossings++; + } + } + } + } + } + d[q] = crossings; + } + c.set(p, d); + } + + r_layers.set(i, _split(lower_layer, c)); + } +} + +void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { + for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) { + real_t left = 0; + StringName u = E->get(); + StringName v = r_align[u]; + while (u != v && (StringName)r_root[u] != v) { + String _connection = String(u) + " " + String(v); + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[u]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[v]); + + Pair<int, int> ports = r_port_info[_connection]; + int pfrom = ports.first; + int pto = ports.second; + Vector2 frompos = gfrom->get_connection_output_position(pfrom); + Vector2 topos = gto->get_connection_input_position(pto); + + real_t s = (real_t)r_inner_shifts[u] + (frompos.y - topos.y) / zoom; + r_inner_shifts[v] = s; + left = MIN(left, s); + + u = v; + v = (StringName)r_align[v]; + } + + u = E->get(); + do { + r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left; + u = (StringName)r_align[u]; + } while (u != E->get()); + } +} + +float GraphEdit::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) { +#define MAX_ORDER 2147483647 +#define ORDER(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + order = index; \ + break; \ + } \ + order = MAX_ORDER; \ + } + + int order = MAX_ORDER; + float threshold = p_current_threshold; + if (p_v == p_w) { + int min_order = MAX_ORDER; + Connection incoming; + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().to == p_w) { + ORDER(E->get().from, r_layers); + if (min_order > order) { + min_order = order; + incoming = E->get(); + } + } + } + + if (incoming.from != StringName()) { + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[incoming.from]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[p_w]); + Vector2 frompos = gfrom->get_connection_output_position(incoming.from_port); + Vector2 topos = gto->get_connection_input_position(incoming.to_port); + + //If connected block node is selected, calculate thershold or add current block to list + if (gfrom->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[incoming.from]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed. Calculate threshold + threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; + } + } + } + } + if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) { + //This time, pick an outgoing edge and repeat as above! + int min_order = MAX_ORDER; + Connection outgoing; + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().from == p_w) { + ORDER(E->get().to, r_layers); + if (min_order > order) { + min_order = order; + outgoing = E->get(); + } + } + } + + if (outgoing.to != StringName()) { + GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[p_w]); + GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[outgoing.to]); + Vector2 frompos = gfrom->get_connection_output_position(outgoing.from_port); + Vector2 topos = gto->get_connection_input_position(outgoing.to_port); + + //If connected block node is selected, calculate thershold or add current block to list + if (gto->is_selected()) { + Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to]]; + if (connected_block_pos.y != FLT_MAX) { + //Connected block is placed. Calculate threshold + threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y; + } + } + } + } +#undef MAX_ORDER +#undef ORDER + return threshold; +} + +void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) { +#define PRED(node, layers) \ + for (unsigned int i = 0; i < layers.size(); i++) { \ + int index = layers[i].find(node); \ + if (index > 0) { \ + predecessor = layers[i][index - 1]; \ + break; \ + } \ + predecessor = StringName(); \ + } + + StringName predecessor; + StringName successor; + Vector2 pos = r_node_positions[p_v]; + + if (pos.y == FLT_MAX) { + pos.y = 0; + bool initial = false; + StringName w = p_v; + real_t threshold = FLT_MIN; + do { + PRED(w, r_layers); + if (predecessor != StringName()) { + StringName u = r_root[predecessor]; + _place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions); + threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); + if ((StringName)r_sink[p_v] == p_v) { + r_sink[p_v] = r_sink[u]; + } + + Vector2 predecessor_root_pos = r_node_positions[u]; + Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size(); + if (r_sink[p_v] != r_sink[u]) { + real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta; + r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]); + } else { + real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta; + sb = MAX(sb, threshold); + if (initial) { + pos.y = sb; + } else { + pos.y = MAX(pos.y, sb); + } + initial = false; + } + } + threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); + w = r_align[w]; + } while (w != p_v); + r_node_positions.set(p_v, pos); + } + +#undef PRED +} + +void GraphEdit::arrange_nodes() { + if (!arranging_graph) { + arranging_graph = true; + } else { + return; + } + + Dictionary node_names; + Set<StringName> selected_nodes; + + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn) { + continue; + } + + node_names[gn->get_name()] = gn; + } + + HashMap<StringName, Set<StringName>> upper_neighbours; + HashMap<StringName, Pair<int, int>> port_info; + Vector2 origin(FLT_MAX, FLT_MAX); + + float gap_v = 100.0f; + float gap_h = 100.0f; + + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn) { + continue; + } + + if (gn->is_selected()) { + selected_nodes.insert(gn->get_name()); + origin = origin < gn->get_position_offset() ? origin : gn->get_position_offset(); + Set<StringName> s; + for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { + GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); + if (E->get().to == gn->get_name() && p_from->is_selected()) { + if (!s.has(p_from->get_name())) { + s.insert(p_from->get_name()); + } + String s_connection = String(p_from->get_name()) + " " + String(E->get().to); + StringName _connection(s_connection); + Pair<int, int> ports(E->get().from_port, E->get().to_port); + if (port_info.has(_connection)) { + Pair<int, int> p_ports = port_info[_connection]; + if (p_ports.first < ports.first) { + ports = p_ports; + } + } + port_info.set(_connection, ports); + } + } + upper_neighbours.set(gn->get_name(), s); + } + } + + HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours); + _crossing_minimisation(layers, upper_neighbours); + + Dictionary root, align, sink, shift; + _horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes); + + HashMap<StringName, Vector2> new_positions; + Vector2 default_position(FLT_MAX, FLT_MAX); + Dictionary inner_shift; + Set<StringName> block_heads; + + for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { + inner_shift[E->get()] = 0.0f; + sink[E->get()] = E->get(); + shift[E->get()] = FLT_MAX; + new_positions.set(E->get(), default_position); + if ((StringName)root[E->get()] == E->get()) { + block_heads.insert(E->get()); + } + } + + _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info); + + for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { + _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); + } + + for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { + StringName u = E->get(); + StringName prev = u; + float start_from = origin.y + new_positions[E->get()].y; + do { + Vector2 cal_pos; + cal_pos.y = start_from + (real_t)inner_shift[u]; + new_positions.set(u, cal_pos); + prev = u; + u = align[u]; + } while (u != E->get()); + } + + //Compute horizontal co-ordinates individually for layers to get uniform gap + float start_from = origin.x; + float largest_node_size = 0.0f; + + for (unsigned int i = 0; i < layers.size(); i++) { + Vector<StringName> layer = layers[i]; + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + largest_node_size = MAX(largest_node_size, current_node_size); + } + + for (int j = 0; j < layer.size(); j++) { + float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x; + Vector2 cal_pos = new_positions[layer[j]]; + + if (current_node_size == largest_node_size) { + cal_pos.x = start_from; + } else { + float current_node_start_pos; + if (current_node_size >= largest_node_size / 2) { + current_node_start_pos = start_from; + } else { + current_node_start_pos = start_from + largest_node_size - current_node_size; + } + cal_pos.x = current_node_start_pos; + } + new_positions.set(layer[j], cal_pos); + } + + start_from += largest_node_size + gap_h; + largest_node_size = 0.0f; + } + + emit_signal("begin_node_move"); + for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { + GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); + gn->set_drag(true); + Vector2 pos = (new_positions[E->get()]); + + if (is_using_snap()) { + const int snap = get_snap(); + pos = pos.snapped(Vector2(snap, snap)); + } + gn->set_position_offset(pos); + gn->set_drag(false); + } + emit_signal("end_node_move"); + arranging_graph = false; +} + void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node); ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected); @@ -1707,6 +2202,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); + ClassDB::bind_method(D_METHOD("arrange_nodes"), &GraphEdit::arrange_nodes); + ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); @@ -1851,6 +2348,13 @@ GraphEdit::GraphEdit() { minimap_button->set_focus_mode(FOCUS_NONE); zoom_hb->add_child(minimap_button); + layout_button = memnew(Button); + layout_button->set_flat(true); + zoom_hb->add_child(layout_button); + layout_button->set_tooltip(RTR("Arrange nodes.")); + layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); + layout_button->set_focus_mode(FOCUS_NONE); + Vector2 minimap_size = Vector2(240, 160); float minimap_opacity = 0.65; diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 5251de1722..aeb35f2e02 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -116,6 +116,8 @@ private: Button *minimap_button; + Button *layout_button; + HScrollBar *h_scroll; VScrollBar *v_scroll; @@ -230,6 +232,24 @@ private: bool _check_clickable_control(Control *p_control, const Vector2 &pos); + bool arranging_graph = false; + + enum SET_OPERATIONS { + IS_EQUAL, + IS_SUBSET, + DIFFERENCE, + UNION, + }; + + int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v); + HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); + Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings); + void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes); + void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); + void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); + float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); + void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); + protected: static void _bind_methods(); virtual void add_child_notify(Node *p_child) override; @@ -304,6 +324,8 @@ public: HBoxContainer *get_zoom_hbox(); + void arrange_nodes(); + GraphEdit(); }; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 1603989243..50db1fc3ce 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -95,7 +95,6 @@ void Label::_shape() { 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]); @@ -116,53 +115,19 @@ void Label::_shape() { 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); - } + Vector<Vector2i> line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + for (int i = 0; i < line_breaks.size(); i++) { + RID line = TS->shaped_text_substr(text_rid, line_breaks[i].x, line_breaks[i].y - line_breaks[i].x); 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_mode == AUTOWRAP_OFF) { minsize.width = 0.0f; for (int i = 0; i < lines_rid.size(); i++) { @@ -173,19 +138,59 @@ void Label::_shape() { } if (lines_dirty) { + uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + 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; + } + // Fill after min_size calculation. - if (align == ALIGN_FILL) { + + if (autowrap_mode != 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 |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + } + if (align == ALIGN_FILL) { + for (int i = 0; i < lines_rid.size(); i++) { + if (i < visible_lines - 1 || lines_rid.size() == 1) { + TS->shaped_text_fit_to_width(lines_rid[i], width); + } else if (i == (visible_lines - 1)) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + } + + } else if (lines_hidden) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + + } else { + // Autowrap disabled. for (int i = 0; i < lines_rid.size(); i++) { - if (overrun_behavior != OVERRUN_NO_TRIMMING && autowrap_mode == AUTOWRAP_OFF) { - float line_unaltered_width = TS->shaped_text_get_width(lines_rid[i]); + if (align == ALIGN_FILL) { 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); - } + overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + TS->shaped_text_fit_to_width(lines_rid[i], width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } else { - TS->shaped_text_fit_to_width(lines_rid[i], width); + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); } } } @@ -219,11 +224,30 @@ void Label::_update_visible() { } } +inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { + if (p_gl.font_rid != RID()) { + if (p_font_shadow_color.a > 0) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); + if (p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), p_gl.index, p_font_shadow_color); + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); + } + } + if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); + } + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } else { + TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } +} + void Label::_notification(int p_what) { if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { String new_text = atr(text); if (new_text == xl_text) { - return; //nothing new + return; // Nothing new. } xl_text = new_text; dirty = true; @@ -253,7 +277,7 @@ void Label::_notification(int p_what) { Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); - bool rtl = is_layout_rtl(); + bool rtl = TS->shaped_text_get_direction(text_rid); style->draw(ci, Rect2(Point2(0, 0), get_size())); @@ -286,7 +310,7 @@ void Label::_notification(int p_what) { if (lines_visible > 0) { switch (valign) { case VALIGN_TOP: { - //nothing + // Nothing. } break; case VALIGN_CENTER: { vbegin = (size.y - (total_h - line_spacing)) / 2; @@ -331,83 +355,84 @@ void Label::_notification(int p_what) { Vector2 ofs; ofs.y = style->get_offset().y + vbegin; for (int i = lines_skipped; i < last_line; i++) { + Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); ofs.x = 0; ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(Font::SPACING_TOP); switch (align) { case ALIGN_FILL: - case ALIGN_LEFT: { - if (rtl) { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); + if (rtl && autowrap_mode != AUTOWRAP_OFF) { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); } else { ofs.x = style->get_offset().x; } + break; + case ALIGN_LEFT: { + ofs.x = style->get_offset().x; } break; case ALIGN_CENTER: { - ofs.x = int(size.width - TS->shaped_text_get_size(lines_rid[i]).x) / 2; + ofs.x = int(size.width - line_size.width) / 2; } break; case ALIGN_RIGHT: { - if (rtl) { - ofs.x = style->get_offset().x; - } else { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); - } + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); } break; } const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(lines_rid[i]); const TextServer::Glyph *glyphs = visual.ptr(); int gl_size = visual.size(); + TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]); + + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } - float x = ofs.x; - int outlines_drawn = glyhps_drawn; + // Draw main text. for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { - if (glyphs[j].font_rid != RID()) { - if (font_shadow_color.a > 0) { - TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + shadow_ofs, glyphs[j].index, font_shadow_color); - if (shadow_outline_size > 0) { - //draw shadow - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), glyphs[j].index, font_shadow_color); - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_shadow_color); - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_shadow_color); + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; } } - if (font_outline_color.a != 0.0 && outline_size > 0) { - TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_outline_color); - } } - ofs.x += glyphs[j].advance; - } - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - outlines_drawn++; - if (outlines_drawn >= visible_glyphs) { - break; + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } } } - } - } - ofs.x = x; - for (int j = 0; j < gl_size; j++) { - for (int k = 0; k < glyphs[j].repeat; k++) { - if (glyphs[j].font_rid != RID()) { - TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); - } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); - } + // Draw glyph outlines and shadow. + draw_glyph(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); ofs.x += glyphs[j].advance; + glyhps_drawn++; } - if (visible_glyphs != -1) { - if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - glyhps_drawn++; - if (glyhps_drawn >= visible_glyphs) { - return; - } + } + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } } - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(Font::SPACING_BOTTOM); } } @@ -624,6 +649,9 @@ void Label::set_visible_characters(int p_amount) { } else { percent_visible = 1.0; } + if (p_amount == -1) { + lines_dirty = true; + } update(); } @@ -635,7 +663,7 @@ void Label::set_percent_visible(float p_percent) { if (p_percent < 0 || p_percent >= 1) { visible_chars = -1; percent_visible = 1; - + lines_dirty = true; } else { visible_chars = get_total_character_count() * p_percent; percent_visible = p_percent; @@ -793,7 +821,7 @@ void Label::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign"); 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::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"); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index efc37709a5..ceb4c0faa3 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -955,6 +955,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("more", "GraphEdit", make_icon(icon_zoom_more_png)); theme->set_icon("snap", "GraphEdit", make_icon(icon_snap_grid_png)); theme->set_icon("minimap", "GraphEdit", make_icon(icon_grid_minimap_png)); + theme->set_icon("layout", "GraphEdit", make_icon(icon_grid_layout_png)); theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5)); theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05)); theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2)); diff --git a/scene/resources/default_theme/icon_grid_layout.png b/scene/resources/default_theme/icon_grid_layout.png Binary files differnew file mode 100644 index 0000000000..00a6179d5e --- /dev/null +++ b/scene/resources/default_theme/icon_grid_layout.png diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index 97e64e4746..865ee86c76 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -182,6 +182,10 @@ static const unsigned char icon_grid_minimap_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +static const unsigned char icon_grid_layout_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +}; + static const unsigned char icon_parent_folder_png[] = { 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x68, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x33, 0xb8, 0x27, 0xfe, 0xe0, 0xfc, 0x83, 0x73, 0xf7, 0xc4, 0x71, 0x48, 0xdf, 0x11, 0x7b, 0x78, 0xe9, 0xc1, 0x3f, 0x20, 0xbc, 0xfe, 0x40, 0x12, 0x8f, 0x34, 0x4c, 0x9, 0xa6, 0xe1, 0x57, 0x80, 0x12, 0x17, 0x81, 0xf8, 0x2f, 0x58, 0xe1, 0x15, 0x34, 0x8b, 0x1e, 0x9c, 0x5, 0xa, 0x5e, 0xb8, 0x23, 0x6, 0x52, 0x70, 0x5b, 0x14, 0xac, 0xf0, 0xc, 0xaa, 0x82, 0x7d, 0xf, 0x8e, 0xde, 0x14, 0xf9, 0xcf, 0x8, 0x52, 0xc0, 0xc0, 0x70, 0x5b, 0xf4, 0xe1, 0xc9, 0x7, 0x47, 0xb1, 0xb8, 0x3, 0xaa, 0x0, 0xa, 0x48, 0x52, 0x80, 0xb0, 0xea, 0xc8, 0xc3, 0x83, 0xc, 0x83, 0xe, 0x0, 0x0, 0xb8, 0x27, 0x55, 0x4c, 0xbe, 0xc0, 0xd2, 0xac, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp index 87371224e0..7d5868e3a6 100644 --- a/scene/resources/style_box.cpp +++ b/scene/resources/style_box.cpp @@ -87,9 +87,6 @@ void StyleBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_margin", "margin", "offset"), &StyleBox::set_default_margin); ClassDB::bind_method(D_METHOD("get_default_margin", "margin"), &StyleBox::get_default_margin); - //ClassDB::bind_method(D_METHOD("set_default_margin"),&StyleBox::set_default_margin); - //ClassDB::bind_method(D_METHOD("get_default_margin"),&StyleBox::get_default_margin); - ClassDB::bind_method(D_METHOD("get_margin", "margin"), &StyleBox::get_margin); ClassDB::bind_method(D_METHOD("get_minimum_size"), &StyleBox::get_minimum_size); ClassDB::bind_method(D_METHOD("get_center_size"), &StyleBox::get_center_size); @@ -464,12 +461,12 @@ bool StyleBoxFlat::is_anti_aliased() const { return anti_aliased; } -void StyleBoxFlat::set_aa_size(const int &p_aa_size) { - aa_size = CLAMP(p_aa_size, 1, 5); +void StyleBoxFlat::set_aa_size(const real_t &p_aa_size) { + aa_size = CLAMP(p_aa_size, 0.01, 10); emit_changed(); } -int StyleBoxFlat::get_aa_size() const { +float StyleBoxFlat::get_aa_size() const { return aa_size; } @@ -486,31 +483,32 @@ Size2 StyleBoxFlat::get_center_size() const { return Size2(); } -inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const int corner_radius[4], int *inner_corner_radius) { - int border_left = inner_rect.position.x - style_rect.position.x; - int border_top = inner_rect.position.y - style_rect.position.y; - int border_right = style_rect.size.width - inner_rect.size.width - border_left; - int border_bottom = style_rect.size.height - inner_rect.size.height - border_top; +inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_rect, const real_t corner_radius[4], real_t *inner_corner_radius) { + real_t border_left = inner_rect.position.x - style_rect.position.x; + real_t border_top = inner_rect.position.y - style_rect.position.y; + real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; + real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; + + real_t rad; - int rad; - //tl + // Top left. rad = MIN(border_top, border_left); inner_corner_radius[0] = MAX(corner_radius[0] - rad, 0); - //tr + // Top right; rad = MIN(border_top, border_right); inner_corner_radius[1] = MAX(corner_radius[1] - rad, 0); - //br + // Bottom right. rad = MIN(border_bottom, border_right); inner_corner_radius[2] = MAX(corner_radius[2] - rad, 0); - //bl + // Bottom left. rad = MIN(border_bottom, border_left); inner_corner_radius[3] = MAX(corner_radius[3] - rad, 0); } -inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const int corner_radius[4], +inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color> &colors, const Rect2 &style_rect, const real_t corner_radius[4], const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const bool fill_center = false) { int vert_offset = verts.size(); if (!vert_offset) { @@ -519,17 +517,17 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color int adapted_corner_detail = (corner_radius[0] == 0 && corner_radius[1] == 0 && corner_radius[2] == 0 && corner_radius[3] == 0) ? 1 : corner_detail; - int ring_corner_radius[4]; + real_t ring_corner_radius[4]; set_inner_corner_radius(style_rect, ring_rect, corner_radius, ring_corner_radius); - //corner radius center points + // Corner radius center points. Vector<Point2> outer_points; outer_points.push_back(ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0])); //tl outer_points.push_back(Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1], ring_rect.position.y + ring_corner_radius[1])); //tr outer_points.push_back(ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2])); //br outer_points.push_back(Point2(ring_rect.position.x + ring_corner_radius[3], ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3])); //bl - int inner_corner_radius[4]; + real_t inner_corner_radius[4]; set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); Vector<Point2> inner_points; @@ -538,11 +536,11 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color inner_points.push_back(inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2])); //br inner_points.push_back(Point2(inner_rect.position.x + inner_corner_radius[3], inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3])); //bl - //calculate the vert array + // Calculate the vertices. for (int corner_index = 0; corner_index < 4; corner_index++) { for (int detail = 0; detail <= adapted_corner_detail; detail++) { for (int inner_outer = 0; inner_outer < 2; inner_outer++) { - float radius; + real_t radius; Color color; Point2 corner_point; if (inner_outer == 0) { @@ -564,7 +562,7 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color int ring_vert_count = verts.size() - vert_offset; - //fill the indices and the colors for the border + // Fill the indices and the colors for the border. for (int i = 0; i < ring_vert_count; i++) { indices.push_back(vert_offset + ((i + 0) % ring_vert_count)); indices.push_back(vert_offset + ((i + 2) % ring_vert_count)); @@ -572,14 +570,14 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color } if (fill_center) { - //fill the indices and the colors for the center + //Fill the indices and the colors for the center. for (int index = 0; index < ring_vert_count / 2; index += 2) { int i = index; - //poly 1 + // Polygon 1. indices.push_back(vert_offset + i); indices.push_back(vert_offset + ring_vert_count - 4 - i); indices.push_back(vert_offset + i + 2); - //poly 2 + // Polygon 2. indices.push_back(vert_offset + i); indices.push_back(vert_offset + ring_vert_count - 2 - i); indices.push_back(vert_offset + ring_vert_count - 4 - i); @@ -587,20 +585,20 @@ inline void draw_ring(Vector<Vector2> &verts, Vector<int> &indices, Vector<Color } } -inline void adapt_values(int p_index_a, int p_index_b, int *adapted_values, const int *p_values, const real_t p_width, const int p_max_a, const int p_max_b) { +inline void adapt_values(int p_index_a, int p_index_b, real_t *adapted_values, const real_t *p_values, const real_t p_width, const real_t p_max_a, const real_t p_max_b) { if (p_values[p_index_a] + p_values[p_index_b] > p_width) { - float factor; - int newValue; + real_t factor; + real_t new_value; - factor = (float)p_width / (float)(p_values[p_index_a] + p_values[p_index_b]); + factor = (real_t)p_width / (real_t)(p_values[p_index_a] + p_values[p_index_b]); - newValue = (int)(p_values[p_index_a] * factor); - if (newValue < adapted_values[p_index_a]) { - adapted_values[p_index_a] = newValue; + new_value = (p_values[p_index_a] * factor); + if (new_value < adapted_values[p_index_a]) { + adapted_values[p_index_a] = new_value; } - newValue = (int)(p_values[p_index_b] * factor); - if (newValue < adapted_values[p_index_b]) { - adapted_values[p_index_b] = newValue; + new_value = (p_values[p_index_b] * factor); + if (new_value < adapted_values[p_index_b]) { + adapted_values[p_index_b] = new_value; } } else { adapted_values[p_index_a] = MIN(p_values[p_index_a], adapted_values[p_index_a]); @@ -623,7 +621,6 @@ Rect2 StyleBoxFlat::get_draw_rect(const Rect2 &p_rect) const { } void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { - //PREPARATIONS bool draw_border = (border_width[0] > 0) || (border_width[1] > 0) || (border_width[2] > 0) || (border_width[3] > 0); bool draw_shadow = (shadow_size > 0); if (!draw_border && !draw_center && !draw_shadow) { @@ -637,7 +634,6 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { bool rounded_corners = (corner_radius[0] > 0) || (corner_radius[1] > 0) || (corner_radius[2] > 0) || (corner_radius[3] > 0); bool aa_on = rounded_corners && anti_aliased; - float aa_size_grow = 0.5 * ((float)aa_size + 1.0); bool blend_on = blend_border && draw_border; @@ -645,15 +641,15 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Color border_color_blend = (draw_center ? bg_color : border_color_alpha); Color border_color_inner = blend_on ? border_color_blend : border_color; - //adapt borders (prevent weird overlapping/glitchy drawings) - int width = MAX(style_rect.size.width, 0); - int height = MAX(style_rect.size.height, 0); - int adapted_border[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX }; + // Adapt borders (prevent weird overlapping/glitchy drawings). + real_t width = MAX(style_rect.size.width, 0); + real_t height = MAX(style_rect.size.height, 0); + real_t adapted_border[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; adapt_values(SIDE_TOP, SIDE_BOTTOM, adapted_border, border_width, height, height, height); adapt_values(SIDE_LEFT, SIDE_RIGHT, adapted_border, border_width, width, width, width); - //adapt corners (prevent weird overlapping/glitchy drawings) - int adapted_corner[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX }; + // Adapt corners (prevent weird overlapping/glitchy drawings). + real_t adapted_corner[4] = { 1000000.0, 1000000.0, 1000000.0, 1000000.0 }; adapt_values(CORNER_TOP_RIGHT, CORNER_BOTTOM_RIGHT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); adapt_values(CORNER_TOP_LEFT, CORNER_BOTTOM_LEFT, adapted_corner, corner_radius, height, height - adapted_border[SIDE_BOTTOM], height - adapted_border[SIDE_TOP]); adapt_values(CORNER_TOP_LEFT, CORNER_TOP_RIGHT, adapted_corner, corner_radius, width, width - adapted_border[SIDE_RIGHT], width - adapted_border[SIDE_LEFT]); @@ -665,7 +661,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { if (aa_on) { for (int i = 0; i < 4; i++) { if (border_width[i] > 0) { - border_style_rect = border_style_rect.grow_side((Side)i, -aa_size_grow); + border_style_rect = border_style_rect.grow_side((Side)i, -aa_size); } } } @@ -675,7 +671,7 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { Vector<Color> colors; Vector<Point2> uvs; - //DRAW SHADOW + // Create shadow if (draw_shadow) { Rect2 shadow_inner_rect = style_rect; shadow_inner_rect.position += shadow_offset; @@ -694,35 +690,35 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { } } - //DRAW border - if (draw_border) { + // Create border (no AA). + if (draw_border && !aa_on) { draw_ring(verts, indices, colors, border_style_rect, adapted_corner, border_style_rect, infill_rect, border_color_inner, border_color, corner_detail); } - //DRAW INFILL + // Create infill (no AA). if (draw_center && (!aa_on || blend_on || !draw_border)) { draw_ring(verts, indices, colors, border_style_rect, adapted_corner, infill_rect, infill_rect, bg_color, bg_color, corner_detail, true); } if (aa_on) { - int aa_border_width[4]; - int aa_fill_width[4]; + real_t aa_border_width[4]; + real_t aa_fill_width[4]; if (draw_border) { for (int i = 0; i < 4; i++) { if (border_width[i] > 0) { - aa_border_width[i] = aa_size_grow; + aa_border_width[i] = aa_size; aa_fill_width[i] = 0; } else { aa_border_width[i] = 0; - aa_fill_width[i] = aa_size_grow; + aa_fill_width[i] = aa_size; } } } else { for (int i = 0; i < 4; i++) { aa_border_width[i] = 0; - aa_fill_width[i] = aa_size_grow; + aa_fill_width[i] = aa_size; } } @@ -731,45 +727,58 @@ void StyleBoxFlat::draw(RID p_canvas_item, const Rect2 &p_rect) const { if (draw_center) { if (!blend_on && draw_border) { - //DRAW INFILL WITHIN BORDER AA + Rect2 infill_inner_rect_aa = infill_inner_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + // Create infill within AA border. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_inner_rect, infill_inner_rect, bg_color, bg_color, corner_detail, true); + infill_inner_rect_aa, infill_inner_rect_aa, bg_color, bg_color, corner_detail, true); } if (!blend_on || !draw_border) { - Rect2 infill_aa_rect = infill_rect.grow_individual(aa_fill_width[SIDE_LEFT], aa_fill_width[SIDE_TOP], + Rect2 infill_rect_aa = infill_rect.grow_individual(aa_fill_width[SIDE_LEFT], aa_fill_width[SIDE_TOP], aa_fill_width[SIDE_RIGHT], aa_fill_width[SIDE_BOTTOM]); Color alpha_bg = Color(bg_color.r, bg_color.g, bg_color.b, 0); - //INFILL AA + // Create infill fake AA gradient. draw_ring(verts, indices, colors, style_rect, adapted_corner, - infill_aa_rect, infill_rect, bg_color, alpha_bg, corner_detail); + infill_rect_aa, infill_rect, bg_color, alpha_bg, corner_detail); } } if (draw_border) { + Rect2 infill_rect_aa = infill_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + Rect2 style_rect_aa = style_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + Rect2 border_style_rect_aa = border_style_rect.grow_individual(aa_border_width[SIDE_LEFT], aa_border_width[SIDE_TOP], + aa_border_width[SIDE_RIGHT], aa_border_width[SIDE_BOTTOM]); + + // Create border. + draw_ring(verts, indices, colors, border_style_rect, adapted_corner, + border_style_rect_aa, ((blend_on) ? infill_rect : infill_rect_aa), border_color_inner, border_color, corner_detail); + if (!blend_on) { - //DRAW INNER BORDER AA + // Create inner border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - infill_rect, infill_inner_rect, border_color_blend, border_color, corner_detail); + infill_rect_aa, infill_rect, border_color_blend, border_color, corner_detail); } - //DRAW OUTER BORDER AA + // Create outer border fake AA gradient. draw_ring(verts, indices, colors, border_style_rect, adapted_corner, - style_rect, border_style_rect, border_color, border_color_alpha, corner_detail); + style_rect_aa, border_style_rect_aa, border_color, border_color_alpha, corner_detail); } } - //COMPUTE UV COORDINATES - Rect2 uv_rect = style_rect.grow(aa_on ? aa_size_grow : 0); + // Compute UV coordinates. + Rect2 uv_rect = style_rect.grow(aa_on ? aa_size : 0); uvs.resize(verts.size()); for (int i = 0; i < verts.size(); i++) { uvs.write[i].x = (verts[i].x - uv_rect.position.x) / uv_rect.size.width; uvs.write[i].y = (verts[i].y - uv_rect.position.y) / uv_rect.size.height; } - //DRAWING + // Draw stylebox. RenderingServer *vs = RenderingServer::get_singleton(); vs->canvas_item_add_triangle_array(p_canvas_item, indices, verts, colors, uvs); } @@ -869,7 +878,7 @@ void StyleBoxFlat::_bind_methods() { ADD_GROUP("Anti Aliasing", "anti_aliasing_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "anti_aliasing"), "set_anti_aliased", "is_anti_aliased"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "1,5,1"), "set_aa_size", "get_aa_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "anti_aliasing_size", PROPERTY_HINT_RANGE, "0.01,10,0.001"), "set_aa_size", "get_aa_size"); } StyleBoxFlat::StyleBoxFlat() {} diff --git a/scene/resources/style_box.h b/scene/resources/style_box.h index dd5c873a00..124521b915 100644 --- a/scene/resources/style_box.h +++ b/scene/resources/style_box.h @@ -143,9 +143,9 @@ class StyleBoxFlat : public StyleBox { Color shadow_color = Color(0, 0, 0, 0.6); Color border_color = Color(0.8, 0.8, 0.8); - int border_width[4] = {}; - int expand_margin[4] = {}; - int corner_radius[4] = {}; + real_t border_width[4] = {}; + real_t expand_margin[4] = {}; + real_t corner_radius[4] = {}; bool draw_center = true; bool blend_border = false; @@ -154,7 +154,7 @@ class StyleBoxFlat : public StyleBox { int corner_detail = 8; int shadow_size = 0; Point2 shadow_offset; - int aa_size = 1; + real_t aa_size = 0.625; protected: virtual float get_style_margin(Side p_side) const override; @@ -162,27 +162,21 @@ protected: void _validate_property(PropertyInfo &property) const override; public: - //Color void set_bg_color(const Color &p_color); Color get_bg_color() const; - //Border Color void set_border_color(const Color &p_color); Color get_border_color() const; - //BORDER - //width void set_border_width_all(int p_size); int get_border_width_min() const; void set_border_width(Side p_side, int p_width); int get_border_width(Side p_side) const; - //blend void set_border_blend(bool p_blend); bool get_border_blend() const; - //CORNER void set_corner_radius_all(int radius); void set_corner_radius_individual(const int radius_top_left, const int radius_top_right, const int radius_bottom_right, const int radius_bottom_left); @@ -192,17 +186,14 @@ public: void set_corner_detail(const int &p_corner_detail); int get_corner_detail() const; - //EXPANDS void set_expand_margin_size(Side p_expand_side, float p_size); void set_expand_margin_size_all(float p_expand_margin_size); void set_expand_margin_size_individual(float p_left, float p_top, float p_right, float p_bottom); float get_expand_margin_size(Side p_expand_side) const; - //DRAW CENTER void set_draw_center(bool p_enabled); bool is_draw_center_enabled() const; - //SHADOW void set_shadow_color(const Color &p_color); Color get_shadow_color() const; @@ -212,12 +203,10 @@ public: void set_shadow_offset(const Point2 &p_offset); Point2 get_shadow_offset() const; - //ANTI_ALIASING void set_anti_aliased(const bool &p_anti_aliased); bool is_anti_aliased() const; - //tempAA - void set_aa_size(const int &p_aa_size); - int get_aa_size() const; + void set_aa_size(const float &p_aa_size); + float get_aa_size() const; virtual Size2 get_center_size() const override; @@ -228,7 +217,7 @@ public: ~StyleBoxFlat(); }; -// just used to draw lines. +// Just used to draw lines. class StyleBoxLine : public StyleBox { GDCLASS(StyleBoxLine, StyleBox); Color color; diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 5e60c7c4ed..0807a062f2 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -76,6 +76,11 @@ void TextLine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab"), "set_flags", "get_flags"); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextLine::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextLine::get_text_overrun_behavior); + + 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"); + ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects); ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect); @@ -93,6 +98,12 @@ void TextLine::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_outline", "canvas", "pos", "outline_size", "color"), &TextLine::draw_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("hit_test", "coords"), &TextLine::hit_test); + + 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); } void TextLine::_shape() { @@ -100,7 +111,38 @@ void TextLine::_shape() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(rid, tab_stops); } - if (align == HALIGN_FILL) { + + uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + if (overrun_behavior != OVERRUN_NO_TRIMMING) { + 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 (align == HALIGN_FILL) { + TS->shaped_text_fit_to_width(rid, width, flags); + overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); + } else { + TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); + } + } else if (align == HALIGN_FILL) { TS->shaped_text_fit_to_width(rid, width, flags); } dirty = false; @@ -225,9 +267,20 @@ uint8_t TextLine::get_flags() const { return flags; } +void TextLine::set_text_overrun_behavior(TextLine::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + dirty = true; + } +} + +TextLine::OverrunBehavior TextLine::get_text_overrun_behavior() const { + return overrun_behavior; +} + void TextLine::set_width(float p_width) { width = p_width; - if (align == HALIGN_FILL) { + if (align == HALIGN_FILL || overrun_behavior != OVERRUN_NO_TRIMMING) { dirty = true; } } diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h index b773bd05be..9ed9c2f177 100644 --- a/scene/resources/text_line.h +++ b/scene/resources/text_line.h @@ -39,6 +39,16 @@ class TextLine : public RefCounted { GDCLASS(TextLine, RefCounted); +public: + enum OverrunBehavior { + OVERRUN_NO_TRIMMING, + OVERRUN_TRIM_CHAR, + OVERRUN_TRIM_WORD, + OVERRUN_TRIM_ELLIPSIS, + OVERRUN_TRIM_WORD_ELLIPSIS, + }; + +private: RID rid; int spacing_top = 0; int spacing_bottom = 0; @@ -48,6 +58,7 @@ class TextLine : public RefCounted { float width = -1.0; uint8_t flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; HAlign align = HALIGN_LEFT; + OverrunBehavior overrun_behavior = OVERRUN_TRIM_ELLIPSIS; Vector<float> tab_stops; @@ -87,6 +98,9 @@ public: void set_flags(uint8_t p_flags); uint8_t get_flags() const; + void set_text_overrun_behavior(OverrunBehavior p_behavior); + OverrunBehavior get_text_overrun_behavior() const; + void set_width(float p_width); float get_width() const; @@ -113,4 +127,6 @@ public: ~TextLine(); }; +VARIANT_ENUM_CAST(TextLine::OverrunBehavior); + #endif // TEXT_LINE_H diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index ffae551570..357411ae04 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -74,6 +74,11 @@ void TextParagraph::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "flags", PROPERTY_HINT_FLAGS, "Kashida Justify,Word Justify,Trim Edge Spaces After Justify,Justify Only After Last Tab,Break Mandatory,Break Words,Break Graphemes"), "set_flags", "get_flags"); + ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextParagraph::set_text_overrun_behavior); + ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextParagraph::get_text_overrun_behavior); + + 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"); + ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width); ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width); @@ -88,6 +93,11 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &TextParagraph::get_line_count); + ClassDB::bind_method(D_METHOD("set_max_lines_visible", "max_lines_visible"), &TextParagraph::set_max_lines_visible); + ClassDB::bind_method(D_METHOD("get_max_lines_visible"), &TextParagraph::get_max_lines_visible); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible"), "set_max_lines_visible", "get_max_lines_visible"); + ClassDB::bind_method(D_METHOD("get_line_objects", "line"), &TextParagraph::get_line_objects); ClassDB::bind_method(D_METHOD("get_line_object_rect", "line", "key"), &TextParagraph::get_line_object_rect); ClassDB::bind_method(D_METHOD("get_line_size", "line"), &TextParagraph::get_line_size); @@ -114,14 +124,20 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_dropcap_outline", "canvas", "pos", "outline_size", "color"), &TextParagraph::draw_dropcap_outline, DEFVAL(1), DEFVAL(Color(1, 1, 1))); ClassDB::bind_method(D_METHOD("hit_test", "coords"), &TextParagraph::hit_test); + + 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); } void TextParagraph::_shape_lines() { - if (dirty_lines) { - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + if (lines_dirty) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(rid, tab_stops); @@ -153,13 +169,10 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - if (align == HALIGN_FILL && (line_breaks.size() == 1 || i < line_breaks.size() - 1)) { - TS->shaped_text_fit_to_width(line, width - h_offset, flags); - } dropcap_lines++; v_offset -= h; start = line_breaks[i].y; - lines.push_back(line); + lines_rid.push_back(line); } } // Use fixed for the rest of lines. @@ -169,12 +182,69 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - if (align == HALIGN_FILL && (line_breaks.size() == 1 || i < line_breaks.size() - 1)) { - TS->shaped_text_fit_to_width(line, width, flags); + lines_rid.push_back(line); + } + + uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING; + if (overrun_behavior != OVERRUN_NO_TRIMMING) { + 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; + } + } + + bool autowrap_enabled = ((flags & TextServer::BREAK_WORD_BOUND) == TextServer::BREAK_WORD_BOUND) || ((flags & TextServer::BREAK_GRAPHEME_BOUND) == TextServer::BREAK_GRAPHEME_BOUND); + + // Fill after min_size calculation. + if (autowrap_enabled) { + int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size(); + if (lines_hidden) { + overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + } + if (align == HALIGN_FILL) { + for (int i = 0; i < lines_rid.size(); i++) { + if (i < visible_lines - 1 || lines_rid.size() == 1) { + TS->shaped_text_fit_to_width(lines_rid[i], width, flags); + } else if (i == (visible_lines - 1)) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + } + + } else if (lines_hidden) { + TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + } + + } else { + // Autowrap disabled. + for (int i = 0; i < lines_rid.size(); i++) { + if (align == HALIGN_FILL) { + TS->shaped_text_fit_to_width(lines_rid[i], width, flags); + overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + TS->shaped_text_fit_to_width(lines_rid[i], width, flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); + } else { + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + } } - lines.push_back(line); } - dirty_lines = false; + lines_dirty = false; } } @@ -184,8 +254,8 @@ RID TextParagraph::get_rid() const { RID TextParagraph::get_line_rid(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), RID()); - return lines[p_line]; + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), RID()); + return lines_rid[p_line]; } RID TextParagraph::get_dropcap_rid() const { @@ -195,10 +265,10 @@ RID TextParagraph::get_dropcap_rid() const { void TextParagraph::clear() { spacing_top = 0; spacing_bottom = 0; - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); TS->shaped_text_clear(rid); TS->shaped_text_clear(dropcap_rid); } @@ -206,7 +276,7 @@ void TextParagraph::clear() { void TextParagraph::set_preserve_invalid(bool p_enabled) { TS->shaped_text_set_preserve_invalid(rid, p_enabled); TS->shaped_text_set_preserve_invalid(dropcap_rid, p_enabled); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::get_preserve_invalid() const { @@ -216,7 +286,7 @@ bool TextParagraph::get_preserve_invalid() const { void TextParagraph::set_preserve_control(bool p_enabled) { TS->shaped_text_set_preserve_control(rid, p_enabled); TS->shaped_text_set_preserve_control(dropcap_rid, p_enabled); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::get_preserve_control() const { @@ -226,7 +296,7 @@ bool TextParagraph::get_preserve_control() const { void TextParagraph::set_direction(TextServer::Direction p_direction) { TS->shaped_text_set_direction(rid, p_direction); TS->shaped_text_set_direction(dropcap_rid, p_direction); - dirty_lines = true; + lines_dirty = true; } TextServer::Direction TextParagraph::get_direction() const { @@ -237,7 +307,7 @@ TextServer::Direction TextParagraph::get_direction() const { void TextParagraph::set_orientation(TextServer::Orientation p_orientation) { TS->shaped_text_set_orientation(rid, p_orientation); TS->shaped_text_set_orientation(dropcap_rid, p_orientation); - dirty_lines = true; + lines_dirty = true; } TextServer::Orientation TextParagraph::get_orientation() const { @@ -250,14 +320,14 @@ bool TextParagraph::set_dropcap(const String &p_text, const Ref<Font> &p_fonts, TS->shaped_text_clear(dropcap_rid); dropcap_margins = p_dropcap_margins; bool res = TS->shaped_text_add_string(dropcap_rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); - dirty_lines = true; + lines_dirty = true; return res; } void TextParagraph::clear_dropcap() { dropcap_margins = Rect2(); TS->shaped_text_clear(dropcap_rid); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) { @@ -265,7 +335,7 @@ bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, i bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language); spacing_top = p_fonts->get_spacing(Font::SPACING_TOP); spacing_bottom = p_fonts->get_spacing(Font::SPACING_BOTTOM); - dirty_lines = true; + lines_dirty = true; return res; } @@ -287,18 +357,18 @@ void TextParagraph::_set_bidi_override(const Array &p_override) { void TextParagraph::set_bidi_override(const Vector<Vector2i> &p_override) { TS->shaped_text_set_bidi_override(rid, p_override); - dirty_lines = true; + lines_dirty = true; } bool TextParagraph::add_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align, int p_length) { bool res = TS->shaped_text_add_object(rid, p_key, p_size, p_inline_align, p_length); - dirty_lines = true; + lines_dirty = true; return res; } bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlign p_inline_align) { bool res = TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align); - dirty_lines = true; + lines_dirty = true; return res; } @@ -306,7 +376,7 @@ void TextParagraph::set_align(HAlign p_align) { if (align != p_align) { if (align == HALIGN_FILL || p_align == HALIGN_FILL) { align = p_align; - dirty_lines = true; + lines_dirty = true; } else { align = p_align; } @@ -319,13 +389,13 @@ HAlign TextParagraph::get_align() const { void TextParagraph::tab_align(const Vector<float> &p_tab_stops) { tab_stops = p_tab_stops; - dirty_lines = true; + lines_dirty = true; } void TextParagraph::set_flags(uint8_t p_flags) { if (flags != p_flags) { flags = p_flags; - dirty_lines = true; + lines_dirty = true; } } @@ -333,10 +403,21 @@ uint8_t TextParagraph::get_flags() const { return flags; } +void TextParagraph::set_text_overrun_behavior(TextParagraph::OverrunBehavior p_behavior) { + if (overrun_behavior != p_behavior) { + overrun_behavior = p_behavior; + lines_dirty = true; + } +} + +TextParagraph::OverrunBehavior TextParagraph::get_text_overrun_behavior() const { + return overrun_behavior; +} + void TextParagraph::set_width(float p_width) { if (width != p_width) { width = p_width; - dirty_lines = true; + lines_dirty = true; } } @@ -356,9 +437,9 @@ Size2 TextParagraph::get_non_wraped_size() const { Size2 TextParagraph::get_size() const { const_cast<TextParagraph *>(this)->_shape_lines(); Size2 size; - for (int i = 0; i < lines.size(); i++) { - Size2 lsize = TS->shaped_text_get_size(lines[i]); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + for (int i = 0; i < lines_rid.size(); i++) { + Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { size.x = MAX(size.x, lsize.x); size.y += lsize.y + spacing_top + spacing_bottom; } else { @@ -371,22 +452,33 @@ Size2 TextParagraph::get_size() const { int TextParagraph::get_line_count() const { const_cast<TextParagraph *>(this)->_shape_lines(); - return lines.size(); + return lines_rid.size(); +} + +void TextParagraph::set_max_lines_visible(int p_lines) { + if (p_lines != max_lines_visible) { + max_lines_visible = p_lines; + lines_dirty = true; + } +} + +int TextParagraph::get_max_lines_visible() const { + return max_lines_visible; } Array TextParagraph::get_line_objects(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Array()); - return TS->shaped_text_get_objects(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Array()); + return TS->shaped_text_get_objects(lines_rid[p_line]); } Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Rect2()); - Rect2 xrect = TS->shaped_text_get_object_rect(lines[p_line], p_key); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Rect2()); + Rect2 xrect = TS->shaped_text_get_object_rect(lines_rid[p_line], p_key); for (int i = 0; i < p_line; i++) { - Size2 lsize = TS->shaped_text_get_size(lines[i]); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { xrect.position.y += lsize.y + spacing_top + spacing_bottom; } else { xrect.position.x += lsize.x + spacing_top + spacing_bottom; @@ -397,48 +489,48 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { Size2 TextParagraph::get_line_size(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Size2()); - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - return Size2(TS->shaped_text_get_size(lines[p_line]).x, TS->shaped_text_get_size(lines[p_line]).y + spacing_top + spacing_bottom); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Size2()); + if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { + return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x, TS->shaped_text_get_size(lines_rid[p_line]).y + spacing_top + spacing_bottom); } else { - return Size2(TS->shaped_text_get_size(lines[p_line]).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(lines[p_line]).y); + return Size2(TS->shaped_text_get_size(lines_rid[p_line]).x + spacing_top + spacing_bottom, TS->shaped_text_get_size(lines_rid[p_line]).y); } } Vector2i TextParagraph::get_line_range(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), Vector2i()); - return TS->shaped_text_get_range(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), Vector2i()); + return TS->shaped_text_get_range(lines_rid[p_line]); } float TextParagraph::get_line_ascent(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } float TextParagraph::get_line_descent(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_descent(lines[p_line]) + spacing_bottom; + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_descent(lines_rid[p_line]) + spacing_bottom; } float TextParagraph::get_line_width(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_width(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_width(lines_rid[p_line]); } float TextParagraph::get_line_underline_position(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_underline_position(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_underline_position(lines_rid[p_line]); } float TextParagraph::get_line_underline_thickness(int p_line) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND_V(p_line < 0 || p_line >= lines.size(), 0.f); - return TS->shaped_text_get_underline_thickness(lines[p_line]); + ERR_FAIL_COND_V(p_line < 0 || p_line >= lines_rid.size(), 0.f); + return TS->shaped_text_get_underline_thickness(lines_rid[p_line]); } Size2 TextParagraph::get_dropcap_size() const { @@ -472,11 +564,13 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo TS->shaped_text_draw(dropcap_rid, p_canvas, dc_off + Vector2(0, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.size.y + dropcap_margins.position.y / 2), -1, -1, p_dc_color); } - for (int i = 0; i < lines.size(); i++) { + int lines_visible = (max_lines_visible >= 0) ? MIN(max_lines_visible, lines_rid.size()) : lines_rid.size(); + + for (int i = 0; i < lines_visible; i++) { float l_width = width; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_ascent(lines[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -485,7 +579,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo } } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_ascent(lines[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -493,21 +587,29 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines[i]); + float length = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: + if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } + break; case HALIGN_LEFT: break; case HALIGN_CENTER: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += Math::floor((l_width - length) / 2.0); } else { ofs.y += Math::floor((l_width - length) / 2.0); } } break; case HALIGN_RIGHT: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += l_width - length; } else { ofs.y += l_width - length; @@ -516,18 +618,18 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo } } float clip_l; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { clip_l = MAX(0, p_pos.x - ofs.x); } else { clip_l = MAX(0, p_pos.y - ofs.y); } - TS->shaped_text_draw(lines[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + TS->shaped_text_draw(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_color); + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } } } @@ -556,11 +658,11 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli TS->shaped_text_draw_outline(dropcap_rid, p_canvas, dc_off + Vector2(dropcap_margins.position.x, TS->shaped_text_get_ascent(dropcap_rid) + dropcap_margins.position.y), -1, -1, p_outline_size, p_dc_color); } - for (int i = 0; i < lines.size(); i++) { + for (int i = 0; i < lines_rid.size(); i++) { float l_width = width; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_ascent(lines[i]) + spacing_top; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -569,7 +671,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli } } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_ascent(lines[i]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top; if (i <= dropcap_lines) { if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { ofs.x -= h_offset; @@ -577,21 +679,29 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli l_width -= h_offset; } } - float length = TS->shaped_text_get_width(lines[i]); + float length = TS->shaped_text_get_width(lines_rid[i]); if (width > 0) { switch (align) { case HALIGN_FILL: + if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.x += l_width - length; + } else { + ofs.y += l_width - length; + } + } + break; case HALIGN_LEFT: break; case HALIGN_CENTER: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += Math::floor((l_width - length) / 2.0); } else { ofs.y += Math::floor((l_width - length) / 2.0); } } break; case HALIGN_RIGHT: { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x += l_width - length; } else { ofs.y += l_width - length; @@ -600,18 +710,18 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli } } float clip_l; - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { clip_l = MAX(0, p_pos.x - ofs.x); } else { clip_l = MAX(0, p_pos.y - ofs.y); } - TS->shaped_text_draw_outline(lines[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color); - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { + TS->shaped_text_draw_outline(lines_rid[i], p_canvas, ofs, clip_l, clip_l + l_width, p_outline_size, p_color); + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { ofs.x = p_pos.x; - ofs.y += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } else { ofs.y = p_pos.y; - ofs.x += TS->shaped_text_get_descent(lines[i]) + spacing_bottom; + ofs.x += TS->shaped_text_get_descent(lines_rid[i]) + spacing_bottom; } } } @@ -628,17 +738,17 @@ int TextParagraph::hit_test(const Point2 &p_coords) const { return 0; } } - for (int i = 0; i < lines.size(); i++) { - if (TS->shaped_text_get_orientation(lines[i]) == TextServer::ORIENTATION_HORIZONTAL) { - if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(lines[i]).y)) { - return TS->shaped_text_hit_test_position(lines[i], p_coords.x); + for (int i = 0; i < lines_rid.size(); i++) { + if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if ((p_coords.y >= ofs.y) && (p_coords.y <= ofs.y + TS->shaped_text_get_size(lines_rid[i]).y)) { + return TS->shaped_text_hit_test_position(lines_rid[i], p_coords.x); } - ofs.y += TS->shaped_text_get_size(lines[i]).y + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).y + spacing_bottom + spacing_top; } else { - if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(lines[i]).x)) { - return TS->shaped_text_hit_test_position(lines[i], p_coords.y); + if ((p_coords.x >= ofs.x) && (p_coords.x <= ofs.x + TS->shaped_text_get_size(lines_rid[i]).x)) { + return TS->shaped_text_hit_test_position(lines_rid[i], p_coords.y); } - ofs.y += TS->shaped_text_get_size(lines[i]).x + spacing_bottom + spacing_top; + ofs.y += TS->shaped_text_get_size(lines_rid[i]).x + spacing_bottom + spacing_top; } } return TS->shaped_text_get_range(rid).y; @@ -690,29 +800,29 @@ void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int void TextParagraph::draw_line(RID p_canvas, const Vector2 &p_pos, int p_line, const Color &p_color) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); Vector2 ofs = p_pos; - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } else { - ofs.x += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } - return TS->shaped_text_draw(lines[p_line], p_canvas, ofs, -1, -1, p_color); + return TS->shaped_text_draw(lines_rid[p_line], p_canvas, ofs, -1, -1, p_color); } void TextParagraph::draw_line_outline(RID p_canvas, const Vector2 &p_pos, int p_line, int p_outline_size, const Color &p_color) const { const_cast<TextParagraph *>(this)->_shape_lines(); - ERR_FAIL_COND(p_line < 0 || p_line >= lines.size()); + ERR_FAIL_COND(p_line < 0 || p_line >= lines_rid.size()); Vector2 ofs = p_pos; - if (TS->shaped_text_get_orientation(lines[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { - ofs.y += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + if (TS->shaped_text_get_orientation(lines_rid[p_line]) == TextServer::ORIENTATION_HORIZONTAL) { + ofs.y += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } else { - ofs.x += TS->shaped_text_get_ascent(lines[p_line]) + spacing_top; + ofs.x += TS->shaped_text_get_ascent(lines_rid[p_line]) + spacing_top; } - return TS->shaped_text_draw_outline(lines[p_line], p_canvas, ofs, -1, -1, p_outline_size, p_color); + return TS->shaped_text_draw_outline(lines_rid[p_line], p_canvas, ofs, -1, -1, p_outline_size, p_color); } TextParagraph::TextParagraph(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, float p_width, TextServer::Direction p_direction, TextServer::Orientation p_orientation) { @@ -729,10 +839,10 @@ TextParagraph::TextParagraph() { } TextParagraph::~TextParagraph() { - for (int i = 0; i < lines.size(); i++) { - TS->free(lines[i]); + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); } - lines.clear(); + lines_rid.clear(); TS->free(rid); TS->free(dropcap_rid); } diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index d0747a9e03..ee7bbab9c5 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -39,19 +39,33 @@ class TextParagraph : public RefCounted { GDCLASS(TextParagraph, RefCounted); +public: + enum OverrunBehavior { + OVERRUN_NO_TRIMMING, + OVERRUN_TRIM_CHAR, + OVERRUN_TRIM_WORD, + OVERRUN_TRIM_ELLIPSIS, + OVERRUN_TRIM_WORD_ELLIPSIS, + }; + +private: RID dropcap_rid; int dropcap_lines = 0; Rect2 dropcap_margins; RID rid; - Vector<RID> lines; + Vector<RID> lines_rid; int spacing_top = 0; int spacing_bottom = 0; - bool dirty_lines = true; + bool lines_dirty = true; float width = -1.0; + int max_lines_visible = -1; + uint8_t flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; + OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING; + HAlign align = HALIGN_LEFT; Vector<float> tab_stops; @@ -97,9 +111,15 @@ public: void set_flags(uint8_t p_flags); uint8_t get_flags() const; + void set_text_overrun_behavior(OverrunBehavior p_behavior); + OverrunBehavior get_text_overrun_behavior() const; + void set_width(float p_width); float get_width() const; + void set_max_lines_visible(int p_lines); + int get_max_lines_visible() const; + Size2 get_non_wraped_size() const; Size2 get_size() const; @@ -140,4 +160,6 @@ public: ~TextParagraph(); }; +VARIANT_ENUM_CAST(TextParagraph::OverrunBehavior); + #endif // TEXT_PARAGRAPH_H diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index 7039ea2942..5920444f7a 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -208,11 +208,10 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte //normalized blinn float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25; - float blinn = pow(cNdotH, shininess) * cNdotL; - blinn *= (shininess + 8.0) * (1.0 / (8.0 * M_PI)); - float intensity = blinn; + float blinn = pow(cNdotH, shininess); + blinn *= (shininess + 2.0) * (1.0 / (8.0 * M_PI)); - specular_light += light_color * intensity * attenuation * specular_amount; + specular_light += light_color * attenuation * specular_amount * blinn * f0 * unpackUnorm4x8(orms).w; #elif defined(SPECULAR_PHONG) @@ -220,10 +219,9 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, float atte float cRdotV = clamp(A + dot(R, V), 0.0, 1.0); float shininess = exp2(15.0 * (1.0 - roughness) + 1.0) * 0.25; float phong = pow(cRdotV, shininess); - phong *= (shininess + 8.0) * (1.0 / (8.0 * M_PI)); - float intensity = (phong) / max(4.0 * cNdotV * cNdotL, 0.75); + phong *= (shininess + 1.0) * (1.0 / (8.0 * M_PI)); - specular_light += light_color * intensity * attenuation * specular_amount; + specular_light += light_color * attenuation * specular_amount * phong * f0 * unpackUnorm4x8(orms).w; #elif defined(SPECULAR_TOON) diff --git a/servers/text_server.cpp b/servers/text_server.cpp index e32ea9cdea..6bace8cf9e 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -739,6 +739,11 @@ Vector<Vector2i> TextServer::shaped_text_get_word_breaks(RID p_shaped, int p_gra return words; } +TextServer::TrimData TextServer::shaped_text_get_trim_data(RID p_shaped) const { + WARN_PRINT("Getting overrun data not supported by this TextServer."); + return TrimData(); +} + void TextServer::shaped_text_get_carets(RID p_shaped, int p_position, Rect2 &p_leading_caret, Direction &p_leading_dir, Rect2 &p_trailing_caret, Direction &p_trailing_dir) const { Vector<Rect2> carets; const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); @@ -976,7 +981,7 @@ Vector<Vector2> TextServer::shaped_text_get_selection(RID p_shaped, int p_start, ranges.push_back(Vector2(off + char_adv * (glyphs[i].end - start), off + advance)); } } - // Selection range is within grapheme + // Selection range is within grapheme. if (glyphs[i].start < start && glyphs[i].end > end) { float advance = 0.f; for (int j = 0; j < glyphs[i].count; j++) { @@ -1137,10 +1142,26 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped); bool hex_codes = shaped_text_get_preserve_control(p_shaped) || shaped_text_get_preserve_invalid(p_shaped); + bool rtl = shaped_text_get_direction(p_shaped) == DIRECTION_RTL; + TrimData trim_data = shaped_text_get_trim_data(p_shaped); + int v_size = visual.size(); const Glyph *glyphs = visual.ptr(); Vector2 ofs = p_pos; + // Draw RTL ellipsis string when needed. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int i = trim_data.ellipsis_glyph_buf.size() - 1; i >= 0; i--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[i].repeat; j++) { + font_draw_glyph(trim_data.ellipsis_glyph_buf[i].font_rid, p_canvas, trim_data.ellipsis_glyph_buf[i].font_size, ofs + Vector2(trim_data.ellipsis_glyph_buf[i].x_off, trim_data.ellipsis_glyph_buf[i].y_off), trim_data.ellipsis_glyph_buf[i].index, p_color); + if (orientation == ORIENTATION_HORIZONTAL) { + ofs.x += trim_data.ellipsis_glyph_buf[i].advance; + } else { + ofs.y += trim_data.ellipsis_glyph_buf[i].advance; + } + } + } + } // Draw at the baseline. for (int i = 0; i < v_size; i++) { for (int j = 0; j < glyphs[i].repeat; j++) { @@ -1170,6 +1191,18 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p } } } + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (i < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (i >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } + if (glyphs[i].font_rid != RID()) { font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) { @@ -1182,15 +1215,44 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p } } } + // Draw LTR ellipsis string when needed. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int i = 0; i < trim_data.ellipsis_glyph_buf.size(); i++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[i].repeat; j++) { + font_draw_glyph(trim_data.ellipsis_glyph_buf[i].font_rid, p_canvas, trim_data.ellipsis_glyph_buf[i].font_size, ofs + Vector2(trim_data.ellipsis_glyph_buf[i].x_off, trim_data.ellipsis_glyph_buf[i].y_off), trim_data.ellipsis_glyph_buf[i].index, p_color); + if (orientation == ORIENTATION_HORIZONTAL) { + ofs.x += trim_data.ellipsis_glyph_buf[i].advance; + } else { + ofs.y += trim_data.ellipsis_glyph_buf[i].advance; + } + } + } + } } void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, int p_outline_size, const Color &p_color) const { const Vector<TextServer::Glyph> visual = shaped_text_get_glyphs(p_shaped); TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped); + bool rtl = (shaped_text_get_direction(p_shaped) == DIRECTION_RTL); + TrimData trim_data = shaped_text_get_trim_data(p_shaped); + int v_size = visual.size(); const Glyph *glyphs = visual.ptr(); Vector2 ofs = p_pos; + // Draw RTL ellipsis string when needed. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int i = trim_data.ellipsis_glyph_buf.size() - 1; i >= 0; i--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[i].repeat; j++) { + font_draw_glyph(trim_data.ellipsis_glyph_buf[i].font_rid, p_canvas, trim_data.ellipsis_glyph_buf[i].font_size, ofs + Vector2(trim_data.ellipsis_glyph_buf[i].x_off, trim_data.ellipsis_glyph_buf[i].y_off), trim_data.ellipsis_glyph_buf[i].index, p_color); + if (orientation == ORIENTATION_HORIZONTAL) { + ofs.x += trim_data.ellipsis_glyph_buf[i].advance; + } else { + ofs.y += trim_data.ellipsis_glyph_buf[i].advance; + } + } + } + } // Draw at the baseline. for (int i = 0; i < v_size; i++) { for (int j = 0; j < glyphs[i].repeat; j++) { @@ -1220,6 +1282,17 @@ void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vect } } } + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (i < trim_data.trim_pos) { + continue; + } + } else { + if (i >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } if (glyphs[i].font_rid != RID()) { font_draw_glyph_outline(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, p_outline_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); } @@ -1230,6 +1303,19 @@ void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vect } } } + // Draw LTR ellipsis string when needed. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int i = 0; i < trim_data.ellipsis_glyph_buf.size(); i++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[i].repeat; j++) { + font_draw_glyph(trim_data.ellipsis_glyph_buf[i].font_rid, p_canvas, trim_data.ellipsis_glyph_buf[i].font_size, ofs + Vector2(trim_data.ellipsis_glyph_buf[i].x_off, trim_data.ellipsis_glyph_buf[i].y_off), trim_data.ellipsis_glyph_buf[i].index, p_color); + if (orientation == ORIENTATION_HORIZONTAL) { + ofs.x += trim_data.ellipsis_glyph_buf[i].advance; + } else { + ofs.y += trim_data.ellipsis_glyph_buf[i].advance; + } + } + } + } } RID TextServer::_create_font_memory(const PackedByteArray &p_data, const String &p_type, int p_base_size) { diff --git a/servers/text_server.h b/servers/text_server.h index d824504bf4..26993838a3 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -59,7 +59,8 @@ public: JUSTIFICATION_KASHIDA = 1 << 0, JUSTIFICATION_WORD_BOUND = 1 << 1, JUSTIFICATION_TRIM_EDGE_SPACES = 1 << 2, - JUSTIFICATION_AFTER_LAST_TAB = 1 << 3 + JUSTIFICATION_AFTER_LAST_TAB = 1 << 3, + JUSTIFICATION_CONSTRAIN_ELLIPSIS = 1 << 4, }; enum LineBreakFlag { @@ -67,7 +68,7 @@ public: BREAK_MANDATORY = 1 << 4, BREAK_WORD_BOUND = 1 << 5, BREAK_GRAPHEME_BOUND = 1 << 6, - BREAK_WORD_BOUND_ADAPTIVE = 1 << 5 | 1 << 7 + BREAK_WORD_BOUND_ADAPTIVE = 1 << 5 | 1 << 7, }; enum TextOverrunFlag { @@ -75,7 +76,8 @@ public: OVERRUN_TRIM = 1 << 0, OVERRUN_TRIM_WORD_ONLY = 1 << 1, OVERRUN_ADD_ELLIPSIS = 1 << 2, - OVERRUN_ENFORCE_ELLIPSIS = 1 << 3 + OVERRUN_ENFORCE_ELLIPSIS = 1 << 3, + OVERRUN_JUSTIFICATION_AWARE = 1 << 4, }; enum GraphemeFlag { @@ -154,6 +156,12 @@ public: } }; + struct TrimData { + int trim_pos = -1; + int ellipsis_pos = -1; + Vector<TextServer::Glyph> ellipsis_glyph_buf; + }; + struct ShapedTextData { /* Source data */ RID parent; // Substring parent ShapedTextData. @@ -192,6 +200,7 @@ public: bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. bool sort_valid = false; + bool text_trimmed = false; bool preserve_invalid = true; // Draw hex code box instead of missing characters. bool preserve_control = false; // Draw control characters. @@ -199,10 +208,14 @@ public: float ascent = 0.f; // Ascent for horizontal layout, 1/2 of width for vertical. float descent = 0.f; // Descent for horizontal layout, 1/2 of width for vertical. float width = 0.f; // Width for horizontal layout, height for vertical. + float width_trimmed = 0.f; float upos = 0.f; float uthk = 0.f; + TrimData overrun_trim_data; + bool fit_width_minimum_reached = false; + Vector<TextServer::Glyph> glyphs; Vector<TextServer::Glyph> glyphs_logical; }; @@ -357,7 +370,8 @@ public: 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, int p_grapheme_flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_PUNCTUATION) const; - virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_clip_flags) = 0; + virtual TrimData shaped_text_get_trim_data(RID p_shaped) const; + virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint8_t p_trim_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; |