Client LuaCsForBarotrauma
GUITextBlock.cs
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using System;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Linq;
9 
10 namespace Barotrauma
11 {
12  public enum ForceUpperCase
13  {
14  Inherit,
15  No,
16  Yes
17  }
18 
19  public class GUITextBlock : GUIComponent
20  {
21  protected RichString text;
22 
23  protected Alignment textAlignment;
24 
25  private float textScale = 1;
26 
27  protected Vector2 textPos;
28  protected Vector2 origin;
29 
30  protected Color textColor, disabledTextColor, selectedTextColor;
31 
32  private LocalizedString wrappedText;
33  private string censoredText;
34 
37 
38  public bool Wrap;
39 
40  public bool RoundToNearestPixel = true;
41 
42  private bool overflowClipActive;
43  public bool OverflowClip;
44 
45  public bool OverflowClipActive
46  {
47  get { return overflowClipActive; }
48  }
49 
50  private float textDepth;
51 
52  public Vector2 TextOffset { get; set; }
53 
54  private Vector4 padding;
55  public Vector4 Padding
56  {
57  get { return padding; }
58  set
59  {
60  padding = value;
61  SetTextPos();
62  }
63  }
64 
65  public override GUIFont Font
66  {
67  get
68  {
69  return base.Font;
70  }
71  set
72  {
73  if (base.Font == value) { return; }
74  base.Font = value;
75  if (text != null) { Text = text; }
76  SetTextPos();
77  }
78  }
79 
81  {
82  get { return text; }
83  set
84  {
85  #warning TODO: Remove this eventually. Nobody should want to pass null.
86  value ??= "";
87  RichString newText = forceUpperCase switch
88  {
89  ForceUpperCase.Inherit => value.CaseTiedToFontAndStyle(Font, Style),
90  ForceUpperCase.No => value.CaseTiedToFontAndStyle(null, null),
91  ForceUpperCase.Yes => value.ToUpper()
92  };
93 
94  if (Text == newText) { return; }
95 
96  //reset scale, it gets recalculated in SetTextPos
97  if (autoScaleHorizontal || autoScaleVertical) { textScale = 1.0f; }
98 
99  text = newText;
100  wrappedText = newText.SanitizedString;
101  SetTextPos();
102  }
103  }
104 
106  {
107  get { return wrappedText; }
108  }
109 
110  public float TextDepth
111  {
112  get { return textDepth; }
113  set { textDepth = MathHelper.Clamp(value, 0.0f, 1.0f); }
114  }
115 
116  public Vector2 TextPos
117  {
118  get { return textPos; }
119  set
120  {
121  textPos = value;
123  }
124  }
125 
126  public float TextScale
127  {
128  get { return textScale; }
129  set
130  {
131  if (value != textScale)
132  {
133  textScale = value;
134  SetTextPos();
135  }
136  }
137  }
138 
139  private bool autoScaleHorizontal, autoScaleVertical;
140 
145  {
146  get { return autoScaleHorizontal; }
147  set
148  {
149  if (autoScaleHorizontal == value) { return; }
150  autoScaleHorizontal = value;
151  if (autoScaleHorizontal)
152  {
153  SetTextPos();
154  }
155  }
156  }
157 
161  public bool AutoScaleVertical
162  {
163  get { return autoScaleVertical; }
164  set
165  {
166  if (autoScaleVertical == value) { return; }
167  autoScaleVertical = value;
168  if (autoScaleVertical)
169  {
170  SetTextPos();
171  }
172  }
173  }
174 
175  private ForceUpperCase forceUpperCase = ForceUpperCase.Inherit;
177  {
178  get { return forceUpperCase; }
179  set
180  {
181  if (forceUpperCase == value) { return; }
182 
183  forceUpperCase = value;
184  if (text != null) { Text = text; }
185  }
186  }
187 
188  public Vector2 Origin
189  {
190  get { return origin; }
191  }
192 
193  public Vector2 TextSize
194  {
195  get;
196  private set;
197  }
198 
199  public Color TextColor
200  {
201  get { return textColor; }
202  set { textColor = value; }
203  }
204 
205  public Color DisabledTextColor
206  {
207  get => disabledTextColor;
208  set => disabledTextColor = value;
209  }
210 
211  private Color? hoverTextColor;
212  public Color HoverTextColor
213  {
214  get { return hoverTextColor ?? textColor; }
215  set { hoverTextColor = value; }
216  }
217 
218  public Color SelectedTextColor
219  {
220  get { return selectedTextColor; }
221  set { selectedTextColor = value; }
222  }
223 
224  public Alignment TextAlignment
225  {
226  get { return textAlignment; }
227  set
228  {
229  if (textAlignment == value) return;
230  textAlignment = value;
231  SetTextPos();
232  }
233  }
234 
235  public bool Censor
236  {
237  get;
238  set;
239  }
240 
241  public string CensoredText
242  {
243  get { return censoredText; }
244  }
245 
247  {
248  public Color Color { get; set; } = GUIStyle.Red;
249  private int thickness;
250  private int expand;
251 
252  public StrikethroughSettings(Color? color = null, int thickness = 1, int expand = 0)
253  {
254  if (color != null) { Color = color.Value; }
255  this.thickness = thickness;
256  this.expand = expand;
257  }
258 
259  public void Draw(SpriteBatch spriteBatch, float textSizeHalf, float xPos, float yPos)
260  {
261  ShapeExtensions.DrawLine(spriteBatch, new Vector2(xPos - textSizeHalf - expand, yPos), new Vector2(xPos + textSizeHalf + expand, yPos), Color, thickness);
262  }
263  }
264 
266 
267  public ImmutableArray<RichTextData>? RichTextData => text.RichTextData;
268 
269  public bool HasColorHighlight => RichTextData.HasValue;
270 
271  public bool OverrideRichTextDataAlpha = true;
272 
273  public struct ClickableArea
274  {
276 
277  public delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area);
280  }
281  public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>();
282 
283  public bool Shadow { get; set; }
284 
289  public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
290  Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
291  : base(style, rectT)
292  {
293  if (color.HasValue)
294  {
295  this.color = color.Value;
296  }
297  if (textColor.HasValue)
298  {
300  }
301 
302  //if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
303  //use the default CJK font as a fallback
304  var selectedFont = font ?? GUIStyle.Font;
305  this.Font = selectedFont;
306  this.textAlignment = textAlignment;
307  this.Wrap = wrap;
308  this.Text = text ?? "";
309  if (rectT.Rect.Height == 0 && !text.IsNullOrEmpty())
310  {
312  }
313  SetTextPos();
314 
317 
318  Enabled = true;
319  Censor = false;
320  }
321 
322  public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false)
323  {
324  if (wrappedText == null) { return; }
325  RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText, removeExtraSpacing).Y + padding));
326  }
327 
328  public void SetRichText(LocalizedString richText)
329  {
330  Text = RichString.Rich(richText);
331  }
332 
333  public override void ApplyStyle(GUIComponentStyle componentStyle)
334  {
335  if (componentStyle == null) { return; }
336  base.ApplyStyle(componentStyle);
337  padding = componentStyle.Padding;
338 
339  textColor = componentStyle.TextColor;
340  hoverTextColor = componentStyle.HoverTextColor;
341  disabledTextColor = componentStyle.DisabledTextColor;
342  selectedTextColor = componentStyle.SelectedTextColor;
343 
344  if (Font == null || !componentStyle.Font.IsEmpty)
345  {
346  Font = GUIStyle.Fonts[componentStyle.Font.AppendIfMissing("Font")];
347  }
348  }
349 
350  public void ClearCaretPositions()
351  {
352  cachedCaretPositions = ImmutableArray<Vector2>.Empty;
353  }
354 
355  public void SetTextPos()
356  {
358  if (text == null) { return; }
359 
360  censoredText = text.IsNullOrEmpty() ? "" : new string('\u2022', text.Length);
361 
362  var rect = Rect;
363 
364  overflowClipActive = false;
365  wrappedText = text.SanitizedString;
366 
367  TextSize = MeasureText(text.SanitizedString);
368 
369  if (Wrap && rect.Width > 0)
370  {
371  wrappedText = ToolBox.WrapText(text.SanitizedString, rect.Width - padding.X - padding.Z, Font, textScale);
372  TextSize = MeasureText(wrappedText);
373  }
374  else if (OverflowClip)
375  {
376  overflowClipActive = TextSize.X > rect.Width - padding.X - padding.Z;
377  }
378 
379  Vector2 minSize = new Vector2(
380  Math.Max(rect.Width - padding.X - padding.Z, 5.0f),
381  Math.Max(rect.Height - padding.Y - padding.W, 5.0f));
382  if (!autoScaleHorizontal) { minSize.X = float.MaxValue; }
383  if (!Wrap && !autoScaleVertical) { minSize.Y = float.MaxValue; }
384 
385  if ((autoScaleHorizontal || autoScaleVertical) && textScale > 0.1f &&
386  (TextSize.X * textScale > minSize.X || TextSize.Y * textScale > minSize.Y))
387  {
388  TextScale = Math.Max(0.1f, Math.Min(minSize.X / TextSize.X, minSize.Y / TextSize.Y)) - 0.01f;
389  return;
390  }
391 
392  textPos = new Vector2(padding.X + (rect.Width - padding.Z - padding.X) / 2.0f, padding.Y + (rect.Height - padding.Y - padding.W) / 2.0f);
393  origin = TextSize * 0.5f;
394 
395  origin.X = 0;
396  if (textAlignment.HasFlag(Alignment.Left))
397  {
398  textPos.X = padding.X;
399  }
400  if (textAlignment.HasFlag(Alignment.Right))
401  {
402  textPos.X = rect.Width - padding.Z;
403  }
404  if (textAlignment.HasFlag(Alignment.Top))
405  {
406  textPos.Y = padding.Y;
407  origin.Y = 0;
408  }
409  if (textAlignment.HasFlag(Alignment.Bottom))
410  {
411  textPos.Y = rect.Height - padding.W;
412  origin.Y = TextSize.Y;
413  }
414 
415  origin.X = (int)(origin.X);
416  origin.Y = (int)(origin.Y);
417 
418  textPos.X = (int)textPos.X;
419  textPos.Y = (int)textPos.Y;
420  }
421 
422  private Vector2 MeasureText(LocalizedString text)
423  {
424  return MeasureText(text.Value);
425  }
426 
427  private Vector2 MeasureText(string text)
428  {
429  if (Font == null) return Vector2.Zero;
430 
431  if (string.IsNullOrEmpty(text))
432  {
433  return Font.MeasureString(" ");
434  }
435 
436  return Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text);
437  }
438 
439  protected override void SetAlpha(float a)
440  {
441  textColor = new Color(TextColor, a);
442  if (hoverTextColor.HasValue)
443  {
444  hoverTextColor = new Color(hoverTextColor.Value, a);
445  }
446  }
447 
451  public void OverrideTextColor(Color color)
452  {
453  textColor = color;
454  hoverTextColor = color;
455  selectedTextColor = color;
456  disabledTextColor = color;
457  }
458 
459  private ImmutableArray<Vector2> cachedCaretPositions = ImmutableArray<Vector2>.Empty;
460  //which text were the cached caret positions calculated for?
461  private string cachedCaretPositionsText;
462  public ImmutableArray<Vector2> GetAllCaretPositions()
463  {
464  string textDrawn = Censor ? CensoredText : Text.SanitizedValue;
465  if (cachedCaretPositions.Any() &&
466  textDrawn == cachedCaretPositionsText)
467  {
468  return cachedCaretPositions;
469  }
470  float w = Wrap
471  ? (Rect.Width - Padding.X - Padding.Z) / TextScale
472  : float.PositiveInfinity;
473  string wrapped = Font.WrapText(textDrawn, w, out Vector2[] positions);
474  int textWidth = (int)Font.MeasureString(wrapped).X;
475  int alignmentXDiff
476  = textAlignment.HasFlag(Alignment.Right) ? textWidth
477  : textAlignment.HasFlag(Alignment.Center) ? textWidth / 2
478  : 0;
479  cachedCaretPositions = positions
480  .Select(p => p - new Vector2(alignmentXDiff, 0))
481  .Select(p => p * TextScale + TextPos - Origin * TextScale)
482  .ToImmutableArray();
483  cachedCaretPositionsText = textDrawn;
484  return cachedCaretPositions;
485  }
486 
487  public int GetCaretIndexFromScreenPos(in Vector2 pos)
488  {
489  return GetCaretIndexFromLocalPos(pos - Rect.Location.ToVector2());
490  }
491 
492  public int GetCaretIndexFromLocalPos(in Vector2 pos)
493  {
494  var positions = GetAllCaretPositions();
495  if (positions.Length == 0) { return 0; }
496 
497  float closestXDist = float.PositiveInfinity;
498  float closestYDist = float.PositiveInfinity;
499  int closestIndex = -1;
500  for (int i = 0; i < positions.Length; i++)
501  {
502  float xDist = Math.Abs(pos.X - positions[i].X);
503  float yDist = Math.Abs(pos.Y - (positions[i].Y + Font.LineHeight * 0.5f));
504  if (yDist < closestYDist || (MathUtils.NearlyEqual(yDist, closestYDist) && xDist < closestXDist))
505  {
506  closestIndex = i;
507  closestXDist = xDist;
508  closestYDist = yDist;
509  }
510  }
511 
512  return closestIndex >= 0 ? closestIndex : Text.Length;
513  }
514 
515  protected override void Update(float deltaTime)
516  {
517  base.Update(deltaTime);
518 
519  if (ClickableAreas.Any() && ((GUI.MouseOn?.IsParentOf(this) ?? true) || GUI.MouseOn == this))
520  {
521  if (!Rect.Contains(PlayerInput.MousePosition)) { return; }
523  foreach (ClickableArea clickableArea in ClickableAreas)
524  {
525  if (clickableArea.Data.StartIndex <= index && index <= clickableArea.Data.EndIndex)
526  {
527  GUI.MouseCursor = CursorState.Hand;
529  {
530  clickableArea.OnClick?.Invoke(this, clickableArea);
531  }
533  {
534  clickableArea.OnSecondaryClick?.Invoke(this, clickableArea);
535  }
536  break;
537  }
538  }
539  }
540  }
541 
542  protected override void Draw(SpriteBatch spriteBatch)
543  {
544  if (!Visible) { return; }
545 
546  Color currColor = GetColor(State);
547 
548  var rect = Rect;
549 
550  base.Draw(spriteBatch);
551 
552  if (TextGetter != null) { Text = TextGetter(); }
553 
554  string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
555 
556  Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
557  if (overflowClipActive)
558  {
559  Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
560  if (!scissorRect.Intersects(prevScissorRect)) { return; }
561  spriteBatch.End();
562  spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, scissorRect);
563  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
564  }
565 
566  if (!string.IsNullOrEmpty(textToShow))
567  {
568  Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
570  {
571  pos.X = (int)pos.X;
572  pos.Y = (int)pos.Y;
573  }
574 
575  Color currentTextColor = State is ComponentState.Hover or ComponentState.HoverSelected ? HoverTextColor : TextColor;
576 
577  if (!enabled)
578  {
579  currentTextColor = disabledTextColor;
580  }
581  else if (State == ComponentState.Selected)
582  {
583  currentTextColor = selectedTextColor;
584  }
585 
586  if (!HasColorHighlight)
587  {
588  Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
589  if (TextManager.DebugDraw)
590  {
592  {
593  colorToShow = Color.Magenta;
594  }
595  }
596 
597  if (Shadow)
598  {
599  Vector2 shadowOffset = new Vector2(Math.Max(GUI.IntScale(2), 1));
600  Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase);
601  }
602 
603  Font.DrawString(spriteBatch, textToShow, pos, colorToShow, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase);
604  }
605  else
606  {
608  {
609  RichTextData?.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
610  }
611  Font.DrawStringWithColors(spriteBatch, textToShow, pos,
612  currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData, alignment: textAlignment, forceUpperCase: ForceUpperCase);
613  }
614 
615  Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X,
616  /* TODO: ???? */ForceUpperCase == ForceUpperCase.Yes ? pos.Y : pos.Y + GUI.Scale * 2f);
617  }
618 
619  if (overflowClipActive)
620  {
621  spriteBatch.End();
622  spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
623  spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
624  }
625 
626  if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
627  }
628 
632  public static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
633  {
634  AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>());
635  }
636 
640  public static void AutoScaleAndNormalize(bool scaleHorizontal = true, bool scaleVertical = false, params GUITextBlock[] textBlocks)
641  {
642  AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>(), scaleHorizontal, scaleVertical);
643  }
644 
648  public static void AutoScaleAndNormalize(IEnumerable<GUITextBlock> textBlocks, bool scaleHorizontal = true, bool scaleVertical = false, float? defaultScale = null)
649  {
650  if (!textBlocks.Any()) { return; }
651  float minScale = Math.Max(textBlocks.First().TextScale, 1.0f);
652  foreach (GUITextBlock textBlock in textBlocks)
653  {
654  if (defaultScale.HasValue) { textBlock.TextScale = defaultScale.Value; }
655  textBlock.AutoScaleHorizontal = scaleHorizontal;
656  textBlock.AutoScaleVertical = scaleVertical;
657  minScale = Math.Min(textBlock.TextScale, minScale);
658  }
659 
660  foreach (GUITextBlock textBlock in textBlocks)
661  {
662  textBlock.AutoScaleHorizontal = false;
663  textBlock.AutoScaleVertical = false;
664  textBlock.TextScale = minScale;
665  }
666  }
667  }
668 }
virtual ComponentState State
virtual Color GetColor(ComponentState state)
virtual Color OutlineColor
virtual Rectangle Rect
GUIComponentStyle Style
SpriteEffects SpriteEffects
readonly Color SelectedTextColor
readonly Identifier Font
readonly Color DisabledTextColor
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
Definition: GUIPrefab.cs:284
string WrapText(string text, float width)
void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray< RichTextData >? richTextData, int rtdOffset=0, Alignment alignment=Alignment.TopLeft, ForceUpperCase forceUpperCase=Barotrauma.ForceUpperCase.Inherit)
Definition: GUIPrefab.cs:279
void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
Definition: GUIPrefab.cs:249
StrikethroughSettings(Color? color=null, int thickness=1, int expand=0)
void Draw(SpriteBatch spriteBatch, float textSizeHalf, float xPos, float yPos)
override GUIFont Font
Definition: GUITextBlock.cs:66
StrikethroughSettings Strikethrough
override void ApplyStyle(GUIComponentStyle componentStyle)
ImmutableArray< Vector2 > GetAllCaretPositions()
override void Update(float deltaTime)
void OverrideTextColor(Color color)
Overrides the color for all the states.
GUITextBlock(RectTransform rectT, RichString text, Color? textColor=null, GUIFont font=null, Alignment textAlignment=Alignment.Left, bool wrap=false, string style="", Color? color=null)
This is the new constructor. If the rectT height is set 0, the height is calculated from the text.
void CalculateHeightFromText(int padding=0, bool removeExtraSpacing=false)
static void AutoScaleAndNormalize(bool scaleHorizontal=true, bool scaleVertical=false, params GUITextBlock[] textBlocks)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
bool AutoScaleHorizontal
When enabled, the text is automatically scaled down to fit the textblock horizontally.
static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
int GetCaretIndexFromScreenPos(in Vector2 pos)
override void Draw(SpriteBatch spriteBatch)
static void AutoScaleAndNormalize(IEnumerable< GUITextBlock > textBlocks, bool scaleHorizontal=true, bool scaleVertical=false, float? defaultScale=null)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
override void SetAlpha(float a)
int GetCaretIndexFromLocalPos(in Vector2 pos)
LocalizedString WrappedText
bool AutoScaleVertical
When enabled, the text is automatically scaled down to fit the textblock vertically.
List< ClickableArea > ClickableAreas
void SetRichText(LocalizedString richText)
TextGetterHandler TextGetter
Definition: GUITextBlock.cs:36
delegate LocalizedString TextGetterHandler()
static RasterizerState ScissorTestEnable
Definition: GameMain.cs:195
LanguageIdentifier Language
void Resize(Point absoluteSize, bool resizeChildren=true)
LocalizedString NestedStr
Definition: RichString.cs:30
ImmutableArray< RichTextData >? RichTextData
Definition: RichString.cs:42
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
readonly LocalizedString SanitizedString
Definition: RichString.cs:31
CursorState
Definition: GUI.cs:40
delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area)
static readonly LanguageIdentifier None
Definition: TextPack.cs:12