mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-12-24 01:09:23 +01:00
added text formatting
This commit is contained in:
parent
b6d3496987
commit
e1baacdb0d
11 changed files with 316 additions and 29 deletions
|
@ -20,6 +20,27 @@
|
|||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFont.spritefont
|
||||
|
||||
#begin Fonts/TestFont.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFont.spritefont
|
||||
|
||||
#begin Fonts/TestFontBold.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFontBold.spritefont
|
||||
|
||||
#begin Fonts/TestFontItalic.spritefont
|
||||
/importer:FontDescriptionImporter
|
||||
/processor:FontDescriptionProcessor
|
||||
/processorParam:PremultiplyAlpha=True
|
||||
/processorParam:TextureFormat=Compressed
|
||||
/build:Fonts/TestFontItalic.spritefont
|
||||
|
||||
#begin Textures/Anim.png
|
||||
/importer:TextureImporter
|
||||
/processor:TextureProcessor
|
||||
|
|
60
Demos/Content/Fonts/TestFontBold.spritefont
Normal file
60
Demos/Content/Fonts/TestFontBold.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains an xml description of a font, and will be read by the XNA
|
||||
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||
of the font in your game, and to change the characters which are available to draw
|
||||
with.
|
||||
-->
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
|
||||
<!--
|
||||
Modify this string to change the font that will be imported.
|
||||
-->
|
||||
<FontName>Arial Bold</FontName>
|
||||
|
||||
<!--
|
||||
Size is a float value, measured in points. Modify this value to change
|
||||
the size of the font.
|
||||
-->
|
||||
<Size>32</Size>
|
||||
|
||||
<!--
|
||||
Spacing is a float value, measured in pixels. Modify this value to change
|
||||
the amount of spacing in between characters.
|
||||
-->
|
||||
<Spacing>0</Spacing>
|
||||
|
||||
<!--
|
||||
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||
will be used when placing characters.
|
||||
-->
|
||||
<UseKerning>true</UseKerning>
|
||||
|
||||
<!--
|
||||
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||
and "Bold, Italic", and are case sensitive.
|
||||
-->
|
||||
<Style>Regular</Style>
|
||||
|
||||
<!--
|
||||
If you uncomment this line, the default character will be substituted if you draw
|
||||
or measure text that contains characters which were not included in the font.
|
||||
-->
|
||||
<DefaultCharacter>*</DefaultCharacter>
|
||||
|
||||
<!--
|
||||
CharacterRegions control what letters are available in the font. Every
|
||||
character from Start to End will be built and made available for drawing. The
|
||||
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||
character set. The characters are ordered according to the Unicode standard.
|
||||
See the documentation for more information.
|
||||
-->
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>ɏ</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
60
Demos/Content/Fonts/TestFontItalic.spritefont
Normal file
60
Demos/Content/Fonts/TestFontItalic.spritefont
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file contains an xml description of a font, and will be read by the XNA
|
||||
Framework Content Pipeline. Follow the comments to customize the appearance
|
||||
of the font in your game, and to change the characters which are available to draw
|
||||
with.
|
||||
-->
|
||||
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
|
||||
<Asset Type="Graphics:FontDescription">
|
||||
|
||||
<!--
|
||||
Modify this string to change the font that will be imported.
|
||||
-->
|
||||
<FontName>Arial Italic</FontName>
|
||||
|
||||
<!--
|
||||
Size is a float value, measured in points. Modify this value to change
|
||||
the size of the font.
|
||||
-->
|
||||
<Size>32</Size>
|
||||
|
||||
<!--
|
||||
Spacing is a float value, measured in pixels. Modify this value to change
|
||||
the amount of spacing in between characters.
|
||||
-->
|
||||
<Spacing>0</Spacing>
|
||||
|
||||
<!--
|
||||
UseKerning controls the layout of the font. If this value is true, kerning information
|
||||
will be used when placing characters.
|
||||
-->
|
||||
<UseKerning>true</UseKerning>
|
||||
|
||||
<!--
|
||||
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
|
||||
and "Bold, Italic", and are case sensitive.
|
||||
-->
|
||||
<Style>Regular</Style>
|
||||
|
||||
<!--
|
||||
If you uncomment this line, the default character will be substituted if you draw
|
||||
or measure text that contains characters which were not included in the font.
|
||||
-->
|
||||
<DefaultCharacter>*</DefaultCharacter>
|
||||
|
||||
<!--
|
||||
CharacterRegions control what letters are available in the font. Every
|
||||
character from Start to End will be built and made available for drawing. The
|
||||
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
|
||||
character set. The characters are ordered according to the Unicode standard.
|
||||
See the documentation for more information.
|
||||
-->
|
||||
<CharacterRegions>
|
||||
<CharacterRegion>
|
||||
<Start> </Start>
|
||||
<End>ɏ</End>
|
||||
</CharacterRegion>
|
||||
</CharacterRegions>
|
||||
</Asset>
|
||||
</XnaContent>
|
|
@ -42,6 +42,8 @@ namespace Demos {
|
|||
// 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<SpriteFont>("Fonts/TestFont")),
|
||||
BoldFont = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFontBold")),
|
||||
ItalicFont = new GenericSpriteFont(LoadContent<SpriteFont>("Fonts/TestFontItalic")),
|
||||
TextScale = 0.1F,
|
||||
PanelTexture = this.testPatch,
|
||||
ButtonTexture = new NinePatch(new TextureRegion(this.testTexture, 24, 8, 16, 16), 4),
|
||||
|
@ -77,6 +79,7 @@ namespace Demos {
|
|||
image.IsHidden = !image.IsHidden;
|
||||
}
|
||||
});
|
||||
|
||||
root.AddChild(new VerticalSpace(3));
|
||||
root.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Note that the default style does not contain any textures or font files and, as such, is quite bland. However, the default style is quite easy to override."));
|
||||
root.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 10), "Change Style") {
|
||||
|
@ -92,6 +95,10 @@ namespace Demos {
|
|||
HoveredColor = Color.LightGray
|
||||
});
|
||||
|
||||
root.AddChild(new VerticalSpace(3));
|
||||
// a paragraph with formatting codes. To see them all or to add more, check the TextFormatting class
|
||||
root.AddChild(new Paragraph(Anchor.AutoLeft, 1,"Paragraphs can also contain [Blue]formatting codes[White], including colors and [Italic]text styles[Regular]. The names of all [Orange]MonoGame Colors[White] can be used, as well as the codes [Italic]Italic[Regular] and [Bold]Bold[Regular]. \n[Italic]Even [CornflowerBlue]Cornflower Blue[White] works!"));
|
||||
|
||||
root.AddChild(new VerticalSpace(3));
|
||||
root.AddChild(new Paragraph(Anchor.AutoCenter, 1, "Text input:", true));
|
||||
root.AddChild(new TextField(Anchor.AutoLeft, new Vector2(1, 10)) {
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace MLEM.Ui.Elements {
|
|||
public Color HoveredColor;
|
||||
public TextureRegion Checkmark;
|
||||
public Paragraph Label;
|
||||
public float TextOffsetX = 2;
|
||||
|
||||
private bool checced;
|
||||
public bool Checked {
|
||||
|
@ -35,15 +36,17 @@ namespace MLEM.Ui.Elements {
|
|||
};
|
||||
|
||||
if (label != null) {
|
||||
this.Label = new Paragraph(Anchor.CenterRight, 0, label, true);
|
||||
this.Label = new Paragraph(Anchor.CenterLeft, 0, label);
|
||||
this.AddChild(this.Label);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Point CalcActualSize(Rectangle parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
if (this.Label != null)
|
||||
this.Label.Size = new Vector2((size.X - size.Y) / this.Scale, 1);
|
||||
if (this.Label != null) {
|
||||
this.Label.Size = new Vector2((size.X - size.Y) / this.Scale - this.TextOffsetX, 1);
|
||||
this.Label.PositionOffset = new Vector2(size.Y / this.Scale + this.TextOffsetX, 0);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics;
|
|||
using MLEM.Extensions;
|
||||
using MLEM.Font;
|
||||
using MLEM.Textures;
|
||||
using MLEM.Ui.Format;
|
||||
using MLEM.Ui.Style;
|
||||
|
||||
namespace MLEM.Ui.Elements {
|
||||
|
@ -16,8 +17,10 @@ namespace MLEM.Ui.Elements {
|
|||
private float lineHeight;
|
||||
private float longestLineLength;
|
||||
private string[] splitText;
|
||||
private IGenericFont font;
|
||||
private readonly bool centerText;
|
||||
private Dictionary<int, FormattingCode> codeLocations;
|
||||
private IGenericFont regularFont;
|
||||
private IGenericFont boldFont;
|
||||
private IGenericFont italicFont;
|
||||
|
||||
public NinePatch Background;
|
||||
public Color BackgroundColor;
|
||||
|
@ -36,27 +39,28 @@ namespace MLEM.Ui.Elements {
|
|||
public TextCallback GetTextCallback;
|
||||
public float LineSpace = 1;
|
||||
|
||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false, IGenericFont font = null)
|
||||
: this(anchor, width, "", centerText, font) {
|
||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false)
|
||||
: this(anchor, width, "", centerText) {
|
||||
this.GetTextCallback = textCallback;
|
||||
this.Text = textCallback(this);
|
||||
}
|
||||
|
||||
public Paragraph(Anchor anchor, float width, string text, bool centerText = false, IGenericFont font = null) : base(anchor, new Vector2(width, 0)) {
|
||||
public Paragraph(Anchor anchor, float width, string text, bool centerText = false) : base(anchor, new Vector2(width, 0)) {
|
||||
this.text = text;
|
||||
this.font = font;
|
||||
this.centerText = centerText;
|
||||
this.AutoAdjustWidth = centerText;
|
||||
this.IgnoresMouse = true;
|
||||
}
|
||||
|
||||
protected override Point CalcActualSize(Rectangle parentArea) {
|
||||
var size = base.CalcActualSize(parentArea);
|
||||
this.splitText = this.font.SplitString(this.text, size.X - this.ScaledPadding.X * 2, this.TextScale * this.Scale).ToArray();
|
||||
var sc = this.TextScale * this.Scale;
|
||||
this.splitText = this.regularFont.SplitString(this.text.RemoveFormatting(), size.X - this.ScaledPadding.X * 2, sc).ToArray();
|
||||
this.codeLocations = this.text.GetFormattingCodes();
|
||||
|
||||
this.lineHeight = 0;
|
||||
this.longestLineLength = 0;
|
||||
foreach (var strg in this.splitText) {
|
||||
var strgScale = this.font.MeasureString(strg) * this.TextScale * this.Scale;
|
||||
var strgScale = this.regularFont.MeasureString(strg) * sc;
|
||||
if (strgScale.Y + 1 > this.lineHeight)
|
||||
this.lineHeight = strgScale.Y + 1;
|
||||
if (strgScale.X > this.longestLineLength)
|
||||
|
@ -78,13 +82,56 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
var pos = this.DisplayArea.Location.ToVector2();
|
||||
var off = offset.ToVector2();
|
||||
foreach (var line in this.splitText) {
|
||||
if (this.centerText) {
|
||||
this.font.DrawCenteredString(batch, line, pos + off + new Vector2(this.DisplayArea.Width / 2, 0), this.TextScale * this.Scale, this.TextColor * alpha);
|
||||
} else {
|
||||
this.font.DrawString(batch, line, pos + off, this.TextColor * alpha, 0, Vector2.Zero, this.TextScale * this.Scale, SpriteEffects.None, 0);
|
||||
var sc = this.TextScale * this.Scale;
|
||||
|
||||
// if we don't have any formatting codes, then we don't need to do complex drawing
|
||||
if (this.codeLocations.Count <= 0) {
|
||||
foreach (var line in this.splitText) {
|
||||
this.regularFont.DrawString(batch, line, pos + off, this.TextColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
|
||||
off.Y += this.lineHeight;
|
||||
}
|
||||
} else {
|
||||
// if we have formatting codes, we need to go through each index and see how it should be drawn
|
||||
var characterCounter = 0;
|
||||
var currColor = this.TextColor;
|
||||
var currFont = this.regularFont;
|
||||
|
||||
foreach (var line in this.splitText) {
|
||||
var lineOffset = new Vector2();
|
||||
foreach (var c in line) {
|
||||
// check if the current character's index has a formatting code
|
||||
this.codeLocations.TryGetValue(characterCounter, out var code);
|
||||
if (code != null) {
|
||||
// if so, apply it
|
||||
if (code.IsColorCode) {
|
||||
currColor = code.Color;
|
||||
} else {
|
||||
switch (code.Style) {
|
||||
case TextStyle.Regular:
|
||||
currFont = this.regularFont;
|
||||
break;
|
||||
case TextStyle.Bold:
|
||||
currFont = this.boldFont;
|
||||
break;
|
||||
case TextStyle.Italic:
|
||||
currFont = this.italicFont;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cSt = c.ToString();
|
||||
currFont.DrawString(batch, cSt, pos + off + lineOffset, currColor * alpha, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
|
||||
|
||||
// get the width based on the regular font so that the split text doesn't overshoot the borders
|
||||
// this is a bit of a hack, but bold fonts shouldn't be that much thicker so it won't look bad
|
||||
lineOffset.X += this.regularFont.MeasureString(cSt).X * sc;
|
||||
characterCounter++;
|
||||
}
|
||||
// spaces are replaced by newline characters, account for that
|
||||
characterCounter++;
|
||||
off.Y += this.lineHeight;
|
||||
}
|
||||
off.Y += this.lineHeight;
|
||||
}
|
||||
base.Draw(time, batch, alpha, offset);
|
||||
}
|
||||
|
@ -92,7 +139,9 @@ namespace MLEM.Ui.Elements {
|
|||
protected override void InitStyle(UiStyle style) {
|
||||
base.InitStyle(style);
|
||||
this.TextScale = style.TextScale;
|
||||
this.font = style.Font;
|
||||
this.regularFont = style.Font;
|
||||
this.boldFont = style.BoldFont ?? style.Font;
|
||||
this.italicFont = style.ItalicFont ?? style.Font;
|
||||
}
|
||||
|
||||
public delegate string TextCallback(Paragraph paragraph);
|
||||
|
|
|
@ -9,8 +9,8 @@ namespace MLEM.Ui.Elements {
|
|||
|
||||
public Vector2 MouseOffset = new Vector2(2, 3);
|
||||
|
||||
public Tooltip(float width, string text, IGenericFont font = null) :
|
||||
base(Anchor.TopLeft, width, text, false, font) {
|
||||
public Tooltip(float width, string text) :
|
||||
base(Anchor.TopLeft, width, text) {
|
||||
this.AutoAdjustWidth = true;
|
||||
this.Padding = new Point(2);
|
||||
}
|
||||
|
|
29
MLEM.Ui/Format/FormattingCode.cs
Normal file
29
MLEM.Ui/Format/FormattingCode.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MLEM.Ui.Format {
|
||||
public class FormattingCode {
|
||||
|
||||
public readonly Color Color;
|
||||
public readonly TextStyle Style;
|
||||
public readonly bool IsColorCode;
|
||||
|
||||
public FormattingCode(Color color) {
|
||||
this.Color = color;
|
||||
this.IsColorCode = true;
|
||||
}
|
||||
|
||||
public FormattingCode(TextStyle style) {
|
||||
this.Style = style;
|
||||
this.IsColorCode = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum TextStyle {
|
||||
|
||||
Regular,
|
||||
Bold,
|
||||
Italic
|
||||
|
||||
}
|
||||
}
|
54
MLEM.Ui/Format/TextFormatting.cs
Normal file
54
MLEM.Ui/Format/TextFormatting.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MLEM.Ui.Format {
|
||||
public static class TextFormatting {
|
||||
|
||||
private static Regex formatRegex;
|
||||
|
||||
public static readonly Dictionary<string, FormattingCode> FormattingCodes = new Dictionary<string, FormattingCode>();
|
||||
|
||||
static TextFormatting() {
|
||||
SetFormatIndicators('[', ']');
|
||||
|
||||
FormattingCodes["regular"] = new FormattingCode(TextStyle.Regular);
|
||||
FormattingCodes["italic"] = new FormattingCode(TextStyle.Italic);
|
||||
FormattingCodes["bold"] = new FormattingCode(TextStyle.Bold);
|
||||
|
||||
var colors = typeof(Color).GetProperties();
|
||||
foreach (var color in colors) {
|
||||
if (color.GetGetMethod().IsStatic)
|
||||
FormattingCodes[color.Name.ToLowerInvariant()] = new FormattingCode((Color) color.GetValue(null));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFormatIndicators(char opener, char closer) {
|
||||
// escape the opener and closer so that any character can be used
|
||||
var op = "\\" + opener;
|
||||
var cl = "\\" + closer;
|
||||
// find any text that is surrounded by the opener and closer
|
||||
formatRegex = new Regex($"{op}[^{op}{cl}]*{cl}");
|
||||
}
|
||||
|
||||
public static string RemoveFormatting(this string s) {
|
||||
return formatRegex.Replace(s, string.Empty);
|
||||
}
|
||||
|
||||
public static Dictionary<int, FormattingCode> GetFormattingCodes(this string s, bool indicesIgnoreCode = true) {
|
||||
var codes = new Dictionary<int, FormattingCode>();
|
||||
var codeLengths = 0;
|
||||
foreach (Match match in formatRegex.Matches(s)) {
|
||||
var rawCode = match.Value.Substring(1, match.Value.Length - 2).ToLowerInvariant();
|
||||
codes[match.Index - codeLengths] = FormattingCodes[rawCode];
|
||||
// if indices of formatting codes should ignore the codes themselves, then the lengths of all
|
||||
// of the codes we have sound so far needs to be subtracted from the found code's index
|
||||
if (indicesIgnoreCode)
|
||||
codeLengths += match.Length;
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -25,7 +25,8 @@ namespace MLEM.Ui.Style {
|
|||
public NinePatch TooltipBackground;
|
||||
public Color TooltipBackgroundColor;
|
||||
public IGenericFont Font;
|
||||
public IGenericFont BoldFont;
|
||||
public IGenericFont ItalicFont;
|
||||
public float TextScale = 1;
|
||||
|
||||
}
|
||||
}
|
|
@ -7,15 +7,18 @@ namespace MLEM.Extensions {
|
|||
|
||||
public static IEnumerable<string> SplitString(this SpriteFont font, string text, float width, float scale) {
|
||||
var builder = new StringBuilder();
|
||||
foreach (var word in text.Split(' ')) {
|
||||
builder.Append(word).Append(' ');
|
||||
if (font.MeasureString(builder).X * scale >= width) {
|
||||
var len = builder.Length - word.Length - 1;
|
||||
yield return builder.ToString(0, len - 1);
|
||||
builder.Remove(0, len);
|
||||
foreach (var line in text.Split('\n')) {
|
||||
foreach (var word in line.Split(' ')) {
|
||||
builder.Append(word).Append(' ');
|
||||
if (font.MeasureString(builder).X * scale >= width) {
|
||||
var len = builder.Length - word.Length - 1;
|
||||
yield return builder.ToString(0, len - 1);
|
||||
builder.Remove(0, len);
|
||||
}
|
||||
}
|
||||
yield return builder.ToString(0, builder.Length - 1);
|
||||
builder.Clear();
|
||||
}
|
||||
yield return builder.ToString(0, builder.Length - 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue