1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-29 15:58:33 +01:00

document raw content and text formatting

This commit is contained in:
Ellpeck 2020-05-21 12:53:42 +02:00
parent 89646b1c67
commit e9cc9b7d99
26 changed files with 275 additions and 6 deletions

View file

@ -57,6 +57,7 @@ namespace MLEM.Content {
return reader; return reader;
} }
/// <inheritdoc/>
protected override void ReloadAsset<T>(string originalAssetName, T currentAsset) { protected override void ReloadAsset<T>(string originalAssetName, T currentAsset) {
this.Read(originalAssetName, currentAsset); this.Read(originalAssetName, currentAsset);
} }
@ -80,6 +81,7 @@ namespace MLEM.Content {
throw new ContentLoadException($"Asset {assetName} not found"); throw new ContentLoadException($"Asset {assetName} not found");
} }
/// <inheritdoc/>
public override void Unload() { public override void Unload() {
foreach (var d in this.disposableAssets) foreach (var d in this.disposableAssets)
d.Dispose(); d.Dispose();
@ -87,6 +89,7 @@ namespace MLEM.Content {
base.Unload(); base.Unload();
} }
/// <inheritdoc/>
public void Initialize() { public void Initialize() {
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.Xna.Framework.Content;
namespace MLEM.Content { namespace MLEM.Content {
/// <summary> /// <summary>
@ -11,14 +12,14 @@ namespace MLEM.Content {
/// Returns if the given type can be loaded by this content reader /// Returns if the given type can be loaded by this content reader
/// </summary> /// </summary>
/// <param name="t">The type of asset</param> /// <param name="t">The type of asset</param>
/// <returns>If <see cref="t"/> can be loaded by this content reader</returns> /// <returns>If the type can be loaded by this content reader</returns>
public abstract bool CanRead(Type t); public abstract bool CanRead(Type t);
/// <summary> /// <summary>
/// Reads the content file from disk and returns it. /// Reads the content file from disk and returns it.
/// </summary> /// </summary>
/// <param name="manager">The <see cref="RawContentManager"/> that is loading the asset</param> /// <param name="manager">The <see cref="RawContentManager"/> that is loading the asset</param>
/// <param name="assetPath">The full path to the asset, starting from the <see cref="RawContentManager.RootDirectory"/></param> /// <param name="assetPath">The full path to the asset, starting from the <see cref="ContentManager.RootDirectory"/></param>
/// <param name="stream">A stream that leads to this asset</param> /// <param name="stream">A stream that leads to this asset</param>
/// <param name="t">The type of asset to load</param> /// <param name="t">The type of asset to load</param>
/// <param name="existing">If this asset is being reloaded, this value contains the previous version of the asset.</param> /// <param name="existing">If this asset is being reloaded, this value contains the previous version of the asset.</param>
@ -50,9 +51,8 @@ namespace MLEM.Content {
/// Reads the content file that is represented by our generic type from disk. /// Reads the content file that is represented by our generic type from disk.
/// </summary> /// </summary>
/// <param name="manager">The <see cref="RawContentManager"/> that is loading the asset</param> /// <param name="manager">The <see cref="RawContentManager"/> that is loading the asset</param>
/// <param name="assetPath">The full path to the asset, starting from the <see cref="RawContentManager.RootDirectory"/></param> /// <param name="assetPath">The full path to the asset, starting from the <see cref="ContentManager.RootDirectory"/></param>
/// <param name="stream">A stream that leads to this asset</param> /// <param name="stream">A stream that leads to this asset</param>
/// <param name="t">The type of asset to load</param>
/// <param name="existing">If this asset is being reloaded, this value contains the previous version of the asset.</param> /// <param name="existing">If this asset is being reloaded, this value contains the previous version of the asset.</param>
/// <returns>The loaded asset</returns> /// <returns>The loaded asset</returns>
protected abstract T Read(RawContentManager manager, string assetPath, Stream stream, T existing); protected abstract T Read(RawContentManager manager, string assetPath, Stream stream, T existing);

View file

@ -3,12 +3,15 @@ using System.IO;
using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Media;
namespace MLEM.Content { namespace MLEM.Content {
/// <inheritdoc />
public class SongReader : RawContentReader<Song> { public class SongReader : RawContentReader<Song> {
/// <inheritdoc />
protected override Song Read(RawContentManager manager, string assetPath, Stream stream, Song existing) { protected override Song Read(RawContentManager manager, string assetPath, Stream stream, Song existing) {
return Song.FromUri(Path.GetFileNameWithoutExtension(assetPath), new Uri(assetPath)); return Song.FromUri(Path.GetFileNameWithoutExtension(assetPath), new Uri(assetPath));
} }
/// <inheritdoc />
public override string[] GetFileExtensions() { public override string[] GetFileExtensions() {
return new[] {"ogg", "wav", "mp3"}; return new[] {"ogg", "wav", "mp3"};
} }

View file

@ -3,12 +3,15 @@ using System.IO;
using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Audio;
namespace MLEM.Content { namespace MLEM.Content {
/// <inheritdoc />
public class SoundEffectReader : RawContentReader<SoundEffect> { public class SoundEffectReader : RawContentReader<SoundEffect> {
/// <inheritdoc />
protected override SoundEffect Read(RawContentManager manager, string assetPath, Stream stream, SoundEffect existing) { protected override SoundEffect Read(RawContentManager manager, string assetPath, Stream stream, SoundEffect existing) {
return SoundEffect.FromStream(stream); return SoundEffect.FromStream(stream);
} }
/// <inheritdoc />
public override string[] GetFileExtensions() { public override string[] GetFileExtensions() {
return new[] {"ogg", "wav", "mp3"}; return new[] {"ogg", "wav", "mp3"};
} }

View file

@ -2,8 +2,10 @@ using System.IO;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace MLEM.Content { namespace MLEM.Content {
/// <inheritdoc />
public class Texture2DReader : RawContentReader<Texture2D> { public class Texture2DReader : RawContentReader<Texture2D> {
/// <inheritdoc />
protected override Texture2D Read(RawContentManager manager, string assetPath, Stream stream, Texture2D existing) { protected override Texture2D Read(RawContentManager manager, string assetPath, Stream stream, Texture2D existing) {
if (existing != null) { if (existing != null) {
existing.Reload(stream); existing.Reload(stream);
@ -13,6 +15,7 @@ namespace MLEM.Content {
} }
} }
/// <inheritdoc />
public override string[] GetFileExtensions() { public override string[] GetFileExtensions() {
return new[] {"png", "bmp", "gif", "jpg", "tif", "dds"}; return new[] {"png", "bmp", "gif", "jpg", "tif", "dds"};
} }

View file

@ -3,16 +3,20 @@ using System.IO;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace MLEM.Content { namespace MLEM.Content {
/// <inheritdoc />
public class XmlReader : RawContentReader { public class XmlReader : RawContentReader {
/// <inheritdoc />
public override bool CanRead(Type t) { public override bool CanRead(Type t) {
return true; return true;
} }
/// <inheritdoc />
public override object Read(RawContentManager manager, string assetPath, Stream stream, Type t, object existing) { public override object Read(RawContentManager manager, string assetPath, Stream stream, Type t, object existing) {
return new XmlSerializer(t).Deserialize(stream); return new XmlSerializer(t).Deserialize(stream);
} }
/// <inheritdoc />
public override string[] GetFileExtensions() { public override string[] GetFileExtensions() {
return new[] {"xml"}; return new[] {"xml"};
} }

View file

@ -3,6 +3,9 @@ using System.Globalization;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing with <see cref="Color"/> objects
/// </summary>
public static class ColorExtensions { public static class ColorExtensions {
/// <summary> /// <summary>

View file

@ -4,6 +4,9 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing with <see cref="GraphicsDevice"/> and <see cref="GraphicsDeviceManager"/>
/// </summary>
public static class GraphicsExtensions { public static class GraphicsExtensions {
private static int lastWidth; private static int lastWidth;

View file

@ -3,6 +3,9 @@ using Microsoft.Xna.Framework;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing with <see cref="float"/>, <see cref="Vector2"/>, <see cref="Vector3"/>, <see cref="Vector4"/>, <see cref="Point"/>, <see cref="Rectangle"/> and <see cref="RectangleF"/>
/// </summary>
public static class NumberExtensions { public static class NumberExtensions {
/// <inheritdoc cref="Math.Floor(decimal)"/> /// <inheritdoc cref="Math.Floor(decimal)"/>

View file

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing with <see cref="Random"/>
/// </summary>
public static class RandomExtensions { public static class RandomExtensions {
/// <summary> /// <summary>

View file

@ -2,8 +2,15 @@ using System;
using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Audio;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing wiht <see cref="SoundEffectInstance"/>
/// </summary>
public static class SoundExtensions { public static class SoundExtensions {
/// <summary>
/// Stops and plays a sound effect instance in one call
/// </summary>
/// <param name="sound">The sound to stop and play</param>
[Obsolete("When using the .NET Core version of MonoGame, the replay issue has been fixed. Just call Play() instead.")] [Obsolete("When using the .NET Core version of MonoGame, the replay issue has been fixed. Just call Play() instead.")]
public static void Replay(this SoundEffectInstance sound) { public static void Replay(this SoundEffectInstance sound) {
sound.Stop(); sound.Stop();

View file

@ -4,6 +4,9 @@ using MLEM.Misc;
using MLEM.Textures; using MLEM.Textures;
namespace MLEM.Extensions { namespace MLEM.Extensions {
/// <summary>
/// A set of extensions for dealing with <see cref="SpriteBatch"/>
/// </summary>
public static class SpriteBatchExtensions { public static class SpriteBatchExtensions {
private static Texture2D blankTexture; private static Texture2D blankTexture;

View file

@ -171,11 +171,26 @@ namespace MLEM.Font {
} }
/// <summary>
/// An enum that represents the text alignment options for <see cref="GenericFont.DrawString(SpriteBatch,string,Vector2,TextAlign,Color)"/>
/// </summary>
public enum TextAlign { public enum TextAlign {
/// <summary>
/// The text is aligned as normal
/// </summary>
Left, Left,
/// <summary>
/// The position passed represents the center of the resulting string in the x axis
/// </summary>
Center, Center,
/// <summary>
/// The position passed represents the right edge of the resulting string
/// </summary>
Right, Right,
/// <summary>
/// The position passed represents the center of the resulting string, both in the x and y axes
/// </summary>
CenterBothAxes CenterBothAxes
} }

View file

@ -8,6 +8,9 @@ namespace MLEM.Font {
/// <inheritdoc/> /// <inheritdoc/>
public class GenericSpriteFont : GenericFont { public class GenericSpriteFont : GenericFont {
/// <summary>
/// The <see cref="SpriteFont"/> that is being wrapped by this generic font
/// </summary>
public readonly SpriteFont Font; public readonly SpriteFont Font;
/// <inheritdoc/> /// <inheritdoc/>
public override GenericFont Bold { get; } public override GenericFont Bold { get; }

View file

@ -1,11 +1,14 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class AnimatedCode : Code { public class AnimatedCode : Code {
/// <inheritdoc />
public AnimatedCode(Match match, Regex regex) : base(match, regex) { public AnimatedCode(Match match, Regex regex) : base(match, regex) {
} }
/// <inheritdoc />
public override bool EndsHere(Code other) { public override bool EndsHere(Code other) {
return other is AnimatedCode; return other is AnimatedCode;
} }

View file

@ -5,43 +5,88 @@ using MLEM.Font;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <summary>
/// An instance of a formatting code that can be used for a <see cref="TextFormatter"/>.
/// To add a new formatting code, see <see cref="TextFormatter.Codes"/>
/// </summary>
public class Code : GenericDataHolder { public class Code : GenericDataHolder {
/// <summary>
/// The regex that this code was created from
/// </summary>
public readonly Regex Regex; public readonly Regex Regex;
/// <summary>
/// The match that this code encompasses
/// </summary>
public readonly Match Match; public readonly Match Match;
/// <summary>
/// The token that this formatting code is a part of
/// </summary>
public Token Token { get; internal set; } public Token Token { get; internal set; }
/// <summary>
/// Creates a new formatting code based on a formatting code regex and its match.
/// </summary>
/// <param name="match">The match</param>
/// <param name="regex">The regex</param>
protected Code(Match match, Regex regex) { protected Code(Match match, Regex regex) {
this.Match = match; this.Match = match;
this.Regex = regex; this.Regex = regex;
} }
/// <summary>
/// Returns whether this formatting code should end when the passed formatting code starts.
/// If this method returns true, a new <see cref="Token"/> is started at its position.
/// </summary>
/// <param name="other">The code that is started here</param>
/// <returns>If this code should end</returns>
public virtual bool EndsHere(Code other) { public virtual bool EndsHere(Code other) {
return other.GetType() == this.GetType(); return other.GetType() == this.GetType();
} }
/// <inheritdoc cref="Formatting.Token.GetColor"/>
public virtual Color? GetColor(Color defaultPick) { public virtual Color? GetColor(Color defaultPick) {
return null; return null;
} }
/// <inheritdoc cref="Formatting.Token.GetFont"/>
public virtual GenericFont GetFont(GenericFont defaultPick) { public virtual GenericFont GetFont(GenericFont defaultPick) {
return null; return null;
} }
/// <summary>
/// Update this formatting code's animations etc.
/// </summary>
/// <param name="time">The game's time</param>
public virtual void Update(GameTime time) { public virtual void Update(GameTime time) {
} }
/// <summary>
/// Returns the string that this formatting code should be replaced with.
/// Usually, you'll just want an empty string here, but some formatting codes (like <see cref="ImageCode"/>) require their space to be filled by spaces.
/// </summary>
/// <param name="font">The font that is used</param>
/// <returns>The replacement string for this formatting code</returns>
public virtual string GetReplacementString(GenericFont font) { public virtual string GetReplacementString(GenericFont font) {
return string.Empty; return string.Empty;
} }
/// <inheritdoc cref="Formatting.Token.DrawCharacter"/>
public virtual 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 virtual 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) {
return false; return false;
} }
/// <inheritdoc cref="Formatting.Token.DrawSelf"/>
public virtual void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) { public virtual void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
} }
/// <summary>
/// Creates a new formatting code from the given regex and regex match.
/// <seealso cref="TextFormatter.Codes"/>
/// </summary>
/// <param name="formatter">The text formatter that created this code</param>
/// <param name="match">The match for the code's regex</param>
/// <param name="regex">The regex used to create this code</param>
public delegate Code Constructor(TextFormatter formatter, Match match, Regex regex); public delegate Code Constructor(TextFormatter formatter, Match match, Regex regex);
} }

View file

@ -2,14 +2,17 @@ using System.Text.RegularExpressions;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class ColorCode : Code { public class ColorCode : Code {
private readonly Color? color; private readonly Color? color;
/// <inheritdoc />
public ColorCode(Match match, Regex regex, Color? color) : base(match, regex) { public ColorCode(Match match, Regex regex, Color? color) : base(match, regex) {
this.color = color; this.color = color;
} }
/// <inheritdoc />
public override Color? GetColor(Color defaultPick) { public override Color? GetColor(Color defaultPick) {
return this.color; return this.color;
} }

View file

@ -3,18 +3,22 @@ using System.Text.RegularExpressions;
using MLEM.Font; using MLEM.Font;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class FontCode : Code { public class FontCode : Code {
private readonly Func<GenericFont, GenericFont> font; private readonly Func<GenericFont, GenericFont> font;
/// <inheritdoc />
public FontCode(Match match, Regex regex, Func<GenericFont, GenericFont> font) : base(match, regex) { public FontCode(Match match, Regex regex, Func<GenericFont, GenericFont> font) : base(match, regex) {
this.font = font; this.font = font;
} }
/// <inheritdoc />
public override GenericFont GetFont(GenericFont defaultPick) { public override GenericFont GetFont(GenericFont defaultPick) {
return this.font?.Invoke(defaultPick); return this.font?.Invoke(defaultPick);
} }
/// <inheritdoc />
public override bool EndsHere(Code other) { public override bool EndsHere(Code other) {
return other is FontCode; return other is FontCode;
} }

View file

@ -9,20 +9,24 @@ using MLEM.Misc;
using MLEM.Textures; using MLEM.Textures;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class ImageCode : Code { public class ImageCode : Code {
private readonly SpriteAnimation image; private readonly SpriteAnimation image;
private string replacement; private string replacement;
private float gapSize; private float gapSize;
/// <inheritdoc />
public ImageCode(Match match, Regex regex, SpriteAnimation image) : base(match, regex) { public ImageCode(Match match, Regex regex, SpriteAnimation image) : base(match, regex) {
this.image = image; this.image = image;
} }
/// <inheritdoc />
public override bool EndsHere(Code other) { public override bool EndsHere(Code other) {
return true; return true;
} }
/// <inheritdoc />
public override string GetReplacementString(GenericFont font) { public override string GetReplacementString(GenericFont font) {
if (this.replacement == null) { if (this.replacement == null) {
// use non-breaking space so that the image won't be line-splitted // use non-breaking space so that the image won't be line-splitted
@ -33,10 +37,12 @@ namespace MLEM.Formatting.Codes {
return this.replacement; return this.replacement;
} }
/// <inheritdoc />
public override void Update(GameTime time) { public override void Update(GameTime time) {
this.image.Update(time); this.image.Update(time);
} }
/// <inheritdoc />
public override void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) { public override void DrawSelf(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
var position = pos + new Vector2(this.gapSize - font.LineHeight, 0) / 2 * scale; var position = pos + new Vector2(this.gapSize - font.LineHeight, 0) / 2 * scale;
batch.Draw(this.image.CurrentRegion, new RectangleF(position, new Vector2(font.LineHeight * scale)), Color.White.CopyAlpha(color)); batch.Draw(this.image.CurrentRegion, new RectangleF(position, new Vector2(font.LineHeight * scale)), Color.White.CopyAlpha(color));
@ -44,12 +50,22 @@ namespace MLEM.Formatting.Codes {
} }
/// <summary>
/// A set of extensions that allow easily adding image formatting codes to a text formatter.
/// </summary>
public static class ImageCodeExtensions { public static class ImageCodeExtensions {
/// <summary>
/// Adds a new image formatting code to the given text formatter
/// </summary>
/// <param name="formatter">The formatter to add the code to</param>
/// <param name="name">The name of the formatting code. The regex for this code will be between angle brackets.</param>
/// <param name="image">The image to render at the code's position</param>
public static void AddImage(this TextFormatter formatter, string name, TextureRegion image) { public static void AddImage(this TextFormatter formatter, string name, TextureRegion image) {
formatter.AddImage(name, new SpriteAnimation(1, image)); formatter.AddImage(name, new SpriteAnimation(1, image));
} }
/// <inheritdoc cref="AddImage(MLEM.Formatting.TextFormatter,string,MLEM.Textures.TextureRegion)"/>
public static void AddImage(this TextFormatter formatter, string name, SpriteAnimation image) { public static void AddImage(this TextFormatter formatter, string name, SpriteAnimation image) {
formatter.Codes.Add(new Regex($"<i {name}>"), (f, m, r) => new ImageCode(m, r, image)); formatter.Codes.Add(new Regex($"<i {name}>"), (f, m, r) => new ImageCode(m, r, image));
} }

View file

@ -5,18 +5,25 @@ using Microsoft.Xna.Framework.Graphics;
using MLEM.Font; using MLEM.Font;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class LinkCode : UnderlineCode { public class LinkCode : UnderlineCode {
private readonly Func<Token, bool> isSelected; private readonly Func<Token, bool> isSelected;
/// <inheritdoc />
public LinkCode(Match match, Regex regex, float thickness, float yOffset, Func<Token, bool> isSelected) : base(match, regex, thickness, yOffset) { public LinkCode(Match match, Regex regex, float thickness, float yOffset, Func<Token, bool> isSelected) : base(match, regex, thickness, yOffset) {
this.isSelected = isSelected; this.isSelected = isSelected;
} }
/// <summary>
/// Returns true if this link formatting code is currently selected or hovered over, based on the selection function.
/// </summary>
/// <returns>True if this code is currently selected</returns>
public virtual bool IsSelected() { public virtual bool IsSelected() {
return this.isSelected(this.Token); return this.isSelected(this.Token);
} }
/// <inheritdoc />
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) {
// since we inherit from UnderlineCode, we can just call base if selected // since we inherit from UnderlineCode, we can just call base if selected
return this.IsSelected() && base.DrawCharacter(time, batch, c, cString, indexInToken, ref pos, font, ref color, ref scale, depth); return this.IsSelected() && base.DrawCharacter(time, batch, c, cString, indexInToken, ref pos, font, ref color, ref scale, depth);

View file

@ -5,16 +5,19 @@ using MLEM.Extensions;
using MLEM.Font; using MLEM.Font;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class ShadowCode : FontCode { public class ShadowCode : FontCode {
private readonly Color color; private readonly Color color;
private readonly Vector2 offset; private readonly Vector2 offset;
/// <inheritdoc />
public ShadowCode(Match match, Regex regex, Color color, Vector2 offset) : base(match, regex, null) { public ShadowCode(Match match, Regex regex, Color color, Vector2 offset) : base(match, regex, null) {
this.color = color; this.color = color;
this.offset = offset; this.offset = offset;
} }
/// <inheritdoc />
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) {
font.DrawString(batch, cString, pos + this.offset * scale, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth); font.DrawString(batch, cString, pos + this.offset * scale, this.color.CopyAlpha(color), 0, Vector2.Zero, scale, SpriteEffects.None, depth);
// we return false since we still want regular drawing to occur // we return false since we still want regular drawing to occur

View file

@ -6,16 +6,19 @@ using MLEM.Font;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class UnderlineCode : FontCode { public class UnderlineCode : FontCode {
private readonly float thickness; private readonly float thickness;
private readonly float yOffset; private readonly float yOffset;
/// <inheritdoc />
public UnderlineCode(Match match, Regex regex, float thickness, float yOffset) : base(match, regex, null) { public UnderlineCode(Match match, Regex regex, float thickness, float yOffset) : base(match, regex, null) {
this.thickness = thickness; this.thickness = thickness;
this.yOffset = yOffset; this.yOffset = yOffset;
} }
/// <inheritdoc />
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.DisplayString.Length > indexInToken + 1 && this.Token.DisplayString[indexInToken + 1] == '\n') if (c == ' ' && this.Token.DisplayString.Length > indexInToken + 1 && this.Token.DisplayString[indexInToken + 1] == '\n')

View file

@ -5,21 +5,29 @@ using Microsoft.Xna.Framework.Graphics;
using MLEM.Font; using MLEM.Font;
namespace MLEM.Formatting.Codes { namespace MLEM.Formatting.Codes {
/// <inheritdoc />
public class WobblyCode : AnimatedCode { public class WobblyCode : AnimatedCode {
private readonly float modifier; private readonly float modifier;
private readonly float heightModifier; private readonly float heightModifier;
/// <summary>
/// The time that this wobbly animation has been running for.
/// To reset its animation progress, reset this value.
/// </summary>
public TimeSpan TimeIntoAnimation; public TimeSpan TimeIntoAnimation;
/// <inheritdoc />
public WobblyCode(Match match, Regex regex, float modifier, float heightModifier) : base(match, regex) { public WobblyCode(Match match, Regex regex, float modifier, float heightModifier) : base(match, regex) {
this.modifier = modifier; this.modifier = modifier;
this.heightModifier = heightModifier; this.heightModifier = heightModifier;
} }
/// <inheritdoc />
public override void Update(GameTime time) { public override void Update(GameTime time) {
this.TimeIntoAnimation += time.ElapsedGameTime; this.TimeIntoAnimation += time.ElapsedGameTime;
} }
/// <inheritdoc />
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) {
var offset = new Vector2(0, (float) Math.Sin(this.Token.Index + indexInToken + this.TimeIntoAnimation.TotalSeconds * this.modifier) * font.LineHeight * this.heightModifier * scale); var offset = new Vector2(0, (float) Math.Sin(this.Token.Index + indexInToken + this.TimeIntoAnimation.TotalSeconds * this.modifier) * font.LineHeight * this.heightModifier * scale);
pos += offset; pos += offset;

View file

@ -10,10 +10,21 @@ using MLEM.Formatting.Codes;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Formatting { namespace MLEM.Formatting {
/// <summary>
/// A text formatter is used for drawing text using <see cref="GenericFont"/> that contains different colors, bold/italic sections and animations.
/// To format a string of text, use the codes as specified in the constructor. To tokenize and render a formatted string, use <see cref="Tokenize"/>.
/// </summary>
public class TextFormatter : GenericDataHolder { public class TextFormatter : GenericDataHolder {
/// <summary>
/// The formatting codes that this text formatter uses.
/// The <see cref="Regex"/> defines how the formatting code should be matched.
/// </summary>
public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>(); public readonly Dictionary<Regex, Code.Constructor> Codes = new Dictionary<Regex, Code.Constructor>();
/// <summary>
/// Creates a new text formatter with a set of default formatting codes.
/// </summary>
public TextFormatter() { public TextFormatter() {
// font codes // font codes
this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold)); this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold));
@ -37,6 +48,12 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex("</a>"), (f, m, r) => new AnimatedCode(m, r)); this.Codes.Add(new Regex("</a>"), (f, m, r) => new AnimatedCode(m, r));
} }
/// <summary>
/// Tokenizes a string, returning a tokenized string that is ready for splitting, measuring and drawing.
/// </summary>
/// <param name="font">The font to use for tokenization. Note that this font needs to be the same that will later be used for splitting, measuring and/or drawing.</param>
/// <param name="s">The string to tokenize</param>
/// <returns></returns>
public TokenizedString Tokenize(GenericFont font, string s) { public TokenizedString Tokenize(GenericFont font, string s) {
var tokens = new List<Token>(); var tokens = new List<Token>();
var codes = new List<Code>(); var codes = new List<Code>();

View file

@ -8,18 +8,39 @@ using MLEM.Formatting.Codes;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Formatting { namespace MLEM.Formatting {
/// <summary>
/// A part of a <see cref="TokenizedString"/> that has a certain list of formatting codes applied.
/// </summary>
public class Token : GenericDataHolder { public class Token : GenericDataHolder {
/// <summary>
/// The formatting codes that are applied on this token.
/// </summary>
public readonly Code[] AppliedCodes; public readonly Code[] AppliedCodes;
/// <summary>
/// The index in the <see cref="Substring"/> that this token starts at.
/// </summary>
public readonly int Index; public readonly int Index;
/// <summary>
/// The index in the <see cref="RawSubstring"/> that this token starts at.
/// </summary>
public readonly int RawIndex; public readonly int RawIndex;
/// <summary>
/// The substring that this token contains.
/// </summary>
public readonly string Substring; public readonly string Substring;
/// <summary>
/// The string that is displayed by this token. If the tokenized string has been split, this string will contain the newline characters.
/// </summary>
public string DisplayString => this.SplitSubstring ?? this.Substring; public string DisplayString => this.SplitSubstring ?? this.Substring;
/// <summary>
/// The substring that this token contains, without the formatting codes removed.
/// </summary>
public readonly string RawSubstring; public readonly string RawSubstring;
internal RectangleF[] Area; internal RectangleF[] Area;
internal string SplitSubstring; internal string SplitSubstring;
public Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) { internal Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) {
this.AppliedCodes = appliedCodes; this.AppliedCodes = appliedCodes;
this.Index = index; this.Index = index;
this.RawIndex = rawIndex; this.RawIndex = rawIndex;
@ -30,19 +51,53 @@ namespace MLEM.Formatting {
code.Token = this; code.Token = this;
} }
/// <summary>
/// Get the color that this token will be rendered with
/// </summary>
/// <param name="defaultPick">The default color, if none is specified</param>
/// <returns>The color to render with</returns>
public Color? GetColor(Color defaultPick) { public Color? GetColor(Color defaultPick) {
return this.AppliedCodes.Select(c => c.GetColor(defaultPick)).FirstOrDefault(c => c.HasValue); return this.AppliedCodes.Select(c => c.GetColor(defaultPick)).FirstOrDefault(c => c.HasValue);
} }
/// <summary>
/// Get the font that this token will be rendered with
/// </summary>
/// <param name="defaultPick">The default font, if none is specified</param>
/// <returns>The font to render with</returns>
public GenericFont GetFont(GenericFont defaultPick) { public GenericFont GetFont(GenericFont defaultPick) {
return this.AppliedCodes.Select(c => c.GetFont(defaultPick)).FirstOrDefault(f => f != null); return this.AppliedCodes.Select(c => c.GetFont(defaultPick)).FirstOrDefault(f => f != null);
} }
/// <summary>
/// Draws the token itself, including all of the <see cref="Code"/> instances that this token contains.
/// Note that, to draw the token's actual string, <see cref="DrawCharacter"/> is used.
/// </summary>
/// <param name="time">The time</param>
/// <param name="batch">The sprite batch to use</param>
/// <param name="pos">The position to draw the token at</param>
/// <param name="font">The font to use to draw</param>
/// <param name="color">The color to draw with</param>
/// <param name="scale">The scale to draw at</param>
/// <param name="depth">The depth to draw at</param>
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) {
foreach (var code in this.AppliedCodes) foreach (var code in this.AppliedCodes)
code.DrawSelf(time, batch, pos, font, color, scale, depth); code.DrawSelf(time, batch, pos, font, color, scale, depth);
} }
/// <summary>
/// Draws a given character using this token's formatting options.
/// </summary>
/// <param name="time">The time</param>
/// <param name="batch">The sprite batch to use</param>
/// <param name="c">The character to draw</param>
/// <param name="cString">A single-character string that contains the character to draw</param>
/// <param name="indexInToken">The index within this token that the character is at</param>
/// <param name="pos">The position to draw the token at</param>
/// <param name="font">The font to use to draw</param>
/// <param name="color">The color to draw with</param>
/// <param name="scale">The scale to draw at</param>
/// <param name="depth">The depth to draw at</param>
public void DrawCharacter(GameTime time, SpriteBatch batch, char c, string cString, int indexInToken, Vector2 pos, GenericFont font, Color color, float scale, float depth) { public void DrawCharacter(GameTime time, SpriteBatch batch, char c, string cString, int indexInToken, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
foreach (var code in this.AppliedCodes) { foreach (var code in this.AppliedCodes) {
if (code.DrawCharacter(time, batch, c, cString, indexInToken, ref pos, font, ref color, ref scale, depth)) if (code.DrawCharacter(time, batch, c, cString, indexInToken, ref pos, font, ref color, ref scale, depth))
@ -53,6 +108,14 @@ 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);
} }
/// <summary>
/// Gets a list of rectangles that encompass this token's area.
/// Note that more than one rectangle is only returned if the string has been split.
/// This can be used to invoke events when the mouse is hovered over the token, for example.
/// </summary>
/// <param name="stringPos">The position that the string is drawn at</param>
/// <param name="scale">The scale that the string is drawn at</param>
/// <returns>A set of rectangles that this token contains</returns>
public IEnumerable<RectangleF> GetArea(Vector2 stringPos, float scale) { public IEnumerable<RectangleF> GetArea(Vector2 stringPos, float scale) {
return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale)); return this.Area.Select(a => new RectangleF(stringPos + a.Location * scale, a.Size * scale));
} }

View file

@ -9,16 +9,36 @@ using MLEM.Formatting.Codes;
using MLEM.Misc; using MLEM.Misc;
namespace MLEM.Formatting { namespace MLEM.Formatting {
/// <summary>
/// A tokenized string that was created using a <see cref="TextFormatter"/>
/// </summary>
public class TokenizedString : GenericDataHolder { public class TokenizedString : GenericDataHolder {
/// <summary>
/// The raw string that was used to create this tokenized string.
/// </summary>
public readonly string RawString; public readonly string RawString;
/// <summary>
/// The <see cref="RawString"/>, but with formatting codes stripped out.
/// </summary>
public readonly string String; public readonly string String;
/// <summary>
/// The string that is actually displayed by this tokenized string.
/// If this string has been <see cref="Split"/>, this string will contain the newline characters.
/// </summary>
public string DisplayString => this.splitString ?? this.String; public string DisplayString => this.splitString ?? this.String;
/// <summary>
/// The tokens that this tokenized string contains.
/// </summary>
public readonly Token[] Tokens; public readonly Token[] Tokens;
/// <summary>
/// All of the formatting codes that are applied over this tokenized string.
/// Note that, to get a formatting code for a certain token, use <see cref="Token.AppliedCodes"/>
/// </summary>
public readonly Code[] AllCodes; public readonly Code[] AllCodes;
private string splitString; private string splitString;
public TokenizedString(GenericFont font, string rawString, string strg, Token[] tokens) { internal 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;
@ -27,6 +47,13 @@ namespace MLEM.Formatting {
this.CalculateTokenAreas(font); this.CalculateTokenAreas(font);
} }
/// <summary>
/// Splits this tokenized string, inserting newline characters if the width of the string is bigger than the maximum width.
/// <seealso cref="GenericFont.SplitString"/>
/// </summary>
/// <param name="font">The font to use for width calculations</param>
/// <param name="width">The maximum width</param>
/// <param name="scale">The scale to use fr width calculations</param>
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
@ -57,19 +84,33 @@ namespace MLEM.Formatting {
this.CalculateTokenAreas(font); this.CalculateTokenAreas(font);
} }
/// <inheritdoc cref="GenericFont.MeasureString(string)"/>
public Vector2 Measure(GenericFont font) { public Vector2 Measure(GenericFont font) {
return font.MeasureString(this.DisplayString); return font.MeasureString(this.DisplayString);
} }
/// <summary>
/// Updates the formatting codes in this formatted string, causing animations to animate etc.
/// </summary>
/// <param name="time">The game's time</param>
public void Update(GameTime time) { public void Update(GameTime time) {
foreach (var code in this.AllCodes) foreach (var code in this.AllCodes)
code.Update(time); code.Update(time);
} }
/// <summary>
/// Returns the token under the given position.
/// This can be used for hovering effects when the mouse is over a token, etc.
/// </summary>
/// <param name="stringPos">The position that the string is drawn at</param>
/// <param name="target">The position to use for checking the token</param>
/// <param name="scale">The scale that the string is drawn at</param>
/// <returns>The token under the target position</returns>
public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale) { public Token GetTokenUnderPos(Vector2 stringPos, Vector2 target, float scale) {
return this.Tokens.FirstOrDefault(t => t.GetArea(stringPos, scale).Any(r => r.Contains(target))); return this.Tokens.FirstOrDefault(t => t.GetArea(stringPos, scale).Any(r => r.Contains(target)));
} }
/// <inheritdoc cref="GenericFont.DrawString(SpriteBatch,string,Vector2,Color,float,Vector2,float,SpriteEffects,float)"/>
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) {