From 226640ef3a3f892bffb9cccefd41006578ed82cc Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sun, 25 Aug 2019 19:07:45 +0200 Subject: [PATCH] simplify text rendering quite a bit after realizing that monogame deals with multiple lines on its own --- Demos/UiDemo.cs | 2 +- .../Extensions/BitmapFontExtensions.cs | 17 +--- MLEM.Extended/Font/GenericBitmapFont.cs | 3 +- MLEM.Ui/Elements/Paragraph.cs | 85 ++++++++----------- MLEM.Ui/Style/UntexturedStyle.cs | 6 +- MLEM/Extensions/SpriteFontExtensions.cs | 24 ++++-- MLEM/Font/GenericSpriteFont.cs | 7 +- MLEM/Font/IGenericFont.cs | 4 +- 8 files changed, 64 insertions(+), 84 deletions(-) diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index d1f03b2..c97b244 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -69,7 +69,7 @@ namespace Demos { var root = new Panel(Anchor.Center, new Vector2(80, 100), Vector2.Zero, false, true, new Point(5, 10)); this.UiSystem.Add("Test", root); - root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of (M)LEM (L)ibrary by (E)llpeck for (M)onoGame.") {LineSpace = 1.5F}); + root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a small demo for MLEM.Ui, a user interface library that is part of (M)LEM (L)ibrary by (E)llpeck for (M)onoGame.")); var image = root.AddChild(new Image(Anchor.AutoCenter, new Vector2(50, 50), new TextureRegion(this.testTexture, 0, 0, 8, 8)) {IsHidden = true, Padding = new Point(3)}); // Setting the x or y coordinate of the size to 1 or a lower number causes the width or height to be a percentage of the parent's width or height // (for example, setting the size's x to 0.75 would make the element's width be 0.75*parentWidth) diff --git a/MLEM.Extended/Extensions/BitmapFontExtensions.cs b/MLEM.Extended/Extensions/BitmapFontExtensions.cs index 58ccdd1..44edf0b 100644 --- a/MLEM.Extended/Extensions/BitmapFontExtensions.cs +++ b/MLEM.Extended/Extensions/BitmapFontExtensions.cs @@ -1,25 +1,14 @@ using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; using MonoGame.Extended.BitmapFonts; namespace MLEM.Extended.Extensions { public static class BitmapFontExtensions { - public static IEnumerable SplitString(this BitmapFont font, string text, float width, float scale) { - var builder = new StringBuilder(); - foreach (var line in text.Split('\n')) { - foreach (var word in line.Split(' ')) { - builder.Append(word).Append(' '); - if (font.MeasureString(builder).Width * scale >= width) { - var len = builder.Length - word.Length - 1; - yield return builder.ToString(0, len - 1); - builder.Remove(0, len); - } - } - yield return builder.ToString(0, builder.Length - 1); - builder.Clear(); - } + public static string SplitString(this BitmapFont font, string text, float width, float scale) { + return SpriteFontExtensions.SplitString(s => font.MeasureString(s).Width, text, width, scale); } } diff --git a/MLEM.Extended/Font/GenericBitmapFont.cs b/MLEM.Extended/Font/GenericBitmapFont.cs index b7d778c..d8389ee 100644 --- a/MLEM.Extended/Font/GenericBitmapFont.cs +++ b/MLEM.Extended/Font/GenericBitmapFont.cs @@ -10,6 +10,7 @@ namespace MLEM.Extended.Font { public class GenericBitmapFont : IGenericFont { public readonly BitmapFont Font; + public float LineHeight => this.Font.LineHeight; public GenericBitmapFont(BitmapFont font) { this.Font = font; @@ -55,7 +56,7 @@ namespace MLEM.Extended.Font { batch.DrawCenteredString(this.Font, text, position, scale, color, horizontal, vertical, addedScale); } - public IEnumerable SplitString(string text, float width, float scale) { + public string SplitString(string text, float width, float scale) { return this.Font.SplitString(text, width, scale); } diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs index 3a9d0ff..0573db6 100644 --- a/MLEM.Ui/Elements/Paragraph.cs +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -14,9 +14,7 @@ namespace MLEM.Ui.Elements { public class Paragraph : Element { private string text; - private float lineHeight; - private float longestLineLength; - private string[] splitText; + private string splitText; private Dictionary codeLocations; private IGenericFont regularFont; private IGenericFont boldFont; @@ -37,7 +35,6 @@ namespace MLEM.Ui.Elements { } public bool AutoAdjustWidth; public TextCallback GetTextCallback; - public float LineSpace = 1; public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false) : this(anchor, width, "", centerText) { @@ -53,21 +50,13 @@ namespace MLEM.Ui.Elements { protected override Point CalcActualSize(Rectangle parentArea) { var size = base.CalcActualSize(parentArea); + var sc = this.TextScale * this.Scale; - this.splitText = this.regularFont.SplitString(this.text.RemoveFormatting(), size.X - this.ScaledPadding.X * 2, sc).ToArray(); + this.splitText = this.regularFont.SplitString(this.text.RemoveFormatting(), size.X - this.ScaledPadding.X * 2, sc); this.codeLocations = this.text.GetFormattingCodes(); - this.lineHeight = 0; - this.longestLineLength = 0; - foreach (var strg in this.splitText) { - var strgScale = this.regularFont.MeasureString(strg) * sc; - if (strgScale.Y + 1 > this.lineHeight) - this.lineHeight = strgScale.Y + 1; - if (strgScale.X > this.longestLineLength) - this.longestLineLength = strgScale.X; - } - this.lineHeight *= this.LineSpace; - return new Point(this.AutoAdjustWidth ? this.longestLineLength.Ceil() + this.ScaledPadding.X * 2 : size.X, (this.lineHeight * this.splitText.Length).Ceil() + this.ScaledPadding.Y * 2); + var textDims = this.regularFont.MeasureString(this.splitText) * sc; + return new Point(this.AutoAdjustWidth ? textDims.X.Ceil() + this.ScaledPadding.X * 2 : size.X, textDims.Y.Ceil() + this.ScaledPadding.Y * 2); } public override void Update(GameTime time) { @@ -86,51 +75,45 @@ namespace MLEM.Ui.Elements { // if we don't have any formatting codes, then we don't need to do complex drawing if (this.codeLocations.Count <= 0) { - foreach (var line in this.splitText) { - this.regularFont.DrawString(batch, line, pos + off, this.TextColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0); - off.Y += this.lineHeight; - } + this.regularFont.DrawString(batch, this.splitText, pos + off, this.TextColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0); } else { // if we have formatting codes, we need to go through each index and see how it should be drawn var characterCounter = 0; var currColor = this.TextColor; var currFont = this.regularFont; - foreach (var line in this.splitText) { - var lineOffset = new Vector2(); - foreach (var c in line) { - // check if the current character's index has a formatting code - this.codeLocations.TryGetValue(characterCounter, out var code); - if (code != null) { - // if so, apply it - if (code.IsColorCode) { - currColor = code.Color; - } else { - switch (code.Style) { - case TextStyle.Regular: - currFont = this.regularFont; - break; - case TextStyle.Bold: - currFont = this.boldFont; - break; - case TextStyle.Italic: - currFont = this.italicFont; - break; - } + var innerOffset = new Vector2(); + foreach (var c in this.splitText) { + // check if the current character's index has a formatting code + this.codeLocations.TryGetValue(characterCounter, out var code); + if (code != null) { + // if so, apply it + if (code.IsColorCode) { + currColor = code.Color; + } else { + switch (code.Style) { + case TextStyle.Regular: + currFont = this.regularFont; + break; + case TextStyle.Bold: + currFont = this.boldFont; + break; + case TextStyle.Italic: + currFont = this.italicFont; + break; } } - - var cSt = c.ToString(); - currFont.DrawString(batch, cSt, pos + off + lineOffset, currColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0); - - // get the width based on the regular font so that the split text doesn't overshoot the borders - // this is a bit of a hack, but bold fonts shouldn't be that much thicker so it won't look bad - lineOffset.X += this.regularFont.MeasureString(cSt).X * sc; - characterCounter++; } - // spaces are replaced by newline characters, account for that characterCounter++; - off.Y += this.lineHeight; + + var cSt = c.ToString(); + if (c == '\n') { + innerOffset.X = 0; + innerOffset.Y += this.regularFont.LineHeight * sc; + } else { + currFont.DrawString(batch, cSt, pos + off + innerOffset, currColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0); + innerOffset.X += this.regularFont.MeasureString(cSt).X * sc; + } } } base.Draw(time, batch, alpha, offset); diff --git a/MLEM.Ui/Style/UntexturedStyle.cs b/MLEM.Ui/Style/UntexturedStyle.cs index ffac0bc..63e9e38 100644 --- a/MLEM.Ui/Style/UntexturedStyle.cs +++ b/MLEM.Ui/Style/UntexturedStyle.cs @@ -45,6 +45,8 @@ namespace MLEM.Ui.Style { private class EmptyFont : IGenericFont { + public float LineHeight => 1; + public Vector2 MeasureString(string text) { return Vector2.One; } @@ -74,8 +76,8 @@ namespace MLEM.Ui.Style { public void DrawCenteredString(SpriteBatch batch, string text, Vector2 position, float scale, Color color, bool horizontal = true, bool vertical = false, float addedScale = 0) { } - public IEnumerable SplitString(string text, float width, float scale) { - yield break; + public string SplitString(string text, float width, float scale) { + return text; } } diff --git a/MLEM/Extensions/SpriteFontExtensions.cs b/MLEM/Extensions/SpriteFontExtensions.cs index e6be953..37c82fb 100644 --- a/MLEM/Extensions/SpriteFontExtensions.cs +++ b/MLEM/Extensions/SpriteFontExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework.Graphics; @@ -5,20 +6,25 @@ using Microsoft.Xna.Framework.Graphics; namespace MLEM.Extensions { public static class SpriteFontExtensions { - public static IEnumerable SplitString(this SpriteFont font, string text, float width, float scale) { - var builder = new StringBuilder(); + public static string SplitString(this SpriteFont font, string text, float width, float scale) { + return SplitString(s => font.MeasureString(s).X, text, width, scale); + } + + public static string SplitString(Func widthFunc, string text, float width, float scale) { + var total = new StringBuilder(); foreach (var line in text.Split('\n')) { + var curr = new StringBuilder(); foreach (var word in line.Split(' ')) { - builder.Append(word).Append(' '); - if (font.MeasureString(builder).X * scale >= width) { - var len = builder.Length - word.Length - 1; - yield return builder.ToString(0, len - 1); - builder.Remove(0, len); + curr.Append(word).Append(' '); + if (widthFunc(curr) * scale >= width) { + var len = curr.Length - word.Length - 1; + total.Append(curr.ToString(0, len - 1)).Append('\n'); + curr.Remove(0, len); } } - yield return builder.ToString(0, builder.Length - 1); - builder.Clear(); + total.Append(curr.ToString(0, curr.Length - 1)).Append('\n'); } + return total.ToString(0, total.Length - 1); } } diff --git a/MLEM/Font/GenericSpriteFont.cs b/MLEM/Font/GenericSpriteFont.cs index e4640f0..3ee8280 100644 --- a/MLEM/Font/GenericSpriteFont.cs +++ b/MLEM/Font/GenericSpriteFont.cs @@ -8,15 +8,12 @@ namespace MLEM.Font { public class GenericSpriteFont : IGenericFont { public readonly SpriteFont Font; + public float LineHeight => this.Font.LineSpacing; public GenericSpriteFont(SpriteFont font) { this.Font = font; } - public static implicit operator GenericSpriteFont(SpriteFont font) { - return new GenericSpriteFont(font); - } - public Vector2 MeasureString(string text) { return this.Font.MeasureString(text); } @@ -53,7 +50,7 @@ namespace MLEM.Font { batch.DrawCenteredString(this.Font, text, position, scale, color, horizontal, vertical, addedScale); } - public IEnumerable SplitString(string text, float width, float scale) { + public string SplitString(string text, float width, float scale) { return this.Font.SplitString(text, width, scale); } diff --git a/MLEM/Font/IGenericFont.cs b/MLEM/Font/IGenericFont.cs index da78954..5b7d77f 100644 --- a/MLEM/Font/IGenericFont.cs +++ b/MLEM/Font/IGenericFont.cs @@ -6,6 +6,8 @@ using Microsoft.Xna.Framework.Graphics; namespace MLEM.Font { public interface IGenericFont { + float LineHeight { get; } + Vector2 MeasureString(string text); Vector2 MeasureString(StringBuilder text); @@ -24,7 +26,7 @@ namespace MLEM.Font { void DrawCenteredString(SpriteBatch batch, string text, Vector2 position, float scale, Color color, bool horizontal = true, bool vertical = false, float addedScale = 0); - IEnumerable SplitString(string text, float width, float scale); + string SplitString(string text, float width, float scale); } } \ No newline at end of file