188 lines
7.3 KiB
C#
188 lines
7.3 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
|
namespace Barotrauma.Steam
|
|
{
|
|
public partial class WorkshopMenu
|
|
{
|
|
private readonly struct BBWord
|
|
{
|
|
[Flags]
|
|
public enum TagType
|
|
{
|
|
None = 0x0,
|
|
Bold = 0x1,
|
|
Italic = 0x2,
|
|
Header = 0x4,
|
|
List = 0x8,
|
|
NewLine = 0x10
|
|
}
|
|
|
|
public readonly string Text;
|
|
public readonly Vector2 Size;
|
|
public readonly TagType TagTypes;
|
|
|
|
public readonly GUIFont Font;
|
|
|
|
public BBWord(string text, TagType tagTypes)
|
|
{
|
|
Text = text;
|
|
TagTypes = tagTypes;
|
|
Font = tagTypes.HasFlag(TagType.Header)
|
|
? GUIStyle.LargeFont
|
|
: tagTypes.HasFlag(TagType.Bold)
|
|
? GUIStyle.SubHeadingFont
|
|
: GUIStyle.Font;
|
|
Size = Font.MeasureString(Text);
|
|
}
|
|
}
|
|
|
|
private static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]",
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
private static GUICustomComponent CreateBBCodeElement(string bbCode, GUIListBox container)
|
|
{
|
|
Point cachedContainerSize = Point.Zero;
|
|
List<BBWord> bbWords = new List<BBWord>();
|
|
Stack<BBWord.TagType> tagStack = new Stack<BBWord.TagType>();
|
|
|
|
void recalculate()
|
|
{
|
|
if (cachedContainerSize == container.Content.RectTransform.NonScaledSize) { return; }
|
|
|
|
bbWords.Clear();
|
|
cachedContainerSize = container.Content.RectTransform.NonScaledSize;
|
|
|
|
var matches = new Stack<Match>(bbTagRegex.Matches(bbCode).Reverse());
|
|
Match? nextTag = null;
|
|
matches.TryPop(out nextTag);
|
|
int wordStart = 0;
|
|
BBWord.TagType currTagType;
|
|
for (int i = 0; i < bbCode.Length; i++)
|
|
{
|
|
char currChar = bbCode[i];
|
|
currTagType = tagStack.TryPeek(out var t) ? t : BBWord.TagType.None;
|
|
|
|
bool charIsCJK = TextManager.IsCJK($"{currChar}");
|
|
bool wordEnd = char.IsWhiteSpace(currChar) || charIsCJK;
|
|
int reachedTagLength = 0;
|
|
if (nextTag is { Index: int tagIndex, Length: int tagLength }
|
|
&& i == tagIndex)
|
|
{
|
|
reachedTagLength = tagLength;
|
|
string tagStr = nextTag.Value.Replace("[", "").Replace("]", "").Trim();
|
|
bool isClosing = tagStr.StartsWith("/");
|
|
tagStr = tagStr.Replace("/", "").Trim().ToLowerInvariant();
|
|
BBWord.TagType tagType = tagStr switch
|
|
{
|
|
"b" => BBWord.TagType.Bold,
|
|
"i" => BBWord.TagType.Italic,
|
|
"h1" => BBWord.TagType.Header,
|
|
_ => BBWord.TagType.None
|
|
};
|
|
|
|
if (tagType != BBWord.TagType.None)
|
|
{
|
|
if (isClosing)
|
|
{
|
|
if (currTagType == tagType)
|
|
{
|
|
tagStack.Pop();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tagStack.Push(tagType);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wordEnd || reachedTagLength > 0)
|
|
{
|
|
string word = bbCode[wordStart..i];
|
|
if (charIsCJK) { word = bbCode[wordStart..(i + 1)]; }
|
|
else if (char.IsWhiteSpace(currChar) && currChar != '\n') { word += " "; }
|
|
|
|
if (!word.IsNullOrEmpty())
|
|
{
|
|
bbWords.Add(new BBWord(word, currTagType));
|
|
}
|
|
else if (currChar == '\n')
|
|
{
|
|
bbWords.Add(new BBWord("", BBWord.TagType.NewLine));
|
|
}
|
|
|
|
if (reachedTagLength > 0)
|
|
{
|
|
i += reachedTagLength - 1;
|
|
nextTag = matches.TryPop(out var tag) ? tag : null;
|
|
}
|
|
|
|
wordStart = i + 1;
|
|
}
|
|
}
|
|
|
|
currTagType = tagStack.TryPeek(out var ft) ? ft : BBWord.TagType.None;
|
|
string finalWord = bbCode[wordStart..];
|
|
if (!finalWord.IsNullOrEmpty())
|
|
{
|
|
bbWords.Add(new BBWord(finalWord, currTagType));
|
|
}
|
|
}
|
|
|
|
void draw(SpriteBatch spriteBatch, GUICustomComponent component)
|
|
{
|
|
recalculate();
|
|
Vector2 currPos = Vector2.Zero;
|
|
Vector2 rectPos = component.Rect.Location.ToVector2();
|
|
for (int i = 0; i < bbWords.Count; i++)
|
|
{
|
|
var bbWord = bbWords[i];
|
|
if (currPos.X > 0.0f
|
|
&& currPos.X + bbWord.Size.X >= component.Rect.Width)
|
|
{
|
|
//wrap because we went over width limit
|
|
currPos = (0.0f, currPos.Y + bbWord.Size.Y);
|
|
}
|
|
|
|
bbWord.Font.DrawString(
|
|
spriteBatch,
|
|
bbWord.Text,
|
|
(currPos + rectPos).ToPoint().ToVector2(),
|
|
GUIStyle.TextColorNormal,
|
|
forceUpperCase: ForceUpperCase.No,
|
|
italics: bbWord.TagTypes.HasFlag(BBWord.TagType.Italic));
|
|
bool breakLine
|
|
= bbWord.TagTypes.HasFlag(BBWord.TagType.NewLine)
|
|
|| (i < bbWords.Count - 1 &&
|
|
bbWords[i + 1].TagTypes.HasFlag(BBWord.TagType.Header) !=
|
|
bbWord.TagTypes.HasFlag(BBWord.TagType.Header));
|
|
if (breakLine)
|
|
{
|
|
//break line because of a header change or newline was found
|
|
currPos = (0.0f, currPos.Y + bbWord.Size.Y);
|
|
}
|
|
else
|
|
{
|
|
currPos.X += bbWord.Size.X;
|
|
}
|
|
}
|
|
|
|
component.RectTransform.NonScaledSize
|
|
= (component.RectTransform.NonScaledSize.X,
|
|
(int)(currPos.Y + bbWords.LastOrDefault().Size.Y));
|
|
component.RectTransform.RelativeSize
|
|
= component.RectTransform.NonScaledSize.ToVector2() / component.Parent.Rect.Size.ToVector2();
|
|
}
|
|
|
|
return new GUICustomComponent(new RectTransform(Vector2.One, container.Content.RectTransform),
|
|
onDraw: draw);
|
|
}
|
|
}
|
|
}
|