Client LuaCsForBarotrauma
BBCode.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text.RegularExpressions;
6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
8 
9 namespace Barotrauma.Steam
10 {
11  abstract partial class WorkshopMenu
12  {
13  protected readonly struct BBWord
14  {
15  [Flags]
16  public enum TagType
17  {
18  None = 0x0,
19  Bold = 0x1,
20  Italic = 0x2,
21  Header = 0x4,
22  List = 0x8,
23  NewLine = 0x10
24  }
25 
26  public readonly string Text;
27  public readonly Vector2 Size;
28  public readonly TagType TagTypes;
29 
30  public readonly GUIFont Font;
31 
32  public BBWord(string text, TagType tagTypes)
33  {
34  Text = text;
35  TagTypes = tagTypes;
36  Font = tagTypes.HasFlag(TagType.Header)
37  ? GUIStyle.LargeFont
38  : tagTypes.HasFlag(TagType.Bold)
39  ? GUIStyle.SubHeadingFont
40  : GUIStyle.Font;
42  }
43  }
44 
45  protected static readonly Regex bbTagRegex = new Regex(@"\[(.+?)\]",
46  RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
47 
48  protected static void CreateBBCodeElement(Steamworks.Ugc.Item workshopItem, GUIListBox container)
49  {
50  Point cachedContainerSize = Point.Zero;
51  List<BBWord> bbWords = new List<BBWord>();
52  Stack<BBWord.TagType> tagStack = new Stack<BBWord.TagType>();
53 
54  string bbCode = "";
55 
56  void forceReset()
57  {
58  bbWords.Clear();
59  cachedContainerSize = Point.Zero;
60  }
61 
62  void recalculate(GUICustomComponent component)
63  {
64  if (cachedContainerSize == component.RectTransform.NonScaledSize) { return; }
65 
66  bbWords.Clear();
67  cachedContainerSize = component.RectTransform.NonScaledSize;
68 
69  var matches = new Stack<Match>(bbTagRegex.Matches(bbCode).Reverse());
70  Match? nextTag = null;
71  matches.TryPop(out nextTag);
72  int wordStart = 0;
73  BBWord.TagType currTagType;
74  for (int i = 0; i < bbCode.Length; i++)
75  {
76  char currChar = bbCode[i];
77  currTagType = tagStack.TryPeek(out var t) ? t : BBWord.TagType.None;
78 
79  bool charIsCJK = TextManager.IsCJK($"{currChar}");
80  bool wordEnd = char.IsWhiteSpace(currChar) || charIsCJK;
81  int reachedTagLength = 0;
82  if (nextTag is { Index: int tagIndex, Length: int tagLength }
83  && i == tagIndex)
84  {
85  reachedTagLength = tagLength;
86  string tagStr = nextTag.Value.Replace("[", "").Replace("]", "").Trim();
87  bool isClosing = tagStr.StartsWith("/");
88  tagStr = tagStr.Replace("/", "").Trim().ToLowerInvariant();
89  BBWord.TagType tagType = tagStr switch
90  {
91  "b" => BBWord.TagType.Bold,
92  "i" => BBWord.TagType.Italic,
93  "h1" => BBWord.TagType.Header,
94  _ => BBWord.TagType.None
95  };
96 
97  if (tagType != BBWord.TagType.None)
98  {
99  if (isClosing)
100  {
101  if (currTagType == tagType)
102  {
103  tagStack.Pop();
104  }
105  }
106  else
107  {
108  tagStack.Push(tagType);
109  }
110  }
111  }
112 
113  if (wordEnd || reachedTagLength > 0)
114  {
115  string word = bbCode[wordStart..i];
116  if (charIsCJK) { word = bbCode[wordStart..(i + 1)]; }
117  else if (char.IsWhiteSpace(currChar) && currChar != '\n') { word += " "; }
118 
119  if (!word.IsNullOrEmpty())
120  {
121  bbWords.Add(new BBWord(word, currTagType));
122  }
123  else if (currChar == '\n')
124  {
125  bbWords.Add(new BBWord("", BBWord.TagType.NewLine));
126  }
127 
128  if (reachedTagLength > 0)
129  {
130  i += reachedTagLength - 1;
131  nextTag = matches.TryPop(out var tag) ? tag : null;
132  }
133 
134  wordStart = i + 1;
135  }
136  }
137 
138  currTagType = tagStack.TryPeek(out var ft) ? ft : BBWord.TagType.None;
139  string finalWord = bbCode[wordStart..];
140  if (!finalWord.IsNullOrEmpty())
141  {
142  bbWords.Add(new BBWord(finalWord, currTagType));
143  }
144 
145  container.RecalculateChildren();
146  container.UpdateScrollBarSize();
147  }
148 
149  void draw(SpriteBatch spriteBatch, GUICustomComponent component)
150  {
151  recalculate(component);
152  Vector2 currPos = Vector2.Zero;
153  Vector2 rectPos = component.Rect.Location.ToVector2();
154  for (int i = 0; i < bbWords.Count; i++)
155  {
156  var bbWord = bbWords[i];
157  if (currPos.X > 0.0f
158  && currPos.X + bbWord.Size.X >= component.Rect.Width)
159  {
160  //wrap because we went over width limit
161  currPos = (0.0f, currPos.Y + bbWord.Size.Y);
162  }
163 
164  bbWord.Font.DrawString(
165  spriteBatch,
166  bbWord.Text,
167  (currPos + rectPos).ToPoint().ToVector2(),
168  GUIStyle.TextColorNormal,
169  forceUpperCase: ForceUpperCase.No,
170  italics: bbWord.TagTypes.HasFlag(BBWord.TagType.Italic));
171  bool breakLine
172  = bbWord.TagTypes.HasFlag(BBWord.TagType.NewLine)
173  || (i < bbWords.Count - 1 &&
174  bbWords[i + 1].TagTypes.HasFlag(BBWord.TagType.Header) !=
175  bbWord.TagTypes.HasFlag(BBWord.TagType.Header));
176  if (breakLine)
177  {
178  //break line because of a header change or newline was found
179  currPos = (0.0f, currPos.Y + bbWord.Size.Y);
180  }
181  else
182  {
183  currPos.X += bbWord.Size.X;
184  }
185  }
186 
187  component.RectTransform.NonScaledSize
188  = (component.RectTransform.NonScaledSize.X,
189  (int)(currPos.Y + bbWords.LastOrDefault().Size.Y));
190  component.RectTransform.RelativeSize
191  = component.RectTransform.NonScaledSize.ToVector2() / component.Parent.Rect.Size.ToVector2();
192  }
193 
194  TaskPool.Add(
195  $"GetWorkshopItemLongDescriptionFor{workshopItem.Id.Value}",
196  SteamManager.Workshop.GetItemAsap(workshopItem.Id.Value, withLongDescription: true),
197  t =>
198  {
199  if (!t.TryGetResult(out Option<Steamworks.Ugc.Item> workshopItemWithDescription)) { return; }
200 
201  bbCode = workshopItemWithDescription.TryUnwrap(out var item) ? (item.Description ?? "") : "";
202  forceReset();
203  });
204 
205  new GUICustomComponent(
206  new RectTransform(Vector2.One, container.Content.RectTransform),
207  onDraw: draw);
208  }
209  }
210 }
virtual Rectangle Rect
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
Definition: GUIPrefab.cs:284
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
Vector2 RelativeSize
Relative to the parent rect.
Point NonScaledSize
Size before scale multiplications.
static readonly Regex bbTagRegex
Definition: BBCode.cs:45
static void CreateBBCodeElement(Steamworks.Ugc.Item workshopItem, GUIListBox container)
Definition: BBCode.cs:48
BBWord(string text, TagType tagTypes)
Definition: BBCode.cs:32