mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-22 20:58:34 +01:00
streamline ui text formatting and paragraph links
This commit is contained in:
parent
fad06f28be
commit
037ed43410
16 changed files with 138 additions and 78 deletions
|
@ -37,9 +37,8 @@ namespace Demos {
|
||||||
var style = new UntexturedStyle(this.SpriteBatch) {
|
var style = new UntexturedStyle(this.SpriteBatch) {
|
||||||
// when using a SpriteFont, use GenericSpriteFont. When using a MonoGame.Extended BitmapFont, use GenericBitmapFont.
|
// 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
|
// Wrapping fonts like this allows for both types to be usable within MLEM.Ui easily
|
||||||
Font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont")),
|
// Supplying a bold and an italic version is optional
|
||||||
BoldFont = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFontBold")),
|
Font = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFont"), LoadContent<SpriteFont>("Fonts/TestFontBold"), LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||||
ItalicFont = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
|
||||||
TextScale = 0.1F,
|
TextScale = 0.1F,
|
||||||
PanelTexture = this.testPatch,
|
PanelTexture = this.testPatch,
|
||||||
ButtonTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
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 <c Blue>formatting codes</c>, including colors and <i>text styles</i>. The names of all <c Orange>MonoGame Colors</c> can be used, as well as the codes <i>Italic</i>, <b>Bold</b>, <s>Drop Shadow'd</s> and <s><c Pink>mixed formatting</s></c>. \n<i>Even <c #ff611f82>inline custom colors</c> work!</i>"));
|
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Paragraphs can also contain <c Blue>formatting codes</c>, including colors and <i>text styles</i>. The names of all <c Orange>MonoGame Colors</c> can be used, as well as the codes <i>Italic</i>, <b>Bold</b>, <s>Drop Shadow'd</s> and <s><c Pink>mixed formatting</s></c>. \n<i>Even <c #ff611f82>inline custom colors</c> work!</i>"));
|
||||||
|
|
||||||
// adding some custom image formatting codes
|
// 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 <i Grass> images and more!"));
|
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Additionally, you can create custom formatting codes that contain <i Grass> images and more!"));
|
||||||
p.Formatter.AddImage("Grass", image.Texture);
|
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 <a wobbly>wobbly text</a> at <a wobbly 8 0.25>different intensities</a>. Of course, more animations can be added though."));
|
this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Defining text animations as formatting codes is also possible, including <a wobbly>wobbly text</a> at <a wobbly 8 0.25>different intensities</a>. Of course, more animations can be added though."));
|
||||||
|
|
||||||
this.root.AddChild(new VerticalSpace(3));
|
this.root.AddChild(new VerticalSpace(3));
|
||||||
|
|
|
@ -10,10 +10,14 @@ namespace MLEM.Extended.Font {
|
||||||
public class GenericBitmapFont : GenericFont {
|
public class GenericBitmapFont : GenericFont {
|
||||||
|
|
||||||
public readonly BitmapFont Font;
|
public readonly BitmapFont Font;
|
||||||
|
public override GenericFont Bold { get; }
|
||||||
|
public override GenericFont Italic { get; }
|
||||||
public override float LineHeight => this.Font.LineHeight;
|
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.Font = font;
|
||||||
|
this.Bold = bold != null ? new GenericBitmapFont(bold) : this;
|
||||||
|
this.Italic = italic != null ? new GenericBitmapFont(italic) : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 MeasureString(string text) {
|
public override Vector2 MeasureString(string text) {
|
||||||
|
|
|
@ -22,13 +22,13 @@ namespace MLEM.Ui.Elements {
|
||||||
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
||||||
public FormattingCodeCollection Formatting;
|
public FormattingCodeCollection Formatting;
|
||||||
public StyleProp<GenericFont> RegularFont;
|
public StyleProp<GenericFont> RegularFont;
|
||||||
|
[Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")]
|
||||||
public StyleProp<GenericFont> BoldFont;
|
public StyleProp<GenericFont> BoldFont;
|
||||||
|
[Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")]
|
||||||
public StyleProp<GenericFont> ItalicFont;
|
public StyleProp<GenericFont> ItalicFont;
|
||||||
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
||||||
public StyleProp<FormatSettings> FormatSettings;
|
public StyleProp<FormatSettings> FormatSettings;
|
||||||
public readonly TextFormatter Formatter;
|
|
||||||
public TokenizedString TokenizedText { get; private set; }
|
public TokenizedString TokenizedText { get; private set; }
|
||||||
public Token HoveredToken { get; private set; }
|
|
||||||
|
|
||||||
public StyleProp<Color> TextColor;
|
public StyleProp<Color> TextColor;
|
||||||
public StyleProp<float> TextScale;
|
public StyleProp<float> TextScale;
|
||||||
|
@ -39,6 +39,8 @@ namespace MLEM.Ui.Elements {
|
||||||
this.text = value;
|
this.text = value;
|
||||||
this.IsHidden = string.IsNullOrWhiteSpace(this.text);
|
this.IsHidden = string.IsNullOrWhiteSpace(this.text);
|
||||||
this.SetAreaDirty();
|
this.SetAreaDirty();
|
||||||
|
// cause text to be re-tokenized
|
||||||
|
this.TokenizedText = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,20 +66,6 @@ namespace MLEM.Ui.Elements {
|
||||||
this.AutoAdjustWidth = centerText;
|
this.AutoAdjustWidth = centerText;
|
||||||
this.CanBeSelected = false;
|
this.CanBeSelected = false;
|
||||||
this.CanBeMoused = false;
|
this.CanBeMoused = false;
|
||||||
|
|
||||||
this.Formatter = new TextFormatter(() => this.BoldFont, () => this.ItalicFont);
|
|
||||||
this.Formatter.Codes.Add(new Regex("<l ([^>]+)>"), (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<LinkCode>()) {
|
|
||||||
try {
|
|
||||||
Process.Start(code.Match.Groups[1].Value);
|
|
||||||
} catch (Exception) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Vector2 CalcActualSize(RectangleF parentArea) {
|
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);
|
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.TokenizedText.Split(this.RegularFont, size.X - this.ScaledPadding.Width, sc);
|
||||||
this.CanBeMoused = this.TokenizedText.AllCodes.OfType<LinkCode>().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<Link>(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;
|
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);
|
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() {
|
public override void ForceUpdateArea() {
|
||||||
if (this.GetTextCallback != null)
|
if (this.GetTextCallback != null)
|
||||||
this.Text = this.GetTextCallback(this);
|
this.Text = this.GetTextCallback(this);
|
||||||
|
|
||||||
|
if (this.TokenizedText == null)
|
||||||
|
this.TokenizedText = this.System.TextFormatter.Tokenize(this.RegularFont, this.text);
|
||||||
base.ForceUpdateArea();
|
base.ForceUpdateArea();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,10 +117,8 @@ namespace MLEM.Ui.Elements {
|
||||||
this.Text = this.GetTextCallback(this);
|
this.Text = this.GetTextCallback(this);
|
||||||
this.TimeIntoAnimation += time.ElapsedGameTime;
|
this.TimeIntoAnimation += time.ElapsedGameTime;
|
||||||
|
|
||||||
if (this.TokenizedText != null) {
|
if (this.TokenizedText != null)
|
||||||
this.TokenizedText.Update(time);
|
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) {
|
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")]
|
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
||||||
public delegate string TextModifier(string text);
|
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<LinkCode>()) {
|
||||||
|
try {
|
||||||
|
Process.Start(code.Match.Groups[1].Value);
|
||||||
|
} catch (Exception) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,7 +37,9 @@ namespace MLEM.Ui.Style {
|
||||||
public NinePatch ProgressBarProgressTexture;
|
public NinePatch ProgressBarProgressTexture;
|
||||||
public Color ProgressBarProgressColor;
|
public Color ProgressBarProgressColor;
|
||||||
public GenericFont Font;
|
public GenericFont Font;
|
||||||
|
[Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")]
|
||||||
public GenericFont BoldFont;
|
public GenericFont BoldFont;
|
||||||
|
[Obsolete("Use the new GenericFont.Bold and GenericFont.Italic instead")]
|
||||||
public GenericFont ItalicFont;
|
public GenericFont ItalicFont;
|
||||||
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
|
||||||
public FormatSettings FormatSettings;
|
public FormatSettings FormatSettings;
|
||||||
|
|
|
@ -36,6 +36,8 @@ namespace MLEM.Ui.Style {
|
||||||
|
|
||||||
private class EmptyFont : GenericFont {
|
private class EmptyFont : GenericFont {
|
||||||
|
|
||||||
|
public override GenericFont Bold => this;
|
||||||
|
public override GenericFont Italic => this;
|
||||||
public override float LineHeight => 1;
|
public override float LineHeight => 1;
|
||||||
|
|
||||||
public override Vector2 MeasureString(string text) {
|
public override Vector2 MeasureString(string text) {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MLEM.Extensions;
|
using MLEM.Extensions;
|
||||||
using MLEM.Font;
|
using MLEM.Font;
|
||||||
|
using MLEM.Formatting;
|
||||||
|
using MLEM.Formatting.Codes;
|
||||||
using MLEM.Input;
|
using MLEM.Input;
|
||||||
using MLEM.Misc;
|
using MLEM.Misc;
|
||||||
using MLEM.Textures;
|
using MLEM.Textures;
|
||||||
|
@ -50,6 +53,7 @@ namespace MLEM.Ui {
|
||||||
public float DrawAlpha = 1;
|
public float DrawAlpha = 1;
|
||||||
public BlendState BlendState;
|
public BlendState BlendState;
|
||||||
public SamplerState SamplerState = SamplerState.PointClamp;
|
public SamplerState SamplerState = SamplerState.PointClamp;
|
||||||
|
public TextFormatter TextFormatter;
|
||||||
public UiControls Controls;
|
public UiControls Controls;
|
||||||
|
|
||||||
public Element.DrawCallback OnElementDrawn = (e, time, batch, alpha) => e.OnDrawn?.Invoke(e, time, batch, alpha);
|
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)
|
if (e.OnSecondaryPressed != null)
|
||||||
e.SecondActionSound.Value?.Replay();
|
e.SecondActionSound.Value?.Replay();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.TextFormatter = new TextFormatter();
|
||||||
|
this.TextFormatter.Codes.Add(new Regex("<l ([^>]+)>"), (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) {
|
public override void Update(GameTime time) {
|
||||||
|
|
|
@ -7,6 +7,10 @@ using Microsoft.Xna.Framework.Graphics;
|
||||||
namespace MLEM.Font {
|
namespace MLEM.Font {
|
||||||
public abstract class GenericFont {
|
public abstract class GenericFont {
|
||||||
|
|
||||||
|
public abstract GenericFont Bold { get; }
|
||||||
|
|
||||||
|
public abstract GenericFont Italic { get; }
|
||||||
|
|
||||||
public abstract float LineHeight { get; }
|
public abstract float LineHeight { get; }
|
||||||
|
|
||||||
public abstract Vector2 MeasureString(string text);
|
public abstract Vector2 MeasureString(string text);
|
||||||
|
|
|
@ -8,10 +8,14 @@ namespace MLEM.Font {
|
||||||
public class GenericSpriteFont : GenericFont {
|
public class GenericSpriteFont : GenericFont {
|
||||||
|
|
||||||
public readonly SpriteFont Font;
|
public readonly SpriteFont Font;
|
||||||
|
public override GenericFont Bold { get; }
|
||||||
|
public override GenericFont Italic { get; }
|
||||||
public override float LineHeight => this.Font.LineSpacing;
|
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.Font = font;
|
||||||
|
this.Bold = bold != null ? new GenericSpriteFont(bold) : this;
|
||||||
|
this.Italic = italic != null ? new GenericSpriteFont(italic) : this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 MeasureString(string text) {
|
public override Vector2 MeasureString(string text) {
|
||||||
|
|
|
@ -20,11 +20,11 @@ namespace MLEM.Formatting.Codes {
|
||||||
return other.GetType() == this.GetType();
|
return other.GetType() == this.GetType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Color? GetColor() {
|
public virtual Color? GetColor(Color defaultPick) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual GenericFont GetFont() {
|
public virtual GenericFont GetFont(GenericFont defaultPick) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace MLEM.Formatting.Codes {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Color? GetColor() {
|
public override Color? GetColor(Color defaultPick) {
|
||||||
return this.color;
|
return this.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using MLEM.Font;
|
using MLEM.Font;
|
||||||
|
|
||||||
namespace MLEM.Formatting.Codes {
|
namespace MLEM.Formatting.Codes {
|
||||||
public class FontCode : Code {
|
public class FontCode : Code {
|
||||||
|
|
||||||
private readonly GenericFont font;
|
private readonly Func<GenericFont, GenericFont> font;
|
||||||
|
|
||||||
public FontCode(Match match, Regex regex, GenericFont font) : base(match, regex) {
|
public FontCode(Match match, Regex regex, Func<GenericFont, GenericFont> font) : base(match, regex) {
|
||||||
this.font = font;
|
this.font = font;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override GenericFont GetFont() {
|
public override GenericFont GetFont(GenericFont defaultPick) {
|
||||||
return this.font;
|
return this.font?.Invoke(defaultPick);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EndsHere(Code other) {
|
public override bool EndsHere(Code other) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace MLEM.Formatting.Codes {
|
||||||
this.isSelected = isSelected;
|
this.isSelected = isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSelected() {
|
public virtual bool IsSelected() {
|
||||||
return this.isSelected(this.Token);
|
return this.isSelected(this.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
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
|
// 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;
|
return false;
|
||||||
var size = font.MeasureString(cString) * scale;
|
var size = font.MeasureString(cString) * scale;
|
||||||
var thicc = size.Y * this.thickness;
|
var thicc = size.Y * this.thickness;
|
||||||
|
|
|
@ -14,10 +14,10 @@ namespace MLEM.Formatting {
|
||||||
|
|
||||||
public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>();
|
public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>();
|
||||||
|
|
||||||
public TextFormatter(Func<GenericFont> boldFont = null, Func<GenericFont> italicFont = null) {
|
public TextFormatter() {
|
||||||
// font codes
|
// font codes
|
||||||
this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, boldFont?.Invoke()));
|
this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold));
|
||||||
this.Codes.Add(new Regex("<i>"), (f, m, r) => new FontCode(m, r, italicFont?.Invoke()));
|
this.Codes.Add(new Regex("<i>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Italic));
|
||||||
this.Codes.Add(new Regex(@"<s(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (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(@"<s(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (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("<u>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F));
|
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, 1 / 16F, 0.85F));
|
||||||
this.Codes.Add(new Regex("</(b|i|s|u|l)>"), (f, m, r) => new FontCode(m, r, null));
|
this.Codes.Add(new Regex("</(b|i|s|u|l)>"), (f, m, r) => new FontCode(m, r, null));
|
||||||
|
@ -62,7 +62,7 @@ namespace MLEM.Formatting {
|
||||||
codes.RemoveAll(c => c.EndsHere(next));
|
codes.RemoveAll(c => c.EndsHere(next));
|
||||||
codes.Add(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) {
|
private Code GetNextCode(string s, int index) {
|
||||||
|
|
|
@ -13,8 +13,11 @@ namespace MLEM.Formatting {
|
||||||
public readonly Code[] AppliedCodes;
|
public readonly Code[] AppliedCodes;
|
||||||
public readonly int Index;
|
public readonly int Index;
|
||||||
public readonly int RawIndex;
|
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;
|
public readonly string RawSubstring;
|
||||||
|
internal RectangleF[] Area;
|
||||||
|
internal string SplitSubstring;
|
||||||
|
|
||||||
public Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) {
|
public Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) {
|
||||||
this.AppliedCodes = appliedCodes;
|
this.AppliedCodes = appliedCodes;
|
||||||
|
@ -27,12 +30,12 @@ namespace MLEM.Formatting {
|
||||||
code.Token = this;
|
code.Token = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Color? GetColor() {
|
public Color? GetColor(Color defaultPick) {
|
||||||
return this.AppliedCodes.Select(c => c.GetColor()).FirstOrDefault(c => c.HasValue);
|
return this.AppliedCodes.Select(c => c.GetColor(defaultPick)).FirstOrDefault(c => c.HasValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenericFont GetFont() {
|
public GenericFont GetFont(GenericFont defaultPick) {
|
||||||
return this.AppliedCodes.Select(c => c.GetFont()).FirstOrDefault();
|
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) {
|
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);
|
font.DrawString(batch, cString, pos, color, 0, Vector2.Zero, scale, SpriteEffects.None, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<RectangleF> GetArea(Vector2 stringPos, float scale) {
|
||||||
|
return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
@ -11,25 +12,28 @@ namespace MLEM.Formatting {
|
||||||
public class TokenizedString : GenericDataHolder {
|
public class TokenizedString : GenericDataHolder {
|
||||||
|
|
||||||
public readonly string RawString;
|
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 Token[] Tokens;
|
||||||
public readonly Code[] AllCodes;
|
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.RawString = rawString;
|
||||||
this.String = strg;
|
this.String = strg;
|
||||||
this.Tokens = tokens;
|
this.Tokens = tokens;
|
||||||
// since a code can be present in multiple tokens, we use Distinct here
|
// since a code can be present in multiple tokens, we use Distinct here
|
||||||
this.AllCodes = tokens.SelectMany(t => t.AppliedCodes).Distinct().ToArray();
|
this.AllCodes = tokens.SelectMany(t => t.AppliedCodes).Distinct().ToArray();
|
||||||
|
this.CalculateTokenAreas(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Split(GenericFont font, float width, float scale) {
|
public void Split(GenericFont font, float width, float scale) {
|
||||||
// a split string has the same character count as the input string
|
// a split string has the same character count as the input string
|
||||||
// but with newline characters added
|
// 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
|
// skip splitting logic for unformatted text
|
||||||
if (this.Tokens.Length == 1) {
|
if (this.Tokens.Length == 1) {
|
||||||
this.Tokens[0].Substring = this.String;
|
this.Tokens[0].SplitSubstring = this.splitString;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach (var token in this.Tokens) {
|
foreach (var token in this.Tokens) {
|
||||||
|
@ -37,23 +41,24 @@ namespace MLEM.Formatting {
|
||||||
var length = 0;
|
var length = 0;
|
||||||
var ret = new StringBuilder();
|
var ret = new StringBuilder();
|
||||||
// this is basically a substring function that ignores newlines for indexing
|
// 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 we're within the bounds of the token's substring, append to the new substring
|
||||||
if (index >= token.Index && length < token.Substring.Length)
|
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 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)
|
if (index >= token.Index)
|
||||||
length++;
|
length++;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
token.Substring = ret.ToString();
|
token.SplitSubstring = ret.ToString();
|
||||||
}
|
}
|
||||||
|
this.CalculateTokenAreas(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 Measure(GenericFont font) {
|
public Vector2 Measure(GenericFont font) {
|
||||||
return font.MeasureString(this.String);
|
return font.MeasureString(this.DisplayString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(GameTime time) {
|
public void Update(GameTime time) {
|
||||||
|
@ -61,34 +66,17 @@ namespace MLEM.Formatting {
|
||||||
code.Update(time);
|
code.Update(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Token GetTokenUnderPos(GenericFont font, Vector2 stringPos, Vector2 target, float scale) {
|
public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale) {
|
||||||
var innerOffset = new Vector2();
|
return this.Tokens.FirstOrDefault(t => t.GetArea(stringPos, scale).Any(r => r.Contains(target)));
|
||||||
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 void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
|
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
|
||||||
var innerOffset = new Vector2();
|
var innerOffset = new Vector2();
|
||||||
foreach (var token in this.Tokens) {
|
foreach (var token in this.Tokens) {
|
||||||
var drawFont = token.GetFont() ?? font;
|
var drawFont = token.GetFont(font) ?? font;
|
||||||
var drawColor = token.GetColor() ?? color;
|
var drawColor = token.GetColor(color) ?? color;
|
||||||
for (var i = 0; i < token.Substring.Length; i++) {
|
for (var i = 0; i < token.DisplayString.Length; i++) {
|
||||||
var c = token.Substring[i];
|
var c = token.DisplayString[i];
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
innerOffset.X = 0;
|
innerOffset.X = 0;
|
||||||
innerOffset.Y += font.LineHeight * scale;
|
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<RectangleF>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue