mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-26 06:28:35 +01:00
Generified UiMarkdownParser by adding abstract UiParser
This commit is contained in:
parent
963ea557e8
commit
2d3d93c610
3 changed files with 256 additions and 213 deletions
|
@ -31,6 +31,7 @@ Additions
|
||||||
Improvements
|
Improvements
|
||||||
- Allow elements to auto-adjust their size even when their children are aligned oddly
|
- Allow elements to auto-adjust their size even when their children are aligned oddly
|
||||||
- Close other dropdowns when opening a dropdown
|
- Close other dropdowns when opening a dropdown
|
||||||
|
- Generified UiMarkdownParser by adding abstract UiParser
|
||||||
|
|
||||||
Fixes
|
Fixes
|
||||||
- Fixed parents of elements that prevent spill not being notified properly
|
- Fixed parents of elements that prevent spill not being notified properly
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
|
||||||
using MLEM.Formatting;
|
|
||||||
using MLEM.Misc;
|
|
||||||
using MLEM.Textures;
|
|
||||||
using MLEM.Ui.Elements;
|
using MLEM.Ui.Elements;
|
||||||
using MLEM.Ui.Style;
|
|
||||||
|
|
||||||
namespace MLEM.Ui.Parsers {
|
namespace MLEM.Ui.Parsers {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class for parsing Markdown strings into a set of MLEM.Ui elements with styling for each individual <see cref="ElementType"/>.
|
/// A class for parsing Markdown strings into a set of MLEM.Ui elements with styling for each individual <see cref="UiParser.ElementType"/>.
|
||||||
/// To parse, use <see cref="Parse"/> or <see cref="ParseInto"/>. To style the parsed output, use <see cref="Style{T}"/> before parsing.
|
/// To parse, use <see cref="UiParser.Parse"/> or <see cref="UiParser.ParseInto"/>. To style the parsed output, use <see cref="UiParser.Style{T}"/> before parsing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Note that this parser is rather rudimentary and doesn't deal well with very complex Markdown documents. Missing features are as follows:
|
/// Note that this parser is rather rudimentary and doesn't deal well with very complex Markdown documents. Missing features are as follows:
|
||||||
|
@ -25,106 +17,18 @@ namespace MLEM.Ui.Parsers {
|
||||||
/// <item><description>Tables</description></item>
|
/// <item><description>Tables</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class UiMarkdownParser {
|
public class UiMarkdownParser : UiParser {
|
||||||
|
|
||||||
private static readonly ElementType[] ElementTypes = EnumHelper.GetValues<ElementType>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The base path for markdown images, which is prepended to the image link.
|
|
||||||
/// </summary>
|
|
||||||
public string ImageBasePath;
|
|
||||||
/// <summary>
|
|
||||||
/// An action that is invoked when an image fails to load while parsing.
|
|
||||||
/// This action receives the expected location of the image, as well as the <see cref="Exception"/> that occured.
|
|
||||||
/// </summary>
|
|
||||||
public Action<string, Exception> ImageExceptionHandler;
|
|
||||||
/// <summary>
|
|
||||||
/// The graphics device that should be used when loading images and other graphics-dependent content.
|
|
||||||
/// </summary>
|
|
||||||
public GraphicsDevice GraphicsDevice;
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the font used for inline code as well as code blocks.
|
|
||||||
/// This only has an effect if a font with this name is added to the used <see cref="UiStyle"/>'s <see cref="UiStyle.AdditionalFonts"/>.
|
|
||||||
/// This defaults to "Monospaced" if default styling is applied in <see cref="UiMarkdownParser(bool)"/>.
|
|
||||||
/// </summary>
|
|
||||||
public string CodeFont;
|
|
||||||
|
|
||||||
private readonly Dictionary<ElementType, Action<Element>> elementStyles = new Dictionary<ElementType, Action<Element>>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new UI markdown parser and optionally initializes some default style settings.
|
/// Creates a new UI markdown parser and optionally initializes some default style settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="applyDefaultStyling">Whether default style settings should be applied.</param>
|
/// <param name="applyDefaultStyling">Whether default style settings should be applied.</param>
|
||||||
public UiMarkdownParser(bool applyDefaultStyling = true) {
|
public UiMarkdownParser(bool applyDefaultStyling = true) : base(applyDefaultStyling) {}
|
||||||
if (applyDefaultStyling) {
|
|
||||||
this.CodeFont = "Monospaced";
|
|
||||||
this.Style<VerticalSpace>(ElementType.VerticalSpace, v => v.Size = new Vector2(1, 5));
|
|
||||||
for (var i = 0; i < 6; i++) {
|
|
||||||
var level = i;
|
|
||||||
this.Style<Paragraph>(UiMarkdownParser.ElementTypes[Array.IndexOf(UiMarkdownParser.ElementTypes, ElementType.Header1) + i], p => {
|
|
||||||
p.Alignment = TextAlignment.Center;
|
|
||||||
p.TextScaleMultiplier = 2 - level * 0.15F;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Parses the given markdown string into a set of elements (using <see cref="Parse"/>) and adds them as children to the givem <paramref name="element"/>.
|
protected override IEnumerable<(ElementType, Element)> ParseUnstyled(string raw) {
|
||||||
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="markdown">The markdown to parse.</param>
|
|
||||||
/// <param name="element">The element to add the parsed elements to.</param>
|
|
||||||
/// <returns>The <paramref name="element"/>, for chaining.</returns>
|
|
||||||
public Element ParseInto(string markdown, Element element) {
|
|
||||||
foreach (var (_, e) in this.Parse(markdown))
|
|
||||||
element.AddChild(e);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses the given markdown string into a set of elements and returns them along with their <see cref="ElementType"/>.
|
|
||||||
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="markdown">The markdown to parse.</param>
|
|
||||||
/// <returns>The parsed elements.</returns>
|
|
||||||
public IEnumerable<(ElementType, Element)> Parse(string markdown) {
|
|
||||||
foreach (var (t, e) in this.ParseUnstyled(markdown)) {
|
|
||||||
if (this.elementStyles.TryGetValue(t, out var style))
|
|
||||||
style.Invoke(e);
|
|
||||||
yield return (t, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies an action to be invoked when a new element with the given <see cref="ElementType"/> is parsed in <see cref="Parse"/> or <see cref="ParseInto"/>.
|
|
||||||
/// These actions can be used to modify the style properties of the created elements.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="types">The element types that should be styled. Can be a combined flag.</param>
|
|
||||||
/// <param name="style">The action that styles the elements with the given element type.</param>
|
|
||||||
/// <param name="add">Whether the <paramref name="style"/> function should be added to the existing style settings, or replace them.</param>
|
|
||||||
/// <typeparam name="T">The type of elements that the given <see cref="ElementType"/> flags are expected to be.</typeparam>
|
|
||||||
/// <returns>This parser, for chaining.</returns>
|
|
||||||
public UiMarkdownParser Style<T>(ElementType types, Action<T> style, bool add = false) where T : Element {
|
|
||||||
foreach (var type in UiMarkdownParser.ElementTypes) {
|
|
||||||
if (types.HasFlag(type)) {
|
|
||||||
if (add && this.elementStyles.ContainsKey(type)) {
|
|
||||||
this.elementStyles[type] += Action;
|
|
||||||
} else {
|
|
||||||
this.elementStyles[type] = Action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
|
|
||||||
void Action(Element e) {
|
|
||||||
style.Invoke(e as T ?? throw new ArgumentException($"Expected {typeof(T)} for style action but got {e.GetType()}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<(ElementType, Element)> ParseUnstyled(string markdown) {
|
|
||||||
var inCodeBlock = false;
|
var inCodeBlock = false;
|
||||||
foreach (var line in markdown.Split('\n')) {
|
foreach (var line in raw.Split('\n')) {
|
||||||
// code blocks
|
// code blocks
|
||||||
if (line.Trim().StartsWith("```")) {
|
if (line.Trim().StartsWith("```")) {
|
||||||
inCodeBlock = !inCodeBlock;
|
inCodeBlock = !inCodeBlock;
|
||||||
|
@ -151,45 +55,7 @@ namespace MLEM.Ui.Parsers {
|
||||||
// images
|
// images
|
||||||
var imageMatch = Regex.Match(line, @"!\[\]\(([^)]+)\)");
|
var imageMatch = Regex.Match(line, @"!\[\]\(([^)]+)\)");
|
||||||
if (imageMatch.Success) {
|
if (imageMatch.Success) {
|
||||||
if (this.GraphicsDevice == null)
|
yield return (ElementType.Image, this.ParseImage(imageMatch.Groups[1].Value));
|
||||||
throw new NullReferenceException("A markdown parser requires a GraphicsDevice for parsing images");
|
|
||||||
|
|
||||||
TextureRegion image = null;
|
|
||||||
LoadImageAsync();
|
|
||||||
yield return (ElementType.Image, new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
|
|
||||||
OnDisposed = e => image?.Texture.Dispose()
|
|
||||||
});
|
|
||||||
|
|
||||||
async void LoadImageAsync() {
|
|
||||||
var loc = imageMatch.Groups[1].Value;
|
|
||||||
// only apply the base path for relative files
|
|
||||||
if (this.ImageBasePath != null && !loc.StartsWith("http") && !Path.IsPathRooted(loc))
|
|
||||||
loc = $"{this.ImageBasePath}/{loc}";
|
|
||||||
try {
|
|
||||||
Texture2D tex;
|
|
||||||
if (loc.StartsWith("http")) {
|
|
||||||
using (var client = new HttpClient()) {
|
|
||||||
using (var src = await client.GetStreamAsync(loc)) {
|
|
||||||
using (var memory = new MemoryStream()) {
|
|
||||||
// download the full stream before passing it to texture
|
|
||||||
await src.CopyToAsync(memory);
|
|
||||||
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
using (var stream = Path.IsPathRooted(loc) ? File.OpenRead(loc) : TitleContainer.OpenStream(loc))
|
|
||||||
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
|
|
||||||
}
|
|
||||||
image = new TextureRegion(tex);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (this.ImageExceptionHandler != null) {
|
|
||||||
this.ImageExceptionHandler.Invoke(loc, e);
|
|
||||||
} else {
|
|
||||||
throw new Exception($"Couldn't parse image {loc}, and no ImageExceptionHandler was set", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +63,7 @@ namespace MLEM.Ui.Parsers {
|
||||||
var parsedHeader = false;
|
var parsedHeader = false;
|
||||||
for (var h = 6; h >= 1; h--) {
|
for (var h = 6; h >= 1; h--) {
|
||||||
if (line.StartsWith(new string('#', h))) {
|
if (line.StartsWith(new string('#', h))) {
|
||||||
var type = UiMarkdownParser.ElementTypes[Array.IndexOf(UiMarkdownParser.ElementTypes, ElementType.Header1) + h - 1];
|
var type = UiParser.ElementTypes[Array.IndexOf(UiParser.ElementTypes, ElementType.Header1) + h - 1];
|
||||||
yield return (type, new Paragraph(Anchor.AutoLeft, 1, this.ParseParagraph(line.Substring(h).Trim())));
|
yield return (type, new Paragraph(Anchor.AutoLeft, 1, this.ParseParagraph(line.Substring(h).Trim())));
|
||||||
parsedHeader = true;
|
parsedHeader = true;
|
||||||
break;
|
break;
|
||||||
|
@ -226,75 +92,5 @@ namespace MLEM.Ui.Parsers {
|
||||||
return par;
|
return par;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A flags enumeration used by <see cref="UiMarkdownParser"/> that contains the types of elements that can be parsed and returned in <see cref="Parse"/> or <see cref="UiMarkdownParser.ParseInto"/>.
|
|
||||||
/// This is a flags enumeration so that <see cref="UiMarkdownParser.Style{T}"/> can have multiple element types being styled at the same time.
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum ElementType {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A blockquote.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Blockquote = 1,
|
|
||||||
/// <summary>
|
|
||||||
/// A vertical space, which is a gap between multiple markdown paragraphs.
|
|
||||||
/// This element type is a <see cref="VerticalSpace"/>.
|
|
||||||
/// </summary>
|
|
||||||
VerticalSpace = 2,
|
|
||||||
/// <summary>
|
|
||||||
/// An image.
|
|
||||||
/// This element type is an <see cref="Image"/>.
|
|
||||||
/// </summary>
|
|
||||||
Image = 4,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 1.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header1 = 8,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 2.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header2 = 16,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 3.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header3 = 32,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 4.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header4 = 64,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 5.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header5 = 128,
|
|
||||||
/// <summary>
|
|
||||||
/// A header with header level 6.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Header6 = 256,
|
|
||||||
/// <summary>
|
|
||||||
/// A combined flag that contains <see cref="Header1"/> through <see cref="Header6"/>.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Headers = ElementType.Header1 | ElementType.Header2 | ElementType.Header3 | ElementType.Header4 | ElementType.Header5 | ElementType.Header6,
|
|
||||||
/// <summary>
|
|
||||||
/// A paragraph, which is one line of markdown text.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
Paragraph = 512,
|
|
||||||
/// <summary>
|
|
||||||
/// A single line of a code block.
|
|
||||||
/// This element type is a <see cref="Paragraph"/>.
|
|
||||||
/// </summary>
|
|
||||||
CodeBlock = 1024
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
246
MLEM.Ui/Parsers/UiParser.cs
Normal file
246
MLEM.Ui/Parsers/UiParser.cs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using MLEM.Formatting;
|
||||||
|
using MLEM.Misc;
|
||||||
|
using MLEM.Textures;
|
||||||
|
using MLEM.Ui.Elements;
|
||||||
|
using MLEM.Ui.Style;
|
||||||
|
|
||||||
|
namespace MLEM.Ui.Parsers {
|
||||||
|
/// <summary>
|
||||||
|
/// A base class for parsing various types of formatted strings into a set of MLEM.Ui elements with styling for each individual <see cref="ElementType"/>.
|
||||||
|
/// The only parser currently implemented is <see cref="UiMarkdownParser"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class UiParser {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An array containing all of the <see cref="ElementType"/> enum values.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ElementType[] ElementTypes = EnumHelper.GetValues<ElementType>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base path for images, which is prepended to the image link.
|
||||||
|
/// </summary>
|
||||||
|
public string ImageBasePath;
|
||||||
|
/// <summary>
|
||||||
|
/// An action that is invoked when an image fails to load while parsing.
|
||||||
|
/// This action receives the expected location of the image, as well as the <see cref="Exception"/> that occured.
|
||||||
|
/// </summary>
|
||||||
|
public Action<string, Exception> ImageExceptionHandler;
|
||||||
|
/// <summary>
|
||||||
|
/// The graphics device that should be used when loading images and other graphics-dependent content.
|
||||||
|
/// </summary>
|
||||||
|
public GraphicsDevice GraphicsDevice;
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the font used for inline code as well as code blocks.
|
||||||
|
/// This only has an effect if a font with this name is added to the used <see cref="UiStyle"/>'s <see cref="UiStyle.AdditionalFonts"/>.
|
||||||
|
/// This defaults to "Monospaced" if default styling is applied in <see cref="UiParser(bool)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public string CodeFont;
|
||||||
|
|
||||||
|
private readonly Dictionary<ElementType, Action<Element>> elementStyles = new Dictionary<ElementType, Action<Element>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new UI parser and optionally initializes some default style settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applyDefaultStyling">Whether default style settings should be applied.</param>
|
||||||
|
protected UiParser(bool applyDefaultStyling) {
|
||||||
|
if (applyDefaultStyling) {
|
||||||
|
this.CodeFont = "Monospaced";
|
||||||
|
this.Style<VerticalSpace>(ElementType.VerticalSpace, v => v.Size = new Vector2(1, 5));
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
var level = i;
|
||||||
|
this.Style<Paragraph>(UiParser.ElementTypes[Array.IndexOf(UiParser.ElementTypes, UiParser.ElementType.Header1) + i], p => {
|
||||||
|
p.Alignment = TextAlignment.Center;
|
||||||
|
p.TextScaleMultiplier = 2 - level * 0.15F;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given raw formatted string into a set of elements and returns them along with their <see cref="ElementType"/>.
|
||||||
|
/// This method is used by implementors to parse specific text, and it is used by <see cref="Parse"/> and <see cref="ParseInto"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="raw">The raw string to parse.</param>
|
||||||
|
/// <returns>The parsed elements, without styling.</returns>
|
||||||
|
protected abstract IEnumerable<(ElementType, Element)> ParseUnstyled(string raw);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given raw formatted string into a set of elements and returns them along with their <see cref="ElementType"/>.
|
||||||
|
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="raw">The raw string to parse.</param>
|
||||||
|
/// <returns>The parsed elements.</returns>
|
||||||
|
public IEnumerable<(ElementType, Element)> Parse(string raw) {
|
||||||
|
foreach (var (t, e) in this.ParseUnstyled(raw)) {
|
||||||
|
if (this.elementStyles.TryGetValue(t, out var style))
|
||||||
|
style.Invoke(e);
|
||||||
|
yield return (t, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given raw formatted string into a set of elements (using <see cref="Parse"/>) and adds them as children to the givem <paramref name="element"/>.
|
||||||
|
/// During this process, the element stylings specified using <see cref="Style{T}"/> are also applied.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="raw">The raw string to parse.</param>
|
||||||
|
/// <param name="element">The element to add the parsed elements to.</param>
|
||||||
|
/// <returns>The <paramref name="element"/>, for chaining.</returns>
|
||||||
|
public Element ParseInto(string raw, Element element) {
|
||||||
|
foreach (var (_, e) in this.Parse(raw))
|
||||||
|
element.AddChild(e);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies an action to be invoked when a new element with the given <see cref="ElementType"/> is parsed in <see cref="Parse"/> or <see cref="ParseInto"/>.
|
||||||
|
/// These actions can be used to modify the style properties of the created elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="types">The element types that should be styled. Can be a combined flag.</param>
|
||||||
|
/// <param name="style">The action that styles the elements with the given element type.</param>
|
||||||
|
/// <param name="add">Whether the <paramref name="style"/> function should be added to the existing style settings, or replace them.</param>
|
||||||
|
/// <typeparam name="T">The type of elements that the given <see cref="ElementType"/> flags are expected to be.</typeparam>
|
||||||
|
/// <returns>This parser, for chaining.</returns>
|
||||||
|
public UiParser Style<T>(ElementType types, Action<T> style, bool add = false) where T : Element {
|
||||||
|
foreach (var type in UiParser.ElementTypes) {
|
||||||
|
if (types.HasFlag(type)) {
|
||||||
|
if (add && this.elementStyles.ContainsKey(type)) {
|
||||||
|
this.elementStyles[type] += Action;
|
||||||
|
} else {
|
||||||
|
this.elementStyles[type] = Action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
|
||||||
|
void Action(Element e) {
|
||||||
|
style.Invoke(e as T ?? throw new ArgumentException($"Expected {typeof(T)} for style action but got {e.GetType()}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the given path into a <see cref="Image"/> element by loading it from disk or downloading it from the internet.
|
||||||
|
/// Note that, for a <paramref name="path"/> that doesn't start with <c>http</c> and isn't rooted, the <see cref="ImageBasePath"/> is prepended automatically.
|
||||||
|
/// This method invokes an asynchronouns action, meaning the <see cref="Image"/>'s <see cref="Image.Texture"/> will likely not have loaded in when this method returns.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The absolute, relative or web path to the image.</param>
|
||||||
|
/// <returns>The loaded image.</returns>
|
||||||
|
/// <exception cref="NullReferenceException">Thrown if <see cref="GraphicsDevice"/> is null, or if there is an <see cref="Exception"/> loading the image and <see cref="ImageExceptionHandler"/> is unset.</exception>
|
||||||
|
protected Image ParseImage(string path) {
|
||||||
|
if (this.GraphicsDevice == null)
|
||||||
|
throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images");
|
||||||
|
|
||||||
|
TextureRegion image = null;
|
||||||
|
LoadImageAsync();
|
||||||
|
return new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
|
||||||
|
OnDisposed = e => image?.Texture.Dispose()
|
||||||
|
};
|
||||||
|
|
||||||
|
async void LoadImageAsync() {
|
||||||
|
// only apply the base path for relative files
|
||||||
|
if (this.ImageBasePath != null && !path.StartsWith("http") && !Path.IsPathRooted(path))
|
||||||
|
path = $"{this.ImageBasePath}/{path}";
|
||||||
|
try {
|
||||||
|
Texture2D tex;
|
||||||
|
if (path.StartsWith("http")) {
|
||||||
|
using (var client = new HttpClient()) {
|
||||||
|
using (var src = await client.GetStreamAsync(path)) {
|
||||||
|
using (var memory = new MemoryStream()) {
|
||||||
|
// download the full stream before passing it to texture
|
||||||
|
await src.CopyToAsync(memory);
|
||||||
|
tex = Texture2D.FromStream(this.GraphicsDevice, memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path))
|
||||||
|
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
|
||||||
|
}
|
||||||
|
image = new TextureRegion(tex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (this.ImageExceptionHandler != null) {
|
||||||
|
this.ImageExceptionHandler.Invoke(path, e);
|
||||||
|
} else {
|
||||||
|
throw new NullReferenceException($"Couldn't parse image {path}, and no ImageExceptionHandler was set", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A flags enumeration used by <see cref="UiParser"/> that contains the types of elements that can be parsed and returned in <see cref="Parse"/> or <see cref="ParseInto"/>.
|
||||||
|
/// This is a flags enumeration so that <see cref="Style{T}"/> can have multiple element types being styled at the same time.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum ElementType {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A blockquote.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Blockquote = 1,
|
||||||
|
/// <summary>
|
||||||
|
/// A vertical space, which is a gap between multiple paragraphs.
|
||||||
|
/// This element type is a <see cref="VerticalSpace"/>.
|
||||||
|
/// </summary>
|
||||||
|
VerticalSpace = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// An image.
|
||||||
|
/// This element type is an <see cref="Image"/>.
|
||||||
|
/// </summary>
|
||||||
|
Image = 4,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 1.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header1 = 8,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 2.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header2 = 16,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 3.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header3 = 32,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 4.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header4 = 64,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 5.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header5 = 128,
|
||||||
|
/// <summary>
|
||||||
|
/// A header with header level 6.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Header6 = 256,
|
||||||
|
/// <summary>
|
||||||
|
/// A combined flag that contains <see cref="Header1"/> through <see cref="Header6"/>.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Headers = ElementType.Header1 | ElementType.Header2 | ElementType.Header3 | ElementType.Header4 | ElementType.Header5 | ElementType.Header6,
|
||||||
|
/// <summary>
|
||||||
|
/// A paragraph, which is one line (or non-vertically spaced section) of text.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
Paragraph = 512,
|
||||||
|
/// <summary>
|
||||||
|
/// A single line of a code block.
|
||||||
|
/// This element type is a <see cref="Paragraph"/>.
|
||||||
|
/// </summary>
|
||||||
|
CodeBlock = 1024
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue