mirror of
https://github.com/Ellpeck/MLEM.git
synced 2024-11-25 22:18:34 +01:00
Revert "formatting code redesign, part 1: Regex!"
This reverts commit b480026b
This commit is contained in:
parent
d78448a4ed
commit
556998239e
7 changed files with 69 additions and 105 deletions
|
@ -16,7 +16,7 @@ namespace MLEM.Ui.Elements {
|
||||||
|
|
||||||
private string text;
|
private string text;
|
||||||
private string splitText;
|
private string splitText;
|
||||||
public FormattingCodeCollection FormattingCodes { get; private set; }
|
private Dictionary<int, FormattingCode> codeLocations;
|
||||||
public StyleProp<IGenericFont> RegularFont;
|
public StyleProp<IGenericFont> RegularFont;
|
||||||
public StyleProp<IGenericFont> BoldFont;
|
public StyleProp<IGenericFont> BoldFont;
|
||||||
public StyleProp<IGenericFont> ItalicFont;
|
public StyleProp<IGenericFont> ItalicFont;
|
||||||
|
@ -36,6 +36,7 @@ namespace MLEM.Ui.Elements {
|
||||||
}
|
}
|
||||||
public bool AutoAdjustWidth;
|
public bool AutoAdjustWidth;
|
||||||
public TextCallback GetTextCallback;
|
public TextCallback GetTextCallback;
|
||||||
|
public TimeSpan TimeIntoAnimation;
|
||||||
|
|
||||||
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false)
|
public Paragraph(Anchor anchor, float width, TextCallback textCallback, bool centerText = false)
|
||||||
: this(anchor, width, "", centerText) {
|
: this(anchor, width, "", centerText) {
|
||||||
|
@ -59,7 +60,7 @@ namespace MLEM.Ui.Elements {
|
||||||
|
|
||||||
var sc = this.TextScale * this.Scale;
|
var sc = this.TextScale * this.Scale;
|
||||||
this.splitText = this.RegularFont.Value.SplitString(this.text.RemoveFormatting(this.RegularFont.Value), size.X - this.ScaledPadding.Width, sc);
|
this.splitText = this.RegularFont.Value.SplitString(this.text.RemoveFormatting(this.RegularFont.Value), size.X - this.ScaledPadding.Width, sc);
|
||||||
this.FormattingCodes = this.text.GetFormattingCodes(this.RegularFont.Value);
|
this.codeLocations = this.text.GetFormattingCodes(this.RegularFont.Value);
|
||||||
|
|
||||||
var textDims = this.RegularFont.Value.MeasureString(this.splitText) * sc;
|
var textDims = this.RegularFont.Value.MeasureString(this.splitText) * sc;
|
||||||
return new Vector2(this.AutoAdjustWidth ? textDims.X + this.ScaledPadding.Width : size.X, textDims.Y + this.ScaledPadding.Height);
|
return new Vector2(this.AutoAdjustWidth ? textDims.X + this.ScaledPadding.Width : size.X, textDims.Y + this.ScaledPadding.Height);
|
||||||
|
@ -75,8 +76,7 @@ namespace MLEM.Ui.Elements {
|
||||||
base.Update(time);
|
base.Update(time);
|
||||||
if (this.GetTextCallback != null)
|
if (this.GetTextCallback != null)
|
||||||
this.Text = this.GetTextCallback(this);
|
this.Text = this.GetTextCallback(this);
|
||||||
if (this.FormattingCodes != null)
|
this.TimeIntoAnimation += time.ElapsedGameTime;
|
||||||
this.FormattingCodes.Update(time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
|
public override void Draw(GameTime time, SpriteBatch batch, float alpha, BlendState blendState, SamplerState samplerState, Matrix matrix) {
|
||||||
|
@ -85,11 +85,11 @@ namespace MLEM.Ui.Elements {
|
||||||
|
|
||||||
var color = this.TextColor.OrDefault(Color.White) * alpha;
|
var color = this.TextColor.OrDefault(Color.White) * alpha;
|
||||||
// if we don't have any formatting codes, then we don't need to do complex drawing
|
// if we don't have any formatting codes, then we don't need to do complex drawing
|
||||||
if (this.FormattingCodes.Count <= 0) {
|
if (this.codeLocations.Count <= 0) {
|
||||||
this.RegularFont.Value.DrawString(batch, this.splitText, pos, color, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
|
this.RegularFont.Value.DrawString(batch, this.splitText, pos, color, 0, Vector2.Zero, sc, SpriteEffects.None, 0);
|
||||||
} else {
|
} else {
|
||||||
// if we have formatting codes, we should do it
|
// if we have formatting codes, we should do it
|
||||||
this.RegularFont.Value.DrawFormattedString(batch, pos, this.splitText, this.FormattingCodes, color, sc, this.BoldFont.Value, this.ItalicFont.Value, 0, this.FormatSettings);
|
this.RegularFont.Value.DrawFormattedString(batch, pos, this.splitText, this.codeLocations, color, sc, this.BoldFont.Value, this.ItalicFont.Value, 0, this.TimeIntoAnimation, this.FormatSettings);
|
||||||
}
|
}
|
||||||
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
|
base.Draw(time, batch, alpha, blendState, samplerState, matrix);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,9 +49,13 @@ namespace MLEM.Animations {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(GameTime time) {
|
public void Update(GameTime time) {
|
||||||
|
this.SetTime(this.TimeIntoAnimation + time.ElapsedGameTime.TotalSeconds * this.SpeedMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetTime(double totalTime) {
|
||||||
if (this.IsFinished || this.IsPaused)
|
if (this.IsFinished || this.IsPaused)
|
||||||
return;
|
return;
|
||||||
this.TimeIntoAnimation += time.ElapsedGameTime.TotalSeconds * this.SpeedMultiplier;
|
this.TimeIntoAnimation = totalTime;
|
||||||
if (this.TimeIntoAnimation >= this.TotalTime) {
|
if (this.TimeIntoAnimation >= this.TotalTime) {
|
||||||
if (!this.IsLooping) {
|
if (!this.IsLooping) {
|
||||||
this.IsFinished = true;
|
this.IsFinished = true;
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
using System;
|
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
|
||||||
using MLEM.Font;
|
|
||||||
|
|
||||||
namespace MLEM.Formatting {
|
|
||||||
public class AnimationCode : FormattingCode {
|
|
||||||
|
|
||||||
public static readonly DrawCharacter Default = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => {
|
|
||||||
font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
|
||||||
};
|
|
||||||
public static readonly DrawCharacter Wobbly = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => {
|
|
||||||
var offset = new Vector2(0, (float) Math.Sin(index + code.Time.TotalSeconds * settings.WobbleModifier) * font.LineHeight * settings.WobbleHeightModifier * scale);
|
|
||||||
font.DrawString(batch, charSt, position + offset, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
|
||||||
};
|
|
||||||
public static readonly DrawCharacter Typing = (code, settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth) => {
|
|
||||||
if (code.Time.TotalSeconds * settings.TypingSpeed > index - effectStartIndex + 1)
|
|
||||||
font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
|
||||||
};
|
|
||||||
public static readonly AnimationCode DefaultCode = new AnimationCode(Default);
|
|
||||||
|
|
||||||
public readonly DrawCharacter Draw;
|
|
||||||
public TimeSpan Time;
|
|
||||||
|
|
||||||
public AnimationCode(DrawCharacter draw) : base(Type.Animation) {
|
|
||||||
this.Draw = draw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(GameTime time) {
|
|
||||||
this.Time += time.ElapsedGameTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Reset() {
|
|
||||||
this.Time = TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate void DrawCharacter(AnimationCode code, FormatSettings settings, IGenericFont font, SpriteBatch batch, string totalText, int index, int effectStartIndex, string charSt, Vector2 position, Color color, float scale, float layerDepth);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,41 +10,36 @@ namespace MLEM.Formatting {
|
||||||
public readonly Color Color;
|
public readonly Color Color;
|
||||||
public readonly TextStyle Style;
|
public readonly TextStyle Style;
|
||||||
public readonly SpriteAnimation Icon;
|
public readonly SpriteAnimation Icon;
|
||||||
|
public readonly TextAnimation.DrawCharacter Animation;
|
||||||
|
|
||||||
protected FormattingCode(Type type) {
|
public FormattingCode(Color color) {
|
||||||
this.CodeType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FormattingCode(Color color) : this(Type.Color) {
|
|
||||||
this.Color = color;
|
this.Color = color;
|
||||||
|
this.CodeType = Type.Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormattingCode(TextStyle style) : this(Type.Style) {
|
public FormattingCode(TextStyle style) {
|
||||||
this.Style = style;
|
this.Style = style;
|
||||||
|
this.CodeType = Type.Style;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormattingCode(TextureRegion icon) :
|
public FormattingCode(TextureRegion icon) :
|
||||||
this(new SpriteAnimation(0, icon)) {
|
this(new SpriteAnimation(0, icon)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormattingCode(SpriteAnimation icon) : this(Type.Icon) {
|
public FormattingCode(SpriteAnimation icon) {
|
||||||
this.Icon = icon;
|
this.Icon = icon;
|
||||||
|
this.CodeType = Type.Icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattingCode(TextAnimation.DrawCharacter animation) {
|
||||||
|
this.Animation = animation;
|
||||||
|
this.CodeType = Type.Animation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual string GetReplacementString(IGenericFont font) {
|
public virtual string GetReplacementString(IGenericFont font) {
|
||||||
return this.CodeType == Type.Icon ? TextFormatting.GetOneEmString(font) : string.Empty;
|
return this.CodeType == Type.Icon ? TextFormatting.GetOneEmString(font) : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Update(GameTime time) {
|
|
||||||
if (this.CodeType == Type.Icon)
|
|
||||||
this.Icon.Update(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Reset() {
|
|
||||||
if (this.CodeType == Type.Icon)
|
|
||||||
this.Icon.Restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
|
|
||||||
Color,
|
Color,
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
|
|
||||||
namespace MLEM.Formatting {
|
|
||||||
public class FormattingCodeCollection : Dictionary<int, FormattingCode> {
|
|
||||||
|
|
||||||
public void Update(GameTime time) {
|
|
||||||
foreach (var code in this.Values)
|
|
||||||
code.Update(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset() {
|
|
||||||
foreach (var code in this.Values)
|
|
||||||
code.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
26
MLEM/Formatting/TextAnimation.cs
Normal file
26
MLEM/Formatting/TextAnimation.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using MLEM.Font;
|
||||||
|
|
||||||
|
namespace MLEM.Formatting {
|
||||||
|
public static class TextAnimation {
|
||||||
|
|
||||||
|
public static readonly DrawCharacter Default = (settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => {
|
||||||
|
font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly DrawCharacter Wobbly = (settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => {
|
||||||
|
var offset = new Vector2(0, (float) Math.Sin(index + timeIntoAnimation.TotalSeconds * settings.WobbleModifier) * font.LineHeight * settings.WobbleHeightModifier * scale);
|
||||||
|
font.DrawString(batch, charSt, position + offset, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly DrawCharacter Typing = (settings, font, batch, totalText, index, effectStartIndex, charSt, position, color, scale, layerDepth, timeIntoAnimation) => {
|
||||||
|
if (timeIntoAnimation.TotalSeconds * settings.TypingSpeed > index - effectStartIndex + 1)
|
||||||
|
font.DrawString(batch, charSt, position, color, 0, Vector2.Zero, scale, SpriteEffects.None, layerDepth);
|
||||||
|
};
|
||||||
|
|
||||||
|
public delegate void DrawCharacter(FormatSettings settings, IGenericFont font, SpriteBatch batch, string totalText, int index, int effectStartIndex, string charSt, Vector2 position, Color color, float scale, float layerDepth, TimeSpan timeIntoAnimation);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ using MLEM.Textures;
|
||||||
namespace MLEM.Formatting {
|
namespace MLEM.Formatting {
|
||||||
public static class TextFormatting {
|
public static class TextFormatting {
|
||||||
|
|
||||||
public static readonly Dictionary<Regex, Func<Match, FormattingCode>> FormattingCodes = new Dictionary<Regex, Func<Match, FormattingCode>>();
|
public static readonly Dictionary<string, FormattingCode> FormattingCodes = new Dictionary<string, FormattingCode>();
|
||||||
private static readonly Dictionary<IGenericFont, string> OneEmStrings = new Dictionary<IGenericFont, string>();
|
private static readonly Dictionary<IGenericFont, string> OneEmStrings = new Dictionary<IGenericFont, string>();
|
||||||
private static Regex formatRegex;
|
private static Regex formatRegex;
|
||||||
|
|
||||||
|
@ -19,22 +19,22 @@ namespace MLEM.Formatting {
|
||||||
SetFormatIndicators('[', ']');
|
SetFormatIndicators('[', ']');
|
||||||
|
|
||||||
// style codes
|
// style codes
|
||||||
FormattingCodes[new Regex("regular")] = m => new FormattingCode(TextStyle.Regular);
|
FormattingCodes["regular"] = new FormattingCode(TextStyle.Regular);
|
||||||
FormattingCodes[new Regex("italic")] = m => new FormattingCode(TextStyle.Italic);
|
FormattingCodes["italic"] = new FormattingCode(TextStyle.Italic);
|
||||||
FormattingCodes[new Regex("bold")] = m => new FormattingCode(TextStyle.Bold);
|
FormattingCodes["bold"] = new FormattingCode(TextStyle.Bold);
|
||||||
FormattingCodes[new Regex("shadow")] = m => new FormattingCode(TextStyle.Shadow);
|
FormattingCodes["shadow"] = new FormattingCode(TextStyle.Shadow);
|
||||||
|
|
||||||
// color codes
|
// color codes
|
||||||
var colors = typeof(Color).GetProperties();
|
var colors = typeof(Color).GetProperties();
|
||||||
foreach (var color in colors) {
|
foreach (var color in colors) {
|
||||||
if (color.GetGetMethod().IsStatic)
|
if (color.GetGetMethod().IsStatic)
|
||||||
FormattingCodes[new Regex(color.Name.ToLowerInvariant())] = m => new FormattingCode((Color) color.GetValue(null));
|
FormattingCodes[color.Name.ToLowerInvariant()] = new FormattingCode((Color) color.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
// animations
|
// animations
|
||||||
FormattingCodes[new Regex("unanimated")] = m => new AnimationCode(AnimationCode.Default);
|
FormattingCodes["unanimated"] = new FormattingCode(TextAnimation.Default);
|
||||||
FormattingCodes[new Regex("wobbly")] = m => new AnimationCode(AnimationCode.Wobbly);
|
FormattingCodes["wobbly"] = new FormattingCode(TextAnimation.Wobbly);
|
||||||
FormattingCodes[new Regex("typing")] = m => new AnimationCode(AnimationCode.Typing);
|
FormattingCodes["typing"] = new FormattingCode(TextAnimation.Typing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetFormatIndicators(char opener, char closer) {
|
public static void SetFormatIndicators(char opener, char closer) {
|
||||||
|
@ -62,8 +62,8 @@ namespace MLEM.Formatting {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FormattingCodeCollection GetFormattingCodes(this string s, IGenericFont font) {
|
public static Dictionary<int, FormattingCode> GetFormattingCodes(this string s, IGenericFont font) {
|
||||||
var codes = new FormattingCodeCollection();
|
var codes = new Dictionary<int, FormattingCode>();
|
||||||
var codeLengths = 0;
|
var codeLengths = 0;
|
||||||
foreach (Match match in formatRegex.Matches(s)) {
|
foreach (Match match in formatRegex.Matches(s)) {
|
||||||
var code = FromMatch(match);
|
var code = FromMatch(match);
|
||||||
|
@ -75,18 +75,18 @@ namespace MLEM.Formatting {
|
||||||
return codes;
|
return codes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DrawFormattedString(this IGenericFont regularFont, SpriteBatch batch, Vector2 pos, string text, FormattingCodeCollection codes, Color color, float scale, IGenericFont boldFont = null, IGenericFont italicFont = null, float depth = 0, FormatSettings formatSettings = null) {
|
public static void DrawFormattedString(this IGenericFont regularFont, SpriteBatch batch, Vector2 pos, string text, Dictionary<int, FormattingCode> codeLocations, Color color, float scale, IGenericFont boldFont = null, IGenericFont italicFont = null, float depth = 0, TimeSpan timeIntoAnimation = default, FormatSettings formatSettings = null) {
|
||||||
var settings = formatSettings ?? FormatSettings.Default;
|
var settings = formatSettings ?? FormatSettings.Default;
|
||||||
var currColor = color;
|
var currColor = color;
|
||||||
var currFont = regularFont;
|
var currFont = regularFont;
|
||||||
var currStyle = TextStyle.Regular;
|
var currStyle = TextStyle.Regular;
|
||||||
var currAnim = AnimationCode.DefaultCode;
|
var currAnim = TextAnimation.Default;
|
||||||
var animStart = 0;
|
var animStart = 0;
|
||||||
|
|
||||||
var innerOffset = new Vector2();
|
var innerOffset = new Vector2();
|
||||||
for (var i = 0; i < text.Length; i++) {
|
for (var i = 0; i < text.Length; i++) {
|
||||||
// check if the current character's index has a formatting code
|
// check if the current character's index has a formatting code
|
||||||
codes.TryGetValue(i, out var code);
|
codeLocations.TryGetValue(i, out var code);
|
||||||
if (code != null) {
|
if (code != null) {
|
||||||
// if so, apply it
|
// if so, apply it
|
||||||
switch (code.CodeType) {
|
switch (code.CodeType) {
|
||||||
|
@ -108,10 +108,11 @@ namespace MLEM.Formatting {
|
||||||
currStyle = code.Style;
|
currStyle = code.Style;
|
||||||
break;
|
break;
|
||||||
case FormattingCode.Type.Icon:
|
case FormattingCode.Type.Icon:
|
||||||
|
code.Icon.SetTime(timeIntoAnimation.TotalSeconds * code.Icon.SpeedMultiplier % code.Icon.TotalTime);
|
||||||
batch.Draw(code.Icon.CurrentRegion, new RectangleF(pos + innerOffset, new Vector2(regularFont.LineHeight * scale)), color, 0, Vector2.Zero, SpriteEffects.None, depth);
|
batch.Draw(code.Icon.CurrentRegion, new RectangleF(pos + innerOffset, new Vector2(regularFont.LineHeight * scale)), color, 0, Vector2.Zero, SpriteEffects.None, depth);
|
||||||
break;
|
break;
|
||||||
case FormattingCode.Type.Animation:
|
case FormattingCode.Type.Animation:
|
||||||
currAnim = (AnimationCode) code;
|
currAnim = code.Animation;
|
||||||
animStart = i;
|
animStart = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -124,8 +125,8 @@ namespace MLEM.Formatting {
|
||||||
innerOffset.Y += regularFont.LineHeight * scale;
|
innerOffset.Y += regularFont.LineHeight * scale;
|
||||||
} else {
|
} else {
|
||||||
if (currStyle == TextStyle.Shadow)
|
if (currStyle == TextStyle.Shadow)
|
||||||
currAnim.Draw(currAnim, settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset + settings.DropShadowOffset * scale, settings.DropShadowColor, scale, depth);
|
currAnim(settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset + settings.DropShadowOffset * scale, settings.DropShadowColor, scale, depth, timeIntoAnimation);
|
||||||
currAnim.Draw(currAnim, settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset, currColor, scale, depth);
|
currAnim(settings, currFont, batch, text, i, animStart, cSt, pos + innerOffset, currColor, scale, depth, timeIntoAnimation);
|
||||||
innerOffset.X += regularFont.MeasureString(cSt).X * scale;
|
innerOffset.X += regularFont.MeasureString(cSt).X * scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,12 +134,8 @@ namespace MLEM.Formatting {
|
||||||
|
|
||||||
private static FormattingCode FromMatch(Capture match) {
|
private static FormattingCode FromMatch(Capture match) {
|
||||||
var rawCode = match.Value.Substring(1, match.Value.Length - 2).ToLowerInvariant();
|
var rawCode = match.Value.Substring(1, match.Value.Length - 2).ToLowerInvariant();
|
||||||
foreach (var code in FormattingCodes) {
|
FormattingCodes.TryGetValue(rawCode, out var val);
|
||||||
var m = code.Key.Match(rawCode);
|
return val;
|
||||||
if (m.Success)
|
|
||||||
return code.Value(m);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue