From 037ed434100be14c042bed4e94df46d8017d8ad9 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Sun, 17 May 2020 00:10:29 +0200 Subject: [PATCH] streamline ui text formatting and paragraph links --- Demos/UiDemo.cs | 9 ++-- MLEM.Extended/Font/GenericBitmapFont.cs | 6 ++- MLEM.Ui/Elements/Paragraph.cs | 64 ++++++++++++++-------- MLEM.Ui/Style/UiStyle.cs | 2 + MLEM.Ui/Style/UntexturedStyle.cs | 2 + MLEM.Ui/UiSystem.cs | 7 +++ MLEM/Font/GenericFont.cs | 4 ++ MLEM/Font/GenericSpriteFont.cs | 6 ++- MLEM/Formatting/Codes/Code.cs | 4 +- MLEM/Formatting/Codes/ColorCode.cs | 2 +- MLEM/Formatting/Codes/FontCode.cs | 9 ++-- MLEM/Formatting/Codes/LinkCode.cs | 2 +- MLEM/Formatting/Codes/UnderlineCode.cs | 2 +- MLEM/Formatting/TextFormatter.cs | 8 +-- MLEM/Formatting/Token.cs | 17 ++++-- MLEM/Formatting/TokenizedString.cs | 72 ++++++++++++++----------- 16 files changed, 138 insertions(+), 78 deletions(-) diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs index 299cdbc..1c112da 100644 --- a/Demos/UiDemo.cs +++ b/Demos/UiDemo.cs @@ -37,9 +37,8 @@ namespace Demos { var style = new UntexturedStyle(this.SpriteBatch) { // when using a SpriteFont, use GenericSpriteFont. When using a MonoGame.Extended BitmapFont, use GenericBitmapFont. // Wrapping fonts like this allows for both types to be usable within MLEM.Ui easily - Font = new GenericSpriteFont(LoadContent("Fonts/TestFont")), - BoldFont = new GenericSpriteFont(LoadContent("Fonts/TestFontBold")), - ItalicFont = new GenericSpriteFont(LoadContent("Fonts/TestFontItalic")), + // Supplying a bold and an italic version is optional + Font = new GenericSpriteFont(LoadContent("Fonts/TestFont"), LoadContent("Fonts/TestFontBold"), LoadContent("Fonts/TestFontItalic")), TextScale = 0.1F, PanelTexture = this.testPatch, ButtonTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4), @@ -89,8 +88,8 @@ namespace Demos { this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain formatting codes, including colors and text styles. The names of all MonoGame Colors can be used, as well as the codes Italic, Bold, Drop Shadow'd and mixed formatting. \nEven inline custom colors work!")); // adding some custom image formatting codes - var p = this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain images and more!")); - p.Formatter.AddImage("Grass", image.Texture); + this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain images and more!")); + this.UiSystem.TextFormatter.AddImage("Grass", image.Texture); this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Defining text animations as formatting codes is also possible, including wobbly text at different intensities. Of course, more animations can be added though.")); this.root.AddChild(new VerticalSpace(3)); diff --git a/MLEM.Extended/Font/GenericBitmapFont.cs b/MLEM.Extended/Font/GenericBitmapFont.cs index ef7119b..2df049a 100644 --- a/MLEM.Extended/Font/GenericBitmapFont.cs +++ b/MLEM.Extended/Font/GenericBitmapFont.cs @@ -10,10 +10,14 @@ namespace MLEM.Extended.Font { public class GenericBitmapFont : GenericFont { public readonly BitmapFont Font; + public override GenericFont Bold { get; } + public override GenericFont Italic { get; } public override float LineHeight => this.Font.LineHeight; - public GenericBitmapFont(BitmapFont font) { + public GenericBitmapFont(BitmapFont font, BitmapFont bold = null, BitmapFont italic = null) { this.Font = font; + this.Bold = bold != null ? new GenericBitmapFont(bold) : this; + this.Italic = italic != null ? new GenericBitmapFont(italic) : this; } public override Vector2 MeasureString(string text) { diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs index 041787b..35857c9 100644 --- a/MLEM.Ui/Elements/Paragraph.cs +++ b/MLEM.Ui/Elements/Paragraph.cs @@ -22,13 +22,13 @@ namespace MLEM.Ui.Elements { [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public FormattingCodeCollection Formatting; public StyleProp RegularFont; + [Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")] public StyleProp BoldFont; + [Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")] public StyleProp ItalicFont; [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public StyleProp FormatSettings; - public readonly TextFormatter Formatter; public TokenizedString TokenizedText { get; private set; } - public Token HoveredToken { get; private set; } public StyleProp TextColor; public StyleProp TextScale; @@ -39,6 +39,8 @@ namespace MLEM.Ui.Elements { this.text = value; this.IsHidden = string.IsNullOrWhiteSpace(this.text); this.SetAreaDirty(); + // cause text to be re-tokenized + this.TokenizedText = null; } } } @@ -64,20 +66,6 @@ namespace MLEM.Ui.Elements { this.AutoAdjustWidth = centerText; this.CanBeSelected = false; this.CanBeMoused = false; - - this.Formatter = new TextFormatter(() => this.BoldFont, () => this.ItalicFont); - this.Formatter.Codes.Add(new Regex("]+)>"), (f, m, r) => new LinkCode(m, r, 1 / 16F, 0.85F, t => t == this.HoveredToken)); - this.OnPressed += e => { - if (this.HoveredToken == null) - return; - foreach (var code in this.HoveredToken.AppliedCodes.OfType()) { - try { - Process.Start(code.Match.Groups[1].Value); - } catch (Exception) { - // ignored - } - } - }; } protected override Vector2 CalcActualSize(RectangleF parentArea) { @@ -92,9 +80,23 @@ namespace MLEM.Ui.Elements { return new Vector2(this.AutoAdjustWidth ? textDims.X + this.ScaledPadding.Width : size.X, textDims.Y + this.ScaledPadding.Height); } - this.TokenizedText = this.Formatter.Tokenize(this.RegularFont, this.text); this.TokenizedText.Split(this.RegularFont, size.X - this.ScaledPadding.Width, sc); - this.CanBeMoused = this.TokenizedText.AllCodes.OfType().Any(); + var linkTokens = this.TokenizedText.Tokens.Where(t => t.AppliedCodes.Any(c => c is LinkCode)).ToArray(); + // this basically checks if there are any tokens that have an area that doesn't have a link element associated with it + if (linkTokens.Any(t => !t.GetArea(Vector2.Zero, this.TextScale).All(a => this.GetChildren(c => c.PositionOffset == a.Location && c.Size == a.Size).Any()))) { + this.RemoveChildren(c => c is Link); + foreach (var link in linkTokens) { + var areas = link.GetArea(Vector2.Zero, this.TextScale).ToArray(); + for (var i = 0; i < areas.Length; i++) { + var area = areas[i]; + this.AddChild(new Link(Anchor.TopLeft, link, area.Size) { + PositionOffset = area.Location, + // only allow selecting the first part of a link + CanBeSelected = i == 0 + }); + } + } + } var dims = this.TokenizedText.Measure(this.RegularFont) * sc; return new Vector2(this.AutoAdjustWidth ? dims.X + this.ScaledPadding.Width : size.X, dims.Y + this.ScaledPadding.Height); @@ -103,6 +105,9 @@ namespace MLEM.Ui.Elements { public override void ForceUpdateArea() { if (this.GetTextCallback != null) this.Text = this.GetTextCallback(this); + + if (this.TokenizedText == null) + this.TokenizedText = this.System.TextFormatter.Tokenize(this.RegularFont, this.text); base.ForceUpdateArea(); } @@ -112,10 +117,8 @@ namespace MLEM.Ui.Elements { this.Text = this.GetTextCallback(this); this.TimeIntoAnimation += time.ElapsedGameTime; - if (this.TokenizedText != null) { + if (this.TokenizedText != null) this.TokenizedText.Update(time); - this.HoveredToken = this.TokenizedText.GetTokenUnderPos(this.RegularFont, this.DisplayArea.Location, this.Input.MousePosition.ToVector2(), this.TextScale * this.Scale); - } } public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) { @@ -147,5 +150,24 @@ namespace MLEM.Ui.Elements { [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public delegate string TextModifier(string text); + public class Link : Element { + + public readonly Token Token; + + public Link(Anchor anchor, Token token, Vector2 size) : base(anchor, size) { + this.Token = token; + this.OnPressed += e => { + foreach (var code in token.AppliedCodes.OfType()) { + try { + Process.Start(code.Match.Groups[1].Value); + } catch (Exception) { + // ignored + } + } + }; + } + + } + } } \ No newline at end of file diff --git a/MLEM.Ui/Style/UiStyle.cs b/MLEM.Ui/Style/UiStyle.cs index 7f6aba4..eb2fef8 100644 --- a/MLEM.Ui/Style/UiStyle.cs +++ b/MLEM.Ui/Style/UiStyle.cs @@ -37,7 +37,9 @@ namespace MLEM.Ui.Style { public NinePatch ProgressBarProgressTexture; public Color ProgressBarProgressColor; public GenericFont Font; + [Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")] public GenericFont BoldFont; + [Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")] public GenericFont ItalicFont; [Obsolete("Use the new text formatting system in MLEM.Formatting instead")] public FormatSettings FormatSettings; diff --git a/MLEM.Ui/Style/UntexturedStyle.cs b/MLEM.Ui/Style/UntexturedStyle.cs index 8651034..22989c4 100644 --- a/MLEM.Ui/Style/UntexturedStyle.cs +++ b/MLEM.Ui/Style/UntexturedStyle.cs @@ -36,6 +36,8 @@ namespace MLEM.Ui.Style { private class EmptyFont : GenericFont { + public override GenericFont Bold => this; + public override GenericFont Italic => this; public override float LineHeight => 1; public override Vector2 MeasureString(string text) { diff --git a/MLEM.Ui/UiSystem.cs b/MLEM.Ui/UiSystem.cs index 861cc59..5384b83 100644 --- a/MLEM.Ui/UiSystem.cs +++ b/MLEM.Ui/UiSystem.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MLEM.Extensions; using MLEM.Font; +using MLEM.Formatting; +using MLEM.Formatting.Codes; using MLEM.Input; using MLEM.Misc; using MLEM.Textures; @@ -50,6 +53,7 @@ namespace MLEM.Ui { public float DrawAlpha = 1; public BlendState BlendState; public SamplerState SamplerState = SamplerState.PointClamp; + public TextFormatter TextFormatter; public UiControls Controls; public Element.DrawCallback OnElementDrawn = (e, time, batch, alpha) => e.OnDrawn?.Invoke(e, time, batch, alpha); @@ -97,6 +101,9 @@ namespace MLEM.Ui { if (e.OnSecondaryPressed != null) e.SecondActionSound.Value?.Replay(); }; + + this.TextFormatter = new TextFormatter(); + this.TextFormatter.Codes.Add(new Regex("]+)>"), (f, m, r) => new LinkCode(m, r, 1 / 16F, 0.85F, t => this.Controls.MousedElement is Paragraph.Link link && link.Token == t)); } public override void Update(GameTime time) { diff --git a/MLEM/Font/GenericFont.cs b/MLEM/Font/GenericFont.cs index 51993dd..475b516 100644 --- a/MLEM/Font/GenericFont.cs +++ b/MLEM/Font/GenericFont.cs @@ -7,6 +7,10 @@ using Microsoft.Xna.Framework.Graphics; namespace MLEM.Font { public abstract class GenericFont { + public abstract GenericFont Bold { get; } + + public abstract GenericFont Italic { get; } + public abstract float LineHeight { get; } public abstract Vector2 MeasureString(string text); diff --git a/MLEM/Font/GenericSpriteFont.cs b/MLEM/Font/GenericSpriteFont.cs index 862eba9..a85c94e 100644 --- a/MLEM/Font/GenericSpriteFont.cs +++ b/MLEM/Font/GenericSpriteFont.cs @@ -8,10 +8,14 @@ namespace MLEM.Font { public class GenericSpriteFont : GenericFont { public readonly SpriteFont Font; + public override GenericFont Bold { get; } + public override GenericFont Italic { get; } public override float LineHeight => this.Font.LineSpacing; - public GenericSpriteFont(SpriteFont font) { + public GenericSpriteFont(SpriteFont font, SpriteFont bold = null, SpriteFont italic = null) { this.Font = font; + this.Bold = bold != null ? new GenericSpriteFont(bold) : this; + this.Italic = italic != null ? new GenericSpriteFont(italic) : this; } public override Vector2 MeasureString(string text) { diff --git a/MLEM/Formatting/Codes/Code.cs b/MLEM/Formatting/Codes/Code.cs index 4cf2db1..09d1505 100644 --- a/MLEM/Formatting/Codes/Code.cs +++ b/MLEM/Formatting/Codes/Code.cs @@ -20,11 +20,11 @@ namespace MLEM.Formatting.Codes { return other.GetType() == this.GetType(); } - public virtual Color? GetColor() { + public virtual Color? GetColor(Color defaultPick) { return null; } - public virtual GenericFont GetFont() { + public virtual GenericFont GetFont(GenericFont defaultPick) { return null; } diff --git a/MLEM/Formatting/Codes/ColorCode.cs b/MLEM/Formatting/Codes/ColorCode.cs index 216e6b4..4b51a16 100644 --- a/MLEM/Formatting/Codes/ColorCode.cs +++ b/MLEM/Formatting/Codes/ColorCode.cs @@ -10,7 +10,7 @@ namespace MLEM.Formatting.Codes { this.color = color; } - public override Color? GetColor() { + public override Color? GetColor(Color defaultPick) { return this.color; } diff --git a/MLEM/Formatting/Codes/FontCode.cs b/MLEM/Formatting/Codes/FontCode.cs index 163b1d9..c81824b 100644 --- a/MLEM/Formatting/Codes/FontCode.cs +++ b/MLEM/Formatting/Codes/FontCode.cs @@ -1,17 +1,18 @@ +using System; using System.Text.RegularExpressions; using MLEM.Font; namespace MLEM.Formatting.Codes { public class FontCode : Code { - private readonly GenericFont font; + private readonly Func font; - public FontCode(Match match, Regex regex, GenericFont font) : base(match, regex) { + public FontCode(Match match, Regex regex, Func font) : base(match, regex) { this.font = font; } - public override GenericFont GetFont() { - return this.font; + public override GenericFont GetFont(GenericFont defaultPick) { + return this.font?.Invoke(defaultPick); } public override bool EndsHere(Code other) { diff --git a/MLEM/Formatting/Codes/LinkCode.cs b/MLEM/Formatting/Codes/LinkCode.cs index 590a633..d102c82 100644 --- a/MLEM/Formatting/Codes/LinkCode.cs +++ b/MLEM/Formatting/Codes/LinkCode.cs @@ -13,7 +13,7 @@ namespace MLEM.Formatting.Codes { this.isSelected = isSelected; } - public bool IsSelected() { + public virtual bool IsSelected() { return this.isSelected(this.Token); } diff --git a/MLEM/Formatting/Codes/UnderlineCode.cs b/MLEM/Formatting/Codes/UnderlineCode.cs index 9c266e5..9855a29 100644 --- a/MLEM/Formatting/Codes/UnderlineCode.cs +++ b/MLEM/Formatting/Codes/UnderlineCode.cs @@ -18,7 +18,7 @@ namespace MLEM.Formatting.Codes { public override bool DrawCharacter(GameTime time, SpriteBatch batch, char c, string cString, int indexInToken, ref Vector2 pos, GenericFont font, ref Color color, ref float scale, float depth) { // don't underline spaces at the end of lines - if (c == ' ' && this.Token.Substring.Length > indexInToken + 1 && this.Token.Substring[indexInToken + 1] == '\n') + if (c == ' ' && this.Token.DisplayString.Length > indexInToken + 1 && this.Token.DisplayString[indexInToken + 1] == '\n') return false; var size = font.MeasureString(cString) * scale; var thicc = size.Y * this.thickness; diff --git a/MLEM/Formatting/TextFormatter.cs b/MLEM/Formatting/TextFormatter.cs index d241490..3e6ddc2 100644 --- a/MLEM/Formatting/TextFormatter.cs +++ b/MLEM/Formatting/TextFormatter.cs @@ -14,10 +14,10 @@ namespace MLEM.Formatting { public readonly Dictionary Codes = new Dictionary(); - public TextFormatter(Func boldFont = null, Func italicFont = null) { + public TextFormatter() { // font codes - this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, boldFont?.Invoke())); - this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, italicFont?.Invoke())); + this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold)); + this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, fnt => fnt.Italic)); this.Codes.Add(new Regex(@""), (f, m, r) => new ShadowCode(m, r, m.Groups[1].Success ? ColorExtensions.FromHex(m.Groups[1].Value) : Color.Black, new Vector2(float.TryParse(m.Groups[2].Value, out var offset) ? offset : 2))); this.Codes.Add(new Regex(""), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F)); this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, null)); @@ -62,7 +62,7 @@ namespace MLEM.Formatting { codes.RemoveAll(c => c.EndsHere(next)); codes.Add(next); } - return new TokenizedString(s, StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray()); + return new TokenizedString(font, s, StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray()); } private Code GetNextCode(string s, int index) { diff --git a/MLEM/Formatting/Token.cs b/MLEM/Formatting/Token.cs index 886ba3b..55ed053 100644 --- a/MLEM/Formatting/Token.cs +++ b/MLEM/Formatting/Token.cs @@ -13,8 +13,11 @@ namespace MLEM.Formatting { public readonly Code[] AppliedCodes; public readonly int Index; public readonly int RawIndex; - public string Substring { get; internal set; } + public readonly string Substring; + public string DisplayString => this.SplitSubstring ?? this.Substring; public readonly string RawSubstring; + internal RectangleF[] Area; + internal string SplitSubstring; public Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) { this.AppliedCodes = appliedCodes; @@ -27,12 +30,12 @@ namespace MLEM.Formatting { code.Token = this; } - public Color? GetColor() { - return this.AppliedCodes.Select(c => c.GetColor()).FirstOrDefault(c => c.HasValue); + public Color? GetColor(Color defaultPick) { + return this.AppliedCodes.Select(c => c.GetColor(defaultPick)).FirstOrDefault(c => c.HasValue); } - public GenericFont GetFont() { - return this.AppliedCodes.Select(c => c.GetFont()).FirstOrDefault(); + public GenericFont GetFont(GenericFont defaultPick) { + return this.AppliedCodes.Select(c => c.GetFont(defaultPick)).FirstOrDefault(); } public void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) { @@ -50,5 +53,9 @@ namespace MLEM.Formatting { font.DrawString(batch, cString, pos, color, 0, Vector2.Zero, scale, SpriteEffects.None, depth); } + public IEnumerable GetArea(Vector2 stringPos, float scale) { + return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale)); + } + } } \ No newline at end of file diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs index 2263372..021f9e8 100644 --- a/MLEM/Formatting/TokenizedString.cs +++ b/MLEM/Formatting/TokenizedString.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; @@ -11,25 +12,28 @@ namespace MLEM.Formatting { public class TokenizedString : GenericDataHolder { public readonly string RawString; - public string String { get; private set; } + public readonly string String; + public string DisplayString => this.splitString ?? this.String; public readonly Token[] Tokens; public readonly Code[] AllCodes; + private string splitString; - public TokenizedString(string rawString, string strg, Token[] tokens) { + public TokenizedString(GenericFont font, string rawString, string strg, Token[] tokens) { this.RawString = rawString; this.String = strg; this.Tokens = tokens; // since a code can be present in multiple tokens, we use Distinct here this.AllCodes = tokens.SelectMany(t => t.AppliedCodes).Distinct().ToArray(); + this.CalculateTokenAreas(font); } 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.String = font.SplitString(this.String, width, scale); + this.splitString = font.SplitString(this.String, width, scale); // skip splitting logic for unformatted text if (this.Tokens.Length == 1) { - this.Tokens[0].Substring = this.String; + this.Tokens[0].SplitSubstring = this.splitString; return; } foreach (var token in this.Tokens) { @@ -37,23 +41,24 @@ namespace MLEM.Formatting { var length = 0; var ret = new StringBuilder(); // this is basically a substring function that ignores newlines for indexing - for (var i = 0; i < this.String.Length; i++) { + for (var i = 0; i < this.splitString.Length; i++) { // if we're within the bounds of the token's substring, append to the new substring if (index >= token.Index && length < token.Substring.Length) - ret.Append(this.String[i]); + ret.Append(this.splitString[i]); // if the current char is not a newline, we simulate length increase - if (this.String[i] != '\n') { + if (this.splitString[i] != '\n') { if (index >= token.Index) length++; index++; } } - token.Substring = ret.ToString(); + token.SplitSubstring = ret.ToString(); } + this.CalculateTokenAreas(font); } public Vector2 Measure(GenericFont font) { - return font.MeasureString(this.String); + return font.MeasureString(this.DisplayString); } public void Update(GameTime time) { @@ -61,34 +66,17 @@ namespace MLEM.Formatting { code.Update(time); } - public Token GetTokenUnderPos(GenericFont font, Vector2 stringPos, Vector2 target, float scale) { - var innerOffset = new Vector2(); - foreach (var token in this.Tokens) { - var split = token.Substring.Split('\n'); - for (var i = 0; i < split.Length; i++) { - var size = font.MeasureString(split[i]) * scale; - var lineArea = new RectangleF(stringPos + innerOffset, size); - if (lineArea.Contains(target)) - return token; - - if (i < split.Length - 1) { - innerOffset.X = 0; - innerOffset.Y += font.LineHeight * scale; - } else { - innerOffset.X += size.X; - } - } - } - return null; + public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale) { + return this.Tokens.FirstOrDefault(t => t.GetArea(stringPos, scale).Any(r => r.Contains(target))); } 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; - for (var i = 0; i < token.Substring.Length; i++) { - var c = token.Substring[i]; + var drawFont = token.GetFont(font) ?? font; + var drawColor = token.GetColor(color) ?? color; + for (var i = 0; i < token.DisplayString.Length; i++) { + var c = token.DisplayString[i]; if (c == '\n') { innerOffset.X = 0; innerOffset.Y += font.LineHeight * scale; @@ -103,5 +91,25 @@ namespace MLEM.Formatting { } } + private void CalculateTokenAreas(GenericFont font) { + var innerOffset = new Vector2(); + foreach (var token in this.Tokens) { + var area = new List(); + var split = token.DisplayString.Split('\n'); + for (var i = 0; i < split.Length; i++) { + var size = font.MeasureString(split[i]); + area.Add(new RectangleF(innerOffset, size)); + + if (i < split.Length - 1) { + innerOffset.X = 0; + innerOffset.Y += font.LineHeight; + } else { + innerOffset.X += size.X; + } + } + token.Area = area.ToArray(); + } + } + } } \ No newline at end of file