diff --git a/Demos/GameImpl.cs b/Demos/GameImpl.cs
index feea8c5..e048aba 100644
--- a/Demos/GameImpl.cs
+++ b/Demos/GameImpl.cs
@@ -61,7 +61,7 @@ namespace Demos {
IsHidden = true
});
- selection.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Select the demo you want to see below using your mouse, touch input, your keyboard or a controller. Check the demos' source code for more in-depth explanations of their functionality."));
+ selection.AddChild(new Paragraph(Anchor.AutoLeft, 1, "Select the demo you want to see below using your mouse, touch input, your keyboard or a controller. Check the demos' source code for more in-depth explanations of their functionality or the wiki for tutorials."));
selection.AddChild(new VerticalSpace(5));
foreach (var demo in Demos) {
selection.AddChild(new Button(Anchor.AutoCenter, new Vector2(1, 10), demo.Key, demo.Value.Item1) {
diff --git a/MLEM.Ui/Elements/Paragraph.cs b/MLEM.Ui/Elements/Paragraph.cs
index 92736f0..1ed171d 100644
--- a/MLEM.Ui/Elements/Paragraph.cs
+++ b/MLEM.Ui/Elements/Paragraph.cs
@@ -2,11 +2,14 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Text.RegularExpressions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Font;
using MLEM.Formatting;
+using MLEM.Formatting.Codes;
+using MLEM.Input;
using MLEM.Misc;
using MLEM.Textures;
using MLEM.Ui.Style;
@@ -25,6 +28,7 @@ namespace MLEM.Ui.Elements {
public StyleProp FormatSettings;
public readonly TextFormatter Formatter;
public TokenizedString TokenizedText { get; private set; }
+ public Token HoveredToken { get; private set; }
public StyleProp TextColor;
public StyleProp TextScale;
@@ -42,6 +46,7 @@ namespace MLEM.Ui.Elements {
public TextCallback GetTextCallback;
[Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
public TextModifier RenderedTextModifier = text => text;
+ [Obsolete("Use the new text formatting system in MLEM.Formatting instead")]
public TimeSpan TimeIntoAnimation;
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false)
@@ -59,7 +64,17 @@ namespace MLEM.Ui.Elements {
this.AutoAdjustWidth = centerText;
this.CanBeSelected = false;
this.CanBeMoused = false;
+
this.Formatter = new TextFormatter(() => this.BoldFont, () => this.ItalicFont);
+ this.Formatter.Codes.Add(new Regex("]+)>"), (f, m, r) => new LinkCode(m, r, 1 / 16F, 0.85F, t => t == this.HoveredToken, l => {
+ if (!this.Input.IsPressed(MouseButton.Left))
+ return;
+ try {
+ Process.Start(l.Match.Groups[1].Value);
+ } catch (Exception) {
+ // ignored
+ }
+ }));
}
protected override Vector2 CalcActualSize(RectangleF parentArea) {
@@ -76,6 +91,8 @@ namespace MLEM.Ui.Elements {
this.TokenizedText = this.Formatter.Tokenize(this.RegularFont, this.text);
this.TokenizedText.Split(this.RegularFont, size.X - this.ScaledPadding.Width, sc);
+ this.CanBeMoused = this.TokenizedText.AllCodes.OfType().Any();
+
var dims = this.TokenizedText.Measure(this.RegularFont) * sc;
return new Vector2(this.AutoAdjustWidth ? dims.X + this.ScaledPadding.Width : size.X, dims.Y + this.ScaledPadding.Height);
}
@@ -91,7 +108,11 @@ namespace MLEM.Ui.Elements {
if (this.GetTextCallback != null)
this.Text = this.GetTextCallback(this);
this.TimeIntoAnimation += time.ElapsedGameTime;
- this.TokenizedText?.Update(time);
+
+ if (this.TokenizedText != null) {
+ this.TokenizedText.Update(time);
+ this.HoveredToken = this.TokenizedText.GetTokenUnderPos(this.RegularFont, this.DisplayArea.Location, this.Input.MousePosition.ToVector2(), this.TextScale * this.Scale);
+ }
}
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
diff --git a/MLEM/Formatting/Codes/LinkCode.cs b/MLEM/Formatting/Codes/LinkCode.cs
new file mode 100644
index 0000000..e74f4d1
--- /dev/null
+++ b/MLEM/Formatting/Codes/LinkCode.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Text.RegularExpressions;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MLEM.Font;
+
+namespace MLEM.Formatting.Codes {
+ public class LinkCode : UnderlineCode {
+
+ private readonly Action onSelected;
+ private readonly Func isSelected;
+
+ public LinkCode(Match match, Regex regex, float thickness, float yOffset, Func isSelected, Action onSelected) : base(match, regex, thickness, yOffset) {
+ this.onSelected = onSelected;
+ this.isSelected = isSelected;
+ }
+
+ public override void Update(GameTime time) {
+ if (this.isSelected(this.Token))
+ this.onSelected(this);
+ }
+
+ 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
+ return this.isSelected(this.Token) && base.DrawCharacter(time, batch, c, cString, indexInToken, ref pos, font, ref color, ref scale, depth);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MLEM/Formatting/Codes/UnderlineCode.cs b/MLEM/Formatting/Codes/UnderlineCode.cs
index 955ef23..9c266e5 100644
--- a/MLEM/Formatting/Codes/UnderlineCode.cs
+++ b/MLEM/Formatting/Codes/UnderlineCode.cs
@@ -20,8 +20,9 @@ namespace MLEM.Formatting.Codes {
// don't underline spaces at the end of lines
if (c == ' ' && this.Token.Substring.Length > indexInToken + 1 && this.Token.Substring[indexInToken + 1] == '\n')
return false;
- var width = font.MeasureString(cString).X * scale;
- batch.Draw(batch.GetBlankTexture(), new RectangleF(pos.X, pos.Y + this.yOffset * font.LineHeight - this.thickness * scale, width, this.thickness * scale), color);
+ var size = font.MeasureString(cString) * scale;
+ var thicc = size.Y * this.thickness;
+ batch.Draw(batch.GetBlankTexture(), new RectangleF(pos.X, pos.Y + this.yOffset * size.Y - thicc, size.X, thicc), color);
return false;
}
diff --git a/MLEM/Formatting/TextFormatter.cs b/MLEM/Formatting/TextFormatter.cs
index 88443fb..d241490 100644
--- a/MLEM/Formatting/TextFormatter.cs
+++ b/MLEM/Formatting/TextFormatter.cs
@@ -19,8 +19,8 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, boldFont?.Invoke()));
this.Codes.Add(new Regex(""), (f, m, r) => new FontCode(m, r, italicFont?.Invoke()));
this.Codes.Add(new Regex(@""), (f, m, r) => new ShadowCode(m, r, m.Groups[1].Success ? ColorExtensions.FromHex(m.Groups[1].Value) : Color.Black, new Vector2(float.TryParse(m.Groups[2].Value, out var offset) ? offset : 2)));
- this.Codes.Add(new Regex(""), (f, m, r) => new UnderlineCode(m, r, 2, 0.85F));
- this.Codes.Add(new Regex("(b|i|s|u)>"), (f, m, r) => new FontCode(m, r, null));
+ this.Codes.Add(new Regex(""), (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));
// color codes
foreach (var c in typeof(Color).GetProperties()) {
diff --git a/MLEM/Formatting/Token.cs b/MLEM/Formatting/Token.cs
index 2923045..886ba3b 100644
--- a/MLEM/Formatting/Token.cs
+++ b/MLEM/Formatting/Token.cs
@@ -22,7 +22,7 @@ namespace MLEM.Formatting {
this.RawIndex = rawIndex;
this.Substring = substring;
this.RawSubstring = rawSubstring;
-
+
foreach (var code in appliedCodes)
code.Token = this;
}
diff --git a/MLEM/Formatting/TokenizedString.cs b/MLEM/Formatting/TokenizedString.cs
index 9bf864d..2263372 100644
--- a/MLEM/Formatting/TokenizedString.cs
+++ b/MLEM/Formatting/TokenizedString.cs
@@ -61,6 +61,27 @@ namespace MLEM.Formatting {
code.Update(time);
}
+ public Token GetTokenUnderPos(GenericFont font, Vector2 stringPos, Vector2 target, float scale) {
+ var innerOffset = new Vector2();
+ foreach (var token in this.Tokens) {
+ var split = token.Substring.Split('\n');
+ for (var i = 0; i < split.Length; i++) {
+ var size = font.MeasureString(split[i]) * scale;
+ var lineArea = new RectangleF(stringPos + innerOffset, size);
+ if (lineArea.Contains(target))
+ return token;
+
+ if (i < split.Length - 1) {
+ innerOffset.X = 0;
+ innerOffset.Y += font.LineHeight * scale;
+ } else {
+ innerOffset.X += size.X;
+ }
+ }
+ }
+ return null;
+ }
+
public void Draw(GameTime time, SpriteBatch batch, Vector2 pos, GenericFont font, Color color, float scale, float depth) {
var innerOffset = new Vector2();
foreach (var token in this.Tokens) {