From 02b25236b1f61d930862599e3875914e3eb510f8 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 3 Feb 2020 04:25:12 +0100 Subject: [PATCH] part 2: format state and making the codes do the work --- Demos/UiDemo.cs | 6 +- MLEM/Formatting/AnimationCode.cs | 22 ++---- MLEM/Formatting/FormatState.cs | 30 +++++++++ MLEM/Formatting/FormattingCode.cs | 57 ++++++++++++++-- MLEM/Formatting/TextFormatting.cs | 107 ++++++++++++++---------------- 5 files changed, 137 insertions(+), 85 deletions(-) create mode 100644 MLEM/Formatting/FormatState.cs diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index a713d2e..152e72b 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -96,11 +96,11 @@ namespace Demos { // adding some custom image formatting codes // note that all added formatting codes need to be lowercase, while their casing doesn't matter when used - TextFormatting.FormattingCodes[new Regex("grass")] = m => new FormattingCode(image.Texture); - TextFormatting.FormattingCodes[new Regex("tree")] = m => new FormattingCode(tree); + TextFormatting.FormattingCodes[new Regex("grass")] = (m, i) => new FormattingCode(i, image.Texture); + TextFormatting.FormattingCodes[new Regex("tree")] = (m, i) => new FormattingCode(i, tree); // formatting codes can also be sprite animations! var atlas = new UniformTextureAtlas(LoadContent("Textures/Anim"), 4, 4); - TextFormatting.FormattingCodes[new Regex("walk")] = m => new FormattingCode(new SpriteAnimation(0.2F, atlas[0, 0], atlas[0, 1], atlas[0, 2], atlas[0, 3])); + TextFormatting.FormattingCodes[new Regex("walk")] = (m, i) => new FormattingCode(i, new SpriteAnimation(0.2F, atlas[0, 0], atlas[0, 1], atlas[0, 2], atlas[0, 3])); root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain [Grass] images or [Walk] sprite animations! Note that these images have to be square, or [Tree] bad things happen.")); diff --git a/MLEM/Formatting/AnimationCode.cs b/MLEM/Formatting/AnimationCode.cs index aaaec6d..aec9c8b 100644 --- a/MLEM/Formatting/AnimationCode.cs +++ b/MLEM/Formatting/AnimationCode.cs @@ -1,28 +1,14 @@ using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using MLEM.Font; namespace MLEM.Formatting { public class AnimationCode : FormattingCode { - public static readonly DrawCharacter Default = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => { - font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); - }; - public static readonly DrawCharacter Wobbly = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => { - var offset = new Vector2(0, (float) Math.Sin(index + code.Time.TotalSeconds * settings.WobbleModifier) * font.LineHeight * settings.WobbleHeightModifier * scale); - font.DrawString(batch, charSt, position + offset, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); - }; - public static readonly DrawCharacter Typing = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => { - if (code.Time.TotalSeconds * settings.TypingSpeed > index - effectStartIndex + 1) - font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); - }; - public static readonly AnimationCode DefaultCode = new AnimationCode(Default); - - public readonly DrawCharacter Draw; + public readonly FormatState.DrawCharacter Draw; public TimeSpan Time; - public AnimationCode(DrawCharacter draw) : base(Type.Animation) { + public AnimationCode(int startIndex, FormatState.DrawCharacter draw) : base(startIndex, Type.Animation) { this.Draw = draw; } @@ -34,7 +20,9 @@ namespace MLEM.Formatting { this.Time = TimeSpan.Zero; } - public delegate void DrawCharacter(AnimationCode code, FormatSettings settings, IGenericFont font, SpriteBatch batch, string totalText, int index, int effectStartIndex, string charSt, Vector2 position, Color color, float scale, float layerDepth); + public override void ModifyFormatting(ref FormatState state) { + state.DrawBehavior = this.Draw; + } } } \ No newline at end of file diff --git a/MLEM/Formatting/FormatState.cs b/MLEM/Formatting/FormatState.cs new file mode 100644 index 0000000..1a78b99 --- /dev/null +++ b/MLEM/Formatting/FormatState.cs @@ -0,0 +1,30 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Font; + +namespace MLEM.Formatting { + public struct FormatState { + + public static readonly DrawCharacter DefaultDrawBehavior = (state, batch, totalText, index, charSt, position, scale, layerDepth) => { + state.Font.DrawString(batch, charSt, position, state.Color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }; + public FormattingCodeCollection FormattingCodes { get; internal set; } + public IGenericFont RegularFont { get; internal set; } + public IGenericFont ItalicFont { get; internal set; } + public IGenericFont BoldFont { get; internal set; } + public FormatSettings Settings { get; internal set; } + public int CurrentIndex { get; internal set; } + public Vector2 InnerOffset { get; internal set; } + + public Color Color; + public IGenericFont Font; + public DrawCharacter DrawBehavior; + + public void PrependBehavior(DrawCharacter behavior) { + this.DrawBehavior = behavior + this.DrawBehavior; + } + + public delegate void DrawCharacter(FormatState state, SpriteBatch batch, string totalText, int index, string charSt, Vector2 position, float scale, float layerDepth); + + } +} \ No newline at end of file diff --git a/MLEM/Formatting/FormattingCode.cs b/MLEM/Formatting/FormattingCode.cs index 832e0d8..14f7353 100644 --- a/MLEM/Formatting/FormattingCode.cs +++ b/MLEM/Formatting/FormattingCode.cs @@ -1,6 +1,9 @@ +using System; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using MLEM.Animations; using MLEM.Font; +using MLEM.Misc; using MLEM.Textures; namespace MLEM.Formatting { @@ -10,24 +13,26 @@ namespace MLEM.Formatting { public readonly Color Color; public readonly TextStyle Style; public readonly SpriteAnimation Icon; + public readonly int StartIndex; - protected FormattingCode(Type type) { + protected FormattingCode(int startIndex, Type type) { this.CodeType = type; + this.StartIndex = startIndex; } - public FormattingCode(Color color) : this(Type.Color) { + public FormattingCode(int startIndex, Color color) : this(startIndex, Type.Color) { this.Color = color; } - public FormattingCode(TextStyle style) : this(Type.Style) { + public FormattingCode(int startIndex, TextStyle style) : this(startIndex, Type.Style) { this.Style = style; } - public FormattingCode(TextureRegion icon) : - this(new SpriteAnimation(0, icon)) { + public FormattingCode(int startIndex, TextureRegion icon) : + this(startIndex, new SpriteAnimation(0, icon)) { } - public FormattingCode(SpriteAnimation icon) : this(Type.Icon) { + public FormattingCode(int startIndex, SpriteAnimation icon) : this(startIndex, Type.Icon) { this.Icon = icon; } @@ -45,6 +50,46 @@ namespace MLEM.Formatting { this.Icon.Restart(); } + public virtual void ModifyFormatting(ref FormatState state) { + switch (this.CodeType) { + case Type.Color: + state.Color = this.Color; + break; + case Type.Style: + switch (this.Style) { + case TextStyle.Regular: + state.DrawBehavior = FormatState.DefaultDrawBehavior; + state.Font = state.RegularFont; + break; + case TextStyle.Bold: + state.Font = state.BoldFont; + break; + case TextStyle.Italic: + state.Font = state.ItalicFont; + break; + case TextStyle.Shadow: + var formatState = state; + state.PrependBehavior((s, batch, totalText, index, charSt, position, scale, layerDepth) => { + var lastColor = s.Color; + s.Color = s.Settings.DropShadowColor; + // Call the last behavior to draw the shadow + formatState.DrawBehavior(s, batch, totalText, index, charSt, position + s.Settings.DropShadowOffset * scale, scale, layerDepth); + s.Color = lastColor; + }); + break; + default: + throw new ArgumentOutOfRangeException(); + } + break; + case Type.Icon: + state.PrependBehavior((s, batch, totalText, index, charSt, position, scale, layerDepth) => { + if (index == this.StartIndex) + batch.Draw(this.Icon.CurrentRegion, new RectangleF(position, new Vector2(s.RegularFont.LineHeight * scale)), s.Color, 0, Vector2.Zero, SpriteEffects.None, layerDepth); + }); + break; + } + } + public enum Type { Color, diff --git a/MLEM/Formatting/TextFormatting.cs b/MLEM/Formatting/TextFormatting.cs index 801ccdd..56ba221 100644 --- a/MLEM/Formatting/TextFormatting.cs +++ b/MLEM/Formatting/TextFormatting.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using MLEM.Animations; using MLEM.Extensions; using MLEM.Font; using MLEM.Misc; @@ -11,7 +12,7 @@ using MLEM.Textures; namespace MLEM.Formatting { public static class TextFormatting { - public static readonly Dictionary> FormattingCodes = new Dictionary>(); + public static readonly Dictionary> FormattingCodes = new Dictionary>(); private static readonly Dictionary OneEmStrings = new Dictionary(); private static Regex formatRegex; @@ -19,22 +20,30 @@ namespace MLEM.Formatting { SetFormatIndicators('[', ']'); // style codes - FormattingCodes[new Regex("regular")] = m => new FormattingCode(TextStyle.Regular); - FormattingCodes[new Regex("italic")] = m => new FormattingCode(TextStyle.Italic); - FormattingCodes[new Regex("bold")] = m => new FormattingCode(TextStyle.Bold); - FormattingCodes[new Regex("shadow")] = m => new FormattingCode(TextStyle.Shadow); + FormattingCodes[new Regex("regular")] = (m, i) => new FormattingCode(i, TextStyle.Regular); + FormattingCodes[new Regex("italic")] = (m, i) => new FormattingCode(i, TextStyle.Italic); + FormattingCodes[new Regex("bold")] = (m, i) => new FormattingCode(i, TextStyle.Bold); + FormattingCodes[new Regex("shadow")] = (m, i) => new FormattingCode(i, TextStyle.Shadow); // color codes var colors = typeof(Color).GetProperties(); foreach (var color in colors) { if (color.GetGetMethod().IsStatic) - FormattingCodes[new Regex(color.Name.ToLowerInvariant())] = m => new FormattingCode((Color) color.GetValue(null)); + FormattingCodes[new Regex(color.Name.ToLowerInvariant())] = (m, i) => new FormattingCode(i, (Color) color.GetValue(null)); } // animations - FormattingCodes[new Regex("unanimated")] = m => new AnimationCode(AnimationCode.Default); - FormattingCodes[new Regex("wobbly")] = m => new AnimationCode(AnimationCode.Wobbly); - FormattingCodes[new Regex("typing")] = m => new AnimationCode(AnimationCode.Typing); + FormattingCodes[new Regex("unanimated")] = (m, i) => new AnimationCode(i, FormatState.DefaultDrawBehavior); + FormattingCodes[new Regex("wobbly")] = (m, i) => new AnimationCode(i, (state, batch, totalText, index, charSt, position, scale, layerDepth) => { + var code = (AnimationCode) state.FormattingCodes[i]; + var offset = new Vector2(0, (float) Math.Sin(index + code.Time.TotalSeconds * state.Settings.WobbleModifier) * state.Font.LineHeight * state.Settings.WobbleHeightModifier * scale); + state.Font.DrawString(batch, charSt, position + offset, state.Color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }); + FormattingCodes[new Regex("typing")] = (m, i) => new AnimationCode(i, (state, batch, totalText, index, charSt, position, scale, layerDepth) => { + var code = (AnimationCode) state.FormattingCodes[i]; + if (code.Time.TotalSeconds * state.Settings.TypingSpeed > index - i + 1) + state.Font.DrawString(batch, charSt, position, state.Color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth); + }); } public static void SetFormatIndicators(char opener, char closer) { @@ -56,9 +65,15 @@ namespace MLEM.Formatting { } public static string RemoveFormatting(this string s, IGenericFont font) { + var codeLengths = 0; return formatRegex.Replace(s, match => { - var code = FromMatch(match); - return code != null ? code.GetReplacementString(font) : string.Empty; + var code = FromMatch(match, match.Index - codeLengths); + if (code != null) { + var replace = code.GetReplacementString(font); + codeLengths += match.Length - replace.Length; + return replace; + } + return match.Value; }); } @@ -66,77 +81,51 @@ namespace MLEM.Formatting { var codes = new FormattingCodeCollection(); var codeLengths = 0; foreach (Match match in formatRegex.Matches(s)) { - var code = FromMatch(match); + var index = match.Index - codeLengths; + var code = FromMatch(match, index); if (code == null) continue; - codes[match.Index - codeLengths] = code; + codes[index] = code; codeLengths += match.Length - code.GetReplacementString(font).Length; } return codes; } public static void DrawFormattedString(this IGenericFont regularFont, SpriteBatch batch, Vector2 pos, string text, FormattingCodeCollection codes, Color color, float scale, IGenericFont boldFont = null, IGenericFont italicFont = null, float depth = 0, FormatSettings formatSettings = null) { - var settings = formatSettings ?? FormatSettings.Default; - var currColor = color; - var currFont = regularFont; - var currStyle = TextStyle.Regular; - var currAnim = AnimationCode.DefaultCode; - var animStart = 0; + var state = new FormatState { + FormattingCodes = codes, + RegularFont = regularFont, + ItalicFont = italicFont, + BoldFont = boldFont, + Settings = formatSettings ?? FormatSettings.Default, + + Color = color, + Font = regularFont, + DrawBehavior = FormatState.DefaultDrawBehavior + }; - var innerOffset = new Vector2(); for (var i = 0; i < text.Length; i++) { - // check if the current character's index has a formatting code - codes.TryGetValue(i, out var code); - if (code != null) { - // if so, apply it - switch (code.CodeType) { - case FormattingCode.Type.Color: - currColor = code.Color.CopyAlpha(color); - break; - case FormattingCode.Type.Style: - switch (code.Style) { - case TextStyle.Regular: - currFont = regularFont; - break; - case TextStyle.Bold: - currFont = boldFont ?? regularFont; - break; - case TextStyle.Italic: - currFont = italicFont ?? regularFont; - break; - } - currStyle = code.Style; - break; - case FormattingCode.Type.Icon: - batch.Draw(code.Icon.CurrentRegion, new RectangleF(pos + innerOffset, new Vector2(regularFont.LineHeight * scale)), color, 0, Vector2.Zero, SpriteEffects.None, depth); - break; - case FormattingCode.Type.Animation: - currAnim = (AnimationCode) code; - animStart = i; - break; - } - } + state.CurrentIndex = i; + if (codes.TryGetValue(i, out var code)) + code.ModifyFormatting(ref state); var c = text[i]; var cSt = c.ToString(); if (c == '\n') { - innerOffset.X = 0; - innerOffset.Y += regularFont.LineHeight * scale; + state.InnerOffset = new Vector2(0, state.InnerOffset.Y + regularFont.LineHeight * scale); } else { - if (currStyle == TextStyle.Shadow) - currAnim.Draw(currAnim, settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset + settings.DropShadowOffset * scale, settings.DropShadowColor, scale, depth); - currAnim.Draw(currAnim, settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset, currColor, scale, depth); - innerOffset.X += regularFont.MeasureString(cSt).X * scale; + state.DrawBehavior(state, batch, text, i, cSt, pos + state.InnerOffset, scale, depth); + state.InnerOffset += new Vector2(regularFont.MeasureString(cSt).X * scale, 0); } } } - private static FormattingCode FromMatch(Capture match) { + private static FormattingCode FromMatch(Capture match, int index) { var rawCode = match.Value.Substring(1, match.Value.Length - 2).ToLowerInvariant(); foreach (var code in FormattingCodes) { var m = code.Key.Match(rawCode); if (m.Success) - return code.Value(m); + return code.Value(m, index); } return null; }