Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/ItemLabel.cs
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Text;
8 using System.Xml.Linq;
9 
11 {
12  partial class ItemLabel : ItemComponent, IDrawableComponent, IHasExtraTextPickerEntries
13  {
14  private GUITextBlock textBlock;
15 
16  private Color textColor;
17 
18  private float scrollAmount;
19  private string scrollingText;
20  private float scrollPadding;
21  private int scrollIndex;
22  private bool needsScrolling;
23 
24  private float[] charWidths;
25 
26  private float prevScale;
27  private Rectangle prevRect;
28  private StringBuilder sb;
29 
30  private Vector4 padding;
31 
32  [Serialize("0,0,0,0", IsPropertySaveable.Yes, description: "The amount of padding around the text in pixels (left,top,right,bottom).")]
33  public Vector4 Padding
34  {
35  get { return padding; }
36  set
37  {
38  padding = value;
39  TextBlock.Padding = value * item.Scale;
40  }
41  }
42 
43  private string text;
44  [Serialize("", IsPropertySaveable.Yes, translationTextTag: "Label.", description: "The text displayed in the label.", alwaysUseInstanceValues: true), Editable(100)]
45  public string Text
46  {
47  get { return text; }
48  set
49  {
50  if (value == text || item.Rect.Width < 5) { return; }
51 
52  if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height)
53  {
54  textBlock = null;
55  }
56 
57  text = value;
58  SetDisplayText(value);
59  UpdateScrollingText();
60  }
61  }
62 
63  private bool ignoreLocalization;
64 
65  [Editable, Serialize(false, IsPropertySaveable.Yes, "Whether or not to skip localization and always display the raw value.")]
66  public bool IgnoreLocalization
67  {
68  get => ignoreLocalization;
69  set
70  {
71  ignoreLocalization = value;
72  SetDisplayText(Text);
73  }
74  }
75 
77  {
78  get;
79  private set;
80  }
81 
82  [Editable, Serialize("0,0,0,255", IsPropertySaveable.Yes, description: "The color of the text displayed on the label (R,G,B,A).", alwaysUseInstanceValues: true)]
83  public Color TextColor
84  {
85  get { return textColor; }
86  set
87  {
88  if (textBlock != null) { textBlock.TextColor = value; }
89  textColor = value;
90  }
91  }
92 
93  [Editable(0.0f, 10.0f), Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the text displayed on the label.", alwaysUseInstanceValues: true)]
94  public float TextScale
95  {
96  get { return textBlock == null ? 1.0f : textBlock.TextScale / BaseToRealTextScaleFactor; }
97  set
98  {
99  if (textBlock != null)
100  {
101  float prevScale = TextBlock.TextScale;
102  textBlock.TextScale = MathHelper.Clamp(value * BaseToRealTextScaleFactor, 0.1f, 10.0f);
103  if (!MathUtils.NearlyEqual(prevScale, TextBlock.TextScale))
104  {
105  SetScrollingText();
106  }
107  }
108  }
109  }
110 
111  private bool scrollable;
112  [Serialize(false, IsPropertySaveable.Yes, description: "Should the text scroll horizontally across the item if it's too long to be displayed all at once.")]
113  public bool Scrollable
114  {
115  get { return scrollable; }
116  set
117  {
118  scrollable = value;
119  IsActive = value || parseSpecialTextTagOnStart;
120  TextBlock.Wrap = !scrollable;
121  TextBlock.TextAlignment = scrollable ? Alignment.CenterLeft : Alignment.Center;
122  }
123  }
124 
125  [Serialize(20.0f, IsPropertySaveable.Yes, description: "How fast the text scrolls across the item (only valid if Scrollable is set to true).")]
126  public float ScrollSpeed
127  {
128  get;
129  set;
130  }
131 
132  private GUITextBlock TextBlock
133  {
134  get
135  {
136  if (textBlock == null)
137  {
138  RecreateTextBlock();
139  }
140  return textBlock;
141  }
142  }
143 
145  : base(item, element)
146  {
147  }
148 
149  public IEnumerable<string> GetExtraTextPickerEntries()
150  {
151  return SpecialTextTags;
152  }
153 
154  private void SetScrollingText()
155  {
156  if (!scrollable) { return; }
157 
158  float totalWidth = textBlock.Font.MeasureString(DisplayText).X * TextBlock.TextScale;
159  float textAreaWidth = Math.Max(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z, 0);
160  if (totalWidth >= textAreaWidth)
161  {
162  //add enough spaces to fill the rect
163  //(so the text can scroll entirely out of view before we reset it back to start)
164  needsScrolling = true;
165  float spaceWidth = textBlock.Font.MeasureChar(' ').X * TextBlock.TextScale;
166  scrollingText = new string(' ', (int)Math.Ceiling(textAreaWidth / spaceWidth)) + DisplayText.Value;
167  }
168  else
169  {
170  //whole text can fit in the textblock, no need to scroll
171  needsScrolling = false;
172  TextBlock.Text = scrollingText = DisplayText.Value;
173  scrollPadding = 0;
174  scrollAmount = 0.0f;
175  scrollIndex = 0;
176  return;
177  }
178 
179  //calculate character widths
180  scrollPadding = 0;
181  charWidths = new float[scrollingText.Length];
182  for (int i = 0; i < scrollingText.Length; i++)
183  {
184  float charWidth = TextBlock.Font.MeasureChar(scrollingText[i]).X * TextBlock.TextScale;
185  scrollPadding = Math.Max(charWidth, scrollPadding);
186  charWidths[i] = charWidth;
187  }
188 
189  scrollIndex = MathHelper.Clamp(scrollIndex, 0, DisplayText.Length);
190  }
191 
192  private static readonly string[] SpecialTextTags = new string[] { "[CurrentLocationName]", "[CurrentBiomeName]", "[CurrentSubName]" };
193  private bool parseSpecialTextTagOnStart;
194  private void SetDisplayText(string value)
195  {
196  if (SpecialTextTags.Contains(value))
197  {
198  parseSpecialTextTagOnStart = true;
199  IsActive = true;
200  }
201 
202  DisplayText = IgnoreLocalization ? value : TextManager.Get(value).Fallback(value);
203 
204  TextBlock.Text = DisplayText;
205  if (Screen.Selected == GameMain.SubEditorScreen && Scrollable)
206  {
207  TextBlock.Text = ToolBox.LimitString(DisplayText, TextBlock.Font, item.Rect.Width);
208  }
209 
210  SetScrollingText();
211  }
212 
213  private const float BaseTextSize = 12.0f;
214  private float BaseToRealTextScaleFactor => BaseTextSize / GUIStyle.UnscaledSmallFont.Size;
215  private void RecreateTextBlock()
216  {
217  textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "",
218  textColor: textColor, font: GUIStyle.UnscaledSmallFont, textAlignment: scrollable ? Alignment.CenterLeft : Alignment.Center, wrap: !scrollable, style: null)
219  {
220  TextDepth = item.SpriteDepth - 0.00001f,
221  RoundToNearestPixel = false,
222  TextScale = TextScale * BaseToRealTextScaleFactor,
223  Padding = padding * item.Scale
224  };
225  }
226 
227  private void ParseSpecialTextTag()
228  {
229  switch (text)
230  {
231  case "[CurrentLocationName]":
232  SetDisplayText(Level.Loaded?.StartLocation?.DisplayName.Value ?? string.Empty);
233  break;
234  case "[CurrentBiomeName]":
235  SetDisplayText(Level.Loaded?.LevelData?.Biome?.DisplayName.Value ?? string.Empty);
236  break;
237  case "[CurrentSubName]":
238  SetDisplayText(item.Submarine?.Info?.DisplayName.Value ?? string.Empty);
239  break;
240  default:
241  break;
242  }
243  }
244 
245  public override void Update(float deltaTime, Camera cam)
246  {
247  if (parseSpecialTextTagOnStart)
248  {
249  ParseSpecialTextTag();
250  parseSpecialTextTagOnStart = false;
251  }
252 
253  if (!scrollable)
254  {
255  IsActive = false;
256  return;
257  }
258 
259  if (scrollingText == null)
260  {
261  SetScrollingText();
262  }
263 
264  if (!needsScrolling) { return; }
265 
266  scrollAmount -= deltaTime * ScrollSpeed;
267  UpdateScrollingText();
268  }
269 
270  private void UpdateScrollingText()
271  {
272  if (!scrollable || !needsScrolling) { return; }
273 
274  float currLength = 0;
275  sb ??= new StringBuilder();
276  sb.Clear();
277  float textAreaWidth = textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z;
278  for (int i = scrollIndex; i < scrollingText.Length; i++)
279  {
280  //first character is out of view -> skip to next character
281  if (i == scrollIndex && scrollAmount < -charWidths[i])
282  {
283  scrollIndex++;
284  scrollAmount = 0;
285  if (scrollIndex >= scrollingText.Length) //reached the last character, reset
286  {
287  scrollIndex = 0;
288  break;
289  }
290  continue;
291  }
292 
293  //reached the right edge, stop adding more character
294  if (scrollAmount + (currLength + charWidths[i] + scrollPadding) >= textAreaWidth)
295  {
296  break;
297  }
298  else
299  {
300  currLength += charWidths[i];
301  sb.Append(scrollingText[i]);
302  }
303  }
304 
305  TextBlock.Text = sb.ToString();
306  }
307 
308  public override void OnScaleChanged()
309  {
310  RecreateTextBlock();
311  SetDisplayText(Text);
312  prevScale = item.Scale;
313  prevRect = item.Rect;
314  }
315 
316  public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
317  {
318  if (item.ParentInventory != null) { return; }
319  if (editing)
320  {
321  if (!MathUtils.NearlyEqual(prevScale, item.Scale) || prevRect != item.Rect)
322  {
323  RecreateTextBlock();
324  SetDisplayText(Text);
325  prevScale = item.Scale;
326  prevRect = item.Rect;
327  }
328  }
329 
330  var drawPos = new Vector2(
331  item.DrawPosition.X - item.Rect.Width / 2.0f,
332  -(item.DrawPosition.Y + item.Rect.Height / 2.0f));
333 
334  textBlock.TextDepth = item.SpriteDepth - 0.0001f;
335  textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + (editing ? Vector2.Zero : new Vector2(scrollAmount + scrollPadding, 0.0f));
336  textBlock.DrawManually(spriteBatch);
337  }
338 
339  public void ClientEventRead(IReadMessage msg, float sendingTime)
340  {
341  Text = msg.ReadString();
342  }
343 
344  }
345 }
virtual Vector2 DrawPosition
Definition: Entity.cs:51
Submarine Submarine
Definition: Entity.cs:53
virtual Rectangle Rect
Vector2 MeasureChar(char c)
Definition: GUIPrefab.cs:289
override GUIFont Font
Definition: GUITextBlock.cs:66
void Draw(SpriteBatch spriteBatch, bool editing=false, float itemDepth=-1, Color? overrideColor=null)
override string ToString()
Definition: RichString.cs:144