A game about forced loneliness, made by TACStudios
at master 4800 lines 184 kB view raw
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}