Client LuaCsForBarotrauma
GUITextBox.cs
1 using EventInput;
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using Microsoft.Xna.Framework.Input;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
11 
12  public delegate void TextBoxEvent(GUITextBox sender, Keys key);
13 
15  {
16  public event TextBoxEvent OnSelected;
17  public event TextBoxEvent OnDeselected;
18 
19  bool caretVisible;
20  float caretTimer;
21 
22  private readonly GUIFrame frame;
23  private readonly GUITextBlock textBlock;
24  private readonly GUIImage icon;
25 
26  public Func<string, string> textFilterFunction;
27 
28  public delegate bool OnEnterHandler(GUITextBox textBox, string text);
30 
31  public event TextBoxEvent OnKeyHit;
32 
33  public delegate bool OnTextChangedHandler(GUITextBox textBox, string text);
40  {
41  set
42  {
43  OnTextChanged += (GUITextBox a, string b) =>
44  {
45  return value.Invoke(a, b);
46  };
47  }
48  }
49 
50  public bool CaretEnabled { get; set; }
51  public Color? CaretColor { get; set; }
52  public bool DeselectAfterMessage = true;
53 
54  private int? maxTextLength;
55 
56  private int _caretIndex;
57  public int CaretIndex
58  {
59  get { return _caretIndex; }
60  set
61  {
62  if (value >= 0)
63  {
64  _caretIndex = value;
65  caretPosDirty = true;
66  }
67  }
68  }
69  private bool caretPosDirty;
70  protected Vector2 caretPos;
71  public Vector2 CaretScreenPos => Rect.Location.ToVector2() + caretPos;
72 
73  private bool isSelecting;
74  private string selectedText = string.Empty;
75  private int selectedCharacters;
76  private int selectionStartIndex;
77  private int selectionEndIndex;
78  private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
79 
80  private readonly GUICustomComponent caretAndSelectionRenderer;
81 
82  private bool mouseHeldInside;
83 
84  private readonly Memento<string> memento = new Memento<string>();
85 
86  // Skip one update cycle, fixes Enter key instantly deselecting the chatbox
87  private bool skipUpdate;
88 
89  public GUIFrame Frame
90  {
91  get { return frame; }
92  }
93 
95  {
96  get { return textBlock.TextGetter; }
97  set { textBlock.TextGetter = value; }
98  }
99 
100  private new bool selected;
101  public new bool Selected
102  {
103  get
104  {
105  return selected;
106  }
107  set
108  {
109  if (!selected && value)
110  {
111  Select();
112  }
113  else if (selected && !value)
114  {
115  Deselect();
116  }
117  }
118  }
119 
120  public bool Wrap
121  {
122  get { return textBlock.Wrap; }
123  set
124  {
125  textBlock.Wrap = value;
126  }
127  }
128 
130  {
131  get { return textBlock; }
132  }
133 
134  //should the text be limited to the size of the box
135  //ignored when MaxTextLength is set or text wrapping is enabled
136  public bool ClampText
137  {
138  get;
139  set;
140  }
141 
142  public int? MaxTextLength
143  {
144  get { return maxTextLength; }
145  set
146  {
147  textBlock.OverflowClip = value != null;
148  maxTextLength = value;
149  if (Text.Length > MaxTextLength)
150  {
151  SetText(Text.Substring(0, (int)maxTextLength));
152  }
153  }
154  }
155 
159  public bool OverflowClip
160  {
161  get { return textBlock.OverflowClip; }
162  set { textBlock.OverflowClip = value; }
163  }
164 
165  public override bool Enabled
166  {
167  get { return enabled; }
168  set
169  {
170  enabled = frame.Enabled = textBlock.Enabled = value;
171  if (icon != null) { icon.Enabled = value; }
172  if (!enabled && Selected)
173  {
174  Deselect();
175  }
176  }
177  }
178 
179  public bool Censor
180  {
181  get { return textBlock.Censor; }
182  set { textBlock.Censor = value; }
183  }
184 
185  public override RichString ToolTip
186  {
187  get
188  {
189  return base.ToolTip;
190  }
191  set
192  {
193  base.ToolTip = textBlock.ToolTip = caretAndSelectionRenderer.ToolTip = value;
194  }
195  }
196 
197  public override GUIFont Font
198  {
199  get { return textBlock?.Font ?? base.Font; }
200  set
201  {
202  base.Font = value;
203  if (textBlock == null) { return; }
204  textBlock.Font = value;
205  imePreviewTextHandler.Font = Font;
206  }
207  }
208 
209  public override Color Color
210  {
211  get { return color; }
212  set
213  {
214  color = value;
215  textBlock.Color = color;
216  }
217  }
218 
219  public Color TextColor
220  {
221  get { return textBlock.TextColor; }
222  set { textBlock.TextColor = value; }
223  }
224 
225  public override Color HoverColor
226  {
227  get
228  {
229  return base.HoverColor;
230  }
231  set
232  {
233  base.HoverColor = value;
234  textBlock.HoverColor = value;
235  }
236  }
237 
238  public Vector4 Padding
239  {
240  get { return textBlock.Padding; }
241  set { textBlock.Padding = value; }
242  }
243 
244  // TODO: should this be defined in the stylesheet?
245  public Color SelectionColor { get; set; } = Color.White * 0.25f;
246 
247  public string Text
248  {
249  get
250  {
251  return textBlock.Text.SanitizedValue;
252  }
253  set
254  {
255  SetText(value, store: false);
256  CaretIndex = Text.Length;
257  OnTextChanged?.Invoke(this, Text);
258  }
259  }
260 
261  public string WrappedText
262  {
263  get { return textBlock.WrappedText.Value; }
264  }
265 
266  public bool Readonly { get; set; }
267 
268  public override bool PlaySoundOnSelect { get; set; } = true;
269 
270  private readonly IMEPreviewTextHandler imePreviewTextHandler;
271 
272  public bool IsIMEActive => imePreviewTextHandler is { HasText: true };
273 
274  public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
275  Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
276  : base(style, rectT)
277  {
278  HoverCursor = CursorState.IBeam;
279  CanBeFocused = true;
280 
281  this.color = color ?? Color.White;
282  frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
283  GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
284  textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
285  imePreviewTextHandler = new IMEPreviewTextHandler(textBlock.Font);
286  GUIStyle.Apply(textBlock, "", this);
287  if (font != null) { textBlock.Font = font; }
288  CaretEnabled = true;
289  caretPosDirty = true;
290 
291  caretAndSelectionRenderer = new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection);
292 
293  int clearButtonWidth = 0;
294  if (createClearButton)
295  {
296  var clearButton = new GUIButton(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, style: "GUICancelButton")
297  {
298  OnClicked = (bt, userdata) =>
299  {
300  Text = "";
301  frame.Flash(Color.White);
302  return true;
303  }
304  };
305  textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - clearButton.Rect.Height - clearButton.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
306  clearButtonWidth = (int)(clearButton.Rect.Width * 1.2f);
307  }
308 
309  var selfStyle = Style;
310  if (selfStyle != null && selfStyle.ChildStyles.ContainsKey("textboxicon".ToIdentifier()) && createPenIcon)
311  {
312  icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5 + clearButtonWidth, 0) }, null, scaleToFit: true);
313  icon.ApplyStyle(this.Style.ChildStyles["textboxicon".ToIdentifier()]);
314  textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
315  }
316  Font = textBlock.Font;
317  Enabled = true;
318 
319  rectT.SizeChanged += () =>
320  {
321  if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
322  caretPosDirty = true;
323  };
324  rectT.ScaleChanged += () =>
325  {
326  if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
327  caretPosDirty = true;
328  };
329  }
330 
331  private bool SetText(string text, bool store = true)
332  {
333  if (textFilterFunction != null)
334  {
335  text = textFilterFunction(text);
336  }
337  if (Text == text) { return false; }
338  textBlock.Text = text;
339  ClearSelection();
340  if (Text == null) textBlock.Text = "";
341  if (Text != "")
342  {
343  if (maxTextLength != null)
344  {
345  if (textBlock.Text.Length > maxTextLength)
346  {
347  textBlock.Text = Text.Substring(0, (int)maxTextLength);
348  }
349  }
350  else if (!Wrap)
351  {
352  while (ClampText && textBlock.Text.Length > 0 && Font.MeasureString(textBlock.Text).X * TextBlock.TextScale > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
353  {
354  textBlock.Text = Text.Substring(0, textBlock.Text.Length - 1);
355  }
356  }
357  }
358  if (store)
359  {
360  memento.Store(Text);
361  }
362  return true;
363  }
364 
365  private void CalculateCaretPos()
366  {
367  CaretIndex = Math.Clamp(CaretIndex, 0, textBlock.Text.Length);
368  var caretPositions = textBlock.GetAllCaretPositions();
369  if (CaretIndex >= caretPositions.Length)
370  {
371  throw new Exception($"Caret index was outside the bounds of the calculated caret positions. Index: {CaretIndex}, caret positions: {caretPositions.Length}, text: {textBlock.Text}");
372  }
373  caretPos = caretPositions[CaretIndex];
374  caretPosDirty = false;
375  }
376 
377  public void Select(int forcedCaretIndex = -1, bool ignoreSelectSound = false)
378  {
379  skipUpdate = true;
380  if (memento.Current == null)
381  {
382  memento.Store(Text);
383  }
384  CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
385  CalculateCaretPos();
386  ClearSelection();
387  bool wasSelected = selected;
388  selected = true;
389  GUI.KeyboardDispatcher.Subscriber = this;
390  OnSelected?.Invoke(this, Keys.None);
391  if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
392  {
393  SoundPlayer.PlayUISound(GUISoundType.Select);
394  }
395  }
396 
397  public void Deselect()
398  {
399  memento.Clear();
400  selected = false;
401 
402  if (GUI.KeyboardDispatcher.Subscriber == this)
403  {
404  GUI.KeyboardDispatcher.Subscriber = null;
405  }
406 
407  OnDeselected?.Invoke(this, Keys.None);
408  imePreviewTextHandler.Reset();
409  }
410 
411  public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
412  {
413  frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
414  }
415 
416  protected override void Update(float deltaTime)
417  {
418  if (!Visible) return;
419 
420  if (flashTimer > 0.0f) flashTimer -= deltaTime;
421  if (!Enabled) { return; }
422 
423  if (skipUpdate)
424  {
425  skipUpdate = false;
426  return;
427  }
428 
429  bool isMouseOn = MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this)));
430  if (isMouseOn || isSelecting)
431  {
432  State = ComponentState.Hover;
434  {
435  mouseHeldInside = true;
436  Select();
437  }
438  else
439  {
440  isSelecting = PlayerInput.PrimaryMouseButtonHeld();
441  }
443  {
444  SelectAll();
445  }
446  if (isSelecting)
447  {
448  if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0))
449  {
450  CaretIndex = textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
451  CalculateCaretPos();
452  CalculateSelection();
453  }
454  }
455  }
456  else
457  {
459  {
460  if (!mouseHeldInside) { Deselect(); }
461  mouseHeldInside = false;
462  }
463  isSelecting = false;
464  State = ComponentState.None;
465  }
466 
467  if (mouseHeldInside && !PlayerInput.PrimaryMouseButtonHeld())
468  {
469  mouseHeldInside = false;
470  }
471 
472  if (CaretEnabled)
473  {
474  HandleCaretBoundsOverflow();
475  caretTimer += deltaTime;
476  caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
477  if (caretVisible && caretPosDirty)
478  {
479  CalculateCaretPos();
480  }
481  }
482 
483  if (GUI.KeyboardDispatcher.Subscriber == this)
484  {
485  State = ComponentState.Selected;
486  Character.DisableControls = true;
487  if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
488  {
489  OnEnterPressed(this, Text);
490  }
491  }
492  else if (Selected)
493  {
494  Deselect();
495  }
496 
497  textBlock.State = State;
498  }
499 
500  private void HandleCaretBoundsOverflow()
501  {
502  if (textBlock.OverflowClipActive)
503  {
504  CalculateCaretPos();
505  float left = textBlock.Rect.X + textBlock.Padding.X;
506  if (CaretScreenPos.X < left)
507  {
508  float diff = left - CaretScreenPos.X;
509  textBlock.TextPos = new Vector2(textBlock.TextPos.X + diff, textBlock.TextPos.Y);
510  CalculateCaretPos();
511  }
512 
513  float right = textBlock.Rect.Right - textBlock.Padding.Z;
514  if (CaretScreenPos.X > right)
515  {
516  float diff = CaretScreenPos.X - right;
517  textBlock.TextPos = new Vector2(textBlock.TextPos.X - diff, textBlock.TextPos.Y);
518  CalculateCaretPos();
519  }
520  }
521  }
522 
523  private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
524  {
525  if (!Visible) { return; }
526  if (!Selected) { return; }
527 
528  if (caretVisible)
529  {
530  GUI.DrawLine(spriteBatch,
531  new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + 3),
532  new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + Font.LineHeight * textBlock.TextScale - 3),
533  CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
534  }
535  if (selectedCharacters > 0)
536  {
537  DrawSelectionRect(spriteBatch);
538  }
539  }
540 
541  private void DrawSelectionRect(SpriteBatch spriteBatch)
542  {
543  var characterPositions = textBlock.GetAllCaretPositions();
544  (int startIndex, int endIndex) = IsLeftToRight
545  ? (selectionStartIndex, selectionEndIndex)
546  : (selectionEndIndex, selectionStartIndex);
547  endIndex--;
548 
549  void drawRect(Vector2 topLeft, Vector2 bottomRight)
550  {
551  int minWidth = GUI.IntScale(5);
552  if (OverflowClip) { topLeft.X = Math.Max(topLeft.X, 0.0f); }
553  if (bottomRight.X - topLeft.X < minWidth) { bottomRight.X = topLeft.X + minWidth; }
554  GUI.DrawRectangle(spriteBatch,
555  Rect.Location.ToVector2() + topLeft,
556  bottomRight - topLeft,
557  SelectionColor, isFilled: true);
558  }
559 
560  Vector2 topLeft = characterPositions[startIndex];
561  for (int i = startIndex+1; i <= endIndex; i++)
562  {
563  Vector2 currPos = characterPositions[i];
564  if (!MathUtils.NearlyEqual(topLeft.Y, currPos.Y))
565  {
566  Vector2 bottomRight = characterPositions[i - 1];
567  bottomRight += Font.MeasureChar(Text[i - 1]) * TextBlock.TextScale;
568  drawRect(topLeft, bottomRight);
569  topLeft = currPos;
570  }
571  }
572  Vector2 finalBottomRight = characterPositions[endIndex];
573  if (Text.Length > endIndex)
574  {
575  finalBottomRight += Font.MeasureChar(Text[endIndex]) * TextBlock.TextScale;
576  }
577  drawRect(topLeft, finalBottomRight);
578  }
579 
580  public void ReceiveTextInput(char inputChar)
581  {
582  ReceiveTextInput(inputChar.ToString());
583  }
584 
585  public void ReceiveTextInput(string input)
586  {
587  if (Readonly) { return; }
588  if (selectedCharacters > 0)
589  {
590  RemoveSelectedText();
591  }
592  using var _ = new TextPosPreservation(this);
593  if (SetText(Text.Insert(CaretIndex, input)))
594  {
595  CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
596  OnTextChanged?.Invoke(this, Text);
597  imePreviewTextHandler?.Reset();
598  }
599  }
600 
601  private readonly ref struct TextPosPreservation
602  {
603  private readonly GUITextBox textBox;
604  private GUITextBlock textBlock => textBox.TextBlock;
605  private readonly bool wasOverflowClipActive;
606  private readonly Vector2 textPos;
607 
608  public TextPosPreservation(GUITextBox tb)
609  {
610  textBox = tb;
611  wasOverflowClipActive = tb.TextBlock.OverflowClipActive;
612  textPos = tb.TextBlock.TextPos;
613  }
614 
615  public void Dispose()
616  {
617  if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos))
618  {
619  textBlock.TextPos = textPos;
620  }
621  }
622  }
623 
624  public void ReceiveCommandInput(char command)
625  {
626  if (IsIMEActive) { return; }
627 
628  if (Text == null) { Text = ""; }
629 
630  // Prevent alt gr from triggering any of these as that combination is often needed for special characters
631  if (PlayerInput.IsAltDown()) { return; }
632 
633  switch (command)
634  {
635  case '\b' when !Readonly: //backspace
636  {
637  using var _ = new TextPosPreservation(this);
638  if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
639  {
640  SetText(string.Empty, false);
641  CaretIndex = Text.Length;
642  }
643  else if (selectedCharacters > 0)
644  {
645  RemoveSelectedText();
646  }
647  else if (Text.Length > 0 && CaretIndex > 0)
648  {
649  CaretIndex--;
650  SetText(Text.Remove(CaretIndex, 1));
651  CalculateCaretPos();
652  ClearSelection();
653  }
654  OnTextChanged?.Invoke(this, Text);
655  break;
656  }
657  case (char)0x3: // ctrl-c
658  CopySelectedText();
659  break;
660  case (char)0x16 when !Readonly: // ctrl-v
661  string text = GetCopiedText();
662  RemoveSelectedText();
663  if (SetText(Text.Insert(CaretIndex, text)))
664  {
665  CaretIndex = Math.Min(Text.Length, CaretIndex + text.Length);
666  OnTextChanged?.Invoke(this, Text);
667  }
668  break;
669  case (char)0x18: // ctrl-x
670  CopySelectedText();
671  if (!Readonly)
672  {
673  RemoveSelectedText();
674  }
675  break;
676  case (char)0x1: // ctrl-a
677  if (PlayerInput.IsCtrlDown())
678  {
679  SelectAll();
680  }
681  break;
682  case (char)0x1A when !Readonly && !SubEditorScreen.IsSubEditor(): // ctrl-z
683  text = memento.Undo();
684  if (text != Text)
685  {
686  ClearSelection();
687  SetText(text, false);
688  CaretIndex = Text.Length;
689  OnTextChanged?.Invoke(this, Text);
690  }
691  break;
692  case (char)0x12 when !Readonly && !SubEditorScreen.IsSubEditor(): // ctrl-r
693  text = memento.Redo();
694  if (text != Text)
695  {
696  ClearSelection();
697  SetText(text, false);
698  CaretIndex = Text.Length;
699  OnTextChanged?.Invoke(this, Text);
700  }
701  break;
702  }
703  }
704 
705  public void ReceiveEditingInput(string text, int start, int length)
706  {
707  if (string.IsNullOrEmpty(text))
708  {
709  imePreviewTextHandler.Reset();
710  return;
711  }
712 
713  imePreviewTextHandler.UpdateText(text, start, length);
714  }
715 
716  public void ReceiveSpecialInput(Keys key)
717  {
718  if (IsIMEActive) { return; }
719 
720  switch (key)
721  {
722  case Keys.Left:
723  if (isSelecting)
724  {
725  InitSelectionStart();
726  }
727  CaretIndex = Math.Max(CaretIndex - 1, 0);
728  caretTimer = 0;
729  HandleSelection();
730  break;
731  case Keys.Right:
732  if (isSelecting)
733  {
734  InitSelectionStart();
735  }
736  CaretIndex = Math.Min(CaretIndex + 1, Text.Length);
737  caretTimer = 0;
738  HandleSelection();
739  break;
740  case Keys.Up:
741  if (isSelecting)
742  {
743  InitSelectionStart();
744  }
745  float lineHeight = Font.LineHeight * TextBlock.TextScale;
746  int newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y - lineHeight * 0.5f));
747  textBlock.Font.WrapText(
748  textBlock.Text.SanitizedValue,
749  GetWrapWidth(),
750  newIndex,
751  out Vector2 requestedCharPos);
752  requestedCharPos *= TextBlock.TextScale;
753  if (MathUtils.NearlyEqual(requestedCharPos.Y, caretPos.Y)) { newIndex = 0; }
754  CaretIndex = newIndex;
755  caretTimer = 0;
756  HandleSelection();
757  break;
758  case Keys.Down:
759  if (isSelecting)
760  {
761  InitSelectionStart();
762  }
763  lineHeight = Font.LineHeight * TextBlock.TextScale;
764  newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y + lineHeight * 1.5f));
765  textBlock.Font.WrapText(
766  textBlock.Text.SanitizedValue,
767  GetWrapWidth(),
768  newIndex,
769  out Vector2 requestedCharPos2);
770  requestedCharPos2 *= TextBlock.TextScale;
771  if (MathUtils.NearlyEqual(requestedCharPos2.Y, caretPos.Y)) { newIndex = Text.Length; }
772  CaretIndex = newIndex;
773  caretTimer = 0;
774  HandleSelection();
775  break;
776  case Keys.Delete when !Readonly:
777  if (selectedCharacters > 0)
778  {
779  RemoveSelectedText();
780  }
781  else if (Text.Length > 0 && CaretIndex < Text.Length)
782  {
783  SetText(Text.Remove(CaretIndex, 1));
784  OnTextChanged?.Invoke(this, Text);
785  caretPosDirty = true;
786  }
787  break;
788  case Keys.Tab:
789  // Select the next text box.
790  var editor = RectTransform.GetParents().Select(p => p.GUIComponent as SerializableEntityEditor).FirstOrDefault(e => e != null);
791  if (editor == null) { break; }
792  var allTextBoxes = GetAndSortTextBoxes(editor).ToList();
793  if (allTextBoxes.Any())
794  {
795  int currentIndex = allTextBoxes.IndexOf(this);
796  int nextIndex = Math.Min(allTextBoxes.Count - 1, currentIndex + 1);
797  var next = allTextBoxes[nextIndex];
798  if (next != this)
799  {
800  next.Select();
801  next.Flash(Color.White * 0.5f, 0.5f);
802  }
803  else
804  {
805  // Select the first text box in the next editor that has text boxes.
806  var listBox = RectTransform.GetParents().Select(p => p.GUIComponent as GUIListBox).FirstOrDefault(lb => lb != null);
807  if (listBox == null) { break; }
808  // TODO: The get's out of focus if the selection is out of view.
809  // Not sure how's that possible, but it seems to work when the auto scroll is disabled and you handle the scrolling manually.
810  listBox.SelectNext();
811  while (SelectNextTextBox(listBox) == null)
812  {
813  var previous = listBox.SelectedComponent;
814  listBox.SelectNext();
815  if (listBox.SelectedComponent == previous) { break; }
816  }
817  }
818  }
819  IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren<GUITextBox>().OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
820  GUITextBox SelectNextTextBox(GUIListBox listBox)
821  {
822  if (listBox?.SelectedComponent == null) { return null; }
823  var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
824  if (textBoxes.Any())
825  {
826  var next = textBoxes.First();
827  next.Select();
828  next.Flash(Color.White * 0.5f, 0.5f);
829  return next;
830  }
831  return null;
832  }
833  break;
834  }
835  if (caretPosDirty) { CalculateCaretPos(); }
836  OnKeyHit?.Invoke(this, key);
837  void HandleSelection()
838  {
839  if (isSelecting)
840  {
841  InitSelectionStart();
842  CalculateSelection();
843  }
844  else
845  {
846  ClearSelection();
847  }
848  }
849  }
850 
851  public void SelectAll()
852  {
853  CaretIndex = 0;
854  CalculateCaretPos();
855  selectionStartIndex = 0;
856  CaretIndex = Text.Length;
857  CalculateSelection();
858  }
859 
860  private void CopySelectedText()
861  {
862  Clipboard.SetText(selectedText);
863  }
864 
865  private void ClearSelection()
866  {
867  selectedCharacters = 0;
868  selectionStartIndex = -1;
869  selectionEndIndex = -1;
870  selectedText = string.Empty;
871  }
872 
873  private string GetCopiedText()
874  {
875  string t;
876  t = Clipboard.GetText();
877 
878  return t;
879  }
880 
881  private void RemoveSelectedText()
882  {
883  if (selectedText.Length == 0) { return; }
884 
885  int targetCaretIndex = Math.Max(0, Math.Min(selectionEndIndex, Math.Min(selectionStartIndex, Text.Length - 1)));
886  int selectionLength = Math.Min(Text.Length - targetCaretIndex, selectedText.Length);
887  SetText(Text.Remove(targetCaretIndex, selectionLength));
888  CaretIndex = targetCaretIndex;
889 
890  ClearSelection();
891  OnTextChanged?.Invoke(this, Text);
892  }
893 
894  private float GetWrapWidth()
895  => Wrap ? (textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z) / TextBlock.TextScale : float.PositiveInfinity;
896 
897  private void InitSelectionStart()
898  {
899  if (caretPosDirty)
900  {
901  CalculateCaretPos();
902  }
903  if (selectionStartIndex == -1)
904  {
905  selectionStartIndex = CaretIndex;
906  }
907  }
908 
909  public void DrawIMEPreview(SpriteBatch spriteBatch)
910  {
911  imePreviewTextHandler.DrawIMEPreview(spriteBatch, CaretScreenPos, textBlock);
912  }
913 
914  private void CalculateSelection()
915  {
916  string textDrawn = Censor ? textBlock.CensoredText : WrappedText;
917  InitSelectionStart();
918  selectionEndIndex = Math.Min(CaretIndex, textDrawn.Length);
919  selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
920  try
921  {
922  selectedText = Text.Substring(
923  IsLeftToRight ? selectionStartIndex : selectionEndIndex,
924  Math.Min(selectedCharacters, Text.Length));
925  }
926  catch (ArgumentOutOfRangeException exception)
927  {
928  DebugConsole.ThrowError($"GUITextBox: Invalid selection: ({exception})");
929  }
930  }
931  }
932 }
virtual ComponentState State
virtual Rectangle? MouseRect
virtual Rectangle Rect
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
Definition: GUIComponent.cs:49
GUIComponentStyle Style
RectTransform RectTransform
readonly Dictionary< Identifier, GUIComponentStyle > ChildStyles
GUIComponent that can be used to render custom content on the UI
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
Definition: GUIPrefab.cs:284
string WrapText(string text, float width)
Vector2 MeasureChar(char c)
Definition: GUIPrefab.cs:289
GUIComponent SelectedComponent
Definition: GUIListBox.cs:155
override GUIFont Font
Definition: GUITextBlock.cs:66
int GetCaretIndexFromLocalPos(in Vector2 pos)
TextGetterHandler TextGetter
Definition: GUITextBlock.cs:36
delegate LocalizedString TextGetterHandler()
OnTextChangedHandler OnTextChangedDelegate
Definition: GUITextBox.cs:40
TextBoxEvent OnDeselected
Definition: GUITextBox.cs:17
override RichString ToolTip
Definition: GUITextBox.cs:186
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
Definition: GUITextBox.cs:38
delegate bool OnEnterHandler(GUITextBox textBox, string text)
void ReceiveTextInput(string input)
Definition: GUITextBox.cs:585
override GUIFont??? Font
Definition: GUITextBox.cs:198
GUITextBlock.TextGetterHandler TextGetter
Definition: GUITextBox.cs:95
void ReceiveEditingInput(string text, int start, int length)
Definition: GUITextBox.cs:705
override bool PlaySoundOnSelect
Definition: GUITextBox.cs:268
void ReceiveCommandInput(char command)
Definition: GUITextBox.cs:624
void Select(int forcedCaretIndex=-1, bool ignoreSelectSound=false)
Definition: GUITextBox.cs:377
TextBoxEvent OnKeyHit
Definition: GUITextBox.cs:31
void DrawIMEPreview(SpriteBatch spriteBatch)
Definition: GUITextBox.cs:909
override void Update(float deltaTime)
Definition: GUITextBox.cs:416
TextBoxEvent OnSelected
Definition: GUITextBox.cs:16
GUITextBox(RectTransform rectT, string text="", Color? textColor=null, GUIFont font=null, Alignment textAlignment=Alignment.Left, bool wrap=false, string style="", Color? color=null, bool createClearButton=false, bool createPenIcon=true)
Definition: GUITextBox.cs:274
OnEnterHandler OnEnterPressed
Definition: GUITextBox.cs:29
override Color HoverColor
Definition: GUITextBox.cs:226
void ReceiveTextInput(char inputChar)
Definition: GUITextBox.cs:580
GUITextBlock TextBlock
Definition: GUITextBox.cs:130
bool OverflowClip
When enabled, clips the left side of the text if it's too long to fit in the box (i....
Definition: GUITextBox.cs:160
delegate bool OnTextChangedHandler(GUITextBox textBox, string text)
override bool Enabled
Definition: GUITextBox.cs:166
Func< string, string > textFilterFunction
Definition: GUITextBox.cs:26
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
Definition: GUITextBox.cs:411
void ReceiveSpecialInput(Keys key)
Definition: GUITextBox.cs:716
static bool KeyDown(InputType inputType)
IEnumerable< RectTransform > GetParents()
Returns all parent elements in the hierarchy.
GUISoundType
Definition: GUI.cs:21
CursorState
Definition: GUI.cs:40
delegate void TextBoxEvent(GUITextBox sender, Keys key)