From 8398499edde512a90ab59dc35a7e67095a3361a4 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Fri, 15 May 2020 00:34:04 +0200 Subject: [PATCH] new text formatting, part 1 --- MLEM/Formatting/Codes/Code.cs | 35 +++++++++ MLEM/Formatting/Codes/ColorCode.cs | 18 +++++ MLEM/Formatting/Codes/FontCode.cs | 18 +++++ .../{ => Obsolete}/FormatSettings.cs | 2 + .../{ => Obsolete}/FormattingCode.cs | 2 + .../FormattingCodeCollection.cs | 3 + .../{ => Obsolete}/TextAnimation.cs | 1 + .../{ => Obsolete}/TextFormatting.cs | 1 + MLEM/Formatting/TextFormatter.cs | 77 +++++++++++++++++++ MLEM/Formatting/Token.cs | 47 +++++++++++ MLEM/Formatting/TokenizedString.cs | 48 ++++++++++++ Sandbox/GameImpl.cs | 26 +++++-- 12 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 MLEM/Formatting/Codes/Code.cs create mode 100644 MLEM/Formatting/Codes/ColorCode.cs create mode 100644 MLEM/Formatting/Codes/FontCode.cs rename MLEM/Formatting/{ => Obsolete}/FormatSettings.cs (83%) rename MLEM/Formatting/{ => Obsolete}/FormattingCode.cs (94%) rename MLEM/Formatting/{ => Obsolete}/FormattingCodeCollection.cs (78%) rename MLEM/Formatting/{ => Obsolete}/TextAnimation.cs (95%) rename MLEM/Formatting/{ => Obsolete}/TextFormatting.cs (98%) create mode 100644 MLEM/Formatting/TextFormatter.cs create mode 100644 MLEM/Formatting/Token.cs create mode 100644 MLEM/Formatting/TokenizedString.cs diff --git a/MLEM/Formatting/Codes/Code.cs b/MLEM/Formatting/Codes/Code.cs new file mode 100644 index 0000000..e6fe7cc --- /dev/null +++ b/MLEM/Formatting/Codes/Code.cs @@ -0,0 +1,35 @@ +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Font; + +namespace MLEM.Formatting.Codes { + public class Code { + + public readonly Match Match; + public Token Token { get; internal set; } + + public Code(Match match) { + this.Match = match; + } + + public virtual bool EndsHere(Code other) { + return other.GetType() == this.GetType(); + } + + public virtual Color? GetColor() { + return null; + } + + public virtual GenericFont GetFont() { + return null; + } + + public virtual bool DrawCharacter(GameTime time, SpriteBatch batch, char c, string cString, Vector2 pos, GenericFont font, Color color, float scale, float depth) { + return false; + } + + public delegate Code Constructor(TextFormatter formatter, Match match); + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/Codes/ColorCode.cs b/MLEM/Formatting/Codes/ColorCode.cs new file mode 100644 index 0000000..6cb6796 --- /dev/null +++ b/MLEM/Formatting/Codes/ColorCode.cs @@ -0,0 +1,18 @@ +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework; + +namespace MLEM.Formatting.Codes { + public class ColorCode : Code { + + private readonly Color? color; + + public ColorCode(Match match, Color? color) : base(match) { + this.color = color; + } + + public override Color? GetColor() { + return this.color; + } + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/Codes/FontCode.cs b/MLEM/Formatting/Codes/FontCode.cs new file mode 100644 index 0000000..69bce99 --- /dev/null +++ b/MLEM/Formatting/Codes/FontCode.cs @@ -0,0 +1,18 @@ +using System.Text.RegularExpressions; +using MLEM.Font; + +namespace MLEM.Formatting.Codes { + public class FontCode : Code { + + private readonly GenericFont font; + + public FontCode(Match match, GenericFont font) : base(match) { + this.font = font; + } + + public override GenericFont GetFont() { + return this.font; + } + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/FormatSettings.cs b/MLEM/Formatting/Obsolete/FormatSettings.cs similarity index 83% rename from MLEM/Formatting/FormatSettings.cs rename to MLEM/Formatting/Obsolete/FormatSettings.cs index aae442b..f0e1149 100644 --- a/MLEM/Formatting/FormatSettings.cs +++ b/MLEM/Formatting/Obsolete/FormatSettings.cs @@ -1,7 +1,9 @@ +using System; using Microsoft.Xna.Framework; using MLEM.Misc; namespace MLEM.Formatting { + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public class FormatSettings : GenericDataHolder { public static readonly FormatSettings Default = new FormatSettings(); diff --git a/MLEM/Formatting/FormattingCode.cs b/MLEM/Formatting/Obsolete/FormattingCode.cs similarity index 94% rename from MLEM/Formatting/FormattingCode.cs rename to MLEM/Formatting/Obsolete/FormattingCode.cs index 4e0290c..3c1c155 100644 --- a/MLEM/Formatting/FormattingCode.cs +++ b/MLEM/Formatting/Obsolete/FormattingCode.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Xna.Framework; using MLEM.Animations; using MLEM.Extensions; @@ -5,6 +6,7 @@ using MLEM.Font; using MLEM.Textures; namespace MLEM.Formatting { + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public class FormattingCode { public readonly Type CodeType; diff --git a/MLEM/Formatting/FormattingCodeCollection.cs b/MLEM/Formatting/Obsolete/FormattingCodeCollection.cs similarity index 78% rename from MLEM/Formatting/FormattingCodeCollection.cs rename to MLEM/Formatting/Obsolete/FormattingCodeCollection.cs index e6a6fea..e1a5ba3 100644 --- a/MLEM/Formatting/FormattingCodeCollection.cs +++ b/MLEM/Formatting/Obsolete/FormattingCodeCollection.cs @@ -1,12 +1,15 @@ +using System; using System.Collections.Generic; using System.Text.RegularExpressions; using MLEM.Misc; namespace MLEM.Formatting { + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public class FormattingCodeCollection : Dictionary> { } + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public class FormattingCodeData : GenericDataHolder { public readonly FormattingCode Code; diff --git a/MLEM/Formatting/TextAnimation.cs b/MLEM/Formatting/Obsolete/TextAnimation.cs similarity index 95% rename from MLEM/Formatting/TextAnimation.cs rename to MLEM/Formatting/Obsolete/TextAnimation.cs index 869d9f3..5a82645 100644 --- a/MLEM/Formatting/TextAnimation.cs +++ b/MLEM/Formatting/Obsolete/TextAnimation.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics; using MLEM.Font; namespace MLEM.Formatting { + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public static class TextAnimation { public static readonly DrawCharacter Default = (settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => { diff --git a/MLEM/Formatting/TextFormatting.cs b/MLEM/Formatting/Obsolete/TextFormatting.cs similarity index 98% rename from MLEM/Formatting/TextFormatting.cs rename to MLEM/Formatting/Obsolete/TextFormatting.cs index 151ec49..525df1b 100644 --- a/MLEM/Formatting/TextFormatting.cs +++ b/MLEM/Formatting/Obsolete/TextFormatting.cs @@ -9,6 +9,7 @@ using MLEM.Misc; using MLEM.Textures; namespace MLEM.Formatting { + [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public static class TextFormatting { public static readonly Dictionary FormattingCodes = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MLEM/Formatting/TextFormatter.cs b/MLEM/Formatting/TextFormatter.cs new file mode 100644 index 0000000..45a00c6 --- /dev/null +++ b/MLEM/Formatting/TextFormatter.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework; +using MLEM.Extensions; +using MLEM.Font; +using MLEM.Formatting.Codes; + +namespace MLEM.Formatting { + public class TextFormatter { + + public readonly Dictionary Codes = new Dictionary(); + + public TextFormatter(Func boldFont = null, Func italicFont = null) { + // font codes + this.Codes.Add(new Regex(""), (f, m) => new FontCode(m, boldFont?.Invoke())); + this.Codes.Add(new Regex(""), (f, m) => new FontCode(m, italicFont?.Invoke())); + this.Codes.Add(new Regex(""), (f, m) => new FontCode(m, null)); + + // color codes + foreach (var c in typeof(Color).GetProperties()) { + if (c.GetGetMethod().IsStatic) { + var value = (Color) c.GetValue(null); + this.Codes.Add(new Regex($""), (f, m) => new ColorCode(m, value)); + } + } + this.Codes.Add(new Regex(@""), (f, m) => new ColorCode(m, ColorExtensions.FromHex(m.Groups[1].Value))); + this.Codes.Add(new Regex(""), (f, m) => new ColorCode(m, null)); + } + + public TokenizedString Tokenize(string s) { + var tokens = new List(); + var codes = new List(); + var rawIndex = 0; + while (rawIndex < s.Length) { + var index = this.StripFormatting(s.Substring(0, rawIndex)).Length; + var next = this.GetNextCode(s, rawIndex + 1); + // if we've reached the end of the string + if (next == null) { + var sub = s.Substring(rawIndex, s.Length - rawIndex); + tokens.Add(new Token(codes.ToArray(), index, rawIndex, this.StripFormatting(sub), sub)); + break; + } + + // create a new token for the content up to the next code + var ret = s.Substring(rawIndex, next.Match.Index - rawIndex); + tokens.Add(new Token(codes.ToArray(), index, rawIndex, this.StripFormatting(ret), ret)); + + // move to the start of the next code + rawIndex = next.Match.Index; + + // remove all codes that are incompatible with the next one and apply it + codes.RemoveAll(c => c.EndsHere(next)); + codes.Add(next); + } + return new TokenizedString(s, this.StripFormatting(s), tokens.ToArray()); + } + + public string StripFormatting(string s) { + foreach (var regex in this.Codes.Keys) + s = regex.Replace(s, string.Empty); + return s; + } + + private Code GetNextCode(string s, int index) { + var (c, m) = this.Codes + .Select(kv => (c: kv.Value, m: kv.Key.Match(s, index))) + .Where(kv => kv.m.Success) + .OrderBy(kv => kv.m.Index) + .FirstOrDefault(); + return c?.Invoke(this, m); + } + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/Token.cs b/MLEM/Formatting/Token.cs new file mode 100644 index 0000000..bf3f948 --- /dev/null +++ b/MLEM/Formatting/Token.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Font; +using MLEM.Formatting.Codes; + +namespace MLEM.Formatting { + public class Token { + + public readonly Code[] AppliedCodes; + public readonly int Index; + public readonly int RawIndex; + public string Substring { get; internal set; } + public readonly string RawSubstring; + + public Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) { + this.AppliedCodes = appliedCodes; + this.Index = index; + this.RawIndex = rawIndex; + this.Substring = substring; + this.RawSubstring = rawSubstring; + + foreach (var code in appliedCodes) + code.Token = this; + } + + public Color? GetColor() { + return this.AppliedCodes.Select(c => c.GetColor()).FirstOrDefault(c => c.HasValue); + } + + public GenericFont GetFont() { + return this.AppliedCodes.Select(c => c.GetFont()).FirstOrDefault(); + } + + public void DrawCharacter(GameTime time, SpriteBatch batch, char c, string cString, Vector2 pos, GenericFont font, Color color, float scale, float depth) { + foreach (var code in this.AppliedCodes) { + if (code.DrawCharacter(time, batch, c, cString, pos, font, color, scale, depth)) + return; + } + + // if no code drew, we have to do it ourselves + font.DrawString(batch, cString, pos, color, 0, Vector2.Zero, scale, SpriteEffects.None, depth); + } + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs new file mode 100644 index 0000000..9c7c29b --- /dev/null +++ b/MLEM/Formatting/TokenizedString.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Font; + +namespace MLEM.Formatting { + public struct TokenizedString { + + public readonly string RawString; + public readonly string String; + public readonly Token[] Tokens; + + public TokenizedString(string rawString, string strg, Token[] tokens) { + this.RawString = rawString; + this.String = strg; + this.Tokens = tokens; + } + + public void Split(GenericFont font, float width, float scale) { + var split = font.SplitString(this.String, width, scale); + // remove spaces at the end of new lines since we want the same character count + split = split.Replace(" \n", "\n"); + foreach (var token in this.Tokens) + token.Substring = split.Substring(token.Index, token.Substring.Length); + } + + public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) { + var innerOffset = new Vector2(); + foreach (var token in this.Tokens) { + var drawFont = token.GetFont() ?? font; + var drawColor = token.GetColor() ?? color; + foreach (var c in token.Substring) { + if (c == '\n') { + innerOffset.X = 0; + innerOffset.Y += font.LineHeight * scale; + continue; + } + + var cString = c.ToString(); + token.DrawCharacter(time, batch, c, cString, pos + innerOffset, drawFont, drawColor, scale, depth); + innerOffset.X += font.MeasureString(cString).X * scale; + } + } + } + + } +} \ No newline at end of file diff --git a/Sandbox/GameImpl.cs b/Sandbox/GameImpl.cs index 5d7a865..aaf627f 100644 --- a/Sandbox/GameImpl.cs +++ b/Sandbox/GameImpl.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -10,6 +11,7 @@ using MLEM.Extended.Extensions; using MLEM.Extended.Tiled; using MLEM.Extensions; using MLEM.Font; +using MLEM.Formatting; using MLEM.Misc; using MLEM.Startup; using MLEM.Textures; @@ -29,6 +31,7 @@ namespace Sandbox { private IndividualTiledMapRenderer mapRenderer; private TiledMapCollisions collisions; private RawContentManager rawContent; + private TokenizedString tokenized; public GameImpl() { this.IsMouseVisible = true; @@ -72,7 +75,7 @@ namespace Sandbox { this.UiSystem.Add("Panel", panel); panel.SetData("TestKey", new Vector2(10, 2)); - Console.WriteLine(panel.GetData("TestKey")); + //Console.WriteLine(panel.GetData("TestKey")); var obj = new Test { Vec = new Vector2(10, 20), @@ -84,16 +87,16 @@ namespace Sandbox { var writer = new StringWriter(); this.Content.GetJsonSerializer().Serialize(writer, obj); - Console.WriteLine(writer.ToString()); + //Console.WriteLine(writer.ToString()); // {"Vec":"10 20","Point":"20 30","Rectangle":"1 2 3 4","RectangleF":"4 5 6 7"} // Also: //this.Content.AddJsonConverter(new CustomConverter()); var res = this.Content.LoadJson("Test"); - Console.WriteLine(res); + //Console.WriteLine(res); - this.OnDraw += (game, time) => { + /*this.OnDraw += (game, time) => { this.SpriteBatch.Begin(); font.DrawString(this.SpriteBatch, "Left Aligned\nover multiple lines", new Vector2(640, 0), TextAlign.Left, Color.White); font.DrawString(this.SpriteBatch, "Center Aligned\nover multiple lines", new Vector2(640, 100), TextAlign.Center, Color.White); @@ -107,6 +110,17 @@ namespace Sandbox { font.DrawString(this.SpriteBatch, font.TruncateString("This is a very long string", 200, 1, true), new Vector2(200, 500), Color.White); font.DrawString(this.SpriteBatch, font.TruncateString("This is a very long string", 200, 1, true, "..."), new Vector2(200, 550), Color.White); this.SpriteBatch.End(); + };*/ + + var formatter = new TextFormatter(); + var strg = "This is a formatted string with two bits of formatting!"; + this.tokenized = formatter.Tokenize(strg); + this.tokenized.Split(font, 400, 1); + + this.OnDraw += (g, time) => { + this.SpriteBatch.Begin(); + this.tokenized.Draw(time, this.SpriteBatch, new Vector2(100, 20), font, Color.White, 1, 0); + this.SpriteBatch.End(); }; } @@ -124,13 +138,13 @@ namespace Sandbox { protected override void DoDraw(GameTime gameTime) { this.GraphicsDevice.Clear(Color.Black); this.SpriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, this.camera.ViewMatrix); - this.mapRenderer.Draw(this.SpriteBatch, this.camera.GetVisibleRectangle().ToExtended()); + /*this.mapRenderer.Draw(this.SpriteBatch, this.camera.GetVisibleRectangle().ToExtended()); foreach (var tile in this.collisions.GetCollidingTiles(new RectangleF(0, 0, this.map.Width, this.map.Height))) { foreach (var area in tile.Collisions) { this.SpriteBatch.DrawRectangle(area.Position * this.map.GetTileSize(), area.Size * this.map.GetTileSize(), Color.Red); } - } + }*/ this.SpriteBatch.End(); base.DoDraw(gameTime);