2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using Microsoft.Xna.Framework.Input;
6 using System.Collections.Generic;
45 return value.Invoke(a, b);
54 private int? maxTextLength;
56 private int _caretIndex;
59 get {
return _caretIndex; }
69 private bool caretPosDirty;
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;
82 private bool mouseHeldInside;
87 private bool skipUpdate;
97 set { textBlock.TextGetter = value; }
100 private new bool selected;
109 if (!selected && value)
113 else if (selected && !value)
122 get {
return textBlock.Wrap; }
125 textBlock.Wrap = value;
131 get {
return textBlock; }
144 get {
return maxTextLength; }
147 textBlock.OverflowClip = value !=
null;
148 maxTextLength = value;
151 SetText(
Text.Substring(0, (
int)maxTextLength));
161 get {
return textBlock.OverflowClip; }
162 set { textBlock.OverflowClip = value; }
170 enabled = frame.Enabled = textBlock.Enabled = value;
171 if (icon !=
null) { icon.Enabled = value; }
181 get {
return textBlock.Censor; }
182 set { textBlock.Censor = value; }
193 base.ToolTip = textBlock.ToolTip = caretAndSelectionRenderer.ToolTip = value;
199 get {
return textBlock?.Font ?? base.Font; }
203 if (textBlock ==
null) {
return; }
204 textBlock.Font = value;
209 public override Color Color
211 get {
return color; }
215 textBlock.Color =
color;
221 get {
return textBlock.TextColor; }
222 set { textBlock.TextColor = value; }
229 return base.HoverColor;
233 base.HoverColor = value;
234 textBlock.HoverColor = value;
240 get {
return textBlock.Padding; }
241 set { textBlock.Padding = value; }
251 return textBlock.Text.SanitizedValue;
255 SetText(value, store:
false);
263 get {
return textBlock.WrappedText.Value; }
272 public bool IsIMEActive => imePreviewTextHandler is { HasText:
true };
275 Alignment textAlignment = Alignment.Left,
bool wrap =
false,
string style =
"", Color?
color =
null,
bool createClearButton =
false,
bool createPenIcon =
true)
283 GUIStyle.Apply(frame, style ==
"" ?
"GUITextBox" : style);
284 textBlock =
new GUITextBlock(
new RectTransform(Vector2.One, frame.RectTransform,
Anchor.CenterLeft), text ??
"", textColor, font, textAlignment, wrap);
286 GUIStyle.Apply(textBlock,
"",
this);
287 if (font !=
null) { textBlock.Font = font; }
289 caretPosDirty =
true;
293 int clearButtonWidth = 0;
294 if (createClearButton)
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")
298 OnClicked = (bt, userdata) =>
301 frame.Flash(Color.White);
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);
309 var selfStyle =
Style;
310 if (selfStyle !=
null && selfStyle.ChildStyles.ContainsKey(
"textboxicon".ToIdentifier()) && createPenIcon)
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);
314 textBlock.RectTransform.MaxSize =
new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2,
int.MaxValue);
316 Font = textBlock.Font;
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;
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;
331 private bool SetText(
string text,
bool store =
true)
337 if (
Text == text) {
return false; }
338 textBlock.Text = text;
340 if (
Text ==
null) textBlock.Text =
"";
343 if (maxTextLength !=
null)
345 if (textBlock.Text.Length > maxTextLength)
347 textBlock.Text =
Text.Substring(0, (
int)maxTextLength);
354 textBlock.Text =
Text.Substring(0, textBlock.Text.Length - 1);
365 private void CalculateCaretPos()
368 var caretPositions = textBlock.GetAllCaretPositions();
371 throw new Exception($
"Caret index was outside the bounds of the calculated caret positions. Index: {CaretIndex}, caret positions: {caretPositions.Length}, text: {textBlock.Text}");
374 caretPosDirty =
false;
377 public void Select(
int forcedCaretIndex = -1,
bool ignoreSelectSound =
false)
380 if (memento.Current ==
null)
387 bool wasSelected = selected;
389 GUI.KeyboardDispatcher.Subscriber =
this;
402 if (GUI.KeyboardDispatcher.Subscriber ==
this)
404 GUI.KeyboardDispatcher.Subscriber =
null;
408 imePreviewTextHandler.Reset();
411 public override void Flash(Color?
color =
null,
float flashDuration = 1.5f,
bool useRectangleFlash =
false,
bool useCircularFlash =
false, Vector2? flashRectOffset =
null)
413 frame.Flash(
color,
flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
416 protected override void Update(
float deltaTime)
430 if (isMouseOn || isSelecting)
435 mouseHeldInside =
true;
452 CalculateSelection();
460 if (!mouseHeldInside) {
Deselect(); }
461 mouseHeldInside =
false;
469 mouseHeldInside =
false;
474 HandleCaretBoundsOverflow();
475 caretTimer += deltaTime;
476 caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
477 if (caretVisible && caretPosDirty)
483 if (GUI.KeyboardDispatcher.Subscriber ==
this)
497 textBlock.State =
State;
500 private void HandleCaretBoundsOverflow()
502 if (textBlock.OverflowClipActive)
505 float left = textBlock.Rect.X + textBlock.Padding.X;
509 textBlock.TextPos =
new Vector2(textBlock.TextPos.X + diff, textBlock.TextPos.Y);
513 float right = textBlock.Rect.Right - textBlock.Padding.Z;
517 textBlock.TextPos =
new Vector2(textBlock.TextPos.X - diff, textBlock.TextPos.Y);
523 private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
530 GUI.DrawLine(spriteBatch,
533 CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
535 if (selectedCharacters > 0)
537 DrawSelectionRect(spriteBatch);
541 private void DrawSelectionRect(SpriteBatch spriteBatch)
543 var characterPositions = textBlock.GetAllCaretPositions();
544 (
int startIndex,
int endIndex) = IsLeftToRight
545 ? (selectionStartIndex, selectionEndIndex)
546 : (selectionEndIndex, selectionStartIndex);
549 void drawRect(Vector2 topLeft, Vector2 bottomRight)
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,
560 Vector2 topLeft = characterPositions[startIndex];
561 for (
int i = startIndex+1; i <= endIndex; i++)
563 Vector2 currPos = characterPositions[i];
564 if (!MathUtils.NearlyEqual(topLeft.Y, currPos.Y))
566 Vector2 bottomRight = characterPositions[i - 1];
568 drawRect(topLeft, bottomRight);
572 Vector2 finalBottomRight = characterPositions[endIndex];
573 if (
Text.Length > endIndex)
577 drawRect(topLeft, finalBottomRight);
588 if (selectedCharacters > 0)
590 RemoveSelectedText();
592 using var _ =
new TextPosPreservation(
this);
597 imePreviewTextHandler?.Reset();
601 private readonly
ref struct TextPosPreservation
605 private readonly
bool wasOverflowClipActive;
606 private readonly Vector2 textPos;
615 public void Dispose()
637 using var _ =
new TextPosPreservation(
this);
640 SetText(
string.Empty,
false);
643 else if (selectedCharacters > 0)
645 RemoveSelectedText();
661 string text = GetCopiedText();
662 RemoveSelectedText();
673 RemoveSelectedText();
683 text = memento.Undo();
687 SetText(text,
false);
693 text = memento.Redo();
697 SetText(text,
false);
707 if (
string.IsNullOrEmpty(text))
709 imePreviewTextHandler.Reset();
713 imePreviewTextHandler.UpdateText(text, start, length);
725 InitSelectionStart();
734 InitSelectionStart();
743 InitSelectionStart();
751 out Vector2 requestedCharPos);
753 if (MathUtils.NearlyEqual(requestedCharPos.Y,
caretPos.Y)) { newIndex = 0; }
761 InitSelectionStart();
769 out Vector2 requestedCharPos2);
771 if (MathUtils.NearlyEqual(requestedCharPos2.Y,
caretPos.Y)) { newIndex =
Text.Length; }
777 if (selectedCharacters > 0)
779 RemoveSelectedText();
785 caretPosDirty =
true;
791 if (editor ==
null) {
break; }
792 var allTextBoxes = GetAndSortTextBoxes(editor).ToList();
793 if (allTextBoxes.Any())
795 int currentIndex = allTextBoxes.IndexOf(
this);
796 int nextIndex = Math.Min(allTextBoxes.Count - 1, currentIndex + 1);
797 var next = allTextBoxes[nextIndex];
801 next.Flash(Color.White * 0.5f, 0.5f);
807 if (listBox ==
null) {
break; }
810 listBox.SelectNext();
811 while (SelectNextTextBox(listBox) ==
null)
813 var previous = listBox.SelectedComponent;
814 listBox.SelectNext();
815 if (listBox.SelectedComponent == previous) {
break; }
822 if (listBox?.SelectedComponent ==
null) {
return null; }
826 var next = textBoxes.First();
828 next.Flash(Color.White * 0.5f, 0.5f);
835 if (caretPosDirty) { CalculateCaretPos(); }
837 void HandleSelection()
841 InitSelectionStart();
842 CalculateSelection();
855 selectionStartIndex = 0;
857 CalculateSelection();
860 private void CopySelectedText()
862 Clipboard.SetText(selectedText);
865 private void ClearSelection()
867 selectedCharacters = 0;
868 selectionStartIndex = -1;
869 selectionEndIndex = -1;
870 selectedText =
string.Empty;
873 private string GetCopiedText()
876 t = Clipboard.GetText();
881 private void RemoveSelectedText()
883 if (selectedText.Length == 0) {
return; }
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));
894 private float GetWrapWidth()
897 private void InitSelectionStart()
903 if (selectionStartIndex == -1)
911 imePreviewTextHandler.DrawIMEPreview(spriteBatch,
CaretScreenPos, textBlock);
914 private void CalculateSelection()
917 InitSelectionStart();
918 selectionEndIndex = Math.Min(
CaretIndex, textDrawn.Length);
919 selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
922 selectedText =
Text.Substring(
923 IsLeftToRight ? selectionStartIndex : selectionEndIndex,
924 Math.Min(selectedCharacters,
Text.Length));
926 catch (ArgumentOutOfRangeException exception)
928 DebugConsole.ThrowError($
"GUITextBox: Invalid selection: ({exception})");
static bool DisableControls
virtual ComponentState State
virtual Rectangle? MouseRect
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
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)
string WrapText(string text, float width)
Vector2 MeasureChar(char c)
GUIComponent SelectedComponent
int GetCaretIndexFromLocalPos(in Vector2 pos)
TextGetterHandler TextGetter
delegate LocalizedString TextGetterHandler()
OnTextChangedHandler OnTextChangedDelegate
TextBoxEvent OnDeselected
override RichString ToolTip
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
delegate bool OnEnterHandler(GUITextBox textBox, string text)
void ReceiveTextInput(string input)
GUITextBlock.TextGetterHandler TextGetter
void ReceiveEditingInput(string text, int start, int length)
override bool PlaySoundOnSelect
void ReceiveCommandInput(char command)
void Select(int forcedCaretIndex=-1, bool ignoreSelectSound=false)
bool DeselectAfterMessage
void DrawIMEPreview(SpriteBatch spriteBatch)
override void Update(float deltaTime)
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)
OnEnterHandler OnEnterPressed
override Color HoverColor
void ReceiveTextInput(char inputChar)
bool OverflowClip
When enabled, clips the left side of the text if it's too long to fit in the box (i....
delegate bool OnTextChangedHandler(GUITextBox textBox, string text)
Func< string, string > textFilterFunction
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
void ReceiveSpecialInput(Keys key)
static bool IsSubEditor()
delegate void TextBoxEvent(GUITextBox sender, Keys key)