From d385581c25db946ee9520c63a3d652b0ee8673bf Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Tue, 18 May 2021 16:47:38 +0200 Subject: [PATCH] added formatted string truncation to tokenized strings and ui paragraphs --- MLEM.Ui/Elements/Button.cs | 13 +++- MLEM.Ui/Elements/Paragraph.cs | 19 +++++- MLEM/Formatting/Token.cs | 6 +- MLEM/Formatting/TokenizedString.cs | 95 +++++++++++++++++++----------- 4 files changed, 92 insertions(+), 41 deletions(-) diff --git a/MLEM.Ui/Elements/Button.cs b/MLEM.Ui/Elements/Button.cs index aeea6ad..18c5871 100644 --- a/MLEM.Ui/Elements/Button.cs +++ b/MLEM.Ui/Elements/Button.cs @@ -60,6 +60,17 @@ namespace MLEM.Ui.Elements { this.CanBeSelected = !value; } } + /// + /// Whether this button's should be truncated if it exceeds this button's width. + /// Defaults to false. + /// + public bool TruncateTextIfLong { + get => this.Text?.TruncateIfLong ?? false; + set { + if (this.Text != null) + this.Text.TruncateIfLong = value; + } + } /// /// Creates a new button with the given settings @@ -71,7 +82,7 @@ namespace MLEM.Ui.Elements { /// The width of this button's , or 50 by default public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null, float tooltipWidth = 50) : base(anchor, size) { if (text != null) { - this.Text = new Paragraph(Anchor.Center, 1, text, true); + this.Text = new Paragraph(Anchor.Center, 1, text, true) {Padding = new Vector2(1)}; this.AddChild(this.Text); } if (tooltipText != null) diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs index ac2aba5..479fcd0 100644 --- a/MLEM.Ui/Elements/Paragraph.cs +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -64,6 +64,16 @@ namespace MLEM.Ui.Elements { /// public bool AutoAdjustWidth; /// + /// Whether this paragraph should be truncated instead of split if the displayed 's width exceeds the provided width. + /// When the string is truncated, the is added to its end. + /// + public bool TruncateIfLong; + /// + /// The ellipsis characters to use if is enabled and the string is truncated. + /// If this is set to an empty string, no ellipsis will be attached to the truncated string. + /// + public string Ellipsis = "..."; + /// /// An event that gets called when this paragraph's is queried. /// Use this event for setting this paragraph's text if it changes frequently. /// @@ -142,7 +152,14 @@ namespace MLEM.Ui.Elements { foreach (var link in this.TokenizedText.Tokens.Where(t => t.AppliedCodes.Any(c => c is LinkCode))) this.AddChild(new Link(Anchor.TopLeft, link, this.TextScale * this.TextScaleMultiplier)); } - this.TokenizedText.Split(this.RegularFont, size.X - this.ScaledPadding.Width, this.TextScale * this.TextScaleMultiplier * this.Scale); + + var width = size.X - this.ScaledPadding.Width; + var scale = this.TextScale * this.TextScaleMultiplier * this.Scale; + if (this.TruncateIfLong) { + this.TokenizedText.Truncate(this.RegularFont, width, scale, this.Ellipsis); + } else { + this.TokenizedText.Split(this.RegularFont, width, scale); + } } private void QueryTextCallback() { diff --git a/MLEM/Formatting/Token.cs b/MLEM/Formatting/Token.cs index c35ec50..c51d956 100644 --- a/MLEM/Formatting/Token.cs +++ b/MLEM/Formatting/Token.cs @@ -29,15 +29,15 @@ namespace MLEM.Formatting { /// public readonly string Substring; /// - /// The string that is displayed by this token. If the tokenized string has been split, this string will contain the newline characters. + /// The string that is displayed by this token. If the tokenized string has been or has been used, this string will contain the newline characters. /// - public string DisplayString => this.SplitSubstring ?? this.Substring; + public string DisplayString => this.ModifiedSubstring ?? this.Substring; /// /// The substring that this token contains, without the formatting codes removed. /// public readonly string RawSubstring; internal RectangleF[] Area; - internal string SplitSubstring; + internal string ModifiedSubstring; internal Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) { this.AppliedCodes = appliedCodes; diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs index 473b194..b016f01 100644 --- a/MLEM/Formatting/TokenizedString.cs +++ b/MLEM/Formatting/TokenizedString.cs @@ -24,9 +24,9 @@ namespace MLEM.Formatting { public readonly string String; /// /// The string that is actually displayed by this tokenized string. - /// If this string has been , this string will contain the newline characters. + /// If this string has been or has been used, this string will contain the newline characters. /// - public string DisplayString => this.splitString ?? this.String; + public string DisplayString => this.modifiedString ?? this.String; /// /// The tokens that this tokenized string contains. /// @@ -36,7 +36,7 @@ namespace MLEM.Formatting { /// Note that, to get a formatting code for a certain token, use /// public readonly Code[] AllCodes; - private string splitString; + private string modifiedString; internal TokenizedString(GenericFont font, string rawString, string strg, Token[] tokens) { this.RawString = rawString; @@ -49,44 +49,30 @@ namespace MLEM.Formatting { /// /// Splits this tokenized string, inserting newline characters if the width of the string is bigger than the maximum width. + /// Note that a tokenized string can be re-split without losing any of its actual data, as this operation merely modifies the . /// /// /// The font to use for width calculations - /// The maximum width - /// The scale to use fr width calculations + /// The maximum width, in display pixels based on the font and scale + /// The scale to use for width measurements public void Split(GenericFont font, float width, float scale) { - // a split string has the same character count as the input string - // but with newline characters added - this.splitString = font.SplitString(this.String, width, scale); - // skip splitting logic for unformatted text - if (this.Tokens.Length == 1) { - this.Tokens[0].SplitSubstring = this.splitString; - return; - } + // a split string has the same character count as the input string but with newline characters added + this.modifiedString = font.SplitString(this.String, width, scale); + this.StoreModifiedSubstrings(font); + } - // this is basically a substring function that ignores newlines for indexing - var index = 0; - var currToken = 0; - var splitIndex = 0; - var ret = new StringBuilder(); - while (splitIndex < this.splitString.Length) { - var token = this.Tokens[currToken]; - if (token.Substring.Length > 0) { - ret.Append(this.splitString[splitIndex]); - // if the current char is not an added newline, we simulate length increase - if (this.splitString[splitIndex] != '\n' || this.String[index] == '\n') - index++; - splitIndex++; - } - // move on to the next token if we reached its end - if (index >= token.Index + token.Substring.Length) { - token.SplitSubstring = ret.ToString(); - ret.Clear(); - currToken++; - } - } - - this.CalculateTokenAreas(font); + /// + /// Truncates this tokenized string, removing any additional characters that exceed the length from the displayed string. + /// Note that a tokenized string can be re-truncated without losing any of its actual data, as this operation merely modifies the . + /// + /// + /// The font to use for width calculations + /// The maximum width, in display pixels based on the font and scale + /// The scale to use for width measurements + /// The characters to add to the end of the string if it is too long + public void Truncate(GenericFont font, float width, float scale, string ellipsis = "") { + this.modifiedString = font.TruncateString(this.String, width, scale, false, ellipsis); + this.StoreModifiedSubstrings(font); } /// @@ -137,6 +123,43 @@ namespace MLEM.Formatting { } } + private void StoreModifiedSubstrings(GenericFont font) { + // skip substring logic for unformatted text + if (this.Tokens.Length == 1) { + this.Tokens[0].ModifiedSubstring = this.modifiedString; + return; + } + + // this is basically a substring function that ignores added newlines for indexing + var index = 0; + var currToken = 0; + var splitIndex = 0; + var ret = new StringBuilder(); + while (splitIndex < this.modifiedString.Length) { + var token = this.Tokens[currToken]; + if (token.Substring.Length > 0) { + ret.Append(this.modifiedString[splitIndex]); + // if the current char is not an added newline, we simulate length increase + if (this.modifiedString[splitIndex] != '\n' || this.String[index] == '\n') + index++; + splitIndex++; + } + // move on to the next token if we reached its end + if (index >= token.Index + token.Substring.Length) { + token.ModifiedSubstring = ret.ToString(); + ret.Clear(); + currToken++; + } + } + // set additional token contents beyond our string in case we truncated + if (ret.Length > 0) + this.Tokens[currToken - 1].ModifiedSubstring += ret.ToString(); + while (currToken < this.Tokens.Length) + this.Tokens[currToken++].ModifiedSubstring = string.Empty; + + this.CalculateTokenAreas(font); + } + private void CalculateTokenAreas(GenericFont font) { var innerOffset = new Vector2(); foreach (var token in this.Tokens) {