From 3968f7dfaeb577ae58c2af56d601276787658371 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Thu, 23 Feb 2023 15:18:07 +0100 Subject: [PATCH] Added a simple outline formatting code --- CHANGELOG.md | 3 +++ Demos/TextFormattingDemo.cs | 29 ++++++++++++++---------- MLEM/Formatting/Codes/OutlineCode.cs | 34 ++++++++++++++++++++++++++++ MLEM/Formatting/TextFormatter.cs | 19 ++++++++++++++++ 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 MLEM/Formatting/Codes/OutlineCode.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5620938..f505a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Jump to version: ## 6.2.0 (In Development) ### MLEM +Additions +- Added a simple outline formatting code + Fixes - Fixed control characters being included in TextInput - Fixed TextInputs behaving incorrectly when switching between multiline and single-line modes diff --git a/Demos/TextFormattingDemo.cs b/Demos/TextFormattingDemo.cs index 2ba6a02..2170d56 100644 --- a/Demos/TextFormattingDemo.cs +++ b/Demos/TextFormattingDemo.cs @@ -15,7 +15,7 @@ namespace Demos { private const string Text = "MLEM's text formatting system allows for various formatting codes to be applied in the middle of a string. Here's a demonstration of some of them.\n\n" + - "You can write in bold, italics, with an underline, strikethrough, with a drop shadow whose color and offset you can modify in each application of the code, or with various types of combined formatting codes.\n\n" + + "You can write in bold, italics, with an underline, strikethrough, with a drop shadow whose color and offset you can modify in each application of the code, with an outline that you can also modify dynamically, or with various types of combined formatting codes.\n\n" + "You can apply custom colors to text, including all default MonoGame colors and inline custom colors.\n\n" + "You can also use animations like a wobbly one, as well as create custom ones using the Code class.\n\n" + "You can also display icons in your text, and use superscript or subscript formatting!\n\n" + @@ -27,7 +27,13 @@ namespace Demos { private TokenizedString tokenizedText; private GenericFont font; private bool drawBounds; - private float scale = TextFormattingDemo.DefaultScale; + private float Scale { + get { + // calculate our scale based on how much larger the window is, so that the text scales with the window + var viewport = new Rectangle(0, 0, this.Game.Window.ClientBounds.Width, this.Game.Window.ClientBounds.Height); + return TextFormattingDemo.DefaultScale * Math.Min(viewport.Width / 1280F, viewport.Height / 720F); + } + } public TextFormattingDemo(MlemGame game) : base(game) {} @@ -35,7 +41,10 @@ namespace Demos { this.Game.Window.ClientSizeChanged += this.OnResize; // creating a new text formatter as well as a generic font to draw with - this.formatter = new TextFormatter(); + this.formatter = new TextFormatter { + DefaultShadowOffset = new Vector2(4), + DefaultOutlineThickness = 4 + }; // GenericFont and its subtypes are wrappers around various font classes, including SpriteFont, MonoGame.Extended's BitmapFont and FontStashSharp // supplying a bold and italic version of the font here allows for the bold and italic formatting codes to be used this.font = new GenericSpriteFont( @@ -50,7 +59,7 @@ namespace Demos { // tokenizing our text and splitting it to fit the screen // we specify our text alignment here too, so that all data is cached correctly for display this.tokenizedText = this.formatter.Tokenize(this.font, TextFormattingDemo.Text, TextAlignment.Center); - this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.scale, TextAlignment.Center); + this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.Scale, TextAlignment.Center); } public override void DoDraw(GameTime time) { @@ -59,7 +68,7 @@ namespace Demos { // we draw the tokenized text in the center of the screen // since the text is already center-aligned, we only need to align it on the y axis here - var size = this.tokenizedText.GetArea(Vector2.Zero, this.scale).Size; + var size = this.tokenizedText.GetArea(Vector2.Zero, this.Scale).Size; var pos = new Vector2(this.GraphicsDevice.Viewport.Width / 2, (this.GraphicsDevice.Viewport.Height - size.Y) / 2); // draw bounds, which can be toggled with B in this demo @@ -67,13 +76,13 @@ namespace Demos { var blank = this.SpriteBatch.GetBlankTexture(); this.SpriteBatch.Draw(blank, new RectangleF(pos - new Vector2(size.X / 2, 0), size), Color.Red * 0.25F); foreach (var token in this.tokenizedText.Tokens) { - foreach (var area in token.GetArea(pos, this.scale)) + foreach (var area in token.GetArea(pos, this.Scale)) this.SpriteBatch.Draw(blank, area, Color.Black * 0.25F); } } // draw the text itself - this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, this.scale, 0); + this.tokenizedText.Draw(time, this.SpriteBatch, pos, this.font, Color.White, this.Scale, 0); this.SpriteBatch.End(); } @@ -91,13 +100,9 @@ namespace Demos { } private void OnResize(object sender, EventArgs e) { - // scale our text based on window size - var viewport = new Rectangle(0, 0, this.Game.Window.ClientBounds.Width, this.Game.Window.ClientBounds.Height); - this.scale = TextFormattingDemo.DefaultScale * Math.Min(viewport.Width / 1280F, viewport.Height / 720F); - // re-split our text if the window resizes, since it depends on the window size // this doesn't require re-tokenization of the text, since TokenizedString also stores the un-split version - this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.scale, TextAlignment.Center); + this.tokenizedText.Split(this.font, this.GraphicsDevice.Viewport.Width * TextFormattingDemo.WidthMultiplier, this.Scale, TextAlignment.Center); } } diff --git a/MLEM/Formatting/Codes/OutlineCode.cs b/MLEM/Formatting/Codes/OutlineCode.cs new file mode 100644 index 0000000..026ffdd --- /dev/null +++ b/MLEM/Formatting/Codes/OutlineCode.cs @@ -0,0 +1,34 @@ +using System; +using System.Text.RegularExpressions; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MLEM.Extensions; +using MLEM.Font; +using MLEM.Misc; + +namespace MLEM.Formatting.Codes { + /// + public class OutlineCode : Code { + + private readonly Color color; + private readonly float thickness; + private readonly bool diagonals; + + /// + public OutlineCode(Match match, Regex regex, Color color, float thickness, bool diagonals) : base(match, regex) { + this.color = color; + this.thickness = thickness; + this.diagonals = diagonals; + } + + /// + public override bool DrawCharacter(GameTime time, SpriteBatch batch, int codePoint, string character, Token token, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { + foreach (var dir in this.diagonals ? Direction2Helper.AllExceptNone : Direction2Helper.Adjacent) { + var offset = Vector2.Normalize(dir.Offset().ToVector2()) * (this.thickness * scale); + font.DrawString(batch, character, pos + offset, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth); + } + return false; + } + + } +} diff --git a/MLEM/Formatting/TextFormatter.cs b/MLEM/Formatting/TextFormatter.cs index 92c6e01..6132efa 100644 --- a/MLEM/Formatting/TextFormatter.cs +++ b/MLEM/Formatting/TextFormatter.cs @@ -73,6 +73,21 @@ namespace MLEM.Formatting { /// Note that this value only has an effect on the default formatting codes created through the constructor. /// public float DefaultWobblyHeight = 1 / 8F; + /// + /// The default outline thickness used by this text formatter, which determines how the default is drawn if no custom value is used. + /// Note that this value only has an effect on the default formatting codes created through the constructor. + /// + public float DefaultOutlineThickness = 2; + /// + /// The default outline color used by this text formatter, which determines how the default is drawn if no custom value is used. + /// Note that this value only has an effect on the default formatting codes created through the constructor. + /// + public Color DefaultOutlineColor = Color.Black; + /// + /// Whether the default outline used by this text formatter should also draw outlines diagonally, which determines how the default is drawn if no custom value is used. Non-diagonally drawn outlines might generally look better when using a pixelart font. + /// Note that this value only has an effect on the default formatting codes created through the constructor. + /// + public bool OutlineDiagonals = true; /// /// Creates a new text formatter with an optional set of default formatting codes. @@ -95,6 +110,10 @@ namespace MLEM.Formatting { float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? off : this.DefaultSubOffset)); this.Codes.Add(new Regex(@""), (f, m, r) => new SubSupCode(m, r, float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? -off : this.DefaultSupOffset)); + this.Codes.Add(new Regex(@""), (f, m, r) => new OutlineCode(m, r, + m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultOutlineColor, + float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var thickness) ? thickness : this.DefaultOutlineThickness, + this.OutlineDiagonals)); } // color codes