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(""), (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(""), (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) {