A game about forced loneliness, made by TACStudios
1//#define TMP_DEBUG_MODE
2
3using System;
4using System.Collections;
5using System.Collections.Generic;
6using System.Threading;
7using System.Text;
8using System.Text.RegularExpressions;
9using UnityEngine;
10using UnityEngine.UI;
11using UnityEngine.Events;
12using UnityEngine.EventSystems;
13using UnityEngine.Serialization;
14
15
16namespace TMPro
17{
18 /// <summary>
19 /// Editable text input field.
20 /// </summary>
21 [AddComponentMenu("UI/TextMeshPro - Input Field", 11)]
22 #if UNITY_2023_2_OR_NEWER
23 [HelpURL("https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/TextMeshPro/index.html")]
24 #else
25 [HelpURL("https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2")]
26 #endif
27 public class TMP_InputField : Selectable,
28 IUpdateSelectedHandler,
29 IBeginDragHandler,
30 IDragHandler,
31 IEndDragHandler,
32 IPointerClickHandler,
33 ISubmitHandler,
34 ICancelHandler,
35 ICanvasElement,
36 ILayoutElement,
37 IScrollHandler
38 {
39
40 // Setting the content type acts as a shortcut for setting a combination of InputType, CharacterValidation, LineType, and TouchScreenKeyboardType
41 public enum ContentType
42 {
43 Standard,
44 Autocorrected,
45 IntegerNumber,
46 DecimalNumber,
47 Alphanumeric,
48 Name,
49 EmailAddress,
50 Password,
51 Pin,
52 Custom
53 }
54
55 public enum InputType
56 {
57 Standard,
58 AutoCorrect,
59 Password,
60 }
61
62 public enum CharacterValidation
63 {
64 None,
65 Digit,
66 Integer,
67 Decimal,
68 Alphanumeric,
69 Name,
70 Regex,
71 EmailAddress,
72 CustomValidator
73 }
74
75 public enum LineType
76 {
77 SingleLine,
78 MultiLineSubmit,
79 MultiLineNewline
80 }
81
82 public delegate char OnValidateInput(string text, int charIndex, char addedChar);
83
84 [Serializable]
85 public class SubmitEvent : UnityEvent<string> { }
86
87 [Serializable]
88 public class OnChangeEvent : UnityEvent<string> { }
89
90 [Serializable]
91 public class SelectionEvent : UnityEvent<string> { }
92
93 [Serializable]
94 public class TextSelectionEvent : UnityEvent<string, int, int> { }
95
96 [Serializable]
97 public class TouchScreenKeyboardEvent : UnityEvent<TouchScreenKeyboard.Status> { }
98
99 protected TouchScreenKeyboard m_SoftKeyboard;
100 static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' };
101
102 #if UNITY_ANDROID
103 static private bool s_IsQuestDeviceEvaluated = false;
104 #endif // if UNITY_ANDROID
105
106 static private bool s_IsQuestDevice = false;
107
108 #region Exposed properties
109 /// <summary>
110 /// Text Text used to display the input's value.
111 /// </summary>
112
113 protected RectTransform m_RectTransform;
114
115 [SerializeField]
116 protected RectTransform m_TextViewport;
117
118 protected RectMask2D m_TextComponentRectMask;
119
120 protected RectMask2D m_TextViewportRectMask;
121 //private Rect m_CachedViewportRect;
122
123 [SerializeField]
124 protected TMP_Text m_TextComponent;
125
126 protected RectTransform m_TextComponentRectTransform;
127
128 [SerializeField]
129 protected Graphic m_Placeholder;
130
131 [SerializeField]
132 protected Scrollbar m_VerticalScrollbar;
133
134 [SerializeField]
135 protected TMP_ScrollbarEventHandler m_VerticalScrollbarEventHandler;
136 //private bool m_ForceDeactivation;
137
138 private bool m_IsDrivenByLayoutComponents = false;
139 [SerializeField]
140 private LayoutGroup m_LayoutGroup;
141
142 private IScrollHandler m_IScrollHandlerParent;
143
144 /// <summary>
145 /// Used to keep track of scroll position
146 /// </summary>
147 private float m_ScrollPosition;
148
149 /// <summary>
150 ///
151 /// </summary>
152 [SerializeField]
153 protected float m_ScrollSensitivity = 1.0f;
154
155 //[SerializeField]
156 //protected TMP_Text m_PlaceholderTextComponent;
157
158 [SerializeField]
159 private ContentType m_ContentType = ContentType.Standard;
160
161 /// <summary>
162 /// Type of data expected by the input field.
163 /// </summary>
164 [SerializeField]
165 private InputType m_InputType = InputType.Standard;
166
167 /// <summary>
168 /// The character used to hide text in password field.
169 /// </summary>
170 [SerializeField]
171 private char m_AsteriskChar = '*';
172
173 /// <summary>
174 /// Keyboard type applies to mobile keyboards that get shown.
175 /// </summary>
176 [SerializeField]
177 private TouchScreenKeyboardType m_KeyboardType = TouchScreenKeyboardType.Default;
178
179 [SerializeField]
180 private LineType m_LineType = LineType.SingleLine;
181
182 /// <summary>
183 /// Should hide mobile input field part of the virtual keyboard.
184 /// </summary>
185 [SerializeField]
186 private bool m_HideMobileInput = false;
187
188 /// <summary>
189 /// Should hide soft / virtual keyboard.
190 /// </summary>
191 [SerializeField]
192 private bool m_HideSoftKeyboard = false;
193
194 /// <summary>
195 /// What kind of validation to use with the input field's data.
196 /// </summary>
197 [SerializeField]
198 private CharacterValidation m_CharacterValidation = CharacterValidation.None;
199
200 /// <summary>
201 /// The Regex expression used for validating the text input.
202 /// </summary>
203 [SerializeField]
204 private string m_RegexValue = string.Empty;
205
206 /// <summary>
207 /// The point sized used by the placeholder and input text object.
208 /// </summary>
209 [SerializeField]
210 private float m_GlobalPointSize = 14;
211
212 /// <summary>
213 /// Maximum number of characters allowed before input no longer works.
214 /// </summary>
215 [SerializeField]
216 private int m_CharacterLimit = 0;
217
218 /// <summary>
219 /// Event delegates triggered when the input field submits its data.
220 /// </summary>
221 [SerializeField]
222 private SubmitEvent m_OnEndEdit = new SubmitEvent();
223
224 /// <summary>
225 /// Event delegates triggered when the input field submits its data.
226 /// </summary>
227 [SerializeField]
228 private SubmitEvent m_OnSubmit = new SubmitEvent();
229
230 /// <summary>
231 /// Event delegates triggered when the input field is focused.
232 /// </summary>
233 [SerializeField]
234 private SelectionEvent m_OnSelect = new SelectionEvent();
235
236 /// <summary>
237 /// Event delegates triggered when the input field focus is lost.
238 /// </summary>
239 [SerializeField]
240 private SelectionEvent m_OnDeselect = new SelectionEvent();
241
242 /// <summary>
243 /// Event delegates triggered when the text is selected / highlighted.
244 /// </summary>
245 [SerializeField]
246 private TextSelectionEvent m_OnTextSelection = new TextSelectionEvent();
247
248 /// <summary>
249 /// Event delegates triggered when text is no longer select / highlighted.
250 /// </summary>
251 [SerializeField]
252 private TextSelectionEvent m_OnEndTextSelection = new TextSelectionEvent();
253
254 /// <summary>
255 /// Event delegates triggered when the input field changes its data.
256 /// </summary>
257 [SerializeField]
258 private OnChangeEvent m_OnValueChanged = new OnChangeEvent();
259
260 /// <summary>
261 /// Event delegates triggered when the status of the TouchScreenKeyboard changes.
262 /// </summary>
263 [SerializeField]
264 private TouchScreenKeyboardEvent m_OnTouchScreenKeyboardStatusChanged = new TouchScreenKeyboardEvent();
265
266 /// <summary>
267 /// Custom validation callback.
268 /// </summary>
269 [SerializeField]
270 private OnValidateInput m_OnValidateInput;
271
272 [SerializeField]
273 private Color m_CaretColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f);
274
275 [SerializeField]
276 private bool m_CustomCaretColor = false;
277
278 [SerializeField]
279 private Color m_SelectionColor = new Color(168f / 255f, 206f / 255f, 255f / 255f, 192f / 255f);
280
281 /// <summary>
282 /// Input field's value.
283 /// </summary>
284
285 [SerializeField]
286 [TextArea(5, 10)]
287 protected string m_Text = string.Empty;
288
289 [SerializeField]
290 [Range(0f, 4f)]
291 private float m_CaretBlinkRate = 0.85f;
292
293 [SerializeField]
294 [Range(1, 5)]
295 private int m_CaretWidth = 1;
296
297 [SerializeField]
298 private bool m_ReadOnly = false;
299
300 [SerializeField]
301 private bool m_RichText = true;
302
303 #endregion
304
305 protected int m_StringPosition = 0;
306 protected int m_StringSelectPosition = 0;
307 protected int m_CaretPosition = 0;
308 protected int m_CaretSelectPosition = 0;
309
310 private RectTransform caretRectTrans = null;
311 protected UIVertex[] m_CursorVerts = null;
312 private CanvasRenderer m_CachedInputRenderer;
313 private Vector2 m_LastPosition;
314
315 [NonSerialized]
316 protected Mesh m_Mesh;
317 private bool m_AllowInput = false;
318 //bool m_HasLostFocus = false;
319 private bool m_ShouldActivateNextUpdate = false;
320 private bool m_UpdateDrag = false;
321 private bool m_DragPositionOutOfBounds = false;
322 private const float kHScrollSpeed = 0.05f;
323 private const float kVScrollSpeed = 0.10f;
324 protected bool m_CaretVisible;
325 private Coroutine m_BlinkCoroutine = null;
326 private float m_BlinkStartTime = 0.0f;
327 private Coroutine m_DragCoroutine = null;
328 private string m_OriginalText = "";
329 private bool m_WasCanceled = false;
330 private bool m_HasDoneFocusTransition = false;
331 private WaitForSecondsRealtime m_WaitForSecondsRealtime;
332 private bool m_PreventCallback = false;
333
334 private bool m_TouchKeyboardAllowsInPlaceEditing = false;
335
336 private bool m_IsTextComponentUpdateRequired = false;
337
338 private bool m_HasTextBeenRemoved = false;
339 private float m_PointerDownClickStartTime;
340 private float m_KeyDownStartTime;
341 private float m_DoubleClickDelay = 0.5f;
342
343 private bool m_IsApplePlatform = false;
344
345 // Doesn't include dot and @ on purpose! See usage for details.
346 const string kEmailSpecialCharacters = "!#$%&'*+-/=?^_`{|}~";
347 const string kOculusQuestDeviceModel = "Oculus Quest";
348
349 private BaseInput inputSystem
350 {
351 get
352 {
353 if (EventSystem.current && EventSystem.current.currentInputModule)
354 return EventSystem.current.currentInputModule.input;
355 return null;
356 }
357 }
358
359 private string compositionString
360 {
361 get { return inputSystem != null ? inputSystem.compositionString : Input.compositionString; }
362 }
363 private bool m_IsCompositionActive = false;
364 private bool m_ShouldUpdateIMEWindowPosition = false;
365 private int m_PreviousIMEInsertionLine = 0;
366
367 private int compositionLength
368 {
369 get
370 {
371 if (m_ReadOnly)
372 return 0;
373
374 return compositionString.Length;
375 }
376 }
377
378
379
380 protected TMP_InputField()
381 {
382 SetTextComponentWrapMode();
383 }
384
385 protected Mesh mesh
386 {
387 get
388 {
389 if (m_Mesh == null)
390 m_Mesh = new Mesh();
391 return m_Mesh;
392 }
393 }
394
395 /// <summary>
396 /// Should the inputfield be automatically activated upon selection.
397 /// </summary>
398 public virtual bool shouldActivateOnSelect
399 {
400 set
401 {
402 m_ShouldActivateOnSelect = value;
403 }
404 get
405 {
406 return m_ShouldActivateOnSelect && Application.platform != RuntimePlatform.tvOS;
407 }
408 }
409
410 /// <summary>
411 /// Should the mobile keyboard input be hidden.
412 /// </summary>
413 public bool shouldHideMobileInput
414 {
415 get
416 {
417 switch (Application.platform)
418 {
419 case RuntimePlatform.Android:
420 case RuntimePlatform.IPhonePlayer:
421 case RuntimePlatform.tvOS:
422 #if UNITY_2022_1_OR_NEWER
423 case RuntimePlatform.WebGLPlayer:
424 #endif
425 return m_HideMobileInput;
426 default:
427 return true;
428 }
429 }
430
431 set
432 {
433 switch(Application.platform)
434 {
435 case RuntimePlatform.Android:
436 case RuntimePlatform.IPhonePlayer:
437 case RuntimePlatform.tvOS:
438 #if UNITY_2022_1_OR_NEWER
439 case RuntimePlatform.WebGLPlayer:
440 #endif
441 SetPropertyUtility.SetStruct(ref m_HideMobileInput, value);
442 break;
443 default:
444 m_HideMobileInput = true;
445 break;
446 }
447 }
448 }
449
450 public bool shouldHideSoftKeyboard
451 {
452 get
453 {
454 switch (Application.platform)
455 {
456 case RuntimePlatform.Android:
457 case RuntimePlatform.IPhonePlayer:
458 case RuntimePlatform.tvOS:
459 #if UNITY_XR_VISIONOS_SUPPORTED
460 case RuntimePlatform.VisionOS:
461 #endif
462 case RuntimePlatform.WSAPlayerX86:
463 case RuntimePlatform.WSAPlayerX64:
464 case RuntimePlatform.WSAPlayerARM:
465 #if UNITY_2020_2_OR_NEWER
466 case RuntimePlatform.PS4:
467 #if !(UNITY_2020_2_1 || UNITY_2020_2_2)
468 case RuntimePlatform.PS5:
469 #endif
470 #endif
471 #if UNITY_2019_4_OR_NEWER
472 case RuntimePlatform.GameCoreXboxOne:
473 case RuntimePlatform.GameCoreXboxSeries:
474 #endif
475 case RuntimePlatform.Switch:
476 #if UNITY_2022_1_OR_NEWER
477 case RuntimePlatform.WebGLPlayer:
478 #endif
479 return m_HideSoftKeyboard;
480 default:
481 return true;
482 }
483 }
484
485 set
486 {
487 switch (Application.platform)
488 {
489 case RuntimePlatform.Android:
490 case RuntimePlatform.IPhonePlayer:
491 case RuntimePlatform.tvOS:
492 #if UNITY_XR_VISIONOS_SUPPORTED
493 case RuntimePlatform.VisionOS:
494 #endif
495 case RuntimePlatform.WSAPlayerX86:
496 case RuntimePlatform.WSAPlayerX64:
497 case RuntimePlatform.WSAPlayerARM:
498 #if UNITY_2020_2_OR_NEWER
499 case RuntimePlatform.PS4:
500 #if !(UNITY_2020_2_1 || UNITY_2020_2_2)
501 case RuntimePlatform.PS5:
502 #endif
503 #endif
504 #if UNITY_2019_4_OR_NEWER
505 case RuntimePlatform.GameCoreXboxOne:
506 case RuntimePlatform.GameCoreXboxSeries:
507 #endif
508 case RuntimePlatform.Switch:
509 #if UNITY_2022_1_OR_NEWER
510 case RuntimePlatform.WebGLPlayer:
511 #endif
512 SetPropertyUtility.SetStruct(ref m_HideSoftKeyboard, value);
513 break;
514 default:
515 m_HideSoftKeyboard = true;
516 break;
517 }
518
519 if (m_HideSoftKeyboard == true && m_SoftKeyboard != null && TouchScreenKeyboard.isSupported && m_SoftKeyboard.active)
520 {
521 m_SoftKeyboard.active = false;
522 m_SoftKeyboard = null;
523 }
524 }
525 }
526
527 private bool isKeyboardUsingEvents()
528 {
529 switch (Application.platform)
530 {
531 case RuntimePlatform.Android:
532 return InPlaceEditing() && m_HideSoftKeyboard;
533 case RuntimePlatform.IPhonePlayer:
534 case RuntimePlatform.tvOS:
535 #if UNITY_XR_VISIONOS_SUPPORTED
536 case RuntimePlatform.VisionOS:
537 #endif
538 return m_HideSoftKeyboard;
539 #if UNITY_2020_2_OR_NEWER
540 case RuntimePlatform.PS4:
541 #if !(UNITY_2020_2_1 || UNITY_2020_2_2)
542 case RuntimePlatform.PS5:
543 #endif
544 #endif
545 #if UNITY_2019_4_OR_NEWER
546 case RuntimePlatform.GameCoreXboxOne:
547 case RuntimePlatform.GameCoreXboxSeries:
548 #endif
549 case RuntimePlatform.Switch:
550 return false;
551 #if UNITY_2022_1_OR_NEWER
552 case RuntimePlatform.WebGLPlayer:
553 return m_SoftKeyboard == null || !m_SoftKeyboard.active;
554 #endif
555 default:
556 return true;
557 }
558 }
559
560 private bool isUWP()
561 {
562 return Application.platform == RuntimePlatform.WSAPlayerX86 || Application.platform == RuntimePlatform.WSAPlayerX64 || Application.platform == RuntimePlatform.WSAPlayerARM;
563 }
564
565 /// <summary>
566 /// Input field's current text value. This is not necessarily the same as what is visible on screen.
567 /// </summary>
568 /// <remarks>
569 /// Note that null is invalid value for InputField.text.
570 /// </remarks>
571 /// <example>
572 /// <code>
573 /// using UnityEngine;
574 /// using System.Collections;
575 /// using UnityEngine.UI; // Required when Using UI elements.
576 ///
577 /// public class Example : MonoBehaviour
578 /// {
579 /// public InputField mainInputField;
580 ///
581 /// public void Start()
582 /// {
583 /// mainInputField.text = "Enter Text Here...";
584 /// }
585 /// }
586 /// </code>
587 /// </example>
588 public string text
589 {
590 get
591 {
592 return m_Text;
593 }
594 set
595 {
596 SetText(value);
597 }
598 }
599
600 /// <summary>
601 /// Set Input field's current text value without invoke onValueChanged. This is not necessarily the same as what is visible on screen.
602 /// </summary>
603 public void SetTextWithoutNotify(string input)
604 {
605 SetText(input, false);
606 }
607
608 void SetText(string value, bool sendCallback = true)
609 {
610 if (this.text == value)
611 return;
612
613 if (value == null)
614 value = "";
615
616 value = value.Replace("\0", string.Empty); // remove embedded nulls
617
618 m_Text = value;
619
620 /*
621 if (m_LineType == LineType.SingleLine)
622 value = value.Replace("\n", "").Replace("\t", "");
623
624 // If we have an input validator, validate the input and apply the character limit at the same time.
625 if (onValidateInput != null || characterValidation != CharacterValidation.None)
626 {
627 m_Text = "";
628 OnValidateInput validatorMethod = onValidateInput ?? Validate;
629 m_CaretPosition = m_CaretSelectPosition = value.Length;
630 int charactersToCheck = characterLimit > 0 ? Math.Min(characterLimit, value.Length) : value.Length;
631 for (int i = 0; i < charactersToCheck; ++i)
632 {
633 char c = validatorMethod(m_Text, m_Text.Length, value[i]);
634 if (c != 0)
635 m_Text += c;
636 }
637 }
638 else
639 {
640 m_Text = characterLimit > 0 && value.Length > characterLimit ? value.Substring(0, characterLimit) : value;
641 }
642 */
643
644 #if UNITY_EDITOR
645 if (!Application.isPlaying)
646 {
647 SendOnValueChangedAndUpdateLabel();
648 return;
649 }
650 #endif
651
652 if (m_SoftKeyboard != null)
653 m_SoftKeyboard.text = m_Text;
654
655 if (m_StringPosition > m_Text.Length)
656 m_StringPosition = m_StringSelectPosition = m_Text.Length;
657 else if (m_StringSelectPosition > m_Text.Length)
658 m_StringSelectPosition = m_Text.Length;
659
660 m_forceRectTransformAdjustment = true;
661
662 m_IsTextComponentUpdateRequired = true;
663 UpdateLabel();
664
665 if (sendCallback)
666 SendOnValueChanged();
667 }
668
669
670 public bool isFocused
671 {
672 get { return m_AllowInput; }
673 }
674
675 public float caretBlinkRate
676 {
677 get { return m_CaretBlinkRate; }
678 set
679 {
680 if (SetPropertyUtility.SetStruct(ref m_CaretBlinkRate, value))
681 {
682 if (m_AllowInput)
683 SetCaretActive();
684 }
685 }
686 }
687
688 public int caretWidth { get { return m_CaretWidth; } set { if (SetPropertyUtility.SetStruct(ref m_CaretWidth, value)) MarkGeometryAsDirty(); } }
689
690 public RectTransform textViewport { get { return m_TextViewport; } set { SetPropertyUtility.SetClass(ref m_TextViewport, value); } }
691
692 public TMP_Text textComponent
693 {
694 get { return m_TextComponent; }
695 set
696 {
697 if (SetPropertyUtility.SetClass(ref m_TextComponent, value))
698 {
699 SetTextComponentWrapMode();
700 }
701 }
702 }
703
704 //public TMP_Text placeholderTextComponent { get { return m_PlaceholderTextComponent; } set { SetPropertyUtility.SetClass(ref m_PlaceholderTextComponent, value); } }
705
706 public Graphic placeholder { get { return m_Placeholder; } set { SetPropertyUtility.SetClass(ref m_Placeholder, value); } }
707
708 public Scrollbar verticalScrollbar
709 {
710 get { return m_VerticalScrollbar; }
711 set
712 {
713 if (m_VerticalScrollbar != null)
714 m_VerticalScrollbar.onValueChanged.RemoveListener(OnScrollbarValueChange);
715
716 SetPropertyUtility.SetClass(ref m_VerticalScrollbar, value);
717
718 if (m_VerticalScrollbar)
719 {
720 m_VerticalScrollbar.onValueChanged.AddListener(OnScrollbarValueChange);
721
722 }
723 }
724 }
725
726 public float scrollSensitivity { get { return m_ScrollSensitivity; } set { if (SetPropertyUtility.SetStruct(ref m_ScrollSensitivity, value)) MarkGeometryAsDirty(); } }
727
728 public Color caretColor { get { return customCaretColor ? m_CaretColor : textComponent.color; } set { if (SetPropertyUtility.SetColor(ref m_CaretColor, value)) MarkGeometryAsDirty(); } }
729
730 public bool customCaretColor { get { return m_CustomCaretColor; } set { if (m_CustomCaretColor != value) { m_CustomCaretColor = value; MarkGeometryAsDirty(); } } }
731
732 public Color selectionColor { get { return m_SelectionColor; } set { if (SetPropertyUtility.SetColor(ref m_SelectionColor, value)) MarkGeometryAsDirty(); } }
733
734 public SubmitEvent onEndEdit { get { return m_OnEndEdit; } set { SetPropertyUtility.SetClass(ref m_OnEndEdit, value); } }
735
736 public SubmitEvent onSubmit { get { return m_OnSubmit; } set { SetPropertyUtility.SetClass(ref m_OnSubmit, value); } }
737
738 public SelectionEvent onSelect { get { return m_OnSelect; } set { SetPropertyUtility.SetClass(ref m_OnSelect, value); } }
739
740 public SelectionEvent onDeselect { get { return m_OnDeselect; } set { SetPropertyUtility.SetClass(ref m_OnDeselect, value); } }
741
742 public TextSelectionEvent onTextSelection { get { return m_OnTextSelection; } set { SetPropertyUtility.SetClass(ref m_OnTextSelection, value); } }
743
744 public TextSelectionEvent onEndTextSelection { get { return m_OnEndTextSelection; } set { SetPropertyUtility.SetClass(ref m_OnEndTextSelection, value); } }
745
746 public OnChangeEvent onValueChanged { get { return m_OnValueChanged; } set { SetPropertyUtility.SetClass(ref m_OnValueChanged, value); } }
747
748 public TouchScreenKeyboardEvent onTouchScreenKeyboardStatusChanged { get { return m_OnTouchScreenKeyboardStatusChanged; } set { SetPropertyUtility.SetClass(ref m_OnTouchScreenKeyboardStatusChanged, value); } }
749
750 public OnValidateInput onValidateInput { get { return m_OnValidateInput; } set { SetPropertyUtility.SetClass(ref m_OnValidateInput, value); } }
751
752 public int characterLimit
753 {
754 get { return m_CharacterLimit; }
755 set
756 {
757 if (SetPropertyUtility.SetStruct(ref m_CharacterLimit, Math.Max(0, value)))
758 {
759 UpdateLabel();
760 if (m_SoftKeyboard != null)
761 m_SoftKeyboard.characterLimit = value;
762 }
763 }
764 }
765
766 //public bool isInteractableControl { set { if ( } }
767
768 /// <summary>
769 /// Set the point size on both Placeholder and Input text object.
770 /// </summary>
771 public float pointSize
772 {
773 get { return m_GlobalPointSize; }
774 set
775 {
776 if (SetPropertyUtility.SetStruct(ref m_GlobalPointSize, Math.Max(0, value)))
777 {
778 SetGlobalPointSize(m_GlobalPointSize);
779 UpdateLabel();
780 }
781 }
782 }
783
784 /// <summary>
785 /// Sets the Font Asset on both Placeholder and Input child objects.
786 /// </summary>
787 public TMP_FontAsset fontAsset
788 {
789 get { return m_GlobalFontAsset; }
790 set
791 {
792 if (SetPropertyUtility.SetClass(ref m_GlobalFontAsset, value))
793 {
794 SetGlobalFontAsset(m_GlobalFontAsset);
795 UpdateLabel();
796 }
797 }
798 }
799 [SerializeField]
800 protected TMP_FontAsset m_GlobalFontAsset;
801
802 /// <summary>
803 /// Determines if the whole text will be selected when focused.
804 /// </summary>
805 public bool onFocusSelectAll
806 {
807 get { return m_OnFocusSelectAll; }
808 set { m_OnFocusSelectAll = value; }
809 }
810 [SerializeField]
811 protected bool m_OnFocusSelectAll = true;
812 protected bool m_isSelectAll;
813
814 /// <summary>
815 /// Determines if the text and caret position as well as selection will be reset when the input field is deactivated.
816 /// </summary>
817 public bool resetOnDeActivation
818 {
819 get { return m_ResetOnDeActivation; }
820 set { m_ResetOnDeActivation = value; }
821 }
822 [SerializeField]
823 protected bool m_ResetOnDeActivation = true;
824 private bool m_SelectionStillActive = false;
825 private bool m_ReleaseSelection = false;
826 private KeyCode m_LastKeyCode;
827
828 private GameObject m_PreviouslySelectedObject;
829
830 /// <summary>
831 /// Determines if the text selection will remain visible when the input field looses focus and is deactivated.
832 /// </summary>
833 public bool keepTextSelectionVisible
834 {
835 get { return m_KeepTextSelectionVisible; }
836 set { m_KeepTextSelectionVisible = value; }
837 }
838
839 [SerializeField]
840 private bool m_KeepTextSelectionVisible;
841
842 /// <summary>
843 /// Controls whether the original text is restored when pressing "ESC".
844 /// </summary>
845 public bool restoreOriginalTextOnEscape
846 {
847 get { return m_RestoreOriginalTextOnEscape; }
848 set { m_RestoreOriginalTextOnEscape = value; }
849 }
850 [SerializeField]
851 private bool m_RestoreOriginalTextOnEscape = true;
852
853 /// <summary>
854 /// Is Rich Text editing allowed?
855 /// </summary>
856 public bool isRichTextEditingAllowed
857 {
858 get { return m_isRichTextEditingAllowed; }
859 set { m_isRichTextEditingAllowed = value; }
860 }
861 [SerializeField]
862 protected bool m_isRichTextEditingAllowed = false;
863
864
865 // Content Type related
866 public ContentType contentType { get { return m_ContentType; } set { if (SetPropertyUtility.SetStruct(ref m_ContentType, value)) EnforceContentType(); } }
867
868 public LineType lineType
869 {
870 get { return m_LineType; }
871 set
872 {
873 if (SetPropertyUtility.SetStruct(ref m_LineType, value))
874 {
875 SetToCustomIfContentTypeIsNot(ContentType.Standard, ContentType.Autocorrected);
876 SetTextComponentWrapMode();
877 }
878 }
879 }
880
881 /// <summary>
882 /// Limits the number of lines of text in the Input Field.
883 /// </summary>
884 public int lineLimit
885 {
886 get { return m_LineLimit; }
887 set
888 {
889 if (m_LineType == LineType.SingleLine)
890 m_LineLimit = 1;
891 else
892 SetPropertyUtility.SetStruct(ref m_LineLimit, value);
893
894 }
895 }
896 [SerializeField]
897 protected int m_LineLimit = 0;
898
899 /// <summary>
900 /// The type of input expected. See InputField.InputType.
901 /// </summary>
902 public InputType inputType { get { return m_InputType; } set { if (SetPropertyUtility.SetStruct(ref m_InputType, value)) SetToCustom(); } }
903
904 /// <summary>
905 /// The TouchScreenKeyboard being used to edit the Input Field.
906 /// </summary>
907 public TouchScreenKeyboard touchScreenKeyboard { get { return m_SoftKeyboard; } }
908
909 /// <summary>
910 /// They type of mobile keyboard that will be used.
911 /// </summary>
912 public TouchScreenKeyboardType keyboardType
913 {
914 get { return m_KeyboardType; }
915 set
916 {
917 if (SetPropertyUtility.SetStruct(ref m_KeyboardType, value))
918 SetToCustom();
919 }
920 }
921
922 /// <summary>
923 /// Determines if the keyboard is opened in alert mode.
924 /// </summary>
925 public bool isAlert;
926
927 /// <summary>
928 /// The type of validation to perform on a character
929 /// </summary>
930 public CharacterValidation characterValidation { get { return m_CharacterValidation; } set { if (SetPropertyUtility.SetStruct(ref m_CharacterValidation, value)) SetToCustom(); } }
931
932 /// <summary>
933 /// Sets the Input Validation to use a Custom Input Validation script.
934 /// </summary>
935 public TMP_InputValidator inputValidator
936 {
937 get { return m_InputValidator; }
938 set { if (SetPropertyUtility.SetClass(ref m_InputValidator, value)) SetToCustom(CharacterValidation.CustomValidator); }
939 }
940 [SerializeField]
941 protected TMP_InputValidator m_InputValidator = null;
942
943 public bool readOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } }
944
945 [SerializeField]
946 private bool m_ShouldActivateOnSelect = true;
947
948 public bool richText { get { return m_RichText; } set { m_RichText = value; SetTextComponentRichTextMode(); } }
949
950 // Derived property
951 public bool multiLine { get { return m_LineType == LineType.MultiLineNewline || lineType == LineType.MultiLineSubmit; } }
952 // Not shown in Inspector.
953 public char asteriskChar { get { return m_AsteriskChar; } set { if (SetPropertyUtility.SetStruct(ref m_AsteriskChar, value)) UpdateLabel(); } }
954 public bool wasCanceled { get { return m_WasCanceled; } }
955
956
957 protected void ClampStringPos(ref int pos)
958 {
959 if (pos <= 0)
960 pos = 0;
961 else if (pos > text.Length)
962 pos = text.Length;
963 }
964
965 protected void ClampCaretPos(ref int pos)
966 {
967 if (pos > m_TextComponent.textInfo.characterCount - 1)
968 pos = m_TextComponent.textInfo.characterCount - 1;
969
970 if (pos <= 0)
971 pos = 0;
972 }
973
974 int ClampArrayIndex(int index)
975 {
976 if (index < 0)
977 return 0;
978
979 return index;
980 }
981
982 /// <summary>
983 /// Current position of the cursor.
984 /// Getters are public Setters are protected
985 /// </summary>
986
987 protected int caretPositionInternal { get { return m_CaretPosition + compositionLength; } set { m_CaretPosition = value; ClampCaretPos(ref m_CaretPosition); } }
988 protected int stringPositionInternal { get { return m_StringPosition + compositionLength; } set { m_StringPosition = value; ClampStringPos(ref m_StringPosition); } }
989
990 protected int caretSelectPositionInternal { get { return m_CaretSelectPosition + compositionLength; } set { m_CaretSelectPosition = value; ClampCaretPos(ref m_CaretSelectPosition); } }
991 protected int stringSelectPositionInternal { get { return m_StringSelectPosition + compositionLength; } set { m_StringSelectPosition = value; ClampStringPos(ref m_StringSelectPosition); } }
992
993 private bool hasSelection { get { return stringPositionInternal != stringSelectPositionInternal; } }
994 private bool m_isSelected;
995 private bool m_IsStringPositionDirty;
996 private bool m_IsCaretPositionDirty;
997 private bool m_forceRectTransformAdjustment;
998
999 // Primary to track when an user presses on the X to close the keyboard in the HoloLens
1000 private bool m_IsKeyboardBeingClosedInHoloLens = false;
1001
1002 /// <summary>
1003 /// Get: Returns the focus position as thats the position that moves around even during selection.
1004 /// Set: Set both the anchor and focus position such that a selection doesn't happen
1005 /// </summary>
1006 public int caretPosition
1007 {
1008 get => caretSelectPositionInternal;
1009 set { selectionAnchorPosition = value; selectionFocusPosition = value; UpdateStringIndexFromCaretPosition(); }
1010 }
1011
1012 /// <summary>
1013 /// Get: Returns the fixed position of selection
1014 /// Set: If compositionString is 0 set the fixed position
1015 /// </summary>
1016 public int selectionAnchorPosition
1017 {
1018 get
1019 {
1020 return caretPositionInternal;
1021 }
1022
1023 set
1024 {
1025 if (compositionLength != 0)
1026 return;
1027
1028 caretPositionInternal = value;
1029 m_IsStringPositionDirty = true;
1030 }
1031 }
1032
1033 /// <summary>
1034 /// Get: Returns the variable position of selection
1035 /// Set: If compositionString is 0 set the variable position
1036 /// </summary>
1037 public int selectionFocusPosition
1038 {
1039 get
1040 {
1041 return caretSelectPositionInternal;
1042 }
1043 set
1044 {
1045 if (compositionLength != 0)
1046 return;
1047
1048 caretSelectPositionInternal = value;
1049 m_IsStringPositionDirty = true;
1050 }
1051 }
1052
1053
1054 /// <summary>
1055 ///
1056 /// </summary>
1057 public int stringPosition
1058 {
1059 get => stringSelectPositionInternal;
1060 set { selectionStringAnchorPosition = value; selectionStringFocusPosition = value; UpdateCaretPositionFromStringIndex(); }
1061 }
1062
1063
1064 /// <summary>
1065 /// The fixed position of the selection in the raw string which may contains rich text.
1066 /// </summary>
1067 public int selectionStringAnchorPosition
1068 {
1069 get
1070 {
1071 return stringPositionInternal;
1072 }
1073
1074 set
1075 {
1076 if (compositionLength != 0)
1077 return;
1078
1079 stringPositionInternal = value;
1080 m_IsCaretPositionDirty = true;
1081 }
1082 }
1083
1084
1085 /// <summary>
1086 /// The variable position of the selection in the raw string which may contains rich text.
1087 /// </summary>
1088 public int selectionStringFocusPosition
1089 {
1090 get
1091 {
1092 return stringSelectPositionInternal;
1093 }
1094 set
1095 {
1096 if (compositionLength != 0)
1097 return;
1098
1099 stringSelectPositionInternal = value;
1100 m_IsCaretPositionDirty = true;
1101 }
1102 }
1103
1104
1105 #if UNITY_EDITOR
1106 // Remember: This is NOT related to text validation!
1107 // This is Unity's own OnValidate method which is invoked when changing values in the Inspector.
1108 protected override void OnValidate()
1109 {
1110 base.OnValidate();
1111 EnforceContentType();
1112
1113 m_CharacterLimit = Math.Max(0, m_CharacterLimit);
1114
1115 //This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called.
1116 if (!IsActive())
1117 return;
1118
1119 SetTextComponentRichTextMode();
1120
1121 UpdateLabel();
1122
1123 if (m_AllowInput)
1124 SetCaretActive();
1125 }
1126 #endif // if UNITY_EDITOR
1127
1128 #if UNITY_ANDROID
1129 protected override void Awake()
1130 {
1131 base.Awake();
1132
1133 if (s_IsQuestDeviceEvaluated)
1134 return;
1135
1136 // Used for Oculus Quest 1 and 2 software keyboard regression.
1137 // TouchScreenKeyboard.isInPlaceEditingAllowed is always returning true in these devices and would prevent the software keyboard from showing up if that value was used.
1138 s_IsQuestDevice = SystemInfo.deviceModel == kOculusQuestDeviceModel;
1139 s_IsQuestDeviceEvaluated = true;
1140 }
1141 #endif // if UNITY_ANDROID
1142
1143
1144 protected override void OnEnable()
1145 {
1146 //Debug.Log("*** OnEnable() *** - " + this.name);
1147
1148 base.OnEnable();
1149
1150 if (m_Text == null)
1151 m_Text = string.Empty;
1152
1153 m_IsApplePlatform = SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX || SystemInfo.operatingSystem.Contains("iOS") || SystemInfo.operatingSystem.Contains("tvOS");
1154
1155 // Check if Input Field is driven by any layout components
1156 ILayoutController layoutController = GetComponent<ILayoutController>();
1157
1158 if (layoutController != null)
1159 {
1160 m_IsDrivenByLayoutComponents = true;
1161 m_LayoutGroup = GetComponent<LayoutGroup>();
1162 }
1163 else
1164 m_IsDrivenByLayoutComponents = false;
1165
1166 if (Application.isPlaying)
1167 {
1168 if (m_CachedInputRenderer == null && m_TextComponent != null)
1169 {
1170 GameObject go = new GameObject("Caret", typeof(TMP_SelectionCaret));
1171
1172 go.hideFlags = HideFlags.DontSave;
1173 go.transform.SetParent(m_TextComponent.transform.parent);
1174 go.transform.SetAsFirstSibling();
1175 go.layer = gameObject.layer;
1176
1177 caretRectTrans = go.GetComponent<RectTransform>();
1178 m_CachedInputRenderer = go.GetComponent<CanvasRenderer>();
1179 m_CachedInputRenderer.SetMaterial(Graphic.defaultGraphicMaterial, Texture2D.whiteTexture);
1180
1181 // Needed as if any layout is present we want the caret to always be the same as the text area.
1182 go.AddComponent<LayoutElement>().ignoreLayout = true;
1183
1184 AssignPositioningIfNeeded();
1185 }
1186 }
1187
1188 m_RectTransform = GetComponent<RectTransform>();
1189
1190 // Check if parent component has IScrollHandler
1191 IScrollHandler[] scrollHandlers = GetComponentsInParent<IScrollHandler>();
1192 if (scrollHandlers.Length > 1)
1193 m_IScrollHandlerParent = scrollHandlers[1] as ScrollRect;
1194
1195 // Get a reference to the RectMask 2D on the Viewport Text Area object.
1196 if (m_TextViewport != null)
1197 {
1198 m_TextViewportRectMask = m_TextViewport.GetComponent<RectMask2D>();
1199
1200 UpdateMaskRegions();
1201 }
1202
1203 // If we have a cached renderer then we had OnDisable called so just restore the material.
1204 if (m_CachedInputRenderer != null)
1205 m_CachedInputRenderer.SetMaterial(Graphic.defaultGraphicMaterial, Texture2D.whiteTexture);
1206
1207 if (m_TextComponent != null)
1208 {
1209 m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
1210 m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
1211
1212 // Cache reference to Vertical Scrollbar RectTransform and add listener.
1213 if (m_VerticalScrollbar != null)
1214 {
1215 m_VerticalScrollbar.onValueChanged.AddListener(OnScrollbarValueChange);
1216 }
1217
1218 UpdateLabel();
1219 }
1220
1221 #if UNITY_2019_1_OR_NEWER
1222 m_TouchKeyboardAllowsInPlaceEditing = TouchScreenKeyboard.isInPlaceEditingAllowed;
1223 #endif
1224
1225 // Subscribe to event fired when text object has been regenerated.
1226 TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
1227 }
1228
1229 protected override void OnDisable()
1230 {
1231 // the coroutine will be terminated, so this will ensure it restarts when we are next activated
1232 m_BlinkCoroutine = null;
1233
1234 DeactivateInputField();
1235 if (m_TextComponent != null)
1236 {
1237 m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty);
1238 m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel);
1239
1240 if (m_VerticalScrollbar != null)
1241 m_VerticalScrollbar.onValueChanged.RemoveListener(OnScrollbarValueChange);
1242
1243 }
1244 CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);
1245
1246 // Clear needs to be called otherwise sync never happens as the object is disabled.
1247 if (m_CachedInputRenderer != null)
1248 m_CachedInputRenderer.Clear();
1249
1250 if (m_Mesh != null)
1251 DestroyImmediate(m_Mesh);
1252
1253 m_Mesh = null;
1254
1255 // Unsubscribe to event triggered when text object has been regenerated
1256 TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);
1257
1258 base.OnDisable();
1259 }
1260
1261
1262 /// <summary>
1263 /// Method used to update the tracking of the caret position when the text object has been regenerated.
1264 /// </summary>
1265 /// <param name="obj"></param>
1266 private void ON_TEXT_CHANGED(UnityEngine.Object obj)
1267 {
1268 bool isThisObject = obj == m_TextComponent;
1269
1270 if (isThisObject && !m_IsStringPositionDirty)
1271 {
1272 if (Application.isPlaying && compositionLength == 0)
1273 {
1274 UpdateCaretPositionFromStringIndex();
1275
1276 #if TMP_DEBUG_MODE
1277 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
1278 #endif
1279 }
1280
1281 if (m_VerticalScrollbar)
1282 UpdateScrollbar();
1283 }
1284 }
1285
1286
1287 IEnumerator CaretBlink()
1288 {
1289 // Always ensure caret is initially visible since it can otherwise be confusing for a moment.
1290 m_CaretVisible = true;
1291 yield return null;
1292
1293 while ((isFocused || m_SelectionStillActive) && m_CaretBlinkRate > 0)
1294 {
1295 // the blink rate is expressed as a frequency
1296 float blinkPeriod = 1f / m_CaretBlinkRate;
1297
1298 // the caret should be ON if we are in the first half of the blink period
1299 bool blinkState = (Time.unscaledTime - m_BlinkStartTime) % blinkPeriod < blinkPeriod / 2;
1300 if (m_CaretVisible != blinkState)
1301 {
1302 m_CaretVisible = blinkState;
1303 if (!hasSelection)
1304 MarkGeometryAsDirty();
1305 }
1306
1307 // Then wait again.
1308 yield return null;
1309 }
1310 m_BlinkCoroutine = null;
1311 }
1312
1313 void SetCaretVisible()
1314 {
1315 if (!m_AllowInput)
1316 return;
1317
1318 m_CaretVisible = true;
1319 m_BlinkStartTime = Time.unscaledTime;
1320 SetCaretActive();
1321 }
1322
1323 // SetCaretActive will not set the caret immediately visible - it will wait for the next time to blink.
1324 // However, it will handle things correctly if the blink speed changed from zero to non-zero or non-zero to zero.
1325 void SetCaretActive()
1326 {
1327 if (!m_AllowInput)
1328 return;
1329
1330 if (m_CaretBlinkRate > 0.0f)
1331 {
1332 if (m_BlinkCoroutine == null)
1333 m_BlinkCoroutine = StartCoroutine(CaretBlink());
1334 }
1335 else
1336 {
1337 m_CaretVisible = true;
1338 }
1339 }
1340
1341 protected void OnFocus()
1342 {
1343 if (m_OnFocusSelectAll)
1344 SelectAll();
1345 }
1346
1347 protected void SelectAll()
1348 {
1349 m_isSelectAll = true;
1350 stringPositionInternal = text.Length;
1351 stringSelectPositionInternal = 0;
1352 }
1353
1354 /// <summary>
1355 /// Move to the end of the text.
1356 /// </summary>
1357 /// <param name="shift"></param>
1358 public void MoveTextEnd(bool shift)
1359 {
1360 if (m_isRichTextEditingAllowed)
1361 {
1362 int position = text.Length;
1363
1364 if (shift)
1365 {
1366 stringSelectPositionInternal = position;
1367 }
1368 else
1369 {
1370 stringPositionInternal = position;
1371 stringSelectPositionInternal = stringPositionInternal;
1372 }
1373 }
1374 else
1375 {
1376 int position = m_TextComponent.textInfo.characterCount - 1;
1377
1378 if (shift)
1379 {
1380 caretSelectPositionInternal = position;
1381 stringSelectPositionInternal = GetStringIndexFromCaretPosition(position);
1382 }
1383 else
1384 {
1385 caretPositionInternal = caretSelectPositionInternal = position;
1386 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(position);
1387 }
1388 }
1389
1390 UpdateLabel();
1391 }
1392
1393 /// <summary>
1394 /// Move to the start of the text.
1395 /// </summary>
1396 /// <param name="shift"></param>
1397 public void MoveTextStart(bool shift)
1398 {
1399 if (m_isRichTextEditingAllowed)
1400 {
1401 int position = 0;
1402
1403 if (shift)
1404 {
1405 stringSelectPositionInternal = position;
1406 }
1407 else
1408 {
1409 stringPositionInternal = position;
1410 stringSelectPositionInternal = stringPositionInternal;
1411 }
1412 }
1413 else
1414 {
1415 int position = 0;
1416
1417 if (shift)
1418 {
1419 caretSelectPositionInternal = position;
1420 stringSelectPositionInternal = GetStringIndexFromCaretPosition(position);
1421 }
1422 else
1423 {
1424 caretPositionInternal = caretSelectPositionInternal = position;
1425 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(position);
1426 }
1427 }
1428
1429 UpdateLabel();
1430 }
1431
1432
1433 /// <summary>
1434 /// Move to the end of the current line of text.
1435 /// </summary>
1436 /// <param name="shift"></param>
1437 public void MoveToEndOfLine(bool shift, bool ctrl)
1438 {
1439 // Get the line the caret is currently located on.
1440 int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber;
1441
1442 // Get the last character of the given line.
1443 int characterIndex = ctrl == true ? m_TextComponent.textInfo.characterCount - 1 : m_TextComponent.textInfo.lineInfo[currentLine].lastCharacterIndex;
1444
1445 int position = m_TextComponent.textInfo.characterInfo[characterIndex].index;
1446
1447 if (shift)
1448 {
1449 stringSelectPositionInternal = position;
1450
1451 caretSelectPositionInternal = characterIndex;
1452 }
1453 else
1454 {
1455 stringPositionInternal = position;
1456 stringSelectPositionInternal = stringPositionInternal;
1457
1458 caretSelectPositionInternal = caretPositionInternal = characterIndex;
1459 }
1460
1461 UpdateLabel();
1462 }
1463
1464 /// <summary>
1465 /// Move to the start of the current line of text.
1466 /// </summary>
1467 /// <param name="shift"></param>
1468 public void MoveToStartOfLine(bool shift, bool ctrl)
1469 {
1470 // Get the line the caret is currently located on.
1471 int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber;
1472
1473 // Get the first character of the given line.
1474 int characterIndex = ctrl == true ? 0 : m_TextComponent.textInfo.lineInfo[currentLine].firstCharacterIndex;
1475
1476 int position = 0;
1477 if (characterIndex > 0)
1478 position = m_TextComponent.textInfo.characterInfo[characterIndex - 1].index + m_TextComponent.textInfo.characterInfo[characterIndex - 1].stringLength;
1479
1480 if (shift)
1481 {
1482 stringSelectPositionInternal = position;
1483
1484 caretSelectPositionInternal = characterIndex;
1485 }
1486 else
1487 {
1488 stringPositionInternal = position;
1489 stringSelectPositionInternal = stringPositionInternal;
1490
1491 caretSelectPositionInternal = caretPositionInternal = characterIndex;
1492 }
1493
1494 UpdateLabel();
1495 }
1496
1497
1498 static string clipboard
1499 {
1500 get
1501 {
1502 return GUIUtility.systemCopyBuffer;
1503 }
1504 set
1505 {
1506 GUIUtility.systemCopyBuffer = value;
1507 }
1508 }
1509
1510 private bool InPlaceEditing()
1511 {
1512 if (m_TouchKeyboardAllowsInPlaceEditing)
1513 return true;
1514
1515 if (isUWP())
1516 return !TouchScreenKeyboard.isSupported;
1517
1518 if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard)
1519 return true;
1520
1521 if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard == false && shouldHideMobileInput == false)
1522 return false;
1523
1524 return true;
1525 }
1526
1527 // In-place editing can change state if a hardware keyboard becomes available or is hidden while the input field is activated.
1528 private bool InPlaceEditingChanged()
1529 {
1530 return !s_IsQuestDevice && m_TouchKeyboardAllowsInPlaceEditing != TouchScreenKeyboard.isInPlaceEditingAllowed;
1531 }
1532
1533 // Returns true if the TouchScreenKeyboard should be used. On Android and Chrome OS, we only want to use the
1534 // TouchScreenKeyboard if in-place editing is not allowed (i.e. when we do not have a hardware keyboard available).
1535 private bool TouchScreenKeyboardShouldBeUsed()
1536 {
1537 RuntimePlatform platform = Application.platform;
1538 switch (platform)
1539 {
1540 case RuntimePlatform.Android:
1541 if (s_IsQuestDevice)
1542 return TouchScreenKeyboard.isSupported;
1543
1544 return !TouchScreenKeyboard.isInPlaceEditingAllowed;
1545 default:
1546 return TouchScreenKeyboard.isSupported;
1547 }
1548 }
1549
1550 void UpdateKeyboardStringPosition()
1551 {
1552 // On iOS/tvOS we only update SoftKeyboard selection when we know that it might have changed by touch/pointer interactions with InputField
1553 // Setting the TouchScreenKeyboard selection here instead of LateUpdate so that we wouldn't override
1554 // TouchScreenKeyboard selection when it's changed with cmd+a/ctrl+a/arrow/etc. in the TouchScreenKeyboard
1555 // This is only applicable for iOS/tvOS as we have instance of TouchScreenKeyboard even when external keyboard is connected
1556 if (m_HideMobileInput && m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection &&
1557 (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.tvOS))
1558 {
1559 var selectionStart = Mathf.Min(caretSelectPositionInternal, caretPositionInternal);
1560 var selectionLength = Mathf.Abs(caretSelectPositionInternal - caretPositionInternal);
1561 m_SoftKeyboard.selection = new RangeInt(selectionStart, selectionLength);
1562 }
1563 }
1564
1565 void UpdateStringPositionFromKeyboard()
1566 {
1567 // TODO: Might want to add null check here.
1568 var selectionRange = m_SoftKeyboard.selection;
1569
1570 //if (selectionRange.start == 0 && selectionRange.length == 0)
1571 // return;
1572
1573 var selectionStart = selectionRange.start;
1574 var selectionEnd = selectionRange.end;
1575
1576 var stringPositionChanged = false;
1577
1578 if (stringPositionInternal != selectionStart)
1579 {
1580 stringPositionChanged = true;
1581 stringPositionInternal = selectionStart;
1582
1583 caretPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal);
1584 }
1585
1586 if (stringSelectPositionInternal != selectionEnd)
1587 {
1588 stringSelectPositionInternal = selectionEnd;
1589 stringPositionChanged = true;
1590
1591 caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
1592 }
1593
1594 if (stringPositionChanged)
1595 {
1596 m_BlinkStartTime = Time.unscaledTime;
1597
1598 UpdateLabel();
1599 }
1600 }
1601
1602 /// <summary>
1603 /// Update the text based on input.
1604 /// </summary>
1605 // TODO: Make LateUpdate a coroutine instead. Allows us to control the update to only be when the field is active.
1606 protected virtual void LateUpdate()
1607 {
1608 // Only activate if we are not already activated.
1609 if (m_ShouldActivateNextUpdate)
1610 {
1611 if (!isFocused)
1612 {
1613 ActivateInputFieldInternal();
1614 m_ShouldActivateNextUpdate = false;
1615 return;
1616 }
1617
1618 // Reset as we are already activated.
1619 m_ShouldActivateNextUpdate = false;
1620 }
1621
1622 // If the device's state changed in a way that affects whether we should use a touchscreen keyboard or not,
1623 // then deactivate the input field.
1624 if (isFocused && InPlaceEditingChanged())
1625 DeactivateInputField();
1626
1627 // Handle double click to reset / deselect Input Field when ResetOnActivation is false.
1628 if (!isFocused && m_SelectionStillActive)
1629 {
1630 GameObject selectedObject = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null;
1631
1632 if (selectedObject == null && m_ResetOnDeActivation)
1633 {
1634 ReleaseSelection();
1635 return;
1636 }
1637
1638 if (selectedObject != null && selectedObject != this.gameObject)
1639 {
1640 if (selectedObject == m_PreviouslySelectedObject)
1641 return;
1642
1643 m_PreviouslySelectedObject = selectedObject;
1644
1645 // Special handling for Vertical Scrollbar
1646 if (m_VerticalScrollbar && selectedObject == m_VerticalScrollbar.gameObject)
1647 {
1648 // Do not release selection
1649 return;
1650 }
1651
1652 // Release selection for all objects when ResetOnDeActivation is true
1653 if (m_ResetOnDeActivation)
1654 {
1655 ReleaseSelection();
1656 return;
1657 }
1658
1659 // Release current selection of selected object is another Input Field
1660 if (m_KeepTextSelectionVisible == false && selectedObject.GetComponent<TMP_InputField>() != null)
1661 ReleaseSelection();
1662
1663 return;
1664 }
1665
1666 #if ENABLE_INPUT_SYSTEM
1667 if (m_ProcessingEvent != null && m_ProcessingEvent.rawType == EventType.MouseDown && m_ProcessingEvent.button == 0)
1668 {
1669 // Check for Double Click
1670 bool isDoubleClick = false;
1671 float timeStamp = Time.unscaledTime;
1672
1673 if (m_KeyDownStartTime + m_DoubleClickDelay > timeStamp)
1674 isDoubleClick = true;
1675
1676 m_KeyDownStartTime = timeStamp;
1677
1678 if (isDoubleClick)
1679 {
1680 //m_StringPosition = m_StringSelectPosition = 0;
1681 //m_CaretPosition = m_CaretSelectPosition = 0;
1682 //m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition;
1683
1684 //if (caretRectTrans != null)
1685 // caretRectTrans.localPosition = Vector3.zero;
1686
1687 ReleaseSelection();
1688
1689 return;
1690 }
1691 }
1692 #else
1693 if (Input.GetKeyDown(KeyCode.Mouse0))
1694 {
1695 // Check for Double Click
1696 bool isDoubleClick = false;
1697 float timeStamp = Time.unscaledTime;
1698
1699 if (m_KeyDownStartTime + m_DoubleClickDelay > timeStamp)
1700 isDoubleClick = true;
1701
1702 m_KeyDownStartTime = timeStamp;
1703
1704 if (isDoubleClick)
1705 {
1706 //m_StringPosition = m_StringSelectPosition = 0;
1707 //m_CaretPosition = m_CaretSelectPosition = 0;
1708 //m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition;
1709
1710 //if (caretRectTrans != null)
1711 // caretRectTrans.localPosition = Vector3.zero;
1712
1713 ReleaseSelection();
1714
1715 return;
1716 }
1717 }
1718 #endif
1719 }
1720
1721 UpdateMaskRegions();
1722
1723 if (InPlaceEditing() && isKeyboardUsingEvents() || !isFocused)
1724 {
1725 return;
1726 }
1727
1728 AssignPositioningIfNeeded();
1729
1730 if (m_SoftKeyboard == null || m_SoftKeyboard.status != TouchScreenKeyboard.Status.Visible)
1731 {
1732 if (m_SoftKeyboard != null)
1733 {
1734 if (!m_ReadOnly)
1735 text = m_SoftKeyboard.text;
1736
1737 TouchScreenKeyboard.Status status = m_SoftKeyboard.status;
1738
1739 // Special handling for UWP - Hololens which does not support Canceled status
1740 if (m_LastKeyCode != KeyCode.Return && status == TouchScreenKeyboard.Status.Done && isUWP())
1741 {
1742 status = TouchScreenKeyboard.Status.Canceled;
1743 // The HoloLen's X button will not be acting as an ESC Key (TMBP-98)
1744 m_IsKeyboardBeingClosedInHoloLens = true;
1745 }
1746
1747 switch (status)
1748 {
1749 case TouchScreenKeyboard.Status.LostFocus:
1750 SendTouchScreenKeyboardStatusChanged();
1751 break;
1752 case TouchScreenKeyboard.Status.Canceled:
1753 m_ReleaseSelection = true;
1754 m_WasCanceled = true;
1755 SendTouchScreenKeyboardStatusChanged();
1756 break;
1757 case TouchScreenKeyboard.Status.Done:
1758 m_ReleaseSelection = true;
1759 SendTouchScreenKeyboardStatusChanged();
1760 OnSubmit(null);
1761 break;
1762 }
1763 }
1764
1765 OnDeselect(null);
1766 return;
1767 }
1768
1769 string val = m_SoftKeyboard.text;
1770
1771 if (m_Text != val)
1772 {
1773 if (m_ReadOnly)
1774 {
1775 m_SoftKeyboard.text = m_Text;
1776 }
1777 else
1778 {
1779 m_Text = "";
1780
1781 for (int i = 0; i < val.Length; ++i)
1782 {
1783 char c = val[i];
1784 bool hasValidateUpdatedText = false;
1785
1786 if (c == '\r' || c == 3)
1787 c = '\n';
1788
1789 if (onValidateInput != null)
1790 c = onValidateInput(m_Text, m_Text.Length, c);
1791 else if (characterValidation != CharacterValidation.None)
1792 {
1793 string textBeforeValidate = m_Text;
1794 c = Validate(m_Text, m_Text.Length, c);
1795 hasValidateUpdatedText = textBeforeValidate != m_Text;
1796 }
1797
1798 if (lineType != LineType.MultiLineNewline && c == '\n')
1799 {
1800 UpdateLabel();
1801
1802 OnSubmit(null);
1803 OnDeselect(null);
1804 return;
1805 }
1806
1807 // In the case of a Custom Validator, the user is expected to modify the m_Text where as such we do not append c.
1808 // However we will append c if the user did not modify the m_Text (UUM-42147)
1809 if (c != 0 && (characterValidation != CharacterValidation.CustomValidator || !hasValidateUpdatedText))
1810 m_Text += c;
1811 }
1812
1813 if (characterLimit > 0 && m_Text.Length > characterLimit)
1814 m_Text = m_Text.Substring(0, characterLimit);
1815
1816 UpdateStringPositionFromKeyboard();
1817
1818 // Set keyboard text before updating label, as we might have changed it with validation
1819 // and update label will take the old value from keyboard if we don't change it here
1820 if (m_Text != val)
1821 m_SoftKeyboard.text = m_Text;
1822
1823 SendOnValueChangedAndUpdateLabel();
1824 }
1825 }
1826 // On iOS/tvOS we always have TouchScreenKeyboard instance even when using external keyboard
1827 // so we keep track of the caret position there
1828 else if (m_HideMobileInput && m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection &&
1829 Application.platform != RuntimePlatform.IPhonePlayer && Application.platform != RuntimePlatform.tvOS)
1830 {
1831 var selectionStart = Mathf.Min(caretSelectPositionInternal, caretPositionInternal);
1832 var selectionLength = Mathf.Abs(caretSelectPositionInternal - caretPositionInternal);
1833 m_SoftKeyboard.selection = new RangeInt(selectionStart, selectionLength);
1834 }
1835 else if (m_HideMobileInput && Application.platform == RuntimePlatform.Android ||
1836 m_SoftKeyboard.canSetSelection && (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.tvOS))
1837 {
1838 UpdateStringPositionFromKeyboard();
1839 }
1840
1841 //else if (m_HideMobileInput) // m_Keyboard.canSetSelection
1842 //{
1843 // int length = stringPositionInternal < stringSelectPositionInternal ? stringSelectPositionInternal - stringPositionInternal : stringPositionInternal - stringSelectPositionInternal;
1844 // m_SoftKeyboard.selection = new RangeInt(stringPositionInternal < stringSelectPositionInternal ? stringPositionInternal : stringSelectPositionInternal, length);
1845 //}
1846 //else if (!m_HideMobileInput) // m_Keyboard.canGetSelection)
1847 //{
1848 // UpdateStringPositionFromKeyboard();
1849 //}
1850
1851 if (m_SoftKeyboard != null && m_SoftKeyboard.status != TouchScreenKeyboard.Status.Visible)
1852 {
1853 if (m_SoftKeyboard.status == TouchScreenKeyboard.Status.Canceled)
1854 m_WasCanceled = true;
1855
1856 OnDeselect(null);
1857 }
1858 }
1859
1860 private bool MayDrag(PointerEventData eventData)
1861 {
1862 return IsActive() &&
1863 IsInteractable() &&
1864 eventData.button == PointerEventData.InputButton.Left &&
1865 m_TextComponent != null &&
1866 (m_SoftKeyboard == null || shouldHideSoftKeyboard || shouldHideMobileInput);
1867 }
1868
1869 public virtual void OnBeginDrag(PointerEventData eventData)
1870 {
1871 if (!MayDrag(eventData))
1872 return;
1873
1874 m_UpdateDrag = true;
1875 }
1876
1877 public virtual void OnDrag(PointerEventData eventData)
1878 {
1879 if (!MayDrag(eventData))
1880 return;
1881
1882 CaretPosition insertionSide;
1883
1884 int insertionIndex = TMP_TextUtilities.GetCursorIndexFromPosition(m_TextComponent, eventData.position, eventData.pressEventCamera, out insertionSide);
1885
1886 if (m_isRichTextEditingAllowed)
1887 {
1888 if (insertionSide == CaretPosition.Left)
1889 {
1890 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index;
1891 }
1892 else if (insertionSide == CaretPosition.Right)
1893 {
1894 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
1895 }
1896 }
1897 else
1898 {
1899 if (insertionSide == CaretPosition.Left)
1900 {
1901 stringSelectPositionInternal = insertionIndex == 0
1902 ? m_TextComponent.textInfo.characterInfo[0].index
1903 : m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength;
1904 }
1905 else if (insertionSide == CaretPosition.Right)
1906 {
1907 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
1908 }
1909 }
1910
1911 caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
1912
1913 MarkGeometryAsDirty();
1914
1915 m_DragPositionOutOfBounds = !RectTransformUtility.RectangleContainsScreenPoint(textViewport, eventData.position, eventData.pressEventCamera);
1916 if (m_DragPositionOutOfBounds && m_DragCoroutine == null)
1917 m_DragCoroutine = StartCoroutine(MouseDragOutsideRect(eventData));
1918
1919 UpdateKeyboardStringPosition();
1920 eventData.Use();
1921
1922 #if TMP_DEBUG_MODE
1923 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
1924 #endif
1925 }
1926
1927 IEnumerator MouseDragOutsideRect(PointerEventData eventData)
1928 {
1929 while (m_UpdateDrag && m_DragPositionOutOfBounds)
1930 {
1931 Vector2 localMousePos;
1932
1933 RectTransformUtility.ScreenPointToLocalPointInRectangle(textViewport, eventData.position, eventData.pressEventCamera, out localMousePos);
1934
1935 Rect rect = textViewport.rect;
1936
1937 if (multiLine)
1938 {
1939 if (localMousePos.y > rect.yMax)
1940 MoveUp(true, true);
1941 else if (localMousePos.y < rect.yMin)
1942 MoveDown(true, true);
1943 }
1944 else
1945 {
1946 if (localMousePos.x < rect.xMin)
1947 MoveLeft(true, false);
1948 else if (localMousePos.x > rect.xMax)
1949 MoveRight(true, false);
1950 }
1951
1952 UpdateLabel();
1953
1954 float delay = multiLine ? kVScrollSpeed : kHScrollSpeed;
1955
1956 if (m_WaitForSecondsRealtime == null)
1957 m_WaitForSecondsRealtime = new WaitForSecondsRealtime(delay);
1958 else
1959 m_WaitForSecondsRealtime.waitTime = delay;
1960
1961 yield return m_WaitForSecondsRealtime;
1962 }
1963 m_DragCoroutine = null;
1964 }
1965
1966 public virtual void OnEndDrag(PointerEventData eventData)
1967 {
1968 if (!MayDrag(eventData))
1969 return;
1970
1971 m_UpdateDrag = false;
1972 }
1973
1974 public override void OnPointerDown(PointerEventData eventData)
1975 {
1976 if (!MayDrag(eventData))
1977 return;
1978
1979 EventSystem.current.SetSelectedGameObject(gameObject, eventData);
1980
1981 bool hadFocusBefore = m_AllowInput;
1982 base.OnPointerDown(eventData);
1983
1984 if (InPlaceEditing() == false)
1985 {
1986 if (m_SoftKeyboard == null || !m_SoftKeyboard.active)
1987 {
1988 OnSelect(eventData);
1989 return;
1990 }
1991 }
1992
1993 #if ENABLE_INPUT_SYSTEM
1994 Event.PopEvent(m_ProcessingEvent);
1995 bool shift = m_ProcessingEvent != null && (m_ProcessingEvent.modifiers & EventModifiers.Shift) != 0;
1996 #else
1997 bool shift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
1998 #endif
1999
2000 // Check for Double Click
2001 bool isDoubleClick = false;
2002 float timeStamp = Time.unscaledTime;
2003
2004 if (m_PointerDownClickStartTime + m_DoubleClickDelay > timeStamp)
2005 isDoubleClick = true;
2006
2007 m_PointerDownClickStartTime = timeStamp;
2008
2009 // Only set caret position if we didn't just get focus now.
2010 // Otherwise it will overwrite the select all on focus.
2011 if (hadFocusBefore || !m_OnFocusSelectAll)
2012 {
2013 CaretPosition insertionSide;
2014
2015 int insertionIndex = TMP_TextUtilities.GetCursorIndexFromPosition(m_TextComponent, eventData.position, eventData.pressEventCamera, out insertionSide);
2016
2017 if (shift)
2018 {
2019 if (m_isRichTextEditingAllowed)
2020 {
2021 if (insertionSide == CaretPosition.Left)
2022 {
2023 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index;
2024 }
2025 else if (insertionSide == CaretPosition.Right)
2026 {
2027 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
2028 }
2029 }
2030 else
2031 {
2032 if (insertionSide == CaretPosition.Left)
2033 {
2034 stringSelectPositionInternal = insertionIndex == 0
2035 ? m_TextComponent.textInfo.characterInfo[0].index
2036 : m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength;
2037 }
2038 else if (insertionSide == CaretPosition.Right)
2039 {
2040 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
2041 }
2042 }
2043 }
2044 else
2045 {
2046 if (m_isRichTextEditingAllowed)
2047 {
2048 if (insertionSide == CaretPosition.Left)
2049 {
2050 stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index;
2051 }
2052 else if (insertionSide == CaretPosition.Right)
2053 {
2054 stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
2055 }
2056 }
2057 else
2058 {
2059 if (insertionSide == CaretPosition.Left)
2060 {
2061 stringPositionInternal = stringSelectPositionInternal = insertionIndex == 0
2062 ? m_TextComponent.textInfo.characterInfo[0].index
2063 : m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength;
2064 }
2065 else if (insertionSide == CaretPosition.Right)
2066 {
2067 stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
2068 }
2069 }
2070 }
2071
2072
2073 if (isDoubleClick)
2074 {
2075 int wordIndex = TMP_TextUtilities.FindIntersectingWord(m_TextComponent, eventData.position, eventData.pressEventCamera);
2076
2077 if (wordIndex != -1)
2078 {
2079 // TODO: Should behavior be different if rich text editing is enabled or not?
2080
2081 // Select current word
2082 caretPositionInternal = m_TextComponent.textInfo.wordInfo[wordIndex].firstCharacterIndex;
2083 caretSelectPositionInternal = m_TextComponent.textInfo.wordInfo[wordIndex].lastCharacterIndex + 1;
2084
2085 stringPositionInternal = m_TextComponent.textInfo.characterInfo[caretPositionInternal].index;
2086 stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].index + m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].stringLength;
2087 }
2088 else
2089 {
2090 // Select current character
2091 caretPositionInternal = insertionIndex;
2092 caretSelectPositionInternal = caretPositionInternal + 1;
2093
2094 stringPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index;
2095 stringSelectPositionInternal = stringPositionInternal + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength;
2096 }
2097 }
2098 else
2099 {
2100 caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal);
2101 }
2102
2103 m_isSelectAll = false;
2104 }
2105
2106 UpdateLabel();
2107 UpdateKeyboardStringPosition();
2108 eventData.Use();
2109
2110 #if TMP_DEBUG_MODE
2111 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2112 #endif
2113 }
2114
2115 protected enum EditState
2116 {
2117 Continue,
2118 Finish
2119 }
2120
2121 protected EditState KeyPressed(Event evt)
2122 {
2123 var currentEventModifiers = evt.modifiers;
2124 bool ctrl = m_IsApplePlatform ? (currentEventModifiers & EventModifiers.Command) != 0 : (currentEventModifiers & EventModifiers.Control) != 0;
2125 bool shift = (currentEventModifiers & EventModifiers.Shift) != 0;
2126 bool alt = (currentEventModifiers & EventModifiers.Alt) != 0;
2127 bool ctrlOnly = ctrl && !alt && !shift;
2128 m_LastKeyCode = evt.keyCode;
2129
2130 switch (evt.keyCode)
2131 {
2132 case KeyCode.Backspace:
2133 {
2134 Backspace();
2135 return EditState.Continue;
2136 }
2137
2138 case KeyCode.Delete:
2139 {
2140 DeleteKey();
2141 return EditState.Continue;
2142 }
2143
2144 case KeyCode.Home:
2145 {
2146 MoveToStartOfLine(shift, ctrl);
2147 return EditState.Continue;
2148 }
2149
2150 case KeyCode.End:
2151 {
2152 MoveToEndOfLine(shift, ctrl);
2153 return EditState.Continue;
2154 }
2155
2156 // Select All
2157 case KeyCode.A:
2158 {
2159 if (ctrlOnly)
2160 {
2161 SelectAll();
2162 return EditState.Continue;
2163 }
2164 break;
2165 }
2166
2167 // Copy
2168 case KeyCode.C:
2169 {
2170 if (ctrlOnly)
2171 {
2172 if (inputType != InputType.Password)
2173 clipboard = GetSelectedString();
2174 else
2175 clipboard = "";
2176 return EditState.Continue;
2177 }
2178 break;
2179 }
2180
2181 // Paste
2182 case KeyCode.V:
2183 {
2184 if (ctrlOnly)
2185 {
2186 Append(clipboard);
2187 return EditState.Continue;
2188 }
2189 break;
2190 }
2191
2192 // Cut
2193 case KeyCode.X:
2194 {
2195 if (ctrlOnly)
2196 {
2197 if (inputType != InputType.Password)
2198 clipboard = GetSelectedString();
2199 else
2200 clipboard = "";
2201 Delete();
2202 UpdateTouchKeyboardFromEditChanges();
2203 SendOnValueChangedAndUpdateLabel();
2204 return EditState.Continue;
2205 }
2206 break;
2207 }
2208
2209 case KeyCode.LeftArrow:
2210 {
2211 MoveLeft(shift, ctrl);
2212 return EditState.Continue;
2213 }
2214
2215 case KeyCode.RightArrow:
2216 {
2217 MoveRight(shift, ctrl);
2218 return EditState.Continue;
2219 }
2220
2221 case KeyCode.UpArrow:
2222 {
2223 MoveUp(shift);
2224 return EditState.Continue;
2225 }
2226
2227 case KeyCode.DownArrow:
2228 {
2229 MoveDown(shift);
2230 return EditState.Continue;
2231 }
2232
2233 case KeyCode.PageUp:
2234 {
2235 MovePageUp(shift);
2236 return EditState.Continue;
2237 }
2238
2239 case KeyCode.PageDown:
2240 {
2241 MovePageDown(shift);
2242 return EditState.Continue;
2243 }
2244
2245 // Submit
2246 case KeyCode.Return:
2247 case KeyCode.KeypadEnter:
2248 {
2249 if (lineType != LineType.MultiLineNewline)
2250 {
2251 m_ReleaseSelection = true;
2252 return EditState.Finish;
2253 }
2254 else
2255 {
2256 TMP_TextInfo textInfo = m_TextComponent.textInfo;
2257
2258 if (m_LineLimit > 0 && textInfo != null && textInfo.lineCount >= m_LineLimit)
2259 {
2260 m_ReleaseSelection = true;
2261 return EditState.Finish;
2262 }
2263 }
2264 break;
2265 }
2266
2267 case KeyCode.Escape:
2268 {
2269 m_ReleaseSelection = true;
2270 m_WasCanceled = true;
2271 return EditState.Finish;
2272 }
2273 }
2274
2275 char c = evt.character;
2276
2277 // Don't allow return chars or tabulator key to be entered into single line fields.
2278 if (!multiLine && (c == '\t' || c == '\r' || c == '\n'))
2279 return EditState.Continue;
2280
2281 // Convert carriage return and end-of-text characters to newline.
2282 if (c == '\r' || c == 3)
2283 c = '\n';
2284
2285 // Convert Shift Enter to Vertical tab
2286 if (shift && c == '\n')
2287 c = '\v';
2288
2289 if (IsValidChar(c))
2290 {
2291 Append(c);
2292 }
2293
2294 if (c == 0)
2295 {
2296 if (compositionLength > 0)
2297 {
2298 UpdateLabel();
2299 }
2300 }
2301 return EditState.Continue;
2302 }
2303
2304 protected virtual bool IsValidChar(char c)
2305 {
2306 // Delete key on mac
2307 if (c == 127)
2308 return false;
2309
2310 // Accept newline and tab
2311 if (c == '\t' || c == '\n')
2312 return true;
2313
2314 // Control characters (not printable)
2315 if (c < 32)
2316 return false;
2317
2318 return true;
2319
2320 // With the addition of Dynamic support, I think this will best be handled by the text component.
2321 //return m_TextComponent.font.HasCharacter(c, true);
2322 }
2323
2324 /// <summary>
2325 /// Handle the specified event.
2326 /// </summary>
2327 private Event m_ProcessingEvent = new Event();
2328
2329 public void ProcessEvent(Event e)
2330 {
2331 KeyPressed(e);
2332 }
2333
2334
2335 /// <summary>
2336 ///
2337 /// </summary>
2338 /// <param name="eventData"></param>
2339 public virtual void OnUpdateSelected(BaseEventData eventData)
2340 {
2341 if (!isFocused)
2342 return;
2343
2344 bool consumedEvent = false;
2345 EditState editState = EditState.Continue;
2346
2347 while (Event.PopEvent(m_ProcessingEvent))
2348 {
2349 //Debug.Log("Event: " + m_ProcessingEvent.ToString() + " IsCompositionActive= " + m_IsCompositionActive + " Composition Length: " + compositionLength);
2350
2351 EventType eventType = m_ProcessingEvent.rawType;
2352
2353 if (eventType == EventType.KeyUp)
2354 continue;
2355
2356 if (eventType == EventType.KeyDown)
2357 {
2358 consumedEvent = true;
2359
2360 // Special handling on OSX which produces more events which need to be suppressed.
2361 if (m_IsCompositionActive && compositionLength == 0)
2362 {
2363 // Suppress other events related to navigation or termination of composition sequence.
2364 if (m_ProcessingEvent.character == 0 && m_ProcessingEvent.modifiers == EventModifiers.None)
2365 continue;
2366 }
2367
2368 editState = KeyPressed(m_ProcessingEvent);
2369 if (editState == EditState.Finish)
2370 {
2371 if (!m_WasCanceled)
2372 SendOnSubmit();
2373
2374 DeactivateInputField();
2375 break;
2376 }
2377
2378 m_IsTextComponentUpdateRequired = true;
2379 UpdateLabel();
2380
2381 continue;
2382 }
2383
2384 switch (eventType)
2385 {
2386 case EventType.ValidateCommand:
2387 case EventType.ExecuteCommand:
2388 switch (m_ProcessingEvent.commandName)
2389 {
2390 case "SelectAll":
2391 SelectAll();
2392 consumedEvent = true;
2393 break;
2394 }
2395 break;
2396 }
2397
2398 }
2399
2400 if (consumedEvent)
2401 {
2402 UpdateLabel();
2403 eventData.Use();
2404 }
2405 }
2406
2407
2408 /// <summary>
2409 ///
2410 /// </summary>
2411 /// <param name="eventData"></param>
2412 public virtual void OnScroll(PointerEventData eventData)
2413 {
2414 // Return if Single Line
2415 if (m_LineType == LineType.SingleLine)
2416 {
2417 if (m_IScrollHandlerParent != null)
2418 m_IScrollHandlerParent.OnScroll(eventData);
2419
2420 return;
2421 }
2422
2423 if (m_TextComponent.preferredHeight < m_TextViewport.rect.height)
2424 return;
2425
2426 float scrollDirection = -eventData.scrollDelta.y;
2427
2428 // Determine the current scroll position of the text within the viewport
2429 m_ScrollPosition = GetScrollPositionRelativeToViewport();
2430
2431 m_ScrollPosition += (1f / m_TextComponent.textInfo.lineCount) * scrollDirection * m_ScrollSensitivity;
2432
2433 m_ScrollPosition = Mathf.Clamp01(m_ScrollPosition);
2434
2435 AdjustTextPositionRelativeToViewport(m_ScrollPosition);
2436
2437 if (m_VerticalScrollbar)
2438 {
2439 m_VerticalScrollbar.value = m_ScrollPosition;
2440 }
2441
2442 //Debug.Log(GetInstanceID() + "- Scroll Position:" + m_ScrollPosition);
2443 }
2444
2445 float GetScrollPositionRelativeToViewport()
2446 {
2447 // Determine the current scroll position of the text within the viewport
2448 Rect viewportRect = m_TextViewport.rect;
2449
2450 float scrollPosition = (m_TextComponent.textInfo.lineInfo[0].ascender + m_TextComponent.margin.y + m_TextComponent.margin.w - viewportRect.yMax + m_TextComponent.rectTransform.anchoredPosition.y) / ( m_TextComponent.preferredHeight - viewportRect.height);
2451
2452 scrollPosition = (int)((scrollPosition * 1000) + 0.5f) / 1000.0f;
2453
2454 return scrollPosition;
2455 }
2456
2457 private string GetSelectedString()
2458 {
2459 if (!hasSelection)
2460 return "";
2461
2462 int startPos = stringPositionInternal;
2463 int endPos = stringSelectPositionInternal;
2464
2465 // Ensure pos is always less then selPos to make the code simpler
2466 if (startPos > endPos)
2467 {
2468 int temp = startPos;
2469 startPos = endPos;
2470 endPos = temp;
2471 }
2472
2473 //for (int i = m_CaretPosition; i < m_CaretSelectPosition; i++)
2474 //{
2475 // Debug.Log("Character [" + m_TextComponent.textInfo.characterInfo[i].character + "] using Style [" + m_TextComponent.textInfo.characterInfo[i].style + "] has been selected.");
2476 //}
2477
2478
2479 return text.Substring(startPos, endPos - startPos);
2480 }
2481
2482 private int FindNextWordBegin()
2483 {
2484 if (stringSelectPositionInternal + 1 >= text.Length)
2485 return text.Length;
2486
2487 int spaceLoc = text.IndexOfAny(kSeparators, stringSelectPositionInternal + 1);
2488
2489 if (spaceLoc == -1)
2490 spaceLoc = text.Length;
2491 else
2492 spaceLoc++;
2493
2494 return spaceLoc;
2495 }
2496
2497 private void MoveRight(bool shift, bool ctrl)
2498 {
2499 if (hasSelection && !shift)
2500 {
2501 // By convention, if we have a selection and move right without holding shift,
2502 // we just place the cursor at the end.
2503 stringPositionInternal = stringSelectPositionInternal = Mathf.Max(stringPositionInternal, stringSelectPositionInternal);
2504 caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2505
2506 #if TMP_DEBUG_MODE
2507 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2508 #endif
2509 return;
2510 }
2511
2512 int position;
2513 if (ctrl)
2514 position = FindNextWordBegin();
2515 else
2516 {
2517 if (m_isRichTextEditingAllowed)
2518 {
2519 // Special handling for Surrogate pairs and Diacritical marks.
2520 if (stringSelectPositionInternal < text.Length && char.IsHighSurrogate(text[stringSelectPositionInternal]))
2521 position = stringSelectPositionInternal + 2;
2522 else
2523 position = stringSelectPositionInternal + 1;
2524 }
2525 else
2526 {
2527 // Special handling for <CR><LF>
2528 if (m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal].character == '\r' && m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal + 1].character == '\n')
2529 position = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal + 1].index + m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal + 1].stringLength;
2530 else
2531 position = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal].index + m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal].stringLength;
2532 }
2533
2534 }
2535
2536 if (shift)
2537 {
2538 stringSelectPositionInternal = position;
2539 caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2540 }
2541 else
2542 {
2543 stringSelectPositionInternal = stringPositionInternal = position;
2544
2545 // Only increase caret position as we cross character boundary.
2546 if (stringPositionInternal >= m_TextComponent.textInfo.characterInfo[caretPositionInternal].index + m_TextComponent.textInfo.characterInfo[caretPositionInternal].stringLength)
2547 caretSelectPositionInternal = caretPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2548 }
2549
2550 #if TMP_DEBUG_MODE
2551 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2552 #endif
2553 }
2554
2555 private int FindPrevWordBegin()
2556 {
2557 if (stringSelectPositionInternal - 2 < 0)
2558 return 0;
2559
2560 int spaceLoc = text.LastIndexOfAny(kSeparators, stringSelectPositionInternal - 2);
2561
2562 if (spaceLoc == -1)
2563 spaceLoc = 0;
2564 else
2565 spaceLoc++;
2566
2567 return spaceLoc;
2568 }
2569
2570 private void MoveLeft(bool shift, bool ctrl)
2571 {
2572 if (hasSelection && !shift)
2573 {
2574 // By convention, if we have a selection and move left without holding shift,
2575 // we just place the cursor at the start.
2576 stringPositionInternal = stringSelectPositionInternal = Mathf.Min(stringPositionInternal, stringSelectPositionInternal);
2577 caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2578
2579 #if TMP_DEBUG_MODE
2580 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2581 #endif
2582 return;
2583 }
2584
2585 int position;
2586 if (ctrl)
2587 position = FindPrevWordBegin();
2588 else
2589 {
2590 if (m_isRichTextEditingAllowed)
2591 {
2592 // Special handling for Surrogate pairs and Diacritical marks.
2593 if (stringSelectPositionInternal > 0 && char.IsLowSurrogate(text[stringSelectPositionInternal - 1]))
2594 position = stringSelectPositionInternal - 2;
2595 else
2596 position = stringSelectPositionInternal - 1;
2597 }
2598 else
2599 {
2600 position = caretSelectPositionInternal < 1
2601 ? m_TextComponent.textInfo.characterInfo[0].index
2602 : m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].index;
2603
2604 // Special handling for <CR><LF>
2605 if (position > 0 && m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].character == '\n' && m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 2].character == '\r')
2606 position = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 2].index;
2607 }
2608 }
2609
2610 if (shift)
2611 {
2612 stringSelectPositionInternal = position;
2613 caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2614 }
2615 else
2616 {
2617 stringSelectPositionInternal = stringPositionInternal = position;
2618
2619 // Only decrease caret position as we cross character boundary.
2620 if (caretPositionInternal > 0 && stringPositionInternal <= m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1].index)
2621 caretSelectPositionInternal = caretPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
2622 }
2623
2624 #if TMP_DEBUG_MODE
2625 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2626 #endif
2627 }
2628
2629
2630 private int LineUpCharacterPosition(int originalPos, bool goToFirstChar)
2631 {
2632 if (originalPos >= m_TextComponent.textInfo.characterCount)
2633 originalPos -= 1;
2634
2635 TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos];
2636 int originLine = originChar.lineNumber;
2637
2638 // We are on the first line return first character
2639 if (originLine - 1 < 0)
2640 return goToFirstChar ? 0 : originalPos;
2641
2642 int endCharIdx = m_TextComponent.textInfo.lineInfo[originLine].firstCharacterIndex - 1;
2643
2644 int closest = -1;
2645 float distance = TMP_Math.FLOAT_MAX;
2646 float range = 0;
2647
2648 for (int i = m_TextComponent.textInfo.lineInfo[originLine - 1].firstCharacterIndex; i < endCharIdx; ++i)
2649 {
2650 TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i];
2651
2652 float d = originChar.origin - currentChar.origin;
2653 float r = d / (currentChar.xAdvance - currentChar.origin);
2654
2655 if (r >= 0 && r <= 1)
2656 {
2657 if (r < 0.5f)
2658 return i;
2659 else
2660 return i + 1;
2661 }
2662
2663 d = Mathf.Abs(d);
2664
2665 if (d < distance)
2666 {
2667 closest = i;
2668 distance = d;
2669 range = r;
2670 }
2671 }
2672
2673 if (closest == -1) return endCharIdx;
2674
2675 //Debug.Log("Returning nearest character with Range = " + range);
2676
2677 if (range < 0.5f)
2678 return closest;
2679 else
2680 return closest + 1;
2681 }
2682
2683
2684 private int LineDownCharacterPosition(int originalPos, bool goToLastChar)
2685 {
2686 if (originalPos >= m_TextComponent.textInfo.characterCount)
2687 return m_TextComponent.textInfo.characterCount - 1; // text.Length;
2688
2689 TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos];
2690 int originLine = originChar.lineNumber;
2691
2692 //// We are on the last line return last character
2693 if (originLine + 1 >= m_TextComponent.textInfo.lineCount)
2694 return goToLastChar ? m_TextComponent.textInfo.characterCount - 1 : originalPos;
2695
2696 // Need to determine end line for next line.
2697 int endCharIdx = m_TextComponent.textInfo.lineInfo[originLine + 1].lastCharacterIndex;
2698
2699 int closest = -1;
2700 float distance = TMP_Math.FLOAT_MAX;
2701 float range = 0;
2702
2703 for (int i = m_TextComponent.textInfo.lineInfo[originLine + 1].firstCharacterIndex; i < endCharIdx; ++i)
2704 {
2705 TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i];
2706
2707 float d = originChar.origin - currentChar.origin;
2708 float r = d / (currentChar.xAdvance - currentChar.origin);
2709
2710 if (r >= 0 && r <= 1)
2711 {
2712 if (r < 0.5f)
2713 return i;
2714 else
2715 return i + 1;
2716 }
2717
2718 d = Mathf.Abs(d);
2719
2720 if (d < distance)
2721 {
2722 closest = i;
2723 distance = d;
2724 range = r;
2725 }
2726 }
2727
2728 if (closest == -1) return endCharIdx;
2729
2730 //Debug.Log("Returning nearest character with Range = " + range);
2731
2732 if (range < 0.5f)
2733 return closest;
2734 else
2735 return closest + 1;
2736 }
2737
2738
2739 private int PageUpCharacterPosition(int originalPos, bool goToFirstChar)
2740 {
2741 if (originalPos >= m_TextComponent.textInfo.characterCount)
2742 originalPos -= 1;
2743
2744 TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos];
2745 int originLine = originChar.lineNumber;
2746
2747 // We are on the first line return first character
2748 if (originLine - 1 < 0)
2749 return goToFirstChar ? 0 : originalPos;
2750
2751 float viewportHeight = m_TextViewport.rect.height;
2752
2753 int newLine = originLine - 1;
2754 // Iterate through each subsequent line to find the first baseline that is not visible in the viewport.
2755 for (; newLine > 0; newLine--)
2756 {
2757 if (m_TextComponent.textInfo.lineInfo[newLine].baseline > m_TextComponent.textInfo.lineInfo[originLine].baseline + viewportHeight)
2758 break;
2759 }
2760
2761 int endCharIdx = m_TextComponent.textInfo.lineInfo[newLine].lastCharacterIndex;
2762
2763 int closest = -1;
2764 float distance = TMP_Math.FLOAT_MAX;
2765 float range = 0;
2766
2767 for (int i = m_TextComponent.textInfo.lineInfo[newLine].firstCharacterIndex; i < endCharIdx; ++i)
2768 {
2769 TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i];
2770
2771 float d = originChar.origin - currentChar.origin;
2772 float r = d / (currentChar.xAdvance - currentChar.origin);
2773
2774 if (r >= 0 && r <= 1)
2775 {
2776 if (r < 0.5f)
2777 return i;
2778 else
2779 return i + 1;
2780 }
2781
2782 d = Mathf.Abs(d);
2783
2784 if (d < distance)
2785 {
2786 closest = i;
2787 distance = d;
2788 range = r;
2789 }
2790 }
2791
2792 if (closest == -1) return endCharIdx;
2793
2794 //Debug.Log("Returning nearest character with Range = " + range);
2795
2796 if (range < 0.5f)
2797 return closest;
2798 else
2799 return closest + 1;
2800 }
2801
2802
2803 private int PageDownCharacterPosition(int originalPos, bool goToLastChar)
2804 {
2805 if (originalPos >= m_TextComponent.textInfo.characterCount)
2806 return m_TextComponent.textInfo.characterCount - 1;
2807
2808 TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos];
2809 int originLine = originChar.lineNumber;
2810
2811 // We are on the last line return last character
2812 if (originLine + 1 >= m_TextComponent.textInfo.lineCount)
2813 return goToLastChar ? m_TextComponent.textInfo.characterCount - 1 : originalPos;
2814
2815 float viewportHeight = m_TextViewport.rect.height;
2816
2817 int newLine = originLine + 1;
2818 // Iterate through each subsequent line to find the first baseline that is not visible in the viewport.
2819 for (; newLine < m_TextComponent.textInfo.lineCount - 1; newLine++)
2820 {
2821 if (m_TextComponent.textInfo.lineInfo[newLine].baseline < m_TextComponent.textInfo.lineInfo[originLine].baseline - viewportHeight)
2822 break;
2823 }
2824
2825 // Need to determine end line for next line.
2826 int endCharIdx = m_TextComponent.textInfo.lineInfo[newLine].lastCharacterIndex;
2827
2828 int closest = -1;
2829 float distance = TMP_Math.FLOAT_MAX;
2830 float range = 0;
2831
2832 for (int i = m_TextComponent.textInfo.lineInfo[newLine].firstCharacterIndex; i < endCharIdx; ++i)
2833 {
2834 TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i];
2835
2836 float d = originChar.origin - currentChar.origin;
2837 float r = d / (currentChar.xAdvance - currentChar.origin);
2838
2839 if (r >= 0 && r <= 1)
2840 {
2841 if (r < 0.5f)
2842 return i;
2843 else
2844 return i + 1;
2845 }
2846
2847 d = Mathf.Abs(d);
2848
2849 if (d < distance)
2850 {
2851 closest = i;
2852 distance = d;
2853 range = r;
2854 }
2855 }
2856
2857 if (closest == -1) return endCharIdx;
2858
2859 if (range < 0.5f)
2860 return closest;
2861 else
2862 return closest + 1;
2863 }
2864
2865
2866 private void MoveDown(bool shift)
2867 {
2868 MoveDown(shift, true);
2869 }
2870
2871
2872 private void MoveDown(bool shift, bool goToLastChar)
2873 {
2874 if (hasSelection && !shift)
2875 {
2876 // If we have a selection and press down without shift,
2877 // set caret to end of selection before we move it down.
2878 caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal);
2879 }
2880
2881 int position = multiLine ? LineDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : m_TextComponent.textInfo.characterCount - 1; // text.Length;
2882
2883 if (shift)
2884 {
2885 caretSelectPositionInternal = position;
2886 stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2887 }
2888 else
2889 {
2890 caretSelectPositionInternal = caretPositionInternal = position;
2891 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2892 }
2893
2894 #if TMP_DEBUG_MODE
2895 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2896 #endif
2897 }
2898
2899 private void MoveUp(bool shift)
2900 {
2901 MoveUp(shift, true);
2902 }
2903
2904
2905 private void MoveUp(bool shift, bool goToFirstChar)
2906 {
2907 if (hasSelection && !shift)
2908 {
2909 // If we have a selection and press up without shift,
2910 // set caret position to start of selection before we move it up.
2911 caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal);
2912 }
2913
2914 int position = multiLine ? LineUpCharacterPosition(caretSelectPositionInternal, goToFirstChar) : 0;
2915
2916 if (shift)
2917 {
2918 caretSelectPositionInternal = position;
2919 stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2920 }
2921 else
2922 {
2923 caretSelectPositionInternal = caretPositionInternal = position;
2924 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2925 }
2926
2927 #if TMP_DEBUG_MODE
2928 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2929 #endif
2930 }
2931
2932
2933 private void MovePageUp(bool shift)
2934 {
2935 MovePageUp(shift, true);
2936 }
2937
2938 private void MovePageUp(bool shift, bool goToFirstChar)
2939 {
2940 if (hasSelection && !shift)
2941 {
2942 // If we have a selection and press up without shift,
2943 // set caret position to start of selection before we move it up.
2944 caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal);
2945 }
2946
2947 int position = multiLine ? PageUpCharacterPosition(caretSelectPositionInternal, goToFirstChar) : 0;
2948
2949 if (shift)
2950 {
2951 caretSelectPositionInternal = position;
2952 stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2953 }
2954 else
2955 {
2956 caretSelectPositionInternal = caretPositionInternal = position;
2957 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
2958 }
2959
2960
2961 // Scroll to top of viewport
2962 //int currentLine = m_TextComponent.textInfo.characterInfo[position].lineNumber;
2963 //float lineAscender = m_TextComponent.textInfo.lineInfo[currentLine].ascender;
2964
2965 // Adjust text area up or down if not in single line mode.
2966 if (m_LineType != LineType.SingleLine)
2967 {
2968 float offset = m_TextViewport.rect.height; // m_TextViewport.rect.yMax - (m_TextComponent.rectTransform.anchoredPosition.y + lineAscender);
2969
2970 float topTextBounds = m_TextComponent.rectTransform.position.y + m_TextComponent.textBounds.max.y;
2971 float topViewportBounds = m_TextViewport.position.y + m_TextViewport.rect.yMax;
2972
2973 offset = topViewportBounds > topTextBounds + offset ? offset : topViewportBounds - topTextBounds;
2974
2975 m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, offset);
2976 AssignPositioningIfNeeded();
2977 }
2978
2979 #if TMP_DEBUG_MODE
2980 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
2981 #endif
2982
2983 }
2984
2985
2986 private void MovePageDown(bool shift)
2987 {
2988 MovePageDown(shift, true);
2989 }
2990
2991 private void MovePageDown(bool shift, bool goToLastChar)
2992 {
2993 if (hasSelection && !shift)
2994 {
2995 // If we have a selection and press down without shift,
2996 // set caret to end of selection before we move it down.
2997 caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal);
2998 }
2999
3000 int position = multiLine ? PageDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : m_TextComponent.textInfo.characterCount - 1;
3001
3002 if (shift)
3003 {
3004 caretSelectPositionInternal = position;
3005 stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
3006 }
3007 else
3008 {
3009 caretSelectPositionInternal = caretPositionInternal = position;
3010 stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal);
3011 }
3012
3013 // Scroll to top of viewport
3014 //int currentLine = m_TextComponent.textInfo.characterInfo[position].lineNumber;
3015 //float lineAscender = m_TextComponent.textInfo.lineInfo[currentLine].ascender;
3016
3017 // Adjust text area up or down if not in single line mode.
3018 if (m_LineType != LineType.SingleLine)
3019 {
3020 float offset = m_TextViewport.rect.height; // m_TextViewport.rect.yMax - (m_TextComponent.rectTransform.anchoredPosition.y + lineAscender);
3021
3022 float bottomTextBounds = m_TextComponent.rectTransform.position.y + m_TextComponent.textBounds.min.y;
3023 float bottomViewportBounds = m_TextViewport.position.y + m_TextViewport.rect.yMin;
3024
3025 offset = bottomViewportBounds > bottomTextBounds + offset ? offset : bottomViewportBounds - bottomTextBounds;
3026
3027 m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, offset);
3028 AssignPositioningIfNeeded();
3029 }
3030
3031 #if TMP_DEBUG_MODE
3032 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
3033 #endif
3034
3035 }
3036
3037 private void Delete()
3038 {
3039 if (m_ReadOnly)
3040 return;
3041
3042 if (m_StringPosition == m_StringSelectPosition)
3043 return;
3044
3045 if (m_isRichTextEditingAllowed || m_isSelectAll)
3046 {
3047 // Handling of Delete when Rich Text is allowed.
3048 if (m_StringPosition < m_StringSelectPosition)
3049 {
3050 m_Text = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition);
3051 m_StringSelectPosition = m_StringPosition;
3052 }
3053 else
3054 {
3055 m_Text = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition);
3056 m_StringPosition = m_StringSelectPosition;
3057 }
3058
3059 if (m_isSelectAll)
3060 {
3061 m_CaretPosition = m_CaretSelectPosition = 0;
3062 m_isSelectAll = false;
3063 }
3064 }
3065 else
3066 {
3067 if (m_CaretPosition < m_CaretSelectPosition)
3068 {
3069 int index = ClampArrayIndex(m_CaretSelectPosition - 1);
3070 m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition].index;
3071 m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[index].index + m_TextComponent.textInfo.characterInfo[index].stringLength;
3072
3073 m_Text = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition);
3074
3075 m_StringSelectPosition = m_StringPosition;
3076 m_CaretSelectPosition = m_CaretPosition;
3077 }
3078 else
3079 {
3080 int index = ClampArrayIndex(m_CaretPosition - 1);
3081 m_StringPosition = m_TextComponent.textInfo.characterInfo[index].index + m_TextComponent.textInfo.characterInfo[index].stringLength;
3082 m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition].index;
3083
3084 m_Text = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition);
3085
3086 m_StringPosition = m_StringSelectPosition;
3087 m_CaretPosition = m_CaretSelectPosition;
3088 }
3089 }
3090
3091 #if TMP_DEBUG_MODE
3092 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
3093 #endif
3094 }
3095
3096 /// <summary>
3097 /// Handling of DEL key
3098 /// </summary>
3099 private void DeleteKey()
3100 {
3101 if (m_ReadOnly)
3102 return;
3103
3104 if (hasSelection)
3105 {
3106 m_HasTextBeenRemoved = true;
3107
3108 Delete();
3109 UpdateTouchKeyboardFromEditChanges();
3110 SendOnValueChangedAndUpdateLabel();
3111 }
3112 else
3113 {
3114 if (m_isRichTextEditingAllowed)
3115 {
3116 if (stringPositionInternal < text.Length)
3117 {
3118 // Special handling for Surrogate Pairs
3119 if (char.IsHighSurrogate(text[stringPositionInternal]))
3120 m_Text = text.Remove(stringPositionInternal, 2);
3121 else
3122 m_Text = text.Remove(stringPositionInternal, 1);
3123
3124 m_HasTextBeenRemoved = true;
3125
3126 UpdateTouchKeyboardFromEditChanges();
3127 SendOnValueChangedAndUpdateLabel();
3128 }
3129 }
3130 else
3131 {
3132 if (caretPositionInternal < m_TextComponent.textInfo.characterCount - 1)
3133 {
3134 int numberOfCharactersToRemove = m_TextComponent.textInfo.characterInfo[caretPositionInternal].stringLength;
3135
3136 // Special handling for <CR><LF>
3137 if (m_TextComponent.textInfo.characterInfo[caretPositionInternal].character == '\r' && m_TextComponent.textInfo.characterInfo[caretPositionInternal + 1].character == '\n')
3138 numberOfCharactersToRemove += m_TextComponent.textInfo.characterInfo[caretPositionInternal + 1].stringLength;
3139
3140 // Adjust string position to skip any potential rich text tags.
3141 int nextCharacterStringPosition = m_TextComponent.textInfo.characterInfo[caretPositionInternal].index;
3142
3143 m_Text = text.Remove(nextCharacterStringPosition, numberOfCharactersToRemove);
3144
3145 m_HasTextBeenRemoved = true;
3146
3147 SendOnValueChangedAndUpdateLabel();
3148 }
3149 }
3150 }
3151
3152 #if TMP_DEBUG_MODE
3153 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
3154 #endif
3155 }
3156
3157 /// <summary>
3158 /// Handling of Backspace key
3159 /// </summary>
3160 private void Backspace()
3161 {
3162 if (m_ReadOnly)
3163 return;
3164
3165 if (hasSelection)
3166 {
3167 m_HasTextBeenRemoved = true;
3168
3169 Delete();
3170 UpdateTouchKeyboardFromEditChanges();
3171 SendOnValueChangedAndUpdateLabel();
3172 }
3173 else
3174 {
3175 if (m_isRichTextEditingAllowed)
3176 {
3177 if (stringPositionInternal > 0)
3178 {
3179 int numberOfCharactersToRemove = 1;
3180
3181 // Special handling for Surrogate pairs and Diacritical marks
3182 if (char.IsLowSurrogate(text[stringPositionInternal - 1]))
3183 numberOfCharactersToRemove = 2;
3184
3185 stringSelectPositionInternal = stringPositionInternal = stringPositionInternal - numberOfCharactersToRemove;
3186
3187 m_Text = text.Remove(stringPositionInternal, numberOfCharactersToRemove);
3188
3189 caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1;
3190
3191 m_HasTextBeenRemoved = true;
3192
3193 UpdateTouchKeyboardFromEditChanges();
3194 SendOnValueChangedAndUpdateLabel();
3195 }
3196 }
3197 else
3198 {
3199 if (caretPositionInternal > 0)
3200 {
3201 int caretPositionIndex = caretPositionInternal - 1;
3202 int numberOfCharactersToRemove = m_TextComponent.textInfo.characterInfo[caretPositionIndex].stringLength;
3203
3204 // Special handling for <CR><LR>
3205 if (caretPositionIndex > 0 && m_TextComponent.textInfo.characterInfo[caretPositionIndex].character == '\n' && m_TextComponent.textInfo.characterInfo[caretPositionIndex - 1].character == '\r')
3206 {
3207 numberOfCharactersToRemove += m_TextComponent.textInfo.characterInfo[caretPositionIndex - 1].stringLength;
3208 caretPositionIndex -= 1;
3209 }
3210
3211 // Delete the previous character
3212 m_Text = text.Remove(m_TextComponent.textInfo.characterInfo[caretPositionIndex].index, numberOfCharactersToRemove);
3213
3214 // Get new adjusted string position
3215 stringSelectPositionInternal = stringPositionInternal = caretPositionInternal < 1
3216 ? m_TextComponent.textInfo.characterInfo[0].index
3217 : m_TextComponent.textInfo.characterInfo[caretPositionIndex].index;
3218
3219 caretSelectPositionInternal = caretPositionInternal = caretPositionIndex;
3220 }
3221
3222 m_HasTextBeenRemoved = true;
3223
3224 UpdateTouchKeyboardFromEditChanges();
3225 SendOnValueChangedAndUpdateLabel();
3226 }
3227
3228 }
3229
3230 #if TMP_DEBUG_MODE
3231 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
3232 #endif
3233 }
3234
3235
3236 /// <summary>
3237 /// Append the specified text to the end of the current.
3238 /// </summary>
3239 protected virtual void Append(string input)
3240 {
3241 if (m_ReadOnly)
3242 return;
3243
3244 if (InPlaceEditing() == false)
3245 return;
3246
3247 for (int i = 0, imax = input.Length; i < imax; ++i)
3248 {
3249 char c = input[i];
3250
3251 if (c >= ' ' || c == '\t' || c == '\r' || c == '\n')
3252 {
3253 Append(c);
3254 }
3255 }
3256 }
3257
3258 protected virtual void Append(char input)
3259 {
3260 if (m_ReadOnly)
3261 return;
3262
3263 if (InPlaceEditing() == false)
3264 return;
3265
3266 // If we have an input validator, validate the input first
3267 int insertionPosition = Mathf.Min(stringPositionInternal, stringSelectPositionInternal);
3268
3269 //Get the text based on selection for validation instead of whole text(case 1253193).
3270 var validateText = text;
3271
3272 if (selectionFocusPosition != selectionAnchorPosition)
3273 {
3274
3275 m_HasTextBeenRemoved = true;
3276
3277 if (m_isRichTextEditingAllowed || m_isSelectAll)
3278 {
3279 // Handling of Delete when Rich Text is allowed.
3280 if (m_StringPosition < m_StringSelectPosition)
3281 validateText = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition);
3282 else
3283 validateText = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition);
3284 }
3285 else
3286 {
3287 if (m_CaretPosition < m_CaretSelectPosition)
3288 {
3289 m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition].index;
3290 m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].stringLength;
3291
3292 validateText = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition);
3293 }
3294 else
3295 {
3296 m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].stringLength;
3297 m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition].index;
3298
3299 validateText = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition);
3300 }
3301 }
3302 }
3303
3304 if (onValidateInput != null)
3305 {
3306 input = onValidateInput(validateText, insertionPosition, input);
3307 }
3308 else if (characterValidation == CharacterValidation.CustomValidator)
3309 {
3310 input = Validate(validateText, insertionPosition, input);
3311
3312 if (input == 0) return;
3313
3314 SendOnValueChanged();
3315 UpdateLabel();
3316
3317 return;
3318 }
3319 else if (characterValidation != CharacterValidation.None)
3320 {
3321 input = Validate(validateText, insertionPosition, input);
3322 }
3323
3324 // If the input is invalid, skip it
3325 if (input == 0)
3326 return;
3327
3328 // Append the character and update the label
3329 Insert(input);
3330 }
3331
3332
3333 // Insert the character and update the label.
3334 private void Insert(char c)
3335 {
3336 if (m_ReadOnly)
3337 return;
3338
3339 //Debug.Log("Inserting character " + m_IsCompositionActive);
3340
3341 string replaceString = c.ToString();
3342 Delete();
3343
3344 // Can't go past the character limit
3345 if (characterLimit > 0 && text.Length >= characterLimit)
3346 return;
3347
3348 m_Text = text.Insert(m_StringPosition, replaceString);
3349
3350 if (!char.IsHighSurrogate(c))
3351 m_CaretSelectPosition = m_CaretPosition += 1;
3352
3353 m_StringSelectPosition = m_StringPosition += 1;
3354
3355 UpdateTouchKeyboardFromEditChanges();
3356 SendOnValueChanged();
3357
3358 #if TMP_DEBUG_MODE
3359 Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal);
3360 #endif
3361 }
3362
3363 private void UpdateTouchKeyboardFromEditChanges()
3364 {
3365 // Update the TouchKeyboard's text from edit changes
3366 // if in-place editing is allowed
3367 if (m_SoftKeyboard != null && InPlaceEditing())
3368 {
3369 m_SoftKeyboard.text = m_Text;
3370 }
3371 }
3372
3373 private void SendOnValueChangedAndUpdateLabel()
3374 {
3375 UpdateLabel();
3376 SendOnValueChanged();
3377 }
3378
3379 private void SendOnValueChanged()
3380 {
3381 if (onValueChanged != null)
3382 onValueChanged.Invoke(text);
3383 }
3384
3385 /// <summary>
3386 /// Submit the input field's text.
3387 /// </summary>
3388
3389 protected void SendOnEndEdit()
3390 {
3391 if (onEndEdit != null)
3392 onEndEdit.Invoke(m_Text);
3393 }
3394
3395 protected void SendOnSubmit()
3396 {
3397 if (onSubmit != null)
3398 onSubmit.Invoke(m_Text);
3399 }
3400
3401 protected void SendOnFocus()
3402 {
3403 if (onSelect != null)
3404 onSelect.Invoke(m_Text);
3405 }
3406
3407 protected void SendOnFocusLost()
3408 {
3409 if (onDeselect != null)
3410 onDeselect.Invoke(m_Text);
3411 }
3412
3413 protected void SendOnTextSelection()
3414 {
3415 m_isSelected = true;
3416
3417 if (onTextSelection != null)
3418 onTextSelection.Invoke(m_Text, stringPositionInternal, stringSelectPositionInternal);
3419 }
3420
3421 protected void SendOnEndTextSelection()
3422 {
3423 if (!m_isSelected) return;
3424
3425 if (onEndTextSelection != null)
3426 onEndTextSelection.Invoke(m_Text, stringPositionInternal, stringSelectPositionInternal);
3427
3428 m_isSelected = false;
3429 }
3430
3431 protected void SendTouchScreenKeyboardStatusChanged()
3432 {
3433 if (m_SoftKeyboard != null && onTouchScreenKeyboardStatusChanged != null)
3434 onTouchScreenKeyboardStatusChanged.Invoke(m_SoftKeyboard.status);
3435 }
3436
3437
3438 /// <summary>
3439 /// Update the visual text Text.
3440 /// </summary>
3441
3442 protected void UpdateLabel()
3443 {
3444 if (m_TextComponent != null && m_TextComponent.font != null && m_PreventCallback == false)
3445 {
3446 // Prevent callback from the text component as we assign new text. This is to prevent a recursive call.
3447 m_PreventCallback = true;
3448
3449 string fullText;
3450 if (compositionLength > 0 && m_ReadOnly == false)
3451 {
3452 //Input.imeCompositionMode = IMECompositionMode.On;
3453
3454 // Handle selections
3455 Delete();
3456
3457 if (m_RichText)
3458 fullText = text.Substring(0, m_StringPosition) + "<u>" + compositionString + "</u>" + text.Substring(m_StringPosition);
3459 else
3460 fullText = text.Substring(0, m_StringPosition) + compositionString + text.Substring(m_StringPosition);
3461
3462 m_IsCompositionActive = true;
3463
3464 //Debug.Log("[" + Time.frameCount + "] Handling IME Input");
3465 }
3466 else
3467 {
3468 fullText = text;
3469 m_IsCompositionActive = false;
3470 m_ShouldUpdateIMEWindowPosition = true;
3471
3472 }
3473
3474 //Debug.Log("Handling IME Input... [" + compositionString + "] of length [" + compositionLength + "] at StringPosition [" + m_StringPosition + "] IsActive [" + m_IsCompositionActive + "]");
3475
3476 string processed;
3477 if (inputType == InputType.Password)
3478 processed = new string(asteriskChar, fullText.Length);
3479 else
3480 processed = fullText;
3481
3482 bool isEmpty = string.IsNullOrEmpty(fullText);
3483
3484 if (m_Placeholder != null)
3485 m_Placeholder.enabled = isEmpty;
3486
3487 if (!isEmpty && m_ReadOnly == false)
3488 {
3489 SetCaretVisible();
3490 }
3491
3492 m_TextComponent.text = processed + "\u200B"; // Extra space is added for Caret tracking.
3493
3494 // Rebuild layout if using Layout components.
3495 if (m_IsDrivenByLayoutComponents)
3496 LayoutRebuilder.MarkLayoutForRebuild(m_RectTransform);
3497
3498 // Special handling to limit the number of lines of text in the Input Field.
3499 if (m_LineLimit > 0)
3500 {
3501 m_TextComponent.ForceMeshUpdate();
3502
3503 TMP_TextInfo textInfo = m_TextComponent.textInfo;
3504
3505 // Check if text exceeds maximum number of lines.
3506 if (textInfo != null && textInfo.lineCount > m_LineLimit)
3507 {
3508 int lastValidCharacterIndex = textInfo.lineInfo[m_LineLimit - 1].lastCharacterIndex;
3509 int characterStringIndex = textInfo.characterInfo[lastValidCharacterIndex].index + textInfo.characterInfo[lastValidCharacterIndex].stringLength;
3510 text = processed.Remove(characterStringIndex, processed.Length - characterStringIndex);
3511 m_TextComponent.text = text + "\u200B";
3512 }
3513 }
3514
3515 if (m_IsTextComponentUpdateRequired || m_VerticalScrollbar && !(m_IsCaretPositionDirty && m_IsStringPositionDirty))
3516 {
3517 m_IsTextComponentUpdateRequired = false;
3518 m_TextComponent.ForceMeshUpdate();
3519 }
3520
3521 MarkGeometryAsDirty();
3522
3523 m_PreventCallback = false;
3524 }
3525 }
3526
3527
3528 void UpdateScrollbar()
3529 {
3530 // Update Scrollbar
3531 if (m_VerticalScrollbar)
3532 {
3533 Rect viewportRect = m_TextViewport.rect;
3534
3535 float size = viewportRect.height / m_TextComponent.preferredHeight;
3536
3537 m_VerticalScrollbar.size = size;
3538
3539 m_VerticalScrollbar.value = GetScrollPositionRelativeToViewport();
3540
3541 //Debug.Log(GetInstanceID() + "- UpdateScrollbar() - Updating Scrollbar... Value: " + m_VerticalScrollbar.value);
3542 }
3543 }
3544
3545
3546 /// <summary>
3547 /// Function to update the vertical position of the text container when OnValueChanged event is received from the Scrollbar.
3548 /// </summary>
3549 /// <param name="value"></param>
3550 void OnScrollbarValueChange(float value)
3551 {
3552 //if (m_IsUpdatingScrollbarValues)
3553 //{
3554 // m_IsUpdatingScrollbarValues = false;
3555 // return;
3556 //}
3557
3558 if (value < 0 || value > 1) return;
3559
3560 AdjustTextPositionRelativeToViewport(value);
3561
3562 m_ScrollPosition = value;
3563
3564 //Debug.Log(GetInstanceID() + "- OnScrollbarValueChange() - Scrollbar value is: " + value + " Transform POS: " + m_TextComponent.rectTransform.anchoredPosition);
3565 }
3566
3567 void UpdateMaskRegions()
3568 {
3569 // TODO: Figure out a better way to handle adding an offset to the masking region
3570 // This region is defined by the RectTransform of the GameObject that contains the RectMask2D component.
3571 /*
3572 // Update Masking Region
3573 if (m_TextViewportRectMask != null)
3574 {
3575 Rect viewportRect = m_TextViewportRectMask.canvasRect;
3576
3577 if (viewportRect != m_CachedViewportRect)
3578 {
3579 m_CachedViewportRect = viewportRect;
3580
3581 viewportRect.min -= m_TextViewport.offsetMin * 0.5f;
3582 viewportRect.max -= m_TextViewport.offsetMax * 0.5f;
3583
3584 if (m_CachedInputRenderer != null)
3585 m_CachedInputRenderer.EnableRectClipping(viewportRect);
3586
3587 if (m_TextComponent.canvasRenderer != null)
3588 m_TextComponent.canvasRenderer.EnableRectClipping(viewportRect);
3589
3590 if (m_Placeholder != null && m_Placeholder.enabled)
3591 m_Placeholder.canvasRenderer.EnableRectClipping(viewportRect);
3592 }
3593 }
3594 */
3595 }
3596
3597 /// <summary>
3598 /// Adjusts the relative position of the body of the text relative to the viewport.
3599 /// </summary>
3600 /// <param name="relativePosition"></param>
3601 void AdjustTextPositionRelativeToViewport (float relativePosition)
3602 {
3603 if (m_TextViewport == null)
3604 return;
3605
3606 TMP_TextInfo textInfo = m_TextComponent.textInfo;
3607
3608 // Check to make sure we have valid data and lines to query.
3609 if (textInfo == null || textInfo.lineInfo == null || textInfo.lineCount == 0 || textInfo.lineCount > textInfo.lineInfo.Length) return;
3610
3611 float verticalAlignmentOffset = 0;
3612 float textHeight = m_TextComponent.preferredHeight;
3613
3614 switch (m_TextComponent.verticalAlignment)
3615 {
3616 case VerticalAlignmentOptions.Top:
3617 verticalAlignmentOffset = 0;
3618 break;
3619 case VerticalAlignmentOptions.Middle:
3620 verticalAlignmentOffset = 0.5f;
3621 break;
3622 case VerticalAlignmentOptions.Bottom:
3623 verticalAlignmentOffset = 1.0f;
3624 break;
3625 case VerticalAlignmentOptions.Baseline:
3626 break;
3627 case VerticalAlignmentOptions.Geometry:
3628 verticalAlignmentOffset = 0.5f;
3629 textHeight = m_TextComponent.bounds.size.y;
3630 break;
3631 case VerticalAlignmentOptions.Capline:
3632 verticalAlignmentOffset = 0.5f;
3633 break;
3634 }
3635
3636 m_TextComponent.rectTransform.anchoredPosition = new Vector2(m_TextComponent.rectTransform.anchoredPosition.x, (textHeight - m_TextViewport.rect.height) * (relativePosition - verticalAlignmentOffset));
3637
3638 AssignPositioningIfNeeded();
3639
3640 //Debug.Log("Text height: " + m_TextComponent.preferredHeight + " Viewport height: " + m_TextViewport.rect.height + " Adjusted RectTransform anchordedPosition:" + m_TextComponent.rectTransform.anchoredPosition + " Text Bounds: " + m_TextComponent.bounds.ToString("f3"));
3641 }
3642
3643
3644 private int GetCaretPositionFromStringIndex(int stringIndex)
3645 {
3646 int count = m_TextComponent.textInfo.characterCount;
3647
3648 for (int i = 0; i < count; i++)
3649 {
3650 if (m_TextComponent.textInfo.characterInfo[i].index >= stringIndex)
3651 return i;
3652 }
3653
3654 return count;
3655 }
3656
3657 /// <summary>
3658 /// Returns / places the caret before the given character at the string index.
3659 /// </summary>
3660 /// <param name="stringIndex"></param>
3661 /// <returns></returns>
3662 private int GetMinCaretPositionFromStringIndex(int stringIndex)
3663 {
3664 int count = m_TextComponent.textInfo.characterCount;
3665
3666 for (int i = 0; i < count; i++)
3667 {
3668 if (stringIndex < m_TextComponent.textInfo.characterInfo[i].index + m_TextComponent.textInfo.characterInfo[i].stringLength)
3669 return i;
3670 }
3671
3672 return count;
3673 }
3674
3675 /// <summary>
3676 /// Returns / places the caret after the given character at the string index.
3677 /// </summary>
3678 /// <param name="stringIndex"></param>
3679 /// <returns></returns>
3680 private int GetMaxCaretPositionFromStringIndex(int stringIndex)
3681 {
3682 int count = m_TextComponent.textInfo.characterCount;
3683
3684 for (int i = 0; i < count; i++)
3685 {
3686 if (m_TextComponent.textInfo.characterInfo[i].index >= stringIndex)
3687 return i;
3688 }
3689
3690 return count;
3691 }
3692
3693 private int GetStringIndexFromCaretPosition(int caretPosition)
3694 {
3695 // Clamp values between 0 and character count.
3696 ClampCaretPos(ref caretPosition);
3697
3698 return m_TextComponent.textInfo.characterInfo[caretPosition].index;
3699 }
3700
3701 void UpdateStringIndexFromCaretPosition()
3702 {
3703 stringPositionInternal = GetStringIndexFromCaretPosition(m_CaretPosition);
3704 stringSelectPositionInternal = GetStringIndexFromCaretPosition(m_CaretSelectPosition);
3705 m_IsStringPositionDirty = false;
3706 }
3707
3708 void UpdateCaretPositionFromStringIndex()
3709 {
3710 caretPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal);
3711 caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
3712 m_IsCaretPositionDirty = false;
3713 }
3714
3715
3716 public void ForceLabelUpdate()
3717 {
3718 UpdateLabel();
3719 }
3720
3721 private void MarkGeometryAsDirty()
3722 {
3723 #if UNITY_EDITOR
3724 if (!Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this))
3725 return;
3726 #endif
3727
3728 CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);
3729 }
3730
3731 public virtual void Rebuild(CanvasUpdate update)
3732 {
3733 switch (update)
3734 {
3735 case CanvasUpdate.LatePreRender:
3736 UpdateGeometry();
3737 break;
3738 }
3739 }
3740
3741 public virtual void LayoutComplete()
3742 { }
3743
3744 public virtual void GraphicUpdateComplete()
3745 { }
3746
3747 private void UpdateGeometry()
3748 {
3749 #if UNITY_EDITOR
3750 if (!Application.isPlaying)
3751 return;
3752 #endif
3753
3754 // No need to draw a cursor on mobile as its handled by the devices keyboard with the exception of UWP.
3755 if (InPlaceEditing() == false && isUWP() == false)
3756 return;
3757
3758 if (m_CachedInputRenderer == null)
3759 return;
3760
3761 OnFillVBO(mesh);
3762
3763 m_CachedInputRenderer.SetMesh(mesh);
3764 }
3765
3766
3767 /// <summary>
3768 /// Method to keep the Caret RectTransform properties in sync with the text object's RectTransform
3769 /// </summary>
3770 private void AssignPositioningIfNeeded()
3771 {
3772 if (m_TextComponent != null && caretRectTrans != null &&
3773 (caretRectTrans.localPosition != m_TextComponent.rectTransform.localPosition ||
3774 caretRectTrans.localRotation != m_TextComponent.rectTransform.localRotation ||
3775 caretRectTrans.localScale != m_TextComponent.rectTransform.localScale ||
3776 caretRectTrans.anchorMin != m_TextComponent.rectTransform.anchorMin ||
3777 caretRectTrans.anchorMax != m_TextComponent.rectTransform.anchorMax ||
3778 caretRectTrans.anchoredPosition != m_TextComponent.rectTransform.anchoredPosition ||
3779 caretRectTrans.sizeDelta != m_TextComponent.rectTransform.sizeDelta ||
3780 caretRectTrans.pivot != m_TextComponent.rectTransform.pivot))
3781 {
3782 caretRectTrans.localPosition = m_TextComponent.rectTransform.localPosition;
3783 caretRectTrans.localRotation = m_TextComponent.rectTransform.localRotation;
3784 caretRectTrans.localScale = m_TextComponent.rectTransform.localScale;
3785 caretRectTrans.anchorMin = m_TextComponent.rectTransform.anchorMin;
3786 caretRectTrans.anchorMax = m_TextComponent.rectTransform.anchorMax;
3787 caretRectTrans.anchoredPosition = m_TextComponent.rectTransform.anchoredPosition;
3788 caretRectTrans.sizeDelta = m_TextComponent.rectTransform.sizeDelta;
3789 caretRectTrans.pivot = m_TextComponent.rectTransform.pivot;
3790 }
3791 }
3792
3793
3794 private void OnFillVBO(Mesh vbo)
3795 {
3796 using (var helper = new VertexHelper())
3797 {
3798 if (!isFocused && !m_SelectionStillActive)
3799 {
3800 helper.FillMesh(vbo);
3801 return;
3802 }
3803
3804 if (m_IsStringPositionDirty)
3805 UpdateStringIndexFromCaretPosition();
3806
3807 if (m_IsCaretPositionDirty)
3808 UpdateCaretPositionFromStringIndex();
3809
3810 if (!hasSelection)
3811 {
3812 GenerateCaret(helper, Vector2.zero);
3813 SendOnEndTextSelection();
3814 }
3815 else
3816 {
3817 GenerateHighlight(helper, Vector2.zero);
3818 SendOnTextSelection();
3819 }
3820
3821 helper.FillMesh(vbo);
3822 }
3823 }
3824
3825
3826 private void GenerateCaret(VertexHelper vbo, Vector2 roundingOffset)
3827 {
3828 if (m_CaretVisible == false || m_TextComponent.canvas == null || m_ReadOnly)
3829 return;
3830
3831 if (m_CursorVerts == null)
3832 {
3833 CreateCursorVerts();
3834 }
3835
3836 // TODO: Optimize to only update the caret position when needed.
3837
3838 Vector2 startPosition = Vector2.zero;
3839 float height = 0;
3840 TMP_CharacterInfo currentCharacter;
3841
3842 // Make sure caret position does not exceed characterInfo array size or less than zero.
3843 if (caretPositionInternal >= m_TextComponent.textInfo.characterInfo.Length || caretPositionInternal < 0)
3844 return;
3845
3846 int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber;
3847
3848 // Caret is positioned at the origin for the first character of each lines and at the advance for subsequent characters.
3849 if (caretPositionInternal == m_TextComponent.textInfo.lineInfo[currentLine].firstCharacterIndex)
3850 {
3851 currentCharacter = m_TextComponent.textInfo.characterInfo[caretPositionInternal];
3852 height = currentCharacter.ascender - currentCharacter.descender;
3853
3854 if (m_TextComponent.verticalAlignment == VerticalAlignmentOptions.Geometry)
3855 startPosition = new Vector2(currentCharacter.origin, 0 - height / 2);
3856 else
3857 startPosition = new Vector2(currentCharacter.origin, currentCharacter.descender);
3858 }
3859 else
3860 {
3861 currentCharacter = m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1];
3862 height = currentCharacter.ascender - currentCharacter.descender;
3863
3864 if (m_TextComponent.verticalAlignment == VerticalAlignmentOptions.Geometry)
3865 startPosition = new Vector2(currentCharacter.xAdvance, 0 - height / 2);
3866 else
3867 startPosition = new Vector2(currentCharacter.xAdvance, currentCharacter.descender);
3868
3869 }
3870
3871 if (m_SoftKeyboard != null && compositionLength == 0)
3872 {
3873 int selectionStart = m_StringPosition;
3874 int softKeyboardStringLength = m_SoftKeyboard.text == null ? 0 : m_SoftKeyboard.text.Length;
3875
3876 if (selectionStart < 0)
3877 selectionStart = 0;
3878
3879 if (selectionStart > softKeyboardStringLength)
3880 selectionStart = softKeyboardStringLength;
3881
3882 m_SoftKeyboard.selection = new RangeInt(selectionStart, 0);
3883 }
3884
3885 // Adjust the position of the RectTransform based on the caret position in the viewport (only if we have focus).
3886 if (isFocused && startPosition != m_LastPosition || m_forceRectTransformAdjustment || m_HasTextBeenRemoved)
3887 AdjustRectTransformRelativeToViewport(startPosition, height, currentCharacter.isVisible);
3888
3889 m_LastPosition = startPosition;
3890
3891 // Clamp Caret height
3892 float top = startPosition.y + height;
3893 float bottom = top - height;
3894
3895 // Compute the width of the caret which is based on the line height of the primary font asset.
3896 //float width = m_CaretWidth;
3897 TMP_FontAsset fontAsset = m_TextComponent.font;
3898 float baseScale = (m_TextComponent.fontSize / fontAsset.m_FaceInfo.pointSize * fontAsset.m_FaceInfo.scale);
3899 float width = m_CaretWidth * fontAsset.faceInfo.lineHeight * baseScale * 0.05f;
3900
3901 m_CursorVerts[0].position = new Vector3(startPosition.x, bottom, 0.0f);
3902 m_CursorVerts[1].position = new Vector3(startPosition.x, top, 0.0f);
3903 m_CursorVerts[2].position = new Vector3(startPosition.x + width, top, 0.0f);
3904 m_CursorVerts[3].position = new Vector3(startPosition.x + width, bottom, 0.0f);
3905
3906 // Set Vertex Color for the caret color.
3907 m_CursorVerts[0].color = caretColor;
3908 m_CursorVerts[1].color = caretColor;
3909 m_CursorVerts[2].color = caretColor;
3910 m_CursorVerts[3].color = caretColor;
3911
3912 vbo.AddUIVertexQuad(m_CursorVerts);
3913
3914 // Update position of IME window when necessary.
3915 if (m_ShouldUpdateIMEWindowPosition || currentLine != m_PreviousIMEInsertionLine)
3916 {
3917 m_ShouldUpdateIMEWindowPosition = false;
3918 m_PreviousIMEInsertionLine = currentLine;
3919
3920 // Calculate position of IME Window in screen space.
3921 Camera cameraRef;
3922 if (m_TextComponent.canvas.renderMode == RenderMode.ScreenSpaceOverlay)
3923 cameraRef = null;
3924 else
3925 {
3926 cameraRef = m_TextComponent.canvas.worldCamera;
3927
3928 if (cameraRef == null)
3929 cameraRef = Camera.current;
3930 }
3931
3932 Vector3 cursorPosition = m_CachedInputRenderer.gameObject.transform.TransformPoint(m_CursorVerts[0].position);
3933 Vector2 screenPosition = RectTransformUtility.WorldToScreenPoint(cameraRef, cursorPosition);
3934 screenPosition.y = Screen.height - screenPosition.y;
3935
3936 if (inputSystem != null)
3937 inputSystem.compositionCursorPos = screenPosition;
3938
3939 //Debug.Log("[" + Time.frameCount + "] Updating IME Window position Cursor Pos: (" + cursorPosition + ") Screen Pos: (" + screenPosition + ") with Composition Length: " + compositionLength);
3940 }
3941
3942 //#if TMP_DEBUG_MODE
3943 //Debug.Log("Caret position updated at frame: " + Time.frameCount);
3944 //#endif
3945 }
3946
3947
3948 private void CreateCursorVerts()
3949 {
3950 m_CursorVerts = new UIVertex[4];
3951
3952 for (int i = 0; i < m_CursorVerts.Length; i++)
3953 {
3954 m_CursorVerts[i] = UIVertex.simpleVert;
3955 m_CursorVerts[i].uv0 = Vector2.zero;
3956 }
3957 }
3958
3959
3960 private void GenerateHighlight(VertexHelper vbo, Vector2 roundingOffset)
3961 {
3962 // Update Masking Region
3963 UpdateMaskRegions();
3964
3965 // Make sure caret position does not exceed characterInfo array size.
3966 //if (caretSelectPositionInternal >= m_TextComponent.textInfo.characterInfo.Length)
3967 // return;
3968
3969 TMP_TextInfo textInfo = m_TextComponent.textInfo;
3970
3971 // Return if character count is zero as there is nothing to highlight.
3972 if (textInfo.characterCount == 0)
3973 return;
3974
3975 m_CaretPosition = GetCaretPositionFromStringIndex(stringPositionInternal);
3976 m_CaretSelectPosition = GetCaretPositionFromStringIndex(stringSelectPositionInternal);
3977
3978 if (m_SoftKeyboard != null && compositionLength == 0)
3979 {
3980 int stringPosition = m_CaretPosition < m_CaretSelectPosition ? textInfo.characterInfo[m_CaretPosition].index : textInfo.characterInfo[m_CaretSelectPosition].index;
3981 int length = m_CaretPosition < m_CaretSelectPosition ? stringSelectPositionInternal - stringPosition : stringPositionInternal - stringPosition;
3982 m_SoftKeyboard.selection = new RangeInt(stringPosition, length);
3983 }
3984
3985 // Adjust text RectTranform position to make sure it is visible in viewport.
3986 Vector2 caretPosition;
3987 float height = 0;
3988 if (m_CaretSelectPosition < textInfo.characterCount)
3989 {
3990 caretPosition = new Vector2(textInfo.characterInfo[m_CaretSelectPosition].origin, textInfo.characterInfo[m_CaretSelectPosition].descender);
3991 height = textInfo.characterInfo[m_CaretSelectPosition].ascender - textInfo.characterInfo[m_CaretSelectPosition].descender;
3992 }
3993 else
3994 {
3995 caretPosition = new Vector2(textInfo.characterInfo[m_CaretSelectPosition - 1].xAdvance, textInfo.characterInfo[m_CaretSelectPosition - 1].descender);
3996 height = textInfo.characterInfo[m_CaretSelectPosition - 1].ascender - textInfo.characterInfo[m_CaretSelectPosition - 1].descender;
3997 }
3998
3999 // TODO: Don't adjust the position of the RectTransform if Reset On Deactivation is disabled
4000 // and we just selected the Input Field again.
4001 AdjustRectTransformRelativeToViewport(caretPosition, height, true);
4002
4003 int startChar = Mathf.Max(0, m_CaretPosition);
4004 int endChar = Mathf.Max(0, m_CaretSelectPosition);
4005
4006 // Ensure pos is always less then selPos to make the code simpler
4007 if (startChar > endChar)
4008 {
4009 int temp = startChar;
4010 startChar = endChar;
4011 endChar = temp;
4012 }
4013
4014 endChar -= 1;
4015
4016 //Debug.Log("Updating Highlight... Caret Position: " + startChar + " Caret Select POS: " + endChar);
4017
4018
4019 int currentLineIndex = textInfo.characterInfo[startChar].lineNumber;
4020 int nextLineStartIdx = textInfo.lineInfo[currentLineIndex].lastCharacterIndex;
4021
4022 UIVertex vert = UIVertex.simpleVert;
4023 vert.uv0 = Vector2.zero;
4024 vert.color = selectionColor;
4025
4026 int currentChar = startChar;
4027 while (currentChar <= endChar && currentChar < textInfo.characterCount)
4028 {
4029 if (currentChar == nextLineStartIdx || currentChar == endChar)
4030 {
4031 TMP_CharacterInfo startCharInfo = textInfo.characterInfo[startChar];
4032 TMP_CharacterInfo endCharInfo = textInfo.characterInfo[currentChar];
4033
4034 // Extra check to handle Carriage Return
4035 if (currentChar > 0 && endCharInfo.character == '\n' && textInfo.characterInfo[currentChar - 1].character == '\r')
4036 endCharInfo = textInfo.characterInfo[currentChar - 1];
4037
4038 Vector2 startPosition = new Vector2(startCharInfo.origin, textInfo.lineInfo[currentLineIndex].ascender);
4039 Vector2 endPosition = new Vector2(endCharInfo.xAdvance, textInfo.lineInfo[currentLineIndex].descender);
4040
4041 var startIndex = vbo.currentVertCount;
4042 vert.position = new Vector3(startPosition.x, endPosition.y, 0.0f);
4043 vbo.AddVert(vert);
4044
4045 vert.position = new Vector3(endPosition.x, endPosition.y, 0.0f);
4046 vbo.AddVert(vert);
4047
4048 vert.position = new Vector3(endPosition.x, startPosition.y, 0.0f);
4049 vbo.AddVert(vert);
4050
4051 vert.position = new Vector3(startPosition.x, startPosition.y, 0.0f);
4052 vbo.AddVert(vert);
4053
4054 vbo.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
4055 vbo.AddTriangle(startIndex + 2, startIndex + 3, startIndex + 0);
4056
4057 startChar = currentChar + 1;
4058 currentLineIndex++;
4059
4060 if (currentLineIndex < textInfo.lineCount)
4061 nextLineStartIdx = textInfo.lineInfo[currentLineIndex].lastCharacterIndex;
4062 }
4063 currentChar++;
4064 }
4065
4066 //#if TMP_DEBUG_MODE
4067 // Debug.Log("Text selection updated at frame: " + Time.frameCount);
4068 //#endif
4069 }
4070
4071
4072 /// <summary>
4073 ///
4074 /// </summary>
4075 /// <param name="startPosition"></param>
4076 /// <param name="height"></param>
4077 /// <param name="isCharVisible"></param>
4078 private void AdjustRectTransformRelativeToViewport(Vector2 startPosition, float height, bool isCharVisible)
4079 {
4080 //Debug.Log("Adjusting transform position relative to viewport.");
4081
4082 if (m_TextViewport == null)
4083 return;
4084
4085 Vector3 localPosition = transform.localPosition;
4086 Vector3 textComponentLocalPosition = m_TextComponent.rectTransform.localPosition;
4087 Vector3 textViewportLocalPosition = m_TextViewport.localPosition;
4088 Rect textViewportRect = m_TextViewport.rect;
4089
4090 Vector2 caretPosition = new Vector2(startPosition.x + textComponentLocalPosition.x + textViewportLocalPosition.x + localPosition.x, startPosition.y + textComponentLocalPosition.y + textViewportLocalPosition.y + localPosition.y);
4091 Rect viewportWSRect = new Rect(localPosition.x + textViewportLocalPosition.x + textViewportRect.x, localPosition.y + textViewportLocalPosition.y + textViewportRect.y, textViewportRect.width, textViewportRect.height);
4092
4093 // Adjust the position of the RectTransform based on the caret position in the viewport.
4094 float rightOffset = viewportWSRect.xMax - (caretPosition.x + m_TextComponent.margin.z + m_CaretWidth);
4095 if (rightOffset < 0f)
4096 {
4097 if (!multiLine || (multiLine && isCharVisible))
4098 {
4099 //Debug.Log("Shifting text to the LEFT by " + rightOffset.ToString("f3"));
4100 m_TextComponent.rectTransform.anchoredPosition += new Vector2(rightOffset, 0);
4101
4102 AssignPositioningIfNeeded();
4103 }
4104 }
4105
4106 float leftOffset = (caretPosition.x - m_TextComponent.margin.x) - viewportWSRect.xMin;
4107 if (leftOffset < 0f)
4108 {
4109 //Debug.Log("Shifting text to the RIGHT by " + leftOffset.ToString("f3"));
4110 m_TextComponent.rectTransform.anchoredPosition += new Vector2(-leftOffset, 0);
4111 AssignPositioningIfNeeded();
4112 }
4113
4114 // Adjust text area up or down if not in single line mode.
4115 if (m_LineType != LineType.SingleLine)
4116 {
4117 float topOffset = viewportWSRect.yMax - (caretPosition.y + height);
4118 if (topOffset < -0.0001f)
4119 {
4120 //Debug.Log("Shifting text to Up " + topOffset.ToString("f3"));
4121 m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, topOffset);
4122 AssignPositioningIfNeeded();
4123 }
4124
4125 float bottomOffset = caretPosition.y - viewportWSRect.yMin;
4126 if (bottomOffset < 0f)
4127 {
4128 //Debug.Log("Shifting text to Down " + bottomOffset.ToString("f3"));
4129 m_TextComponent.rectTransform.anchoredPosition -= new Vector2(0, bottomOffset);
4130 AssignPositioningIfNeeded();
4131 }
4132 }
4133
4134 // Special handling of backspace/text being removed
4135 if (m_HasTextBeenRemoved)
4136 {
4137 float anchoredPositionX = m_TextComponent.rectTransform.anchoredPosition.x;
4138
4139 float firstCharPosition = localPosition.x + textViewportLocalPosition.x + textComponentLocalPosition.x + m_TextComponent.textInfo.characterInfo[0].origin - m_TextComponent.margin.x;
4140 int lastCharacterIndex = ClampArrayIndex(m_TextComponent.textInfo.characterCount - 1);
4141 float lastCharPosition = localPosition.x + textViewportLocalPosition.x + textComponentLocalPosition.x + m_TextComponent.textInfo.characterInfo[lastCharacterIndex].origin + m_TextComponent.margin.z + m_CaretWidth;
4142
4143 if (anchoredPositionX > 0.0001f && firstCharPosition > viewportWSRect.xMin)
4144 {
4145 float offset = viewportWSRect.xMin - firstCharPosition;
4146
4147 if (anchoredPositionX < -offset)
4148 offset = -anchoredPositionX;
4149
4150 m_TextComponent.rectTransform.anchoredPosition += new Vector2(offset, 0);
4151 AssignPositioningIfNeeded();
4152 }
4153 else if (anchoredPositionX < -0.0001f && lastCharPosition < viewportWSRect.xMax)
4154 {
4155 float offset = viewportWSRect.xMax - lastCharPosition;
4156
4157 if (-anchoredPositionX < offset)
4158 offset = -anchoredPositionX;
4159
4160 m_TextComponent.rectTransform.anchoredPosition += new Vector2(offset, 0);
4161 AssignPositioningIfNeeded();
4162 }
4163
4164 m_HasTextBeenRemoved = false;
4165 }
4166
4167 m_forceRectTransformAdjustment = false;
4168 }
4169
4170 /// <summary>
4171 /// Validate the specified input.
4172 /// </summary>
4173 protected char Validate(string text, int pos, char ch)
4174 {
4175 // Validation is disabled
4176 if (characterValidation == CharacterValidation.None || !enabled)
4177 return ch;
4178
4179 if (characterValidation == CharacterValidation.Integer || characterValidation == CharacterValidation.Decimal)
4180 {
4181 // Integer and decimal
4182 bool cursorBeforeDash = (pos == 0 && text.Length > 0 && text[0] == '-');
4183 bool selectionAtStart = stringPositionInternal == 0 || stringSelectPositionInternal == 0;
4184 if (!cursorBeforeDash)
4185 {
4186 if (ch >= '0' && ch <= '9') return ch;
4187 if (ch == '-' && (pos == 0 || selectionAtStart)) return ch;
4188
4189 var separator = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator;
4190 if (ch == Convert.ToChar(separator) && characterValidation == CharacterValidation.Decimal && !text.Contains(separator)) return ch;
4191 }
4192 }
4193 else if (characterValidation == CharacterValidation.Digit)
4194 {
4195 if (ch >= '0' && ch <= '9') return ch;
4196 }
4197 else if (characterValidation == CharacterValidation.Alphanumeric)
4198 {
4199 // All alphanumeric characters
4200 if (ch >= 'A' && ch <= 'Z') return ch;
4201 if (ch >= 'a' && ch <= 'z') return ch;
4202 if (ch >= '0' && ch <= '9') return ch;
4203 }
4204 else if (characterValidation == CharacterValidation.Name)
4205 {
4206 char prevChar = (text.Length > 0) ? text[Mathf.Clamp(pos - 1, 0, text.Length - 1)] : ' ';
4207 char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' ';
4208 char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n';
4209
4210 if (char.IsLetter(ch))
4211 {
4212 // First letter is always capitalized
4213 if (char.IsLower(ch) && pos == 0)
4214 return char.ToUpper(ch);
4215
4216 // Letter following a space or hyphen is always capitalized
4217 if (char.IsLower(ch) && (prevChar == ' ' || prevChar == '-'))
4218 return char.ToUpper(ch);
4219
4220 // Uppercase letters are only allowed after spaces, apostrophes, hyphens or lowercase letter
4221 if (char.IsUpper(ch) && pos > 0 && prevChar != ' ' && prevChar != '\'' && prevChar != '-' && !char.IsLower(prevChar))
4222 return char.ToLower(ch);
4223
4224 // Do not allow uppercase characters to be inserted before another uppercase character
4225 if (char.IsUpper(ch) && char.IsUpper(lastChar))
4226 return (char)0;
4227
4228 // If character was already in correct case, return it as-is.
4229 // Also, letters that are neither upper nor lower case are always allowed.
4230 return ch;
4231 }
4232 else if (ch == '\'')
4233 {
4234 // Don't allow more than one apostrophe
4235 if (lastChar != ' ' && lastChar != '\'' && nextChar != '\'' && !text.Contains("'"))
4236 return ch;
4237 }
4238
4239 // Allow inserting a hyphen after a character
4240 if (char.IsLetter(prevChar) && ch == '-' && lastChar != '-')
4241 {
4242 return ch;
4243 }
4244
4245 if ((ch == ' ' || ch == '-') && pos != 0)
4246 {
4247 // Don't allow more than one space in a row
4248 if (prevChar != ' ' && prevChar != '\'' && prevChar != '-' &&
4249 lastChar != ' ' && lastChar != '\'' && lastChar != '-' &&
4250 nextChar != ' ' && nextChar != '\'' && nextChar != '-')
4251 return ch;
4252 }
4253 }
4254 else if (characterValidation == CharacterValidation.EmailAddress)
4255 {
4256 // From StackOverflow about allowed characters in email addresses:
4257 // Uppercase and lowercase English letters (a-z, A-Z)
4258 // Digits 0 to 9
4259 // Characters ! # $ % & ' * + - / = ? ^ _ ` { | } ~
4260 // Character . (dot, period, full stop) provided that it is not the first or last character,
4261 // and provided also that it does not appear two or more times consecutively.
4262
4263 if (ch >= 'A' && ch <= 'Z') return ch;
4264 if (ch >= 'a' && ch <= 'z') return ch;
4265 if (ch >= '0' && ch <= '9') return ch;
4266 if (ch == '@' && text.IndexOf('@') == -1) return ch;
4267 if (kEmailSpecialCharacters.IndexOf(ch) != -1) return ch;
4268 if (ch == '.')
4269 {
4270 char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' ';
4271 char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n';
4272 if (lastChar != '.' && nextChar != '.')
4273 return ch;
4274 }
4275 }
4276 else if (characterValidation == CharacterValidation.Regex)
4277 {
4278 // Regex expression
4279 if (Regex.IsMatch(ch.ToString(), m_RegexValue))
4280 {
4281 return ch;
4282 }
4283 }
4284 else if (characterValidation == CharacterValidation.CustomValidator)
4285 {
4286 if (m_InputValidator != null)
4287 {
4288 char c = m_InputValidator.Validate(ref text, ref pos, ch);
4289 m_Text = text;
4290 stringSelectPositionInternal = stringPositionInternal = pos;
4291 return c;
4292 }
4293 }
4294 return (char)0;
4295 }
4296
4297 public void ActivateInputField()
4298 {
4299 if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable())
4300 return;
4301
4302 if (isFocused)
4303 {
4304 if (m_SoftKeyboard != null && !m_SoftKeyboard.active)
4305 {
4306 m_SoftKeyboard.active = true;
4307 m_SoftKeyboard.text = m_Text;
4308 }
4309 }
4310
4311 m_ShouldActivateNextUpdate = true;
4312 }
4313
4314 private void ActivateInputFieldInternal()
4315 {
4316 if (EventSystem.current == null)
4317 return;
4318
4319 if (EventSystem.current.currentSelectedGameObject != gameObject)
4320 EventSystem.current.SetSelectedGameObject(gameObject);
4321
4322 // Cache the value of isInPlaceEditingAllowed, because on UWP this involves calling into native code
4323 // The value only needs to be updated once when the TouchKeyboard is opened.
4324 m_TouchKeyboardAllowsInPlaceEditing = !s_IsQuestDevice && TouchScreenKeyboard.isInPlaceEditingAllowed;
4325
4326 if (TouchScreenKeyboardShouldBeUsed() && shouldHideSoftKeyboard == false)
4327 {
4328 if (inputSystem != null && inputSystem.touchSupported)
4329 {
4330 TouchScreenKeyboard.hideInput = shouldHideMobileInput;
4331 }
4332
4333 if (shouldHideSoftKeyboard == false && m_ReadOnly == false)
4334 {
4335 m_SoftKeyboard = (inputType == InputType.Password) ?
4336 TouchScreenKeyboard.Open(m_Text, keyboardType, false, multiLine, true, isAlert, "", characterLimit) :
4337 TouchScreenKeyboard.Open(m_Text, keyboardType, inputType == InputType.AutoCorrect, multiLine, false, isAlert, "", characterLimit);
4338
4339 OnFocus();
4340
4341 // Opening the soft keyboard sets its selection to the end of the text.
4342 // As such, we set the selection to match the Input Field's internal selection.
4343 if (m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection)
4344 {
4345 int length = stringPositionInternal < stringSelectPositionInternal ? stringSelectPositionInternal - stringPositionInternal : stringPositionInternal - stringSelectPositionInternal;
4346 m_SoftKeyboard.selection = new RangeInt(stringPositionInternal < stringSelectPositionInternal ? stringPositionInternal : stringSelectPositionInternal, length);
4347 }
4348 //}
4349 }
4350 }
4351 else
4352 {
4353 if (!TouchScreenKeyboardShouldBeUsed() && m_ReadOnly == false && inputSystem != null)
4354 inputSystem.imeCompositionMode = IMECompositionMode.On;
4355
4356 OnFocus();
4357 }
4358
4359 m_AllowInput = true;
4360 m_OriginalText = text;
4361 m_WasCanceled = false;
4362 SetCaretVisible();
4363 UpdateLabel();
4364 }
4365
4366 public override void OnSelect(BaseEventData eventData)
4367 {
4368 //Debug.Log("OnSelect()");
4369
4370 base.OnSelect(eventData);
4371 SendOnFocus();
4372
4373 if (shouldActivateOnSelect)
4374 ActivateInputField();
4375 }
4376
4377 public virtual void OnPointerClick(PointerEventData eventData)
4378 {
4379 //Debug.Log("Pointer Click Event...");
4380
4381 if (eventData.button != PointerEventData.InputButton.Left)
4382 return;
4383
4384 ActivateInputField();
4385 }
4386
4387 public void OnControlClick()
4388 {
4389 //Debug.Log("Input Field control click...");
4390 }
4391
4392 public void ReleaseSelection()
4393 {
4394 m_SelectionStillActive = false;
4395 m_ReleaseSelection = false;
4396 m_PreviouslySelectedObject = null;
4397
4398 MarkGeometryAsDirty();
4399
4400 SendOnEndEdit();
4401 SendOnEndTextSelection();
4402 }
4403
4404 public void DeactivateInputField(bool clearSelection = false)
4405 {
4406 //Debug.Log("Deactivate Input Field...");
4407
4408 // Not activated do nothing.
4409 if (!m_AllowInput)
4410 return;
4411
4412 m_HasDoneFocusTransition = false;
4413 m_AllowInput = false;
4414
4415 if (m_Placeholder != null)
4416 m_Placeholder.enabled = string.IsNullOrEmpty(m_Text);
4417
4418 if (m_TextComponent != null && IsInteractable())
4419 {
4420 if (m_WasCanceled && m_RestoreOriginalTextOnEscape && !m_IsKeyboardBeingClosedInHoloLens)
4421 text = m_OriginalText;
4422
4423 if (m_SoftKeyboard != null)
4424 {
4425 m_SoftKeyboard.active = false;
4426 m_SoftKeyboard = null;
4427 }
4428
4429 m_SelectionStillActive = true;
4430
4431 if (m_ResetOnDeActivation || m_ReleaseSelection || clearSelection)
4432 {
4433 //m_StringPosition = m_StringSelectPosition = 0;
4434 //m_CaretPosition = m_CaretSelectPosition = 0;
4435 //m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition;
4436
4437 if (m_VerticalScrollbar == null)
4438 ReleaseSelection();
4439 }
4440
4441 if (inputSystem != null)
4442 inputSystem.imeCompositionMode = IMECompositionMode.Auto;
4443
4444 m_IsKeyboardBeingClosedInHoloLens = false;
4445 }
4446
4447 MarkGeometryAsDirty();
4448 }
4449
4450 public override void OnDeselect(BaseEventData eventData)
4451 {
4452 DeactivateInputField();
4453
4454 base.OnDeselect(eventData);
4455 SendOnFocusLost();
4456 }
4457
4458 public virtual void OnSubmit(BaseEventData eventData)
4459 {
4460 //Debug.Log("OnSubmit()");
4461
4462 if (!IsActive() || !IsInteractable())
4463 return;
4464
4465 if (!isFocused)
4466 m_ShouldActivateNextUpdate = true;
4467
4468 SendOnSubmit();
4469 DeactivateInputField();
4470 eventData?.Use();
4471 }
4472
4473 public virtual void OnCancel(BaseEventData eventData)
4474 {
4475 if (!IsActive() || !IsInteractable())
4476 return;
4477
4478 if (!isFocused)
4479 m_ShouldActivateNextUpdate = true;
4480
4481 m_WasCanceled = true;
4482 DeactivateInputField();
4483 eventData.Use();
4484 }
4485
4486 public override void OnMove(AxisEventData eventData)
4487 {
4488 // Prevent UI navigation while text is being edited.
4489 if (!m_AllowInput)
4490 base.OnMove(eventData);
4491 }
4492
4493 //public virtual void OnLostFocus(BaseEventData eventData)
4494 //{
4495 // if (!IsActive() || !IsInteractable())
4496 // return;
4497 //}
4498
4499 private void EnforceContentType()
4500 {
4501 switch (contentType)
4502 {
4503 case ContentType.Standard:
4504 {
4505 // Don't enforce line type for this content type.
4506 m_InputType = InputType.Standard;
4507 m_KeyboardType = TouchScreenKeyboardType.Default;
4508 m_CharacterValidation = CharacterValidation.None;
4509 break;
4510 }
4511 case ContentType.Autocorrected:
4512 {
4513 // Don't enforce line type for this content type.
4514 m_InputType = InputType.AutoCorrect;
4515 m_KeyboardType = TouchScreenKeyboardType.Default;
4516 m_CharacterValidation = CharacterValidation.None;
4517 break;
4518 }
4519 case ContentType.IntegerNumber:
4520 {
4521 m_LineType = LineType.SingleLine;
4522 m_InputType = InputType.Standard;
4523 m_KeyboardType = TouchScreenKeyboardType.NumberPad;
4524 m_CharacterValidation = CharacterValidation.Integer;
4525 break;
4526 }
4527 case ContentType.DecimalNumber:
4528 {
4529 m_LineType = LineType.SingleLine;
4530 m_InputType = InputType.Standard;
4531 m_KeyboardType = TouchScreenKeyboardType.NumbersAndPunctuation;
4532 m_CharacterValidation = CharacterValidation.Decimal;
4533 break;
4534 }
4535 case ContentType.Alphanumeric:
4536 {
4537 m_LineType = LineType.SingleLine;
4538 m_InputType = InputType.Standard;
4539 m_KeyboardType = TouchScreenKeyboardType.ASCIICapable;
4540 m_CharacterValidation = CharacterValidation.Alphanumeric;
4541 break;
4542 }
4543 case ContentType.Name:
4544 {
4545 m_LineType = LineType.SingleLine;
4546 m_InputType = InputType.Standard;
4547 m_KeyboardType = TouchScreenKeyboardType.Default;
4548 m_CharacterValidation = CharacterValidation.Name;
4549 break;
4550 }
4551 case ContentType.EmailAddress:
4552 {
4553 m_LineType = LineType.SingleLine;
4554 m_InputType = InputType.Standard;
4555 m_KeyboardType = TouchScreenKeyboardType.EmailAddress;
4556 m_CharacterValidation = CharacterValidation.EmailAddress;
4557 break;
4558 }
4559 case ContentType.Password:
4560 {
4561 m_LineType = LineType.SingleLine;
4562 m_InputType = InputType.Password;
4563 m_KeyboardType = TouchScreenKeyboardType.Default;
4564 m_CharacterValidation = CharacterValidation.None;
4565 break;
4566 }
4567 case ContentType.Pin:
4568 {
4569 m_LineType = LineType.SingleLine;
4570 m_InputType = InputType.Password;
4571 m_KeyboardType = TouchScreenKeyboardType.NumberPad;
4572 m_CharacterValidation = CharacterValidation.Digit;
4573 break;
4574 }
4575 default:
4576 {
4577 // Includes Custom type. Nothing should be enforced.
4578 break;
4579 }
4580 }
4581
4582 SetTextComponentWrapMode();
4583 }
4584
4585 void SetTextComponentWrapMode()
4586 {
4587 if (m_TextComponent == null)
4588 return;
4589
4590 if (multiLine)
4591 m_TextComponent.textWrappingMode = TextWrappingModes.Normal;
4592 else
4593 m_TextComponent.textWrappingMode = TextWrappingModes.PreserveWhitespaceNoWrap;
4594 }
4595
4596 // Control Rich Text option on the text component.
4597 void SetTextComponentRichTextMode()
4598 {
4599 if (m_TextComponent == null)
4600 return;
4601
4602 m_TextComponent.richText = m_RichText;
4603 }
4604
4605 void SetToCustomIfContentTypeIsNot(params ContentType[] allowedContentTypes)
4606 {
4607 if (contentType == ContentType.Custom)
4608 return;
4609
4610 for (int i = 0; i < allowedContentTypes.Length; i++)
4611 if (contentType == allowedContentTypes[i])
4612 return;
4613
4614 contentType = ContentType.Custom;
4615 }
4616
4617 void SetToCustom()
4618 {
4619 if (contentType == ContentType.Custom)
4620 return;
4621
4622 contentType = ContentType.Custom;
4623 }
4624
4625 void SetToCustom(CharacterValidation characterValidation)
4626 {
4627 if (contentType == ContentType.Custom)
4628 {
4629 characterValidation = CharacterValidation.CustomValidator;
4630 return;
4631 }
4632
4633 contentType = ContentType.Custom;
4634 characterValidation = CharacterValidation.CustomValidator;
4635 }
4636
4637
4638 protected override void DoStateTransition(SelectionState state, bool instant)
4639 {
4640 if (m_HasDoneFocusTransition)
4641 state = SelectionState.Selected;
4642 else if (state == SelectionState.Pressed)
4643 m_HasDoneFocusTransition = true;
4644
4645 base.DoStateTransition(state, instant);
4646 }
4647
4648
4649 /// <summary>
4650 /// See ILayoutElement.CalculateLayoutInputHorizontal.
4651 /// </summary>
4652 public virtual void CalculateLayoutInputHorizontal()
4653 { }
4654
4655 /// <summary>
4656 /// See ILayoutElement.CalculateLayoutInputVertical.
4657 /// </summary>
4658 public virtual void CalculateLayoutInputVertical()
4659 { }
4660
4661 /// <summary>
4662 /// See ILayoutElement.minWidth.
4663 /// </summary>
4664 public virtual float minWidth { get { return 0; } }
4665
4666 /// <summary>
4667 /// Get the displayed with of all input characters.
4668 /// </summary>
4669 public virtual float preferredWidth
4670 {
4671 get
4672 {
4673 if (textComponent == null)
4674 return 0;
4675
4676 float horizontalPadding = 0;
4677
4678 if (m_LayoutGroup != null)
4679 horizontalPadding = m_LayoutGroup.padding.horizontal;
4680
4681 if (m_TextViewport != null)
4682 horizontalPadding += m_TextViewport.offsetMin.x - m_TextViewport.offsetMax.x;
4683
4684 return m_TextComponent.preferredWidth + horizontalPadding; // Should add some extra padding for caret
4685 }
4686 }
4687
4688 /// <summary>
4689 /// See ILayoutElement.flexibleWidth.
4690 /// </summary>
4691 public virtual float flexibleWidth { get { return -1; } }
4692
4693 /// <summary>
4694 /// See ILayoutElement.minHeight.
4695 /// </summary>
4696 public virtual float minHeight { get { return 0; } }
4697
4698 /// <summary>
4699 /// Get the height of all the text if constrained to the height of the RectTransform.
4700 /// </summary>
4701 public virtual float preferredHeight
4702 {
4703 get
4704 {
4705 if (textComponent == null)
4706 return 0;
4707
4708 float verticalPadding = 0;
4709
4710 if (m_LayoutGroup != null)
4711 verticalPadding = m_LayoutGroup.padding.vertical;
4712
4713 if (m_TextViewport != null)
4714 verticalPadding += m_TextViewport.offsetMin.y - m_TextViewport.offsetMax.y;
4715
4716 return m_TextComponent.preferredHeight + verticalPadding;
4717 }
4718 }
4719
4720 /// <summary>
4721 /// See ILayoutElement.flexibleHeight.
4722 /// </summary>
4723 public virtual float flexibleHeight { get { return -1; } }
4724
4725 /// <summary>
4726 /// See ILayoutElement.layoutPriority.
4727 /// </summary>
4728 public virtual int layoutPriority { get { return 1; } }
4729
4730
4731 /// <summary>
4732 /// Function to conveniently set the point size of both Placeholder and Input Field text object.
4733 /// </summary>
4734 /// <param name="pointSize"></param>
4735 public void SetGlobalPointSize(float pointSize)
4736 {
4737 TMP_Text placeholderTextComponent = m_Placeholder as TMP_Text;
4738
4739 if (placeholderTextComponent != null)
4740 placeholderTextComponent.fontSize = pointSize;
4741
4742 textComponent.fontSize = pointSize;
4743 }
4744
4745 /// <summary>
4746 /// Function to conveniently set the Font Asset of both Placeholder and Input Field text object.
4747 /// </summary>
4748 /// <param name="fontAsset"></param>
4749 public void SetGlobalFontAsset(TMP_FontAsset fontAsset)
4750 {
4751 TMP_Text placeholderTextComponent = m_Placeholder as TMP_Text;
4752
4753 if (placeholderTextComponent != null)
4754 placeholderTextComponent.font = fontAsset;
4755
4756 textComponent.font = fontAsset;
4757 }
4758
4759 }
4760
4761
4762 static class SetPropertyUtility
4763 {
4764 public static bool SetColor(ref Color currentValue, Color newValue)
4765 {
4766 if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a)
4767 return false;
4768
4769 currentValue = newValue;
4770 return true;
4771 }
4772
4773 public static bool SetEquatableStruct<T>(ref T currentValue, T newValue) where T : IEquatable<T>
4774 {
4775 if (currentValue.Equals(newValue))
4776 return false;
4777
4778 currentValue = newValue;
4779 return true;
4780 }
4781
4782 public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
4783 {
4784 if (currentValue.Equals(newValue))
4785 return false;
4786
4787 currentValue = newValue;
4788 return true;
4789 }
4790
4791 public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
4792 {
4793 if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
4794 return false;
4795
4796 currentValue = newValue;
4797 return true;
4798 }
4799 }
4800}