Created
June 9, 2022 05:41
-
-
Save nikescar1/c07f1f61ef422a05b9864c93ac7061fc to your computer and use it in GitHub Desktop.
Since TMP_InputField is essentially unusable with gamepad and keyboard navigation, I upgraded them to work better (don't start editing on select and added OnFocus event)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//#define TMP_DEBUG_MODE | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Threading; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using UnityEngine; | |
using UnityEngine.UI; | |
using UnityEngine.Events; | |
using UnityEngine.EventSystems; | |
using UnityEngine.Serialization; | |
namespace TMPro | |
{ | |
/// <summary> | |
/// Editable text input field. | |
/// </summary> | |
[AddComponentMenu("UI/TextMeshPro - Input Field", 11)] | |
public class TMP_InputField : Selectable, | |
IUpdateSelectedHandler, | |
IBeginDragHandler, | |
IDragHandler, | |
IEndDragHandler, | |
IPointerClickHandler, | |
ISubmitHandler, | |
ICanvasElement, | |
ILayoutElement, | |
IScrollHandler | |
{ | |
// Setting the content type acts as a shortcut for setting a combination of InputType, CharacterValidation, LineType, and TouchScreenKeyboardType | |
public enum ContentType | |
{ | |
Standard, | |
Autocorrected, | |
IntegerNumber, | |
DecimalNumber, | |
Alphanumeric, | |
Name, | |
EmailAddress, | |
Password, | |
Pin, | |
Custom | |
} | |
public enum InputType | |
{ | |
Standard, | |
AutoCorrect, | |
Password, | |
} | |
public enum CharacterValidation | |
{ | |
None, | |
Digit, | |
Integer, | |
Decimal, | |
Alphanumeric, | |
Name, | |
Regex, | |
EmailAddress, | |
CustomValidator | |
} | |
public enum LineType | |
{ | |
SingleLine, | |
MultiLineSubmit, | |
MultiLineNewline | |
} | |
public delegate char OnValidateInput(string text, int charIndex, char addedChar); | |
[Serializable] | |
public class SubmitEvent : UnityEvent<string> { } | |
[Serializable] | |
public class OnChangeEvent : UnityEvent<string> { } | |
[Serializable] | |
public class SelectionEvent : UnityEvent<string> { } | |
[Serializable] | |
public class TextSelectionEvent : UnityEvent<string, int, int> { } | |
[Serializable] | |
public class TouchScreenKeyboardEvent : UnityEvent<TouchScreenKeyboard.Status> { } | |
protected TouchScreenKeyboard m_SoftKeyboard; | |
static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' }; | |
#region Exposed properties | |
/// <summary> | |
/// Text Text used to display the input's value. | |
/// </summary> | |
protected RectTransform m_RectTransform; | |
[SerializeField] | |
protected RectTransform m_TextViewport; | |
protected RectMask2D m_TextComponentRectMask; | |
protected RectMask2D m_TextViewportRectMask; | |
private Rect m_CachedViewportRect; | |
[SerializeField] | |
protected TMP_Text m_TextComponent; | |
protected RectTransform m_TextComponentRectTransform; | |
[SerializeField] | |
protected Graphic m_Placeholder; | |
[SerializeField] | |
protected Scrollbar m_VerticalScrollbar; | |
[SerializeField] | |
protected TMP_ScrollbarEventHandler m_VerticalScrollbarEventHandler; | |
//private bool m_ForceDeactivation; | |
private bool m_IsDrivenByLayoutComponents = false; | |
[SerializeField] | |
private LayoutGroup m_LayoutGroup; | |
private IScrollHandler m_IScrollHandlerParent; | |
/// <summary> | |
/// Used to keep track of scroll position | |
/// </summary> | |
private float m_ScrollPosition; | |
/// <summary> | |
/// | |
/// </summary> | |
[SerializeField] | |
protected float m_ScrollSensitivity = 1.0f; | |
//[SerializeField] | |
//protected TMP_Text m_PlaceholderTextComponent; | |
[SerializeField] | |
private ContentType m_ContentType = ContentType.Standard; | |
/// <summary> | |
/// Type of data expected by the input field. | |
/// </summary> | |
[SerializeField] | |
private InputType m_InputType = InputType.Standard; | |
/// <summary> | |
/// The character used to hide text in password field. | |
/// </summary> | |
[SerializeField] | |
private char m_AsteriskChar = '*'; | |
/// <summary> | |
/// Keyboard type applies to mobile keyboards that get shown. | |
/// </summary> | |
[SerializeField] | |
private TouchScreenKeyboardType m_KeyboardType = TouchScreenKeyboardType.Default; | |
[SerializeField] | |
private LineType m_LineType = LineType.SingleLine; | |
/// <summary> | |
/// Should hide mobile input field part of the virtual keyboard. | |
/// </summary> | |
[SerializeField] | |
private bool m_HideMobileInput = false; | |
/// <summary> | |
/// Should hide soft / virtual keyboard. | |
/// </summary> | |
[SerializeField] | |
private bool m_HideSoftKeyboard = false; | |
/// <summary> | |
/// What kind of validation to use with the input field's data. | |
/// </summary> | |
[SerializeField] | |
private CharacterValidation m_CharacterValidation = CharacterValidation.None; | |
/// <summary> | |
/// The Regex expression used for validating the text input. | |
/// </summary> | |
[SerializeField] | |
private string m_RegexValue = string.Empty; | |
/// <summary> | |
/// The point sized used by the placeholder and input text object. | |
/// </summary> | |
[SerializeField] | |
private float m_GlobalPointSize = 14; | |
/// <summary> | |
/// Maximum number of characters allowed before input no longer works. | |
/// </summary> | |
[SerializeField] | |
private int m_CharacterLimit = 0; | |
/// <summary> | |
/// Event delegates triggered when the input field submits its data. | |
/// </summary> | |
[SerializeField] | |
private SubmitEvent m_OnEndEdit = new SubmitEvent(); | |
/// <summary> | |
/// Event delegates triggered when the input field submits its data. | |
/// </summary> | |
[SerializeField] | |
private SubmitEvent m_OnSubmit = new SubmitEvent(); | |
/// <summary> | |
/// Event delegates triggered when the input field is focused. | |
/// </summary> | |
[SerializeField] | |
private SelectionEvent m_OnSelect = new SelectionEvent(); | |
/// <summary> | |
/// Event delegates triggered when the input field is focused. | |
/// </summary> | |
[SerializeField] | |
private SelectionEvent m_OnFocus = new SelectionEvent(); | |
/// <summary> | |
/// Event delegates triggered when the input field focus is lost. | |
/// </summary> | |
[SerializeField] | |
private SelectionEvent m_OnDeselect = new SelectionEvent(); | |
/// <summary> | |
/// Event delegates triggered when the text is selected / highlighted. | |
/// </summary> | |
[SerializeField] | |
private TextSelectionEvent m_OnTextSelection = new TextSelectionEvent(); | |
/// <summary> | |
/// Event delegates triggered when text is no longer select / highlighted. | |
/// </summary> | |
[SerializeField] | |
private TextSelectionEvent m_OnEndTextSelection = new TextSelectionEvent(); | |
/// <summary> | |
/// Event delegates triggered when the input field changes its data. | |
/// </summary> | |
[SerializeField] | |
private OnChangeEvent m_OnValueChanged = new OnChangeEvent(); | |
/// <summary> | |
/// Event delegates triggered when the status of the TouchScreenKeyboard changes. | |
/// </summary> | |
[SerializeField] | |
private TouchScreenKeyboardEvent m_OnTouchScreenKeyboardStatusChanged = new TouchScreenKeyboardEvent(); | |
/// <summary> | |
/// Custom validation callback. | |
/// </summary> | |
[SerializeField] | |
private OnValidateInput m_OnValidateInput; | |
[SerializeField] | |
private Color m_CaretColor = new Color(50f / 255f, 50f / 255f, 50f / 255f, 1f); | |
[SerializeField] | |
private bool m_CustomCaretColor = false; | |
[SerializeField] | |
private Color m_SelectionColor = new Color(168f / 255f, 206f / 255f, 255f / 255f, 192f / 255f); | |
/// <summary> | |
/// Input field's value. | |
/// </summary> | |
[SerializeField] | |
[TextArea(5, 10)] | |
protected string m_Text = string.Empty; | |
[SerializeField] | |
[Range(0f, 4f)] | |
private float m_CaretBlinkRate = 0.85f; | |
[SerializeField] | |
[Range(1, 5)] | |
private int m_CaretWidth = 1; | |
[SerializeField] | |
private bool m_ReadOnly = false; | |
[SerializeField] | |
private bool m_RichText = true; | |
#endregion | |
protected int m_StringPosition = 0; | |
protected int m_StringSelectPosition = 0; | |
protected int m_CaretPosition = 0; | |
protected int m_CaretSelectPosition = 0; | |
private RectTransform caretRectTrans = null; | |
protected UIVertex[] m_CursorVerts = null; | |
private CanvasRenderer m_CachedInputRenderer; | |
private Vector2 m_LastPosition; | |
[NonSerialized] | |
protected Mesh m_Mesh; | |
private bool m_AllowInput = false; | |
//bool m_HasLostFocus = false; | |
private bool m_ShouldActivateNextUpdate = false; | |
private bool m_UpdateDrag = false; | |
private bool m_DragPositionOutOfBounds = false; | |
private const float kHScrollSpeed = 0.05f; | |
private const float kVScrollSpeed = 0.10f; | |
protected bool m_CaretVisible; | |
private Coroutine m_BlinkCoroutine = null; | |
private float m_BlinkStartTime = 0.0f; | |
private Coroutine m_DragCoroutine = null; | |
private string m_OriginalText = ""; | |
private bool m_WasCanceled = false; | |
private bool m_HasDoneFocusTransition = false; | |
private WaitForSecondsRealtime m_WaitForSecondsRealtime; | |
private bool m_PreventCallback = false; | |
private bool m_TouchKeyboardAllowsInPlaceEditing = false; | |
private bool m_IsTextComponentUpdateRequired = false; | |
private bool m_isLastKeyBackspace = false; | |
private float m_PointerDownClickStartTime; | |
private float m_KeyDownStartTime; | |
private float m_DoubleClickDelay = 0.5f; | |
// Doesn't include dot and @ on purpose! See usage for details. | |
const string kEmailSpecialCharacters = "!#$%&'*+-/=?^_`{|}~"; | |
private BaseInput inputSystem | |
{ | |
get | |
{ | |
if (EventSystem.current && EventSystem.current.currentInputModule) | |
return EventSystem.current.currentInputModule.input; | |
return null; | |
} | |
} | |
private string compositionString | |
{ | |
get { return inputSystem != null ? inputSystem.compositionString : Input.compositionString; } | |
} | |
private bool m_IsCompositionActive = false; | |
private bool m_ShouldUpdateIMEWindowPosition = false; | |
private int m_PreviousIMEInsertionLine = 0; | |
private int compositionLength | |
{ | |
get | |
{ | |
if (m_ReadOnly) | |
return 0; | |
return compositionString.Length; | |
} | |
} | |
protected TMP_InputField() | |
{ | |
SetTextComponentWrapMode(); | |
} | |
protected Mesh mesh | |
{ | |
get | |
{ | |
if (m_Mesh == null) | |
m_Mesh = new Mesh(); | |
return m_Mesh; | |
} | |
} | |
/// <summary> | |
/// Should the mobile keyboard input be hidden. | |
/// </summary> | |
public bool shouldHideMobileInput | |
{ | |
get | |
{ | |
switch (Application.platform) | |
{ | |
case RuntimePlatform.Android: | |
case RuntimePlatform.IPhonePlayer: | |
case RuntimePlatform.tvOS: | |
return m_HideMobileInput; | |
default: | |
return true; | |
} | |
} | |
set | |
{ | |
switch (Application.platform) | |
{ | |
case RuntimePlatform.Android: | |
case RuntimePlatform.IPhonePlayer: | |
case RuntimePlatform.tvOS: | |
SetPropertyUtility.SetStruct(ref m_HideMobileInput, value); | |
break; | |
default: | |
m_HideMobileInput = true; | |
break; | |
} | |
} | |
} | |
public bool shouldHideSoftKeyboard | |
{ | |
get | |
{ | |
switch (Application.platform) | |
{ | |
case RuntimePlatform.Android: | |
case RuntimePlatform.IPhonePlayer: | |
case RuntimePlatform.tvOS: | |
case RuntimePlatform.WSAPlayerX86: | |
case RuntimePlatform.WSAPlayerX64: | |
case RuntimePlatform.WSAPlayerARM: | |
case RuntimePlatform.Stadia: | |
#if UNITY_2020_2_OR_NEWER | |
case RuntimePlatform.PS4: | |
#if !(UNITY_2020_2_1 || UNITY_2020_2_2) | |
case RuntimePlatform.PS5: | |
#endif | |
#endif | |
case RuntimePlatform.Switch: | |
return m_HideSoftKeyboard; | |
default: | |
return true; | |
} | |
} | |
set | |
{ | |
switch (Application.platform) | |
{ | |
case RuntimePlatform.Android: | |
case RuntimePlatform.IPhonePlayer: | |
case RuntimePlatform.tvOS: | |
case RuntimePlatform.WSAPlayerX86: | |
case RuntimePlatform.WSAPlayerX64: | |
case RuntimePlatform.WSAPlayerARM: | |
case RuntimePlatform.Stadia: | |
#if UNITY_2020_2_OR_NEWER | |
case RuntimePlatform.PS4: | |
#if !(UNITY_2020_2_1 || UNITY_2020_2_2) | |
case RuntimePlatform.PS5: | |
#endif | |
#endif | |
case RuntimePlatform.Switch: | |
SetPropertyUtility.SetStruct(ref m_HideSoftKeyboard, value); | |
break; | |
default: | |
m_HideSoftKeyboard = true; | |
break; | |
} | |
if (m_HideSoftKeyboard == true && m_SoftKeyboard != null && TouchScreenKeyboard.isSupported && m_SoftKeyboard.active) | |
{ | |
m_SoftKeyboard.active = false; | |
m_SoftKeyboard = null; | |
} | |
} | |
} | |
private bool isKeyboardUsingEvents() | |
{ | |
switch (Application.platform) | |
{ | |
case RuntimePlatform.Android: | |
case RuntimePlatform.IPhonePlayer: | |
case RuntimePlatform.tvOS: | |
#if UNITY_2020_2_OR_NEWER | |
case RuntimePlatform.PS4: | |
#if !(UNITY_2020_2_1 || UNITY_2020_2_2) | |
case RuntimePlatform.PS5: | |
#endif | |
#endif | |
case RuntimePlatform.Switch: | |
return false; | |
default: | |
return true; | |
} | |
} | |
/// <summary> | |
/// Input field's current text value. This is not necessarily the same as what is visible on screen. | |
/// </summary> | |
/// <remarks> | |
/// Note that null is invalid value for InputField.text. | |
/// </remarks> | |
/// <example> | |
/// <code> | |
/// using UnityEngine; | |
/// using System.Collections; | |
/// using UnityEngine.UI; // Required when Using UI elements. | |
/// | |
/// public class Example : MonoBehaviour | |
/// { | |
/// public InputField mainInputField; | |
/// | |
/// public void Start() | |
/// { | |
/// mainInputField.text = "Enter Text Here..."; | |
/// } | |
/// } | |
/// </code> | |
/// </example> | |
public string text | |
{ | |
get | |
{ | |
return m_Text; | |
} | |
set | |
{ | |
SetText(value); | |
} | |
} | |
/// <summary> | |
/// Set Input field's current text value without invoke onValueChanged. This is not necessarily the same as what is visible on screen. | |
/// </summary> | |
public void SetTextWithoutNotify(string input) | |
{ | |
SetText(input, false); | |
} | |
void SetText(string value, bool sendCallback = true) | |
{ | |
if (this.text == value) | |
return; | |
if (value == null) | |
value = ""; | |
value = value.Replace("\0", string.Empty); // remove embedded nulls | |
m_Text = value; | |
/* | |
if (m_LineType == LineType.SingleLine) | |
value = value.Replace("\n", "").Replace("\t", ""); | |
// If we have an input validator, validate the input and apply the character limit at the same time. | |
if (onValidateInput != null || characterValidation != CharacterValidation.None) | |
{ | |
m_Text = ""; | |
OnValidateInput validatorMethod = onValidateInput ?? Validate; | |
m_CaretPosition = m_CaretSelectPosition = value.Length; | |
int charactersToCheck = characterLimit > 0 ? Math.Min(characterLimit, value.Length) : value.Length; | |
for (int i = 0; i < charactersToCheck; ++i) | |
{ | |
char c = validatorMethod(m_Text, m_Text.Length, value[i]); | |
if (c != 0) | |
m_Text += c; | |
} | |
} | |
else | |
{ | |
m_Text = characterLimit > 0 && value.Length > characterLimit ? value.Substring(0, characterLimit) : value; | |
} | |
*/ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
{ | |
SendOnValueChangedAndUpdateLabel(); | |
return; | |
} | |
#endif | |
if (m_SoftKeyboard != null) | |
m_SoftKeyboard.text = m_Text; | |
if (m_StringPosition > m_Text.Length) | |
m_StringPosition = m_StringSelectPosition = m_Text.Length; | |
else if (m_StringSelectPosition > m_Text.Length) | |
m_StringSelectPosition = m_Text.Length; | |
m_forceRectTransformAdjustment = true; | |
m_IsTextComponentUpdateRequired = true; | |
UpdateLabel(); | |
if (sendCallback) | |
SendOnValueChanged(); | |
} | |
public bool isFocused | |
{ | |
get { return m_AllowInput; } | |
} | |
public bool shouldActivateOnSelect | |
{ | |
get { return m_ShouldActivateOnSelect; } | |
} | |
public bool m_ShouldActivateOnSelect = false; | |
public float caretBlinkRate | |
{ | |
get { return m_CaretBlinkRate; } | |
set | |
{ | |
if (SetPropertyUtility.SetStruct(ref m_CaretBlinkRate, value)) | |
{ | |
if (m_AllowInput) | |
SetCaretActive(); | |
} | |
} | |
} | |
public int caretWidth { get { return m_CaretWidth; } set { if (SetPropertyUtility.SetStruct(ref m_CaretWidth, value)) MarkGeometryAsDirty(); } } | |
public RectTransform textViewport { get { return m_TextViewport; } set { SetPropertyUtility.SetClass(ref m_TextViewport, value); } } | |
public TMP_Text textComponent | |
{ | |
get { return m_TextComponent; } | |
set | |
{ | |
if (SetPropertyUtility.SetClass(ref m_TextComponent, value)) | |
{ | |
SetTextComponentWrapMode(); | |
} | |
} | |
} | |
//public TMP_Text placeholderTextComponent { get { return m_PlaceholderTextComponent; } set { SetPropertyUtility.SetClass(ref m_PlaceholderTextComponent, value); } } | |
public Graphic placeholder { get { return m_Placeholder; } set { SetPropertyUtility.SetClass(ref m_Placeholder, value); } } | |
public Scrollbar verticalScrollbar | |
{ | |
get { return m_VerticalScrollbar; } | |
set | |
{ | |
if (m_VerticalScrollbar != null) | |
m_VerticalScrollbar.onValueChanged.RemoveListener(OnScrollbarValueChange); | |
SetPropertyUtility.SetClass(ref m_VerticalScrollbar, value); | |
if (m_VerticalScrollbar) | |
{ | |
m_VerticalScrollbar.onValueChanged.AddListener(OnScrollbarValueChange); | |
} | |
} | |
} | |
public float scrollSensitivity { get { return m_ScrollSensitivity; } set { if (SetPropertyUtility.SetStruct(ref m_ScrollSensitivity, value)) MarkGeometryAsDirty(); } } | |
public Color caretColor { get { return customCaretColor ? m_CaretColor : textComponent.color; } set { if (SetPropertyUtility.SetColor(ref m_CaretColor, value)) MarkGeometryAsDirty(); } } | |
public bool customCaretColor { get { return m_CustomCaretColor; } set { if (m_CustomCaretColor != value) { m_CustomCaretColor = value; MarkGeometryAsDirty(); } } } | |
public Color selectionColor { get { return m_SelectionColor; } set { if (SetPropertyUtility.SetColor(ref m_SelectionColor, value)) MarkGeometryAsDirty(); } } | |
public SubmitEvent onEndEdit { get { return m_OnEndEdit; } set { SetPropertyUtility.SetClass(ref m_OnEndEdit, value); } } | |
public SubmitEvent onSubmit { get { return m_OnSubmit; } set { SetPropertyUtility.SetClass(ref m_OnSubmit, value); } } | |
public SelectionEvent onSelect { get { return m_OnSelect; } set { SetPropertyUtility.SetClass(ref m_OnSelect, value); } } | |
public SelectionEvent onFocus { get { return m_OnFocus; } set { SetPropertyUtility.SetClass(ref m_OnFocus, value); } } | |
public SelectionEvent onDeselect { get { return m_OnDeselect; } set { SetPropertyUtility.SetClass(ref m_OnDeselect, value); } } | |
public TextSelectionEvent onTextSelection { get { return m_OnTextSelection; } set { SetPropertyUtility.SetClass(ref m_OnTextSelection, value); } } | |
public TextSelectionEvent onEndTextSelection { get { return m_OnEndTextSelection; } set { SetPropertyUtility.SetClass(ref m_OnEndTextSelection, value); } } | |
public OnChangeEvent onValueChanged { get { return m_OnValueChanged; } set { SetPropertyUtility.SetClass(ref m_OnValueChanged, value); } } | |
public TouchScreenKeyboardEvent onTouchScreenKeyboardStatusChanged { get { return m_OnTouchScreenKeyboardStatusChanged; } set { SetPropertyUtility.SetClass(ref m_OnTouchScreenKeyboardStatusChanged, value); } } | |
public OnValidateInput onValidateInput { get { return m_OnValidateInput; } set { SetPropertyUtility.SetClass(ref m_OnValidateInput, value); } } | |
public int characterLimit | |
{ | |
get { return m_CharacterLimit; } | |
set | |
{ | |
if (SetPropertyUtility.SetStruct(ref m_CharacterLimit, Math.Max(0, value))) | |
{ | |
UpdateLabel(); | |
if (m_SoftKeyboard != null) | |
m_SoftKeyboard.characterLimit = value; | |
} | |
} | |
} | |
//public bool isInteractableControl { set { if ( } } | |
/// <summary> | |
/// Set the point size on both Placeholder and Input text object. | |
/// </summary> | |
public float pointSize | |
{ | |
get { return m_GlobalPointSize; } | |
set | |
{ | |
if (SetPropertyUtility.SetStruct(ref m_GlobalPointSize, Math.Max(0, value))) | |
{ | |
SetGlobalPointSize(m_GlobalPointSize); | |
UpdateLabel(); | |
} | |
} | |
} | |
/// <summary> | |
/// Sets the Font Asset on both Placeholder and Input child objects. | |
/// </summary> | |
public TMP_FontAsset fontAsset | |
{ | |
get { return m_GlobalFontAsset; } | |
set | |
{ | |
if (SetPropertyUtility.SetClass(ref m_GlobalFontAsset, value)) | |
{ | |
SetGlobalFontAsset(m_GlobalFontAsset); | |
UpdateLabel(); | |
} | |
} | |
} | |
[SerializeField] | |
protected TMP_FontAsset m_GlobalFontAsset; | |
/// <summary> | |
/// Determines if the whole text will be selected when focused. | |
/// </summary> | |
public bool onFocusSelectAll | |
{ | |
get { return m_OnFocusSelectAll; } | |
set { m_OnFocusSelectAll = value; } | |
} | |
[SerializeField] | |
protected bool m_OnFocusSelectAll = true; | |
protected bool m_isSelectAll; | |
/// <summary> | |
/// Determines if the text and caret position as well as selection will be reset when the input field is deactivated. | |
/// </summary> | |
public bool resetOnDeActivation | |
{ | |
get { return m_ResetOnDeActivation; } | |
set { m_ResetOnDeActivation = value; } | |
} | |
[SerializeField] | |
protected bool m_ResetOnDeActivation = true; | |
private bool m_SelectionStillActive = false; | |
private bool m_ReleaseSelection = false; | |
private GameObject m_PreviouslySelectedObject; | |
/// <summary> | |
/// Controls whether the original text is restored when pressing "ESC". | |
/// </summary> | |
public bool restoreOriginalTextOnEscape | |
{ | |
get { return m_RestoreOriginalTextOnEscape; } | |
set { m_RestoreOriginalTextOnEscape = value; } | |
} | |
[SerializeField] | |
private bool m_RestoreOriginalTextOnEscape = true; | |
/// <summary> | |
/// Is Rich Text editing allowed? | |
/// </summary> | |
public bool isRichTextEditingAllowed | |
{ | |
get { return m_isRichTextEditingAllowed; } | |
set { m_isRichTextEditingAllowed = value; } | |
} | |
[SerializeField] | |
protected bool m_isRichTextEditingAllowed = false; | |
// Content Type related | |
public ContentType contentType { get { return m_ContentType; } set { if (SetPropertyUtility.SetStruct(ref m_ContentType, value)) EnforceContentType(); } } | |
public LineType lineType | |
{ | |
get { return m_LineType; } | |
set | |
{ | |
if (SetPropertyUtility.SetStruct(ref m_LineType, value)) | |
{ | |
SetToCustomIfContentTypeIsNot(ContentType.Standard, ContentType.Autocorrected); | |
SetTextComponentWrapMode(); | |
} | |
} | |
} | |
/// <summary> | |
/// Limits the number of lines of text in the Input Field. | |
/// </summary> | |
public int lineLimit | |
{ | |
get { return m_LineLimit; } | |
set | |
{ | |
if (m_LineType == LineType.SingleLine) | |
m_LineLimit = 1; | |
else | |
SetPropertyUtility.SetStruct(ref m_LineLimit, value); | |
} | |
} | |
[SerializeField] | |
protected int m_LineLimit = 0; | |
public InputType inputType { get { return m_InputType; } set { if (SetPropertyUtility.SetStruct(ref m_InputType, value)) SetToCustom(); } } | |
public TouchScreenKeyboardType keyboardType | |
{ | |
get { return m_KeyboardType; } | |
set | |
{ | |
if (SetPropertyUtility.SetStruct(ref m_KeyboardType, value)) | |
SetToCustom(); | |
} | |
} | |
public CharacterValidation characterValidation { get { return m_CharacterValidation; } set { if (SetPropertyUtility.SetStruct(ref m_CharacterValidation, value)) SetToCustom(); } } | |
/// <summary> | |
/// Sets the Input Validation to use a Custom Input Validation script. | |
/// </summary> | |
public TMP_InputValidator inputValidator | |
{ | |
get { return m_InputValidator; } | |
set { if (SetPropertyUtility.SetClass(ref m_InputValidator, value)) SetToCustom(CharacterValidation.CustomValidator); } | |
} | |
[SerializeField] | |
protected TMP_InputValidator m_InputValidator = null; | |
public bool readOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } } | |
public bool richText { get { return m_RichText; } set { m_RichText = value; SetTextComponentRichTextMode(); } } | |
// Derived property | |
public bool multiLine { get { return m_LineType == LineType.MultiLineNewline || lineType == LineType.MultiLineSubmit; } } | |
// Not shown in Inspector. | |
public char asteriskChar { get { return m_AsteriskChar; } set { if (SetPropertyUtility.SetStruct(ref m_AsteriskChar, value)) UpdateLabel(); } } | |
public bool wasCanceled { get { return m_WasCanceled; } } | |
protected void ClampStringPos(ref int pos) | |
{ | |
if (pos < 0) | |
pos = 0; | |
else if (pos > text.Length) | |
pos = text.Length; | |
} | |
protected void ClampCaretPos(ref int pos) | |
{ | |
if (pos < 0) | |
pos = 0; | |
else if (pos > m_TextComponent.textInfo.characterCount - 1) | |
pos = m_TextComponent.textInfo.characterCount - 1; | |
} | |
/// <summary> | |
/// Current position of the cursor. | |
/// Getters are public Setters are protected | |
/// </summary> | |
protected int caretPositionInternal { get { return m_CaretPosition + compositionLength; } set { m_CaretPosition = value; ClampCaretPos(ref m_CaretPosition); } } | |
protected int stringPositionInternal { get { return m_StringPosition + compositionLength; } set { m_StringPosition = value; ClampStringPos(ref m_StringPosition); } } | |
protected int caretSelectPositionInternal { get { return m_CaretSelectPosition + compositionLength; } set { m_CaretSelectPosition = value; ClampCaretPos(ref m_CaretSelectPosition); } } | |
protected int stringSelectPositionInternal { get { return m_StringSelectPosition + compositionLength; } set { m_StringSelectPosition = value; ClampStringPos(ref m_StringSelectPosition); } } | |
private bool hasSelection { get { return stringPositionInternal != stringSelectPositionInternal; } } | |
private bool m_isSelected; | |
private bool m_IsStringPositionDirty; | |
private bool m_IsCaretPositionDirty; | |
private bool m_forceRectTransformAdjustment; | |
/// <summary> | |
/// Get: Returns the focus position as thats the position that moves around even during selection. | |
/// Set: Set both the anchor and focus position such that a selection doesn't happen | |
/// </summary> | |
public int caretPosition | |
{ | |
get { return caretSelectPositionInternal; } | |
set { selectionAnchorPosition = value; selectionFocusPosition = value; m_IsStringPositionDirty = true; } | |
} | |
/// <summary> | |
/// Get: Returns the fixed position of selection | |
/// Set: If compositionString is 0 set the fixed position | |
/// </summary> | |
public int selectionAnchorPosition | |
{ | |
get | |
{ | |
return caretPositionInternal; | |
} | |
set | |
{ | |
if (compositionLength != 0) | |
return; | |
caretPositionInternal = value; | |
m_IsStringPositionDirty = true; | |
} | |
} | |
/// <summary> | |
/// Get: Returns the variable position of selection | |
/// Set: If compositionString is 0 set the variable position | |
/// </summary> | |
public int selectionFocusPosition | |
{ | |
get | |
{ | |
return caretSelectPositionInternal; | |
} | |
set | |
{ | |
if (compositionLength != 0) | |
return; | |
caretSelectPositionInternal = value; | |
m_IsStringPositionDirty = true; | |
} | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
public int stringPosition | |
{ | |
get { return stringSelectPositionInternal; } | |
set { selectionStringAnchorPosition = value; selectionStringFocusPosition = value; m_IsCaretPositionDirty = true; } | |
} | |
/// <summary> | |
/// The fixed position of the selection in the raw string which may contains rich text. | |
/// </summary> | |
public int selectionStringAnchorPosition | |
{ | |
get | |
{ | |
return stringPositionInternal; | |
} | |
set | |
{ | |
if (compositionLength != 0) | |
return; | |
stringPositionInternal = value; | |
m_IsCaretPositionDirty = true; | |
} | |
} | |
/// <summary> | |
/// The variable position of the selection in the raw string which may contains rich text. | |
/// </summary> | |
public int selectionStringFocusPosition | |
{ | |
get | |
{ | |
return stringSelectPositionInternal; | |
} | |
set | |
{ | |
if (compositionLength != 0) | |
return; | |
stringSelectPositionInternal = value; | |
m_IsCaretPositionDirty = true; | |
} | |
} | |
#if UNITY_EDITOR | |
// Remember: This is NOT related to text validation! | |
// This is Unity's own OnValidate method which is invoked when changing values in the Inspector. | |
protected override void OnValidate() | |
{ | |
base.OnValidate(); | |
EnforceContentType(); | |
m_CharacterLimit = Math.Max(0, m_CharacterLimit); | |
//This can be invoked before OnEnabled is called. So we shouldn't be accessing other objects, before OnEnable is called. | |
if (!IsActive()) | |
return; | |
SetTextComponentRichTextMode(); | |
UpdateLabel(); | |
if (m_AllowInput) | |
SetCaretActive(); | |
} | |
#endif // if UNITY_EDITOR | |
protected override void OnEnable() | |
{ | |
//Debug.Log("*** OnEnable() *** - " + this.name); | |
base.OnEnable(); | |
if (m_Text == null) | |
m_Text = string.Empty; | |
// Check if Input Field is driven by any layout components | |
ILayoutController layoutController = GetComponent<ILayoutController>(); | |
if (layoutController != null) | |
{ | |
m_IsDrivenByLayoutComponents = true; | |
m_LayoutGroup = GetComponent<LayoutGroup>(); | |
} | |
else | |
m_IsDrivenByLayoutComponents = false; | |
if (Application.isPlaying) | |
{ | |
if (m_CachedInputRenderer == null && m_TextComponent != null) | |
{ | |
GameObject go = new GameObject("Caret", typeof(TMP_SelectionCaret)); | |
go.hideFlags = HideFlags.DontSave; | |
go.transform.SetParent(m_TextComponent.transform.parent); | |
go.transform.SetAsFirstSibling(); | |
go.layer = gameObject.layer; | |
caretRectTrans = go.GetComponent<RectTransform>(); | |
m_CachedInputRenderer = go.GetComponent<CanvasRenderer>(); | |
m_CachedInputRenderer.SetMaterial(Graphic.defaultGraphicMaterial, Texture2D.whiteTexture); | |
// Needed as if any layout is present we want the caret to always be the same as the text area. | |
go.AddComponent<LayoutElement>().ignoreLayout = true; | |
AssignPositioningIfNeeded(); | |
} | |
} | |
m_RectTransform = GetComponent<RectTransform>(); | |
// Check if parent component has IScrollHandler | |
IScrollHandler[] scrollHandlers = GetComponentsInParent<IScrollHandler>(); | |
if (scrollHandlers.Length > 1) | |
m_IScrollHandlerParent = scrollHandlers[1] as ScrollRect; | |
// Get a reference to the RectMask 2D on the Viewport Text Area object. | |
if (m_TextViewport != null) | |
{ | |
m_TextViewportRectMask = m_TextViewport.GetComponent<RectMask2D>(); | |
UpdateMaskRegions(); | |
} | |
// If we have a cached renderer then we had OnDisable called so just restore the material. | |
if (m_CachedInputRenderer != null) | |
m_CachedInputRenderer.SetMaterial(Graphic.defaultGraphicMaterial, Texture2D.whiteTexture); | |
if (m_TextComponent != null) | |
{ | |
m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty); | |
m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel); | |
// Cache reference to Vertical Scrollbar RectTransform and add listener. | |
if (m_VerticalScrollbar != null) | |
{ | |
m_VerticalScrollbar.onValueChanged.AddListener(OnScrollbarValueChange); | |
} | |
UpdateLabel(); | |
} | |
// Subscribe to event fired when text object has been regenerated. | |
TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED); | |
} | |
protected override void OnDisable() | |
{ | |
// the coroutine will be terminated, so this will ensure it restarts when we are next activated | |
m_BlinkCoroutine = null; | |
DeactivateInputField(); | |
if (m_TextComponent != null) | |
{ | |
m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty); | |
m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel); | |
if (m_VerticalScrollbar != null) | |
m_VerticalScrollbar.onValueChanged.RemoveListener(OnScrollbarValueChange); | |
} | |
CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this); | |
// Clear needs to be called otherwise sync never happens as the object is disabled. | |
if (m_CachedInputRenderer != null) | |
m_CachedInputRenderer.Clear(); | |
if (m_Mesh != null) | |
DestroyImmediate(m_Mesh); | |
m_Mesh = null; | |
// Unsubscribe to event triggered when text object has been regenerated | |
TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED); | |
base.OnDisable(); | |
} | |
/// <summary> | |
/// Method used to update the tracking of the caret position when the text object has been regenerated. | |
/// </summary> | |
/// <param name="obj"></param> | |
private void ON_TEXT_CHANGED(UnityEngine.Object obj) | |
{ | |
bool isThisObject = obj == m_TextComponent; | |
if (isThisObject) | |
{ | |
if (Application.isPlaying && compositionLength == 0) | |
{ | |
caretPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal); | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
if (m_VerticalScrollbar) | |
UpdateScrollbar(); | |
} | |
} | |
IEnumerator CaretBlink() | |
{ | |
// Always ensure caret is initially visible since it can otherwise be confusing for a moment. | |
m_CaretVisible = true; | |
yield return null; | |
while ((isFocused || m_SelectionStillActive) && m_CaretBlinkRate > 0) | |
{ | |
// the blink rate is expressed as a frequency | |
float blinkPeriod = 1f / m_CaretBlinkRate; | |
// the caret should be ON if we are in the first half of the blink period | |
bool blinkState = (Time.unscaledTime - m_BlinkStartTime) % blinkPeriod < blinkPeriod / 2; | |
if (m_CaretVisible != blinkState) | |
{ | |
m_CaretVisible = blinkState; | |
if (!hasSelection) | |
MarkGeometryAsDirty(); | |
} | |
// Then wait again. | |
yield return null; | |
} | |
m_BlinkCoroutine = null; | |
} | |
void SetCaretVisible() | |
{ | |
if (!m_AllowInput) | |
return; | |
m_CaretVisible = true; | |
m_BlinkStartTime = Time.unscaledTime; | |
SetCaretActive(); | |
} | |
// SetCaretActive will not set the caret immediately visible - it will wait for the next time to blink. | |
// However, it will handle things correctly if the blink speed changed from zero to non-zero or non-zero to zero. | |
void SetCaretActive() | |
{ | |
if (!m_AllowInput) | |
return; | |
if (m_CaretBlinkRate > 0.0f) | |
{ | |
if (m_BlinkCoroutine == null) | |
m_BlinkCoroutine = StartCoroutine(CaretBlink()); | |
} | |
else | |
{ | |
m_CaretVisible = true; | |
} | |
} | |
protected void OnFocus() | |
{ | |
if (onFocus != null) | |
{ | |
onFocus.Invoke(text); | |
} | |
if (m_OnFocusSelectAll) | |
SelectAll(); | |
} | |
protected void SelectAll() | |
{ | |
m_isSelectAll = true; | |
stringPositionInternal = text.Length; | |
stringSelectPositionInternal = 0; | |
} | |
/// <summary> | |
/// Move to the end of the text. | |
/// </summary> | |
/// <param name="shift"></param> | |
public void MoveTextEnd(bool shift) | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
int position = text.Length; | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
} | |
else | |
{ | |
stringPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal; | |
} | |
} | |
else | |
{ | |
int position = m_TextComponent.textInfo.characterCount - 1; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(position); | |
} | |
else | |
{ | |
caretPositionInternal = caretSelectPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(position); | |
} | |
} | |
UpdateLabel(); | |
} | |
/// <summary> | |
/// Move to the start of the text. | |
/// </summary> | |
/// <param name="shift"></param> | |
public void MoveTextStart(bool shift) | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
int position = 0; | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
} | |
else | |
{ | |
stringPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal; | |
} | |
} | |
else | |
{ | |
int position = 0; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(position); | |
} | |
else | |
{ | |
caretPositionInternal = caretSelectPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(position); | |
} | |
} | |
UpdateLabel(); | |
} | |
/// <summary> | |
/// Move to the end of the current line of text. | |
/// </summary> | |
/// <param name="shift"></param> | |
public void MoveToEndOfLine(bool shift, bool ctrl) | |
{ | |
// Get the line the caret is currently located on. | |
int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber; | |
// Get the last character of the given line. | |
int characterIndex = ctrl == true ? m_TextComponent.textInfo.characterCount - 1 : m_TextComponent.textInfo.lineInfo[currentLine].lastCharacterIndex; | |
int position = m_TextComponent.textInfo.characterInfo[characterIndex].index; | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
caretSelectPositionInternal = characterIndex; | |
} | |
else | |
{ | |
stringPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal; | |
caretSelectPositionInternal = caretPositionInternal = characterIndex; | |
} | |
UpdateLabel(); | |
} | |
/// <summary> | |
/// Move to the start of the current line of text. | |
/// </summary> | |
/// <param name="shift"></param> | |
public void MoveToStartOfLine(bool shift, bool ctrl) | |
{ | |
// Get the line the caret is currently located on. | |
int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber; | |
// Get the first character of the given line. | |
int characterIndex = ctrl == true ? 0 : m_TextComponent.textInfo.lineInfo[currentLine].firstCharacterIndex; | |
int position = 0; | |
if (characterIndex > 0) | |
position = m_TextComponent.textInfo.characterInfo[characterIndex - 1].index + m_TextComponent.textInfo.characterInfo[characterIndex - 1].stringLength; | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
caretSelectPositionInternal = characterIndex; | |
} | |
else | |
{ | |
stringPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal; | |
caretSelectPositionInternal = caretPositionInternal = characterIndex; | |
} | |
UpdateLabel(); | |
} | |
static string clipboard | |
{ | |
get | |
{ | |
return GUIUtility.systemCopyBuffer; | |
} | |
set | |
{ | |
GUIUtility.systemCopyBuffer = value; | |
} | |
} | |
private bool InPlaceEditing() | |
{ | |
if (Application.platform == RuntimePlatform.WSAPlayerX86 || Application.platform == RuntimePlatform.WSAPlayerX64 || Application.platform == RuntimePlatform.WSAPlayerARM) | |
return !TouchScreenKeyboard.isSupported || m_TouchKeyboardAllowsInPlaceEditing; | |
if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard) | |
return true; | |
if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard == false && shouldHideMobileInput == false) | |
return false; | |
return true; | |
} | |
void UpdateStringPositionFromKeyboard() | |
{ | |
// TODO: Might want to add null check here. | |
var selectionRange = m_SoftKeyboard.selection; | |
//if (selectionRange.start == 0 && selectionRange.length == 0) | |
// return; | |
var selectionStart = selectionRange.start; | |
var selectionEnd = selectionRange.end; | |
var stringPositionChanged = false; | |
if (stringPositionInternal != selectionStart) | |
{ | |
stringPositionChanged = true; | |
stringPositionInternal = selectionStart; | |
caretPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal); | |
} | |
if (stringSelectPositionInternal != selectionEnd) | |
{ | |
stringSelectPositionInternal = selectionEnd; | |
stringPositionChanged = true; | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
} | |
if (stringPositionChanged) | |
{ | |
m_BlinkStartTime = Time.unscaledTime; | |
UpdateLabel(); | |
} | |
} | |
/// <summary> | |
/// Update the text based on input. | |
/// </summary> | |
// TODO: Make LateUpdate a coroutine instead. Allows us to control the update to only be when the field is active. | |
protected virtual void LateUpdate() | |
{ | |
// Only activate if we are not already activated. | |
if (m_ShouldActivateNextUpdate) | |
{ | |
if (!isFocused) | |
{ | |
ActivateInputFieldInternal(); | |
m_ShouldActivateNextUpdate = false; | |
return; | |
} | |
// Reset as we are already activated. | |
m_ShouldActivateNextUpdate = false; | |
} | |
// Handle double click to reset / deselect Input Field when ResetOnActivation is false. | |
if (!isFocused && m_SelectionStillActive) | |
{ | |
GameObject selectedObject = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null; | |
if (selectedObject == null && m_ResetOnDeActivation) | |
{ | |
ReleaseSelection(); | |
return; | |
} | |
if (selectedObject != null && selectedObject != this.gameObject) | |
{ | |
if (selectedObject == m_PreviouslySelectedObject) | |
return; | |
m_PreviouslySelectedObject = selectedObject; | |
// Special handling for Vertical Scrollbar | |
if (m_VerticalScrollbar && selectedObject == m_VerticalScrollbar.gameObject) | |
{ | |
// Do not release selection | |
return; | |
} | |
// Release selection for all objects when ResetOnDeActivation is true | |
if (m_ResetOnDeActivation) | |
{ | |
ReleaseSelection(); | |
return; | |
} | |
// Release current selection of selected object is another Input Field | |
if (selectedObject.GetComponent<TMP_InputField>() != null) | |
ReleaseSelection(); | |
return; | |
} | |
#if ENABLE_INPUT_SYSTEM | |
if (m_ProcessingEvent != null && m_ProcessingEvent.rawType == EventType.MouseDown && m_ProcessingEvent.button == 0) | |
{ | |
// Check for Double Click | |
bool isDoubleClick = false; | |
float timeStamp = Time.unscaledTime; | |
if (m_KeyDownStartTime + m_DoubleClickDelay > timeStamp) | |
isDoubleClick = true; | |
m_KeyDownStartTime = timeStamp; | |
if (isDoubleClick) | |
{ | |
//m_StringPosition = m_StringSelectPosition = 0; | |
//m_CaretPosition = m_CaretSelectPosition = 0; | |
//m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition; | |
//if (caretRectTrans != null) | |
// caretRectTrans.localPosition = Vector3.zero; | |
ReleaseSelection(); | |
return; | |
} | |
} | |
#else | |
if (Input.GetKeyDown(KeyCode.Mouse0)) | |
{ | |
// Check for Double Click | |
bool isDoubleClick = false; | |
float timeStamp = Time.unscaledTime; | |
if (m_KeyDownStartTime + m_DoubleClickDelay > timeStamp) | |
isDoubleClick = true; | |
m_KeyDownStartTime = timeStamp; | |
if (isDoubleClick) | |
{ | |
//m_StringPosition = m_StringSelectPosition = 0; | |
//m_CaretPosition = m_CaretSelectPosition = 0; | |
//m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition; | |
//if (caretRectTrans != null) | |
// caretRectTrans.localPosition = Vector3.zero; | |
ReleaseSelection(); | |
return; | |
} | |
} | |
#endif | |
} | |
UpdateMaskRegions(); | |
if (InPlaceEditing() && isKeyboardUsingEvents() || !isFocused) | |
{ | |
return; | |
} | |
AssignPositioningIfNeeded(); | |
if (m_SoftKeyboard == null || m_SoftKeyboard.status != TouchScreenKeyboard.Status.Visible) | |
{ | |
if (m_SoftKeyboard != null) | |
{ | |
if (!m_ReadOnly) | |
text = m_SoftKeyboard.text; | |
if (m_SoftKeyboard.status == TouchScreenKeyboard.Status.LostFocus) | |
SendTouchScreenKeyboardStatusChanged(); | |
if (m_SoftKeyboard.status == TouchScreenKeyboard.Status.Canceled) | |
{ | |
m_ReleaseSelection = true; | |
m_WasCanceled = true; | |
SendTouchScreenKeyboardStatusChanged(); | |
} | |
if (m_SoftKeyboard.status == TouchScreenKeyboard.Status.Done) | |
{ | |
m_ReleaseSelection = true; | |
OnSubmit(null); | |
SendTouchScreenKeyboardStatusChanged(); | |
} | |
} | |
OnDeselect(null); | |
return; | |
} | |
string val = m_SoftKeyboard.text; | |
if (m_Text != val) | |
{ | |
if (m_ReadOnly) | |
{ | |
m_SoftKeyboard.text = m_Text; | |
} | |
else | |
{ | |
m_Text = ""; | |
for (int i = 0; i < val.Length; ++i) | |
{ | |
char c = val[i]; | |
if (c == '\r' || c == 3) | |
c = '\n'; | |
if (onValidateInput != null) | |
c = onValidateInput(m_Text, m_Text.Length, c); | |
else if (characterValidation != CharacterValidation.None) | |
c = Validate(m_Text, m_Text.Length, c); | |
if (lineType == LineType.MultiLineSubmit && c == '\n') | |
{ | |
m_SoftKeyboard.text = m_Text; | |
OnSubmit(null); | |
OnDeselect(null); | |
return; | |
} | |
if (c != 0) | |
m_Text += c; | |
} | |
if (characterLimit > 0 && m_Text.Length > characterLimit) | |
m_Text = m_Text.Substring(0, characterLimit); | |
UpdateStringPositionFromKeyboard(); | |
// Set keyboard text before updating label, as we might have changed it with validation | |
// and update label will take the old value from keyboard if we don't change it here | |
if (m_Text != val) | |
m_SoftKeyboard.text = m_Text; | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
} | |
else if (m_HideMobileInput && Application.platform == RuntimePlatform.Android) | |
{ | |
UpdateStringPositionFromKeyboard(); | |
} | |
//else if (m_HideMobileInput) // m_Keyboard.canSetSelection | |
//{ | |
// int length = stringPositionInternal < stringSelectPositionInternal ? stringSelectPositionInternal - stringPositionInternal : stringPositionInternal - stringSelectPositionInternal; | |
// m_SoftKeyboard.selection = new RangeInt(stringPositionInternal < stringSelectPositionInternal ? stringPositionInternal : stringSelectPositionInternal, length); | |
//} | |
//else if (!m_HideMobileInput) // m_Keyboard.canGetSelection) | |
//{ | |
// UpdateStringPositionFromKeyboard(); | |
//} | |
if (m_SoftKeyboard != null && m_SoftKeyboard.status != TouchScreenKeyboard.Status.Visible) | |
{ | |
if (m_SoftKeyboard.status == TouchScreenKeyboard.Status.Canceled) | |
m_WasCanceled = true; | |
OnDeselect(null); | |
} | |
} | |
private bool MayDrag(PointerEventData eventData) | |
{ | |
return IsActive() && | |
IsInteractable() && | |
eventData.button == PointerEventData.InputButton.Left && | |
m_TextComponent != null && | |
(m_SoftKeyboard == null || shouldHideSoftKeyboard || shouldHideMobileInput); | |
} | |
public virtual void OnBeginDrag(PointerEventData eventData) | |
{ | |
if (!MayDrag(eventData)) | |
return; | |
m_UpdateDrag = true; | |
} | |
public virtual void OnDrag(PointerEventData eventData) | |
{ | |
if (!MayDrag(eventData)) | |
return; | |
CaretPosition insertionSide; | |
int insertionIndex = TMP_TextUtilities.GetCursorIndexFromPosition(m_TextComponent, eventData.position, eventData.pressEventCamera, out insertionSide); | |
if (m_isRichTextEditingAllowed) | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
else | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringSelectPositionInternal = insertionIndex == 0 | |
? m_TextComponent.textInfo.characterInfo[0].index | |
: m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
MarkGeometryAsDirty(); | |
m_DragPositionOutOfBounds = !RectTransformUtility.RectangleContainsScreenPoint(textViewport, eventData.position, eventData.pressEventCamera); | |
if (m_DragPositionOutOfBounds && m_DragCoroutine == null) | |
m_DragCoroutine = StartCoroutine(MouseDragOutsideRect(eventData)); | |
eventData.Use(); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
IEnumerator MouseDragOutsideRect(PointerEventData eventData) | |
{ | |
while (m_UpdateDrag && m_DragPositionOutOfBounds) | |
{ | |
Vector2 localMousePos; | |
RectTransformUtility.ScreenPointToLocalPointInRectangle(textViewport, eventData.position, eventData.pressEventCamera, out localMousePos); | |
Rect rect = textViewport.rect; | |
if (multiLine) | |
{ | |
if (localMousePos.y > rect.yMax) | |
MoveUp(true, true); | |
else if (localMousePos.y < rect.yMin) | |
MoveDown(true, true); | |
} | |
else | |
{ | |
if (localMousePos.x < rect.xMin) | |
MoveLeft(true, false); | |
else if (localMousePos.x > rect.xMax) | |
MoveRight(true, false); | |
} | |
UpdateLabel(); | |
float delay = multiLine ? kVScrollSpeed : kHScrollSpeed; | |
if (m_WaitForSecondsRealtime == null) | |
m_WaitForSecondsRealtime = new WaitForSecondsRealtime(delay); | |
else | |
m_WaitForSecondsRealtime.waitTime = delay; | |
yield return m_WaitForSecondsRealtime; | |
} | |
m_DragCoroutine = null; | |
} | |
public virtual void OnEndDrag(PointerEventData eventData) | |
{ | |
if (!MayDrag(eventData)) | |
return; | |
m_UpdateDrag = false; | |
} | |
public override void OnPointerDown(PointerEventData eventData) | |
{ | |
if (!MayDrag(eventData)) | |
return; | |
EventSystem.current.SetSelectedGameObject(gameObject, eventData); | |
bool hadFocusBefore = m_AllowInput; | |
base.OnPointerDown(eventData); | |
if (InPlaceEditing() == false) | |
{ | |
if (m_SoftKeyboard == null || !m_SoftKeyboard.active) | |
{ | |
OnSelect(eventData); | |
return; | |
} | |
} | |
#if ENABLE_INPUT_SYSTEM | |
Event.PopEvent(m_ProcessingEvent); | |
bool shift = m_ProcessingEvent != null && (m_ProcessingEvent.modifiers & EventModifiers.Shift) != 0; | |
#else | |
bool shift = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); | |
#endif | |
// Check for Double Click | |
bool isDoubleClick = false; | |
float timeStamp = Time.unscaledTime; | |
if (m_PointerDownClickStartTime + m_DoubleClickDelay > timeStamp) | |
isDoubleClick = true; | |
m_PointerDownClickStartTime = timeStamp; | |
// Only set caret position if we didn't just get focus now. | |
// Otherwise it will overwrite the select all on focus. | |
if (hadFocusBefore || !m_OnFocusSelectAll) | |
{ | |
CaretPosition insertionSide; | |
int insertionIndex = TMP_TextUtilities.GetCursorIndexFromPosition(m_TextComponent, eventData.position, eventData.pressEventCamera, out insertionSide); | |
if (shift) | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
else | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringSelectPositionInternal = insertionIndex == 0 | |
? m_TextComponent.textInfo.characterInfo[0].index | |
: m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
} | |
else | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
else | |
{ | |
if (insertionSide == CaretPosition.Left) | |
{ | |
stringPositionInternal = stringSelectPositionInternal = insertionIndex == 0 | |
? m_TextComponent.textInfo.characterInfo[0].index | |
: m_TextComponent.textInfo.characterInfo[insertionIndex - 1].index + m_TextComponent.textInfo.characterInfo[insertionIndex - 1].stringLength; | |
} | |
else if (insertionSide == CaretPosition.Right) | |
{ | |
stringPositionInternal = stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
} | |
if (isDoubleClick) | |
{ | |
int wordIndex = TMP_TextUtilities.FindIntersectingWord(m_TextComponent, eventData.position, eventData.pressEventCamera); | |
if (wordIndex != -1) | |
{ | |
// TODO: Should behavior be different if rich text editing is enabled or not? | |
// Select current word | |
caretPositionInternal = m_TextComponent.textInfo.wordInfo[wordIndex].firstCharacterIndex; | |
caretSelectPositionInternal = m_TextComponent.textInfo.wordInfo[wordIndex].lastCharacterIndex + 1; | |
stringPositionInternal = m_TextComponent.textInfo.characterInfo[caretPositionInternal].index; | |
stringSelectPositionInternal = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].index + m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].stringLength; | |
} | |
else | |
{ | |
// Select current character | |
caretPositionInternal = insertionIndex; | |
caretSelectPositionInternal = caretPositionInternal + 1; | |
stringPositionInternal = m_TextComponent.textInfo.characterInfo[insertionIndex].index; | |
stringSelectPositionInternal = stringPositionInternal + m_TextComponent.textInfo.characterInfo[insertionIndex].stringLength; | |
} | |
} | |
else | |
{ | |
caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal); | |
} | |
m_isSelectAll = false; | |
} | |
UpdateLabel(); | |
eventData.Use(); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
protected enum EditState | |
{ | |
Continue, | |
Finish | |
} | |
protected EditState KeyPressed(Event evt) | |
{ | |
var currentEventModifiers = evt.modifiers; | |
bool ctrl = SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX ? (currentEventModifiers & EventModifiers.Command) != 0 : (currentEventModifiers & EventModifiers.Control) != 0; | |
bool shift = (currentEventModifiers & EventModifiers.Shift) != 0; | |
bool alt = (currentEventModifiers & EventModifiers.Alt) != 0; | |
bool ctrlOnly = ctrl && !alt && !shift; | |
switch (evt.keyCode) | |
{ | |
case KeyCode.Backspace: | |
{ | |
Backspace(); | |
return EditState.Continue; | |
} | |
case KeyCode.Delete: | |
{ | |
DeleteKey(); | |
return EditState.Continue; | |
} | |
case KeyCode.Home: | |
{ | |
MoveToStartOfLine(shift, ctrl); | |
return EditState.Continue; | |
} | |
case KeyCode.End: | |
{ | |
MoveToEndOfLine(shift, ctrl); | |
return EditState.Continue; | |
} | |
// Select All | |
case KeyCode.A: | |
{ | |
if (ctrlOnly) | |
{ | |
SelectAll(); | |
return EditState.Continue; | |
} | |
break; | |
} | |
// Copy | |
case KeyCode.C: | |
{ | |
if (ctrlOnly) | |
{ | |
if (inputType != InputType.Password) | |
clipboard = GetSelectedString(); | |
else | |
clipboard = ""; | |
return EditState.Continue; | |
} | |
break; | |
} | |
// Paste | |
case KeyCode.V: | |
{ | |
if (ctrlOnly) | |
{ | |
Append(clipboard); | |
return EditState.Continue; | |
} | |
break; | |
} | |
// Cut | |
case KeyCode.X: | |
{ | |
if (ctrlOnly) | |
{ | |
if (inputType != InputType.Password) | |
clipboard = GetSelectedString(); | |
else | |
clipboard = ""; | |
Delete(); | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
return EditState.Continue; | |
} | |
break; | |
} | |
case KeyCode.LeftArrow: | |
{ | |
MoveLeft(shift, ctrl); | |
return EditState.Continue; | |
} | |
case KeyCode.RightArrow: | |
{ | |
MoveRight(shift, ctrl); | |
return EditState.Continue; | |
} | |
case KeyCode.UpArrow: | |
{ | |
MoveUp(shift); | |
return EditState.Continue; | |
} | |
case KeyCode.DownArrow: | |
{ | |
MoveDown(shift); | |
return EditState.Continue; | |
} | |
case KeyCode.PageUp: | |
{ | |
MovePageUp(shift); | |
return EditState.Continue; | |
} | |
case KeyCode.PageDown: | |
{ | |
MovePageDown(shift); | |
return EditState.Continue; | |
} | |
// Submit | |
case KeyCode.Return: | |
case KeyCode.KeypadEnter: | |
{ | |
if (lineType != LineType.MultiLineNewline) | |
{ | |
m_ReleaseSelection = true; | |
return EditState.Finish; | |
} | |
break; | |
} | |
case KeyCode.Escape: | |
{ | |
m_ReleaseSelection = true; | |
m_WasCanceled = true; | |
return EditState.Finish; | |
} | |
} | |
char c = evt.character; | |
// Don't allow return chars or tabulator key to be entered into single line fields. | |
if (!multiLine && (c == '\t' || c == '\r' || c == 10)) | |
return EditState.Continue; | |
// Convert carriage return and end-of-text characters to newline. | |
if (c == '\r' || (int)c == 3) | |
c = '\n'; | |
// Convert Shift Enter to Vertical tab | |
if (shift && c == '\n') | |
c = '\v'; | |
if (IsValidChar(c)) | |
{ | |
Append(c); | |
} | |
if (c == 0) | |
{ | |
if (compositionLength > 0) | |
{ | |
UpdateLabel(); | |
} | |
} | |
return EditState.Continue; | |
} | |
protected virtual bool IsValidChar(char c) | |
{ | |
// Null character | |
if (c == 0) | |
return false; | |
// Delete key on mac | |
if (c == 127) | |
return false; | |
// Accept newline and tab | |
if (c == '\t' || c == '\n') | |
return true; | |
return true; | |
// With the addition of Dynamic support, I think this will best be handled by the text component. | |
//return m_TextComponent.font.HasCharacter(c, true); | |
} | |
/// <summary> | |
/// Handle the specified event. | |
/// </summary> | |
private Event m_ProcessingEvent = new Event(); | |
public void ProcessEvent(Event e) | |
{ | |
KeyPressed(e); | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="eventData"></param> | |
public virtual void OnUpdateSelected(BaseEventData eventData) | |
{ | |
if (!isFocused) | |
return; | |
bool consumedEvent = false; | |
EditState shouldContinue; | |
while (Event.PopEvent(m_ProcessingEvent)) | |
{ | |
//Debug.Log("Event: " + m_ProcessingEvent.ToString() + " IsCompositionActive= " + m_IsCompositionActive + " Composition Length: " + compositionLength); | |
switch (m_ProcessingEvent.rawType) | |
{ | |
case EventType.KeyUp: | |
// TODO: Figure out way to handle navigation during IME Composition. | |
break; | |
case EventType.KeyDown: | |
consumedEvent = true; | |
// Special handling on OSX which produces more events which need to be suppressed. | |
if (m_IsCompositionActive && compositionLength == 0) | |
{ | |
//if (m_ProcessingEvent.keyCode == KeyCode.Backspace && m_ProcessingEvent.modifiers == EventModifiers.None) | |
//{ | |
// int eventCount = Event.GetEventCount(); | |
// // Suppress all subsequent events | |
// for (int i = 0; i < eventCount; i++) | |
// Event.PopEvent(m_ProcessingEvent); | |
// break; | |
//} | |
// Suppress other events related to navigation or termination of composition sequence. | |
if (m_ProcessingEvent.character == 0 && m_ProcessingEvent.modifiers == EventModifiers.None) | |
break; | |
} | |
shouldContinue = KeyPressed(m_ProcessingEvent); | |
if (shouldContinue == EditState.Finish) | |
{ | |
if (!m_WasCanceled) | |
SendOnSubmit(); | |
DeactivateInputField(); | |
break; | |
} | |
m_IsTextComponentUpdateRequired = true; | |
UpdateLabel(); | |
break; | |
case EventType.ValidateCommand: | |
case EventType.ExecuteCommand: | |
switch (m_ProcessingEvent.commandName) | |
{ | |
case "SelectAll": | |
SelectAll(); | |
consumedEvent = true; | |
break; | |
} | |
break; | |
} | |
} | |
if (consumedEvent) | |
UpdateLabel(); | |
eventData.Use(); | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="eventData"></param> | |
public virtual void OnScroll(PointerEventData eventData) | |
{ | |
// Return if Single Line | |
if (m_LineType == LineType.SingleLine) | |
{ | |
if (m_IScrollHandlerParent != null) | |
m_IScrollHandlerParent.OnScroll(eventData); | |
return; | |
} | |
if (m_TextComponent.preferredHeight < m_TextViewport.rect.height) | |
return; | |
float scrollDirection = -eventData.scrollDelta.y; | |
// Determine the current scroll position of the text within the viewport | |
m_ScrollPosition = GetScrollPositionRelativeToViewport(); | |
m_ScrollPosition += (1f / m_TextComponent.textInfo.lineCount) * scrollDirection * m_ScrollSensitivity; | |
m_ScrollPosition = Mathf.Clamp01(m_ScrollPosition); | |
AdjustTextPositionRelativeToViewport(m_ScrollPosition); | |
if (m_VerticalScrollbar) | |
{ | |
m_VerticalScrollbar.value = m_ScrollPosition; | |
} | |
//Debug.Log(GetInstanceID() + "- Scroll Position:" + m_ScrollPosition); | |
} | |
float GetScrollPositionRelativeToViewport() | |
{ | |
// Determine the current scroll position of the text within the viewport | |
Rect viewportRect = m_TextViewport.rect; | |
float scrollPosition = (m_TextComponent.textInfo.lineInfo[0].ascender - viewportRect.yMax + m_TextComponent.rectTransform.anchoredPosition.y) / (m_TextComponent.preferredHeight - viewportRect.height); | |
scrollPosition = (int)((scrollPosition * 1000) + 0.5f) / 1000.0f; | |
return scrollPosition; | |
} | |
private string GetSelectedString() | |
{ | |
if (!hasSelection) | |
return ""; | |
int startPos = stringPositionInternal; | |
int endPos = stringSelectPositionInternal; | |
// Ensure pos is always less then selPos to make the code simpler | |
if (startPos > endPos) | |
{ | |
int temp = startPos; | |
startPos = endPos; | |
endPos = temp; | |
} | |
//for (int i = m_CaretPosition; i < m_CaretSelectPosition; i++) | |
//{ | |
// Debug.Log("Character [" + m_TextComponent.textInfo.characterInfo[i].character + "] using Style [" + m_TextComponent.textInfo.characterInfo[i].style + "] has been selected."); | |
//} | |
return text.Substring(startPos, endPos - startPos); | |
} | |
private int FindNextWordBegin() | |
{ | |
if (stringSelectPositionInternal + 1 >= text.Length) | |
return text.Length; | |
int spaceLoc = text.IndexOfAny(kSeparators, stringSelectPositionInternal + 1); | |
if (spaceLoc == -1) | |
spaceLoc = text.Length; | |
else | |
spaceLoc++; | |
return spaceLoc; | |
} | |
private void MoveRight(bool shift, bool ctrl) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// By convention, if we have a selection and move right without holding shift, | |
// we just place the cursor at the end. | |
stringPositionInternal = stringSelectPositionInternal = Mathf.Max(stringPositionInternal, stringSelectPositionInternal); | |
caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
return; | |
} | |
int position; | |
if (ctrl) | |
position = FindNextWordBegin(); | |
else | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
// Special handling for Surrogate pairs and Diacritical marks. | |
if (stringSelectPositionInternal < text.Length && char.IsHighSurrogate(text[stringSelectPositionInternal])) | |
position = stringSelectPositionInternal + 2; | |
else | |
position = stringSelectPositionInternal + 1; | |
} | |
else | |
{ | |
position = m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal].index + m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal].stringLength; | |
} | |
} | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
} | |
else | |
{ | |
stringSelectPositionInternal = stringPositionInternal = position; | |
// Only increase caret position as we cross character boundary. | |
if (stringPositionInternal >= m_TextComponent.textInfo.characterInfo[caretPositionInternal].index + m_TextComponent.textInfo.characterInfo[caretPositionInternal].stringLength) | |
caretSelectPositionInternal = caretPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private int FindPrevWordBegin() | |
{ | |
if (stringSelectPositionInternal - 2 < 0) | |
return 0; | |
int spaceLoc = text.LastIndexOfAny(kSeparators, stringSelectPositionInternal - 2); | |
if (spaceLoc == -1) | |
spaceLoc = 0; | |
else | |
spaceLoc++; | |
return spaceLoc; | |
} | |
private void MoveLeft(bool shift, bool ctrl) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// By convention, if we have a selection and move left without holding shift, | |
// we just place the cursor at the start. | |
stringPositionInternal = stringSelectPositionInternal = Mathf.Min(stringPositionInternal, stringSelectPositionInternal); | |
caretPositionInternal = caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
return; | |
} | |
int position; | |
if (ctrl) | |
position = FindPrevWordBegin(); | |
else | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
// Special handling for Surrogate pairs and Diacritical marks. | |
if (stringSelectPositionInternal > 0 && char.IsLowSurrogate(text[stringSelectPositionInternal - 1])) | |
position = stringSelectPositionInternal - 2; | |
else | |
position = stringSelectPositionInternal - 1; | |
} | |
else | |
{ | |
position = caretSelectPositionInternal < 1 | |
? m_TextComponent.textInfo.characterInfo[0].index | |
: m_TextComponent.textInfo.characterInfo[caretSelectPositionInternal - 1].index; | |
} | |
} | |
if (shift) | |
{ | |
stringSelectPositionInternal = position; | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
} | |
else | |
{ | |
stringSelectPositionInternal = stringPositionInternal = position; | |
// Only decrease caret position as we cross character boundary. | |
if (caretPositionInternal > 0 && stringPositionInternal <= m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1].index) | |
caretSelectPositionInternal = caretPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private int LineUpCharacterPosition(int originalPos, bool goToFirstChar) | |
{ | |
if (originalPos >= m_TextComponent.textInfo.characterCount) | |
originalPos -= 1; | |
TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos]; | |
int originLine = originChar.lineNumber; | |
// We are on the first line return first character | |
if (originLine - 1 < 0) | |
return goToFirstChar ? 0 : originalPos; | |
int endCharIdx = m_TextComponent.textInfo.lineInfo[originLine].firstCharacterIndex - 1; | |
int closest = -1; | |
float distance = TMP_Math.FLOAT_MAX; | |
float range = 0; | |
for (int i = m_TextComponent.textInfo.lineInfo[originLine - 1].firstCharacterIndex; i < endCharIdx; ++i) | |
{ | |
TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i]; | |
float d = originChar.origin - currentChar.origin; | |
float r = d / (currentChar.xAdvance - currentChar.origin); | |
if (r >= 0 && r <= 1) | |
{ | |
if (r < 0.5f) | |
return i; | |
else | |
return i + 1; | |
} | |
d = Mathf.Abs(d); | |
if (d < distance) | |
{ | |
closest = i; | |
distance = d; | |
range = r; | |
} | |
} | |
if (closest == -1) return endCharIdx; | |
//Debug.Log("Returning nearest character with Range = " + range); | |
if (range < 0.5f) | |
return closest; | |
else | |
return closest + 1; | |
} | |
private int LineDownCharacterPosition(int originalPos, bool goToLastChar) | |
{ | |
if (originalPos >= m_TextComponent.textInfo.characterCount) | |
return m_TextComponent.textInfo.characterCount - 1; // text.Length; | |
TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos]; | |
int originLine = originChar.lineNumber; | |
//// We are on the last line return last character | |
if (originLine + 1 >= m_TextComponent.textInfo.lineCount) | |
return goToLastChar ? m_TextComponent.textInfo.characterCount - 1 : originalPos; | |
// Need to determine end line for next line. | |
int endCharIdx = m_TextComponent.textInfo.lineInfo[originLine + 1].lastCharacterIndex; | |
int closest = -1; | |
float distance = TMP_Math.FLOAT_MAX; | |
float range = 0; | |
for (int i = m_TextComponent.textInfo.lineInfo[originLine + 1].firstCharacterIndex; i < endCharIdx; ++i) | |
{ | |
TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i]; | |
float d = originChar.origin - currentChar.origin; | |
float r = d / (currentChar.xAdvance - currentChar.origin); | |
if (r >= 0 && r <= 1) | |
{ | |
if (r < 0.5f) | |
return i; | |
else | |
return i + 1; | |
} | |
d = Mathf.Abs(d); | |
if (d < distance) | |
{ | |
closest = i; | |
distance = d; | |
range = r; | |
} | |
} | |
if (closest == -1) return endCharIdx; | |
//Debug.Log("Returning nearest character with Range = " + range); | |
if (range < 0.5f) | |
return closest; | |
else | |
return closest + 1; | |
} | |
private int PageUpCharacterPosition(int originalPos, bool goToFirstChar) | |
{ | |
if (originalPos >= m_TextComponent.textInfo.characterCount) | |
originalPos -= 1; | |
TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos]; | |
int originLine = originChar.lineNumber; | |
// We are on the first line return first character | |
if (originLine - 1 < 0) | |
return goToFirstChar ? 0 : originalPos; | |
float viewportHeight = m_TextViewport.rect.height; | |
int newLine = originLine - 1; | |
// Iterate through each subsequent line to find the first baseline that is not visible in the viewport. | |
for (; newLine > 0; newLine--) | |
{ | |
if (m_TextComponent.textInfo.lineInfo[newLine].baseline > m_TextComponent.textInfo.lineInfo[originLine].baseline + viewportHeight) | |
break; | |
} | |
int endCharIdx = m_TextComponent.textInfo.lineInfo[newLine].lastCharacterIndex; | |
int closest = -1; | |
float distance = TMP_Math.FLOAT_MAX; | |
float range = 0; | |
for (int i = m_TextComponent.textInfo.lineInfo[newLine].firstCharacterIndex; i < endCharIdx; ++i) | |
{ | |
TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i]; | |
float d = originChar.origin - currentChar.origin; | |
float r = d / (currentChar.xAdvance - currentChar.origin); | |
if (r >= 0 && r <= 1) | |
{ | |
if (r < 0.5f) | |
return i; | |
else | |
return i + 1; | |
} | |
d = Mathf.Abs(d); | |
if (d < distance) | |
{ | |
closest = i; | |
distance = d; | |
range = r; | |
} | |
} | |
if (closest == -1) return endCharIdx; | |
//Debug.Log("Returning nearest character with Range = " + range); | |
if (range < 0.5f) | |
return closest; | |
else | |
return closest + 1; | |
} | |
private int PageDownCharacterPosition(int originalPos, bool goToLastChar) | |
{ | |
if (originalPos >= m_TextComponent.textInfo.characterCount) | |
return m_TextComponent.textInfo.characterCount - 1; | |
TMP_CharacterInfo originChar = m_TextComponent.textInfo.characterInfo[originalPos]; | |
int originLine = originChar.lineNumber; | |
// We are on the last line return last character | |
if (originLine + 1 >= m_TextComponent.textInfo.lineCount) | |
return goToLastChar ? m_TextComponent.textInfo.characterCount - 1 : originalPos; | |
float viewportHeight = m_TextViewport.rect.height; | |
int newLine = originLine + 1; | |
// Iterate through each subsequent line to find the first baseline that is not visible in the viewport. | |
for (; newLine < m_TextComponent.textInfo.lineCount - 1; newLine++) | |
{ | |
if (m_TextComponent.textInfo.lineInfo[newLine].baseline < m_TextComponent.textInfo.lineInfo[originLine].baseline - viewportHeight) | |
break; | |
} | |
// Need to determine end line for next line. | |
int endCharIdx = m_TextComponent.textInfo.lineInfo[newLine].lastCharacterIndex; | |
int closest = -1; | |
float distance = TMP_Math.FLOAT_MAX; | |
float range = 0; | |
for (int i = m_TextComponent.textInfo.lineInfo[newLine].firstCharacterIndex; i < endCharIdx; ++i) | |
{ | |
TMP_CharacterInfo currentChar = m_TextComponent.textInfo.characterInfo[i]; | |
float d = originChar.origin - currentChar.origin; | |
float r = d / (currentChar.xAdvance - currentChar.origin); | |
if (r >= 0 && r <= 1) | |
{ | |
if (r < 0.5f) | |
return i; | |
else | |
return i + 1; | |
} | |
d = Mathf.Abs(d); | |
if (d < distance) | |
{ | |
closest = i; | |
distance = d; | |
range = r; | |
} | |
} | |
if (closest == -1) return endCharIdx; | |
if (range < 0.5f) | |
return closest; | |
else | |
return closest + 1; | |
} | |
private void MoveDown(bool shift) | |
{ | |
MoveDown(shift, true); | |
} | |
private void MoveDown(bool shift, bool goToLastChar) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// If we have a selection and press down without shift, | |
// set caret to end of selection before we move it down. | |
caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal); | |
} | |
int position = multiLine ? LineDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : m_TextComponent.textInfo.characterCount - 1; // text.Length; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
else | |
{ | |
caretSelectPositionInternal = caretPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private void MoveUp(bool shift) | |
{ | |
MoveUp(shift, true); | |
} | |
private void MoveUp(bool shift, bool goToFirstChar) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// If we have a selection and press up without shift, | |
// set caret position to start of selection before we move it up. | |
caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal); | |
} | |
int position = multiLine ? LineUpCharacterPosition(caretSelectPositionInternal, goToFirstChar) : 0; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
else | |
{ | |
caretSelectPositionInternal = caretPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private void MovePageUp(bool shift) | |
{ | |
MovePageUp(shift, true); | |
} | |
private void MovePageUp(bool shift, bool goToFirstChar) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// If we have a selection and press up without shift, | |
// set caret position to start of selection before we move it up. | |
caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal); | |
} | |
int position = multiLine ? PageUpCharacterPosition(caretSelectPositionInternal, goToFirstChar) : 0; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
else | |
{ | |
caretSelectPositionInternal = caretPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
// Scroll to top of viewport | |
//int currentLine = m_TextComponent.textInfo.characterInfo[position].lineNumber; | |
//float lineAscender = m_TextComponent.textInfo.lineInfo[currentLine].ascender; | |
// Adjust text area up or down if not in single line mode. | |
if (m_LineType != LineType.SingleLine) | |
{ | |
float offset = m_TextViewport.rect.height; // m_TextViewport.rect.yMax - (m_TextComponent.rectTransform.anchoredPosition.y + lineAscender); | |
float topTextBounds = m_TextComponent.rectTransform.position.y + m_TextComponent.textBounds.max.y; | |
float topViewportBounds = m_TextViewport.position.y + m_TextViewport.rect.yMax; | |
offset = topViewportBounds > topTextBounds + offset ? offset : topViewportBounds - topTextBounds; | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, offset); | |
AssignPositioningIfNeeded(); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private void MovePageDown(bool shift) | |
{ | |
MovePageDown(shift, true); | |
} | |
private void MovePageDown(bool shift, bool goToLastChar) | |
{ | |
if (hasSelection && !shift) | |
{ | |
// If we have a selection and press down without shift, | |
// set caret to end of selection before we move it down. | |
caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal); | |
} | |
int position = multiLine ? PageDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : m_TextComponent.textInfo.characterCount - 1; | |
if (shift) | |
{ | |
caretSelectPositionInternal = position; | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
else | |
{ | |
caretSelectPositionInternal = caretPositionInternal = position; | |
stringSelectPositionInternal = stringPositionInternal = GetStringIndexFromCaretPosition(caretSelectPositionInternal); | |
} | |
// Scroll to top of viewport | |
//int currentLine = m_TextComponent.textInfo.characterInfo[position].lineNumber; | |
//float lineAscender = m_TextComponent.textInfo.lineInfo[currentLine].ascender; | |
// Adjust text area up or down if not in single line mode. | |
if (m_LineType != LineType.SingleLine) | |
{ | |
float offset = m_TextViewport.rect.height; // m_TextViewport.rect.yMax - (m_TextComponent.rectTransform.anchoredPosition.y + lineAscender); | |
float bottomTextBounds = m_TextComponent.rectTransform.position.y + m_TextComponent.textBounds.min.y; | |
float bottomViewportBounds = m_TextViewport.position.y + m_TextViewport.rect.yMin; | |
offset = bottomViewportBounds > bottomTextBounds + offset ? offset : bottomViewportBounds - bottomTextBounds; | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, offset); | |
AssignPositioningIfNeeded(); | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private void Delete() | |
{ | |
if (m_ReadOnly) | |
return; | |
if (m_StringPosition == m_StringSelectPosition) | |
return; | |
if (m_isRichTextEditingAllowed || m_isSelectAll) | |
{ | |
// Handling of Delete when Rich Text is allowed. | |
if (m_StringPosition < m_StringSelectPosition) | |
{ | |
m_Text = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition); | |
m_StringSelectPosition = m_StringPosition; | |
} | |
else | |
{ | |
m_Text = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition); | |
m_StringPosition = m_StringSelectPosition; | |
} | |
if (m_isSelectAll) | |
{ | |
m_CaretPosition = m_CaretSelectPosition = 0; | |
m_isSelectAll = false; | |
} | |
} | |
else | |
{ | |
if (m_CaretPosition < m_CaretSelectPosition) | |
{ | |
m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition].index; | |
m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].stringLength; | |
m_Text = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition); | |
m_StringSelectPosition = m_StringPosition; | |
m_CaretSelectPosition = m_CaretPosition; | |
} | |
else | |
{ | |
m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].stringLength; | |
m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition].index; | |
m_Text = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition); | |
m_StringPosition = m_StringSelectPosition; | |
m_CaretPosition = m_CaretSelectPosition; | |
} | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
/// <summary> | |
/// Handling of DEL key | |
/// </summary> | |
private void DeleteKey() | |
{ | |
if (m_ReadOnly) | |
return; | |
if (hasSelection) | |
{ | |
m_isLastKeyBackspace = true; | |
Delete(); | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
else | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
if (stringPositionInternal < text.Length) | |
{ | |
// Special handling for Surrogate Pairs | |
if (char.IsHighSurrogate(text[stringPositionInternal])) | |
m_Text = text.Remove(stringPositionInternal, 2); | |
else | |
m_Text = text.Remove(stringPositionInternal, 1); | |
m_isLastKeyBackspace = true; | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
} | |
else | |
{ | |
if (caretPositionInternal < m_TextComponent.textInfo.characterCount - 1) | |
{ | |
int numberOfCharactersToRemove = m_TextComponent.textInfo.characterInfo[caretPositionInternal].stringLength; | |
// Adjust string position to skip any potential rich text tags. | |
int nextCharacterStringPosition = m_TextComponent.textInfo.characterInfo[caretPositionInternal].index; | |
m_Text = text.Remove(nextCharacterStringPosition, numberOfCharactersToRemove); | |
m_isLastKeyBackspace = true; | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
} | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
/// <summary> | |
/// Handling of Backspace key | |
/// </summary> | |
private void Backspace() | |
{ | |
if (m_ReadOnly) | |
return; | |
if (hasSelection) | |
{ | |
m_isLastKeyBackspace = true; | |
Delete(); | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
else | |
{ | |
if (m_isRichTextEditingAllowed) | |
{ | |
if (stringPositionInternal > 0) | |
{ | |
int numberOfCharactersToRemove = 1; | |
// Special handling for Surrogate pairs and Diacritical marks | |
if (char.IsLowSurrogate(text[stringPositionInternal - 1])) | |
numberOfCharactersToRemove = 2; | |
stringSelectPositionInternal = stringPositionInternal = stringPositionInternal - numberOfCharactersToRemove; | |
m_Text = text.Remove(stringPositionInternal, numberOfCharactersToRemove); | |
caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1; | |
m_isLastKeyBackspace = true; | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
} | |
else | |
{ | |
if (caretPositionInternal > 0) | |
{ | |
int numberOfCharactersToRemove = m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1].stringLength; | |
// Delete the previous character | |
m_Text = text.Remove(m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1].index, numberOfCharactersToRemove); | |
// Get new adjusted string position | |
stringSelectPositionInternal = stringPositionInternal = caretPositionInternal < 1 | |
? m_TextComponent.textInfo.characterInfo[0].index | |
: m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1].index; | |
caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1; | |
} | |
m_isLastKeyBackspace = true; | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChangedAndUpdateLabel(); | |
} | |
} | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
/// <summary> | |
/// Append the specified text to the end of the current. | |
/// </summary> | |
protected virtual void Append(string input) | |
{ | |
if (m_ReadOnly) | |
return; | |
if (InPlaceEditing() == false) | |
return; | |
for (int i = 0, imax = input.Length; i < imax; ++i) | |
{ | |
char c = input[i]; | |
if (c >= ' ' || c == '\t' || c == '\r' || c == 10 || c == '\n') | |
{ | |
Append(c); | |
} | |
} | |
} | |
protected virtual void Append(char input) | |
{ | |
if (m_ReadOnly) | |
return; | |
if (InPlaceEditing() == false) | |
return; | |
// If we have an input validator, validate the input first | |
int insertionPosition = Mathf.Min(stringPositionInternal, stringSelectPositionInternal); | |
//Get the text based on selection for validation instead of whole text(case 1253193). | |
var validateText = text; | |
if (selectionFocusPosition != selectionAnchorPosition) | |
{ | |
if (m_isRichTextEditingAllowed || m_isSelectAll) | |
{ | |
// Handling of Delete when Rich Text is allowed. | |
if (m_StringPosition < m_StringSelectPosition) | |
validateText = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition); | |
else | |
validateText = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition); | |
} | |
else | |
{ | |
if (m_CaretPosition < m_CaretSelectPosition) | |
{ | |
m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition].index; | |
m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition - 1].stringLength; | |
validateText = text.Remove(m_StringPosition, m_StringSelectPosition - m_StringPosition); | |
} | |
else | |
{ | |
m_StringPosition = m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].index + m_TextComponent.textInfo.characterInfo[m_CaretPosition - 1].stringLength; | |
m_StringSelectPosition = m_TextComponent.textInfo.characterInfo[m_CaretSelectPosition].index; | |
validateText = text.Remove(m_StringSelectPosition, m_StringPosition - m_StringSelectPosition); | |
} | |
} | |
} | |
if (onValidateInput != null) | |
{ | |
input = onValidateInput(validateText, insertionPosition, input); | |
} | |
else if (characterValidation == CharacterValidation.CustomValidator) | |
{ | |
input = Validate(validateText, insertionPosition, input); | |
if (input == 0) return; | |
SendOnValueChanged(); | |
UpdateLabel(); | |
return; | |
} | |
else if (characterValidation != CharacterValidation.None) | |
{ | |
input = Validate(validateText, insertionPosition, input); | |
} | |
// If the input is invalid, skip it | |
if (input == 0) | |
return; | |
// Append the character and update the label | |
Insert(input); | |
} | |
// Insert the character and update the label. | |
private void Insert(char c) | |
{ | |
if (m_ReadOnly) | |
return; | |
//Debug.Log("Inserting character " + m_IsCompositionActive); | |
string replaceString = c.ToString(); | |
Delete(); | |
// Can't go past the character limit | |
if (characterLimit > 0 && text.Length >= characterLimit) | |
return; | |
m_Text = text.Insert(m_StringPosition, replaceString); | |
if (!char.IsHighSurrogate(c)) | |
m_CaretSelectPosition = m_CaretPosition += 1; | |
m_StringSelectPosition = m_StringPosition += 1; | |
UpdateTouchKeyboardFromEditChanges(); | |
SendOnValueChanged(); | |
#if TMP_DEBUG_MODE | |
Debug.Log("Caret Position: " + caretPositionInternal + " Selection Position: " + caretSelectPositionInternal + " String Position: " + stringPositionInternal + " String Select Position: " + stringSelectPositionInternal); | |
#endif | |
} | |
private void UpdateTouchKeyboardFromEditChanges() | |
{ | |
// Update the TouchKeyboard's text from edit changes | |
// if in-place editing is allowed | |
if (m_SoftKeyboard != null && InPlaceEditing()) | |
{ | |
m_SoftKeyboard.text = m_Text; | |
} | |
} | |
private void SendOnValueChangedAndUpdateLabel() | |
{ | |
UpdateLabel(); | |
SendOnValueChanged(); | |
} | |
private void SendOnValueChanged() | |
{ | |
if (onValueChanged != null) | |
onValueChanged.Invoke(text); | |
} | |
/// <summary> | |
/// Submit the input field's text. | |
/// </summary> | |
protected void SendOnEndEdit() | |
{ | |
if (onEndEdit != null) | |
onEndEdit.Invoke(m_Text); | |
} | |
protected void SendOnSubmit() | |
{ | |
if (onSubmit != null) | |
onSubmit.Invoke(m_Text); | |
} | |
protected void SendOnFocus() | |
{ | |
if (onSelect != null) | |
onSelect.Invoke(m_Text); | |
} | |
protected void SendOnFocusLost() | |
{ | |
if (onDeselect != null) | |
onDeselect.Invoke(m_Text); | |
} | |
protected void SendOnTextSelection() | |
{ | |
m_isSelected = true; | |
if (onTextSelection != null) | |
onTextSelection.Invoke(m_Text, stringPositionInternal, stringSelectPositionInternal); | |
} | |
protected void SendOnEndTextSelection() | |
{ | |
if (!m_isSelected) return; | |
if (onEndTextSelection != null) | |
onEndTextSelection.Invoke(m_Text, stringPositionInternal, stringSelectPositionInternal); | |
m_isSelected = false; | |
} | |
protected void SendTouchScreenKeyboardStatusChanged() | |
{ | |
if (onTouchScreenKeyboardStatusChanged != null) | |
onTouchScreenKeyboardStatusChanged.Invoke(m_SoftKeyboard.status); | |
} | |
/// <summary> | |
/// Update the visual text Text. | |
/// </summary> | |
protected void UpdateLabel() | |
{ | |
if (m_TextComponent != null && m_TextComponent.font != null && m_PreventCallback == false) | |
{ | |
// Prevent callback from the text component as we assign new text. This is to prevent a recursive call. | |
m_PreventCallback = true; | |
string fullText; | |
if (compositionLength > 0 && m_ReadOnly == false) | |
{ | |
//Input.imeCompositionMode = IMECompositionMode.On; | |
// Handle selections | |
Delete(); | |
if (m_RichText) | |
fullText = text.Substring(0, m_StringPosition) + "<u>" + compositionString + "</u>" + text.Substring(m_StringPosition); | |
else | |
fullText = text.Substring(0, m_StringPosition) + compositionString + text.Substring(m_StringPosition); | |
m_IsCompositionActive = true; | |
//Debug.Log("[" + Time.frameCount + "] Handling IME Input"); | |
} | |
else | |
{ | |
fullText = text; | |
m_IsCompositionActive = false; | |
m_ShouldUpdateIMEWindowPosition = true; | |
} | |
//Debug.Log("Handling IME Input... [" + compositionString + "] of length [" + compositionLength + "] at StringPosition [" + m_StringPosition + "] IsActive [" + m_IsCompositionActive + "]"); | |
string processed; | |
if (inputType == InputType.Password) | |
processed = new string(asteriskChar, fullText.Length); | |
else | |
processed = fullText; | |
bool isEmpty = string.IsNullOrEmpty(fullText); | |
if (m_Placeholder != null) | |
m_Placeholder.enabled = isEmpty; | |
if (!isEmpty && m_ReadOnly == false) | |
{ | |
SetCaretVisible(); | |
} | |
m_TextComponent.text = processed + "\u200B"; // Extra space is added for Caret tracking. | |
// Rebuild layout if using Layout components. | |
if (m_IsDrivenByLayoutComponents) | |
LayoutRebuilder.MarkLayoutForRebuild(m_RectTransform); | |
// Special handling to limit the number of lines of text in the Input Field. | |
if (m_LineLimit > 0) | |
{ | |
m_TextComponent.ForceMeshUpdate(); | |
TMP_TextInfo textInfo = m_TextComponent.textInfo; | |
// Check if text exceeds maximum number of lines. | |
if (textInfo != null && textInfo.lineCount > m_LineLimit) | |
{ | |
int lastValidCharacterIndex = textInfo.lineInfo[m_LineLimit - 1].lastCharacterIndex; | |
int characterStringIndex = textInfo.characterInfo[lastValidCharacterIndex].index + textInfo.characterInfo[lastValidCharacterIndex].stringLength; | |
text = processed.Remove(characterStringIndex, processed.Length - characterStringIndex); | |
m_TextComponent.text = text + "\u200B"; | |
} | |
} | |
if (m_IsTextComponentUpdateRequired || m_VerticalScrollbar) | |
{ | |
m_IsTextComponentUpdateRequired = false; | |
m_TextComponent.ForceMeshUpdate(); | |
} | |
MarkGeometryAsDirty(); | |
m_PreventCallback = false; | |
} | |
} | |
void UpdateScrollbar() | |
{ | |
// Update Scrollbar | |
if (m_VerticalScrollbar) | |
{ | |
Rect viewportRect = m_TextViewport.rect; | |
float size = viewportRect.height / m_TextComponent.preferredHeight; | |
m_VerticalScrollbar.size = size; | |
m_VerticalScrollbar.value = GetScrollPositionRelativeToViewport(); | |
//Debug.Log(GetInstanceID() + "- UpdateScrollbar() - Updating Scrollbar... Value: " + m_VerticalScrollbar.value); | |
} | |
} | |
/// <summary> | |
/// Function to update the vertical position of the text container when OnValueChanged event is received from the Scrollbar. | |
/// </summary> | |
/// <param name="value"></param> | |
void OnScrollbarValueChange(float value) | |
{ | |
//if (m_IsUpdatingScrollbarValues) | |
//{ | |
// m_IsUpdatingScrollbarValues = false; | |
// return; | |
//} | |
if (value < 0 || value > 1) return; | |
AdjustTextPositionRelativeToViewport(value); | |
m_ScrollPosition = value; | |
//Debug.Log(GetInstanceID() + "- OnScrollbarValueChange() - Scrollbar value is: " + value + " Transform POS: " + m_TextComponent.rectTransform.anchoredPosition); | |
} | |
void UpdateMaskRegions() | |
{ | |
// TODO: Figure out a better way to handle adding an offset to the masking region | |
// This region is defined by the RectTransform of the GameObject that contains the RectMask2D component. | |
/* | |
// Update Masking Region | |
if (m_TextViewportRectMask != null) | |
{ | |
Rect viewportRect = m_TextViewportRectMask.canvasRect; | |
if (viewportRect != m_CachedViewportRect) | |
{ | |
m_CachedViewportRect = viewportRect; | |
viewportRect.min -= m_TextViewport.offsetMin * 0.5f; | |
viewportRect.max -= m_TextViewport.offsetMax * 0.5f; | |
if (m_CachedInputRenderer != null) | |
m_CachedInputRenderer.EnableRectClipping(viewportRect); | |
if (m_TextComponent.canvasRenderer != null) | |
m_TextComponent.canvasRenderer.EnableRectClipping(viewportRect); | |
if (m_Placeholder != null && m_Placeholder.enabled) | |
m_Placeholder.canvasRenderer.EnableRectClipping(viewportRect); | |
} | |
} | |
*/ | |
} | |
/// <summary> | |
/// Adjusts the relative position of the body of the text relative to the viewport. | |
/// </summary> | |
/// <param name="relativePosition"></param> | |
void AdjustTextPositionRelativeToViewport(float relativePosition) | |
{ | |
if (m_TextViewport == null) | |
return; | |
TMP_TextInfo textInfo = m_TextComponent.textInfo; | |
// Check to make sure we have valid data and lines to query. | |
if (textInfo == null || textInfo.lineInfo == null || textInfo.lineCount == 0 || textInfo.lineCount > textInfo.lineInfo.Length) return; | |
float verticalAlignmentOffset = 0; | |
float textHeight = m_TextComponent.preferredHeight; | |
switch (m_TextComponent.verticalAlignment) | |
{ | |
case VerticalAlignmentOptions.Top: | |
verticalAlignmentOffset = 0; | |
break; | |
case VerticalAlignmentOptions.Middle: | |
verticalAlignmentOffset = 0.5f; | |
break; | |
case VerticalAlignmentOptions.Bottom: | |
verticalAlignmentOffset = 1.0f; | |
break; | |
case VerticalAlignmentOptions.Baseline: | |
break; | |
case VerticalAlignmentOptions.Geometry: | |
verticalAlignmentOffset = 0.5f; | |
textHeight = m_TextComponent.bounds.size.y; | |
break; | |
case VerticalAlignmentOptions.Capline: | |
verticalAlignmentOffset = 0.5f; | |
break; | |
} | |
m_TextComponent.rectTransform.anchoredPosition = new Vector2(m_TextComponent.rectTransform.anchoredPosition.x, (textHeight - m_TextViewport.rect.height) * (relativePosition - verticalAlignmentOffset)); | |
AssignPositioningIfNeeded(); | |
//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")); | |
} | |
private int GetCaretPositionFromStringIndex(int stringIndex) | |
{ | |
int count = m_TextComponent.textInfo.characterCount; | |
for (int i = 0; i < count; i++) | |
{ | |
if (m_TextComponent.textInfo.characterInfo[i].index >= stringIndex) | |
return i; | |
} | |
return count; | |
} | |
/// <summary> | |
/// Returns / places the caret before the given character at the string index. | |
/// </summary> | |
/// <param name="stringIndex"></param> | |
/// <returns></returns> | |
private int GetMinCaretPositionFromStringIndex(int stringIndex) | |
{ | |
int count = m_TextComponent.textInfo.characterCount; | |
for (int i = 0; i < count; i++) | |
{ | |
if (stringIndex < m_TextComponent.textInfo.characterInfo[i].index + m_TextComponent.textInfo.characterInfo[i].stringLength) | |
return i; | |
} | |
return count; | |
} | |
/// <summary> | |
/// Returns / places the caret after the given character at the string index. | |
/// </summary> | |
/// <param name="stringIndex"></param> | |
/// <returns></returns> | |
private int GetMaxCaretPositionFromStringIndex(int stringIndex) | |
{ | |
int count = m_TextComponent.textInfo.characterCount; | |
for (int i = 0; i < count; i++) | |
{ | |
if (m_TextComponent.textInfo.characterInfo[i].index >= stringIndex) | |
return i; | |
} | |
return count; | |
} | |
private int GetStringIndexFromCaretPosition(int caretPosition) | |
{ | |
// Clamp values between 0 and character count. | |
ClampCaretPos(ref caretPosition); | |
return m_TextComponent.textInfo.characterInfo[caretPosition].index; | |
} | |
public void ForceLabelUpdate() | |
{ | |
UpdateLabel(); | |
} | |
private void MarkGeometryAsDirty() | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying || UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this)) | |
return; | |
#endif | |
CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); | |
} | |
public virtual void Rebuild(CanvasUpdate update) | |
{ | |
switch (update) | |
{ | |
case CanvasUpdate.LatePreRender: | |
UpdateGeometry(); | |
break; | |
} | |
} | |
public virtual void LayoutComplete() | |
{ } | |
public virtual void GraphicUpdateComplete() | |
{ } | |
private void UpdateGeometry() | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return; | |
#endif | |
// No need to draw a cursor on mobile as its handled by the devices keyboard. | |
if (InPlaceEditing() == false) | |
return; | |
if (m_CachedInputRenderer == null) | |
return; | |
OnFillVBO(mesh); | |
m_CachedInputRenderer.SetMesh(mesh); | |
} | |
/// <summary> | |
/// Method to keep the Caret RectTransform properties in sync with the text object's RectTransform | |
/// </summary> | |
private void AssignPositioningIfNeeded() | |
{ | |
if (m_TextComponent != null && caretRectTrans != null && | |
(caretRectTrans.localPosition != m_TextComponent.rectTransform.localPosition || | |
caretRectTrans.localRotation != m_TextComponent.rectTransform.localRotation || | |
caretRectTrans.localScale != m_TextComponent.rectTransform.localScale || | |
caretRectTrans.anchorMin != m_TextComponent.rectTransform.anchorMin || | |
caretRectTrans.anchorMax != m_TextComponent.rectTransform.anchorMax || | |
caretRectTrans.anchoredPosition != m_TextComponent.rectTransform.anchoredPosition || | |
caretRectTrans.sizeDelta != m_TextComponent.rectTransform.sizeDelta || | |
caretRectTrans.pivot != m_TextComponent.rectTransform.pivot)) | |
{ | |
caretRectTrans.localPosition = m_TextComponent.rectTransform.localPosition; | |
caretRectTrans.localRotation = m_TextComponent.rectTransform.localRotation; | |
caretRectTrans.localScale = m_TextComponent.rectTransform.localScale; | |
caretRectTrans.anchorMin = m_TextComponent.rectTransform.anchorMin; | |
caretRectTrans.anchorMax = m_TextComponent.rectTransform.anchorMax; | |
caretRectTrans.anchoredPosition = m_TextComponent.rectTransform.anchoredPosition; | |
caretRectTrans.sizeDelta = m_TextComponent.rectTransform.sizeDelta; | |
caretRectTrans.pivot = m_TextComponent.rectTransform.pivot; | |
} | |
} | |
private void OnFillVBO(Mesh vbo) | |
{ | |
using (var helper = new VertexHelper()) | |
{ | |
if (!isFocused && !m_SelectionStillActive) | |
{ | |
helper.FillMesh(vbo); | |
return; | |
} | |
if (m_IsStringPositionDirty) | |
{ | |
stringPositionInternal = GetStringIndexFromCaretPosition(m_CaretPosition); | |
stringSelectPositionInternal = GetStringIndexFromCaretPosition(m_CaretSelectPosition); | |
m_IsStringPositionDirty = false; | |
} | |
if (m_IsCaretPositionDirty) | |
{ | |
caretPositionInternal = GetCaretPositionFromStringIndex(stringPositionInternal); | |
caretSelectPositionInternal = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
m_IsCaretPositionDirty = false; | |
} | |
if (!hasSelection) | |
{ | |
GenerateCaret(helper, Vector2.zero); | |
SendOnEndTextSelection(); | |
} | |
else | |
{ | |
GenerateHightlight(helper, Vector2.zero); | |
SendOnTextSelection(); | |
} | |
helper.FillMesh(vbo); | |
} | |
} | |
private void GenerateCaret(VertexHelper vbo, Vector2 roundingOffset) | |
{ | |
if (m_CaretVisible == false || m_TextComponent.canvas == null || m_ReadOnly) | |
return; | |
if (m_CursorVerts == null) | |
{ | |
CreateCursorVerts(); | |
} | |
float width = m_CaretWidth; | |
// TODO: Optimize to only update the caret position when needed. | |
Vector2 startPosition = Vector2.zero; | |
float height = 0; | |
TMP_CharacterInfo currentCharacter; | |
// Make sure caret position does not exceed characterInfo array size. | |
if (caretPositionInternal >= m_TextComponent.textInfo.characterInfo.Length) | |
return; | |
int currentLine = m_TextComponent.textInfo.characterInfo[caretPositionInternal].lineNumber; | |
// Caret is positioned at the origin for the first character of each lines and at the advance for subsequent characters. | |
if (caretPositionInternal == m_TextComponent.textInfo.lineInfo[currentLine].firstCharacterIndex) | |
{ | |
currentCharacter = m_TextComponent.textInfo.characterInfo[caretPositionInternal]; | |
height = currentCharacter.ascender - currentCharacter.descender; | |
if (m_TextComponent.verticalAlignment == VerticalAlignmentOptions.Geometry) | |
startPosition = new Vector2(currentCharacter.origin, 0 - height / 2); | |
else | |
startPosition = new Vector2(currentCharacter.origin, currentCharacter.descender); | |
} | |
else | |
{ | |
currentCharacter = m_TextComponent.textInfo.characterInfo[caretPositionInternal - 1]; | |
height = currentCharacter.ascender - currentCharacter.descender; | |
if (m_TextComponent.verticalAlignment == VerticalAlignmentOptions.Geometry) | |
startPosition = new Vector2(currentCharacter.xAdvance, 0 - height / 2); | |
else | |
startPosition = new Vector2(currentCharacter.xAdvance, currentCharacter.descender); | |
} | |
if (m_SoftKeyboard != null) | |
{ | |
int selectionStart = m_StringPosition; | |
int softKeyboardStringLength = m_SoftKeyboard.text == null ? 0 : m_SoftKeyboard.text.Length; | |
if (selectionStart < 0) | |
selectionStart = 0; | |
if (selectionStart > softKeyboardStringLength) | |
selectionStart = softKeyboardStringLength; | |
m_SoftKeyboard.selection = new RangeInt(selectionStart, 0); | |
} | |
// Adjust the position of the RectTransform based on the caret position in the viewport (only if we have focus). | |
if (isFocused && startPosition != m_LastPosition || m_forceRectTransformAdjustment || m_isLastKeyBackspace) | |
AdjustRectTransformRelativeToViewport(startPosition, height, currentCharacter.isVisible); | |
m_LastPosition = startPosition; | |
// Clamp Caret height | |
float top = startPosition.y + height; | |
float bottom = top - height; | |
// Minor tweak to address caret potentially being too thin based on canvas scaler values. | |
float scale = m_TextComponent.canvas.scaleFactor; | |
m_CursorVerts[0].position = new Vector3(startPosition.x, bottom, 0.0f); | |
m_CursorVerts[1].position = new Vector3(startPosition.x, top, 0.0f); | |
m_CursorVerts[2].position = new Vector3(startPosition.x + width, top, 0.0f); | |
m_CursorVerts[3].position = new Vector3(startPosition.x + width, bottom, 0.0f); | |
// Set Vertex Color for the caret color. | |
m_CursorVerts[0].color = caretColor; | |
m_CursorVerts[1].color = caretColor; | |
m_CursorVerts[2].color = caretColor; | |
m_CursorVerts[3].color = caretColor; | |
vbo.AddUIVertexQuad(m_CursorVerts); | |
// Update position of IME window when necessary. | |
if (m_ShouldUpdateIMEWindowPosition || currentLine != m_PreviousIMEInsertionLine) | |
{ | |
m_ShouldUpdateIMEWindowPosition = false; | |
m_PreviousIMEInsertionLine = currentLine; | |
// Calculate position of IME Window in screen space. | |
Camera cameraRef; | |
if (m_TextComponent.canvas.renderMode == RenderMode.ScreenSpaceOverlay) | |
cameraRef = null; | |
else | |
{ | |
cameraRef = m_TextComponent.canvas.worldCamera; | |
if (cameraRef == null) | |
cameraRef = Camera.current; | |
} | |
Vector3 cursorPosition = m_CachedInputRenderer.gameObject.transform.TransformPoint(m_CursorVerts[0].position); | |
Vector2 screenPosition = RectTransformUtility.WorldToScreenPoint(cameraRef, cursorPosition); | |
screenPosition.y = Screen.height - screenPosition.y; | |
if (inputSystem != null) | |
inputSystem.compositionCursorPos = screenPosition; | |
//Debug.Log("[" + Time.frameCount + "] Updating IME Window position Cursor Pos: (" + cursorPosition + ") Screen Pos: (" + screenPosition + ") with Composition Length: " + compositionLength); | |
} | |
//#if TMP_DEBUG_MODE | |
//Debug.Log("Caret position updated at frame: " + Time.frameCount); | |
//#endif | |
} | |
private void CreateCursorVerts() | |
{ | |
m_CursorVerts = new UIVertex[4]; | |
for (int i = 0; i < m_CursorVerts.Length; i++) | |
{ | |
m_CursorVerts[i] = UIVertex.simpleVert; | |
m_CursorVerts[i].uv0 = Vector2.zero; | |
} | |
} | |
private void GenerateHightlight(VertexHelper vbo, Vector2 roundingOffset) | |
{ | |
// Update Masking Region | |
UpdateMaskRegions(); | |
// Make sure caret position does not exceed characterInfo array size. | |
//if (caretSelectPositionInternal >= m_TextComponent.textInfo.characterInfo.Length) | |
// return; | |
TMP_TextInfo textInfo = m_TextComponent.textInfo; | |
m_CaretPosition = GetCaretPositionFromStringIndex(stringPositionInternal); | |
m_CaretSelectPosition = GetCaretPositionFromStringIndex(stringSelectPositionInternal); | |
if (m_SoftKeyboard != null) | |
{ | |
int stringPosition = m_CaretPosition < m_CaretSelectPosition ? textInfo.characterInfo[m_CaretPosition].index : textInfo.characterInfo[m_CaretSelectPosition].index; | |
int length = m_CaretPosition < m_CaretSelectPosition ? stringSelectPositionInternal - stringPosition : stringPositionInternal - stringPosition; | |
m_SoftKeyboard.selection = new RangeInt(stringPosition, length); | |
} | |
// Adjust text RectTranform position to make sure it is visible in viewport. | |
Vector2 caretPosition; | |
float height = 0; | |
if (m_CaretSelectPosition < textInfo.characterCount) | |
{ | |
caretPosition = new Vector2(textInfo.characterInfo[m_CaretSelectPosition].origin, textInfo.characterInfo[m_CaretSelectPosition].descender); | |
height = textInfo.characterInfo[m_CaretSelectPosition].ascender - textInfo.characterInfo[m_CaretSelectPosition].descender; | |
} | |
else | |
{ | |
caretPosition = new Vector2(textInfo.characterInfo[m_CaretSelectPosition - 1].xAdvance, textInfo.characterInfo[m_CaretSelectPosition - 1].descender); | |
height = textInfo.characterInfo[m_CaretSelectPosition - 1].ascender - textInfo.characterInfo[m_CaretSelectPosition - 1].descender; | |
} | |
// TODO: Don't adjust the position of the RectTransform if Reset On Deactivation is disabled | |
// and we just selected the Input Field again. | |
AdjustRectTransformRelativeToViewport(caretPosition, height, true); | |
int startChar = Mathf.Max(0, m_CaretPosition); | |
int endChar = Mathf.Max(0, m_CaretSelectPosition); | |
// Ensure pos is always less then selPos to make the code simpler | |
if (startChar > endChar) | |
{ | |
int temp = startChar; | |
startChar = endChar; | |
endChar = temp; | |
} | |
endChar -= 1; | |
//Debug.Log("Updating Highlight... Caret Position: " + startChar + " Caret Select POS: " + endChar); | |
int currentLineIndex = textInfo.characterInfo[startChar].lineNumber; | |
int nextLineStartIdx = textInfo.lineInfo[currentLineIndex].lastCharacterIndex; | |
UIVertex vert = UIVertex.simpleVert; | |
vert.uv0 = Vector2.zero; | |
vert.color = selectionColor; | |
int currentChar = startChar; | |
while (currentChar <= endChar && currentChar < textInfo.characterCount) | |
{ | |
if (currentChar == nextLineStartIdx || currentChar == endChar) | |
{ | |
TMP_CharacterInfo startCharInfo = textInfo.characterInfo[startChar]; | |
TMP_CharacterInfo endCharInfo = textInfo.characterInfo[currentChar]; | |
// Extra check to handle Carriage Return | |
if (currentChar > 0 && endCharInfo.character == 10 && textInfo.characterInfo[currentChar - 1].character == 13) | |
endCharInfo = textInfo.characterInfo[currentChar - 1]; | |
Vector2 startPosition = new Vector2(startCharInfo.origin, textInfo.lineInfo[currentLineIndex].ascender); | |
Vector2 endPosition = new Vector2(endCharInfo.xAdvance, textInfo.lineInfo[currentLineIndex].descender); | |
var startIndex = vbo.currentVertCount; | |
vert.position = new Vector3(startPosition.x, endPosition.y, 0.0f); | |
vbo.AddVert(vert); | |
vert.position = new Vector3(endPosition.x, endPosition.y, 0.0f); | |
vbo.AddVert(vert); | |
vert.position = new Vector3(endPosition.x, startPosition.y, 0.0f); | |
vbo.AddVert(vert); | |
vert.position = new Vector3(startPosition.x, startPosition.y, 0.0f); | |
vbo.AddVert(vert); | |
vbo.AddTriangle(startIndex, startIndex + 1, startIndex + 2); | |
vbo.AddTriangle(startIndex + 2, startIndex + 3, startIndex + 0); | |
startChar = currentChar + 1; | |
currentLineIndex++; | |
if (currentLineIndex < textInfo.lineCount) | |
nextLineStartIdx = textInfo.lineInfo[currentLineIndex].lastCharacterIndex; | |
} | |
currentChar++; | |
} | |
//#if TMP_DEBUG_MODE | |
// Debug.Log("Text selection updated at frame: " + Time.frameCount); | |
//#endif | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="startPosition"></param> | |
/// <param name="height"></param> | |
/// <param name="isCharVisible"></param> | |
private void AdjustRectTransformRelativeToViewport(Vector2 startPosition, float height, bool isCharVisible) | |
{ | |
//Debug.Log("Adjusting transform position relative to viewport."); | |
if (m_TextViewport == null) | |
return; | |
Vector3 localPosition = transform.localPosition; | |
Vector3 textComponentLocalPosition = m_TextComponent.rectTransform.localPosition; | |
Vector3 textViewportLocalPosition = m_TextViewport.localPosition; | |
Rect textViewportRect = m_TextViewport.rect; | |
Vector2 caretPosition = new Vector2(startPosition.x + textComponentLocalPosition.x + textViewportLocalPosition.x + localPosition.x, startPosition.y + textComponentLocalPosition.y + textViewportLocalPosition.y + localPosition.y); | |
Rect viewportWSRect = new Rect(localPosition.x + textViewportLocalPosition.x + textViewportRect.x, localPosition.y + textViewportLocalPosition.y + textViewportRect.y, textViewportRect.width, textViewportRect.height); | |
// Adjust the position of the RectTransform based on the caret position in the viewport. | |
float rightOffset = viewportWSRect.xMax - (caretPosition.x + m_TextComponent.margin.z + m_CaretWidth); | |
if (rightOffset < 0f) | |
{ | |
if (!multiLine || (multiLine && isCharVisible)) | |
{ | |
//Debug.Log("Shifting text to the LEFT by " + rightOffset.ToString("f3")); | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(rightOffset, 0); | |
AssignPositioningIfNeeded(); | |
} | |
} | |
float leftOffset = (caretPosition.x - m_TextComponent.margin.x) - viewportWSRect.xMin; | |
if (leftOffset < 0f) | |
{ | |
//Debug.Log("Shifting text to the RIGHT by " + leftOffset.ToString("f3")); | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(-leftOffset, 0); | |
AssignPositioningIfNeeded(); | |
} | |
// Adjust text area up or down if not in single line mode. | |
if (m_LineType != LineType.SingleLine) | |
{ | |
float topOffset = viewportWSRect.yMax - (caretPosition.y + height); | |
if (topOffset < -0.0001f) | |
{ | |
//Debug.Log("Shifting text to Up " + topOffset.ToString("f3")); | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(0, topOffset); | |
AssignPositioningIfNeeded(); | |
} | |
float bottomOffset = caretPosition.y - viewportWSRect.yMin; | |
if (bottomOffset < 0f) | |
{ | |
//Debug.Log("Shifting text to Down " + bottomOffset.ToString("f3")); | |
m_TextComponent.rectTransform.anchoredPosition -= new Vector2(0, bottomOffset); | |
AssignPositioningIfNeeded(); | |
} | |
} | |
// Special handling of backspace | |
if (m_isLastKeyBackspace) | |
{ | |
float anchoredPositionX = m_TextComponent.rectTransform.anchoredPosition.x; | |
float firstCharPosition = localPosition.x + textViewportLocalPosition.x + textComponentLocalPosition.x + m_TextComponent.textInfo.characterInfo[0].origin - m_TextComponent.margin.x; | |
float lastCharPosition = localPosition.x + textViewportLocalPosition.x + textComponentLocalPosition.x + m_TextComponent.textInfo.characterInfo[m_TextComponent.textInfo.characterCount - 1].origin + m_TextComponent.margin.z + m_CaretWidth; | |
if (anchoredPositionX > 0.0001f && firstCharPosition > viewportWSRect.xMin) | |
{ | |
float offset = viewportWSRect.xMin - firstCharPosition; | |
if (anchoredPositionX < -offset) | |
offset = -anchoredPositionX; | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(offset, 0); | |
AssignPositioningIfNeeded(); | |
} | |
else if (anchoredPositionX < -0.0001f && lastCharPosition < viewportWSRect.xMax) | |
{ | |
float offset = viewportWSRect.xMax - lastCharPosition; | |
if (-anchoredPositionX < offset) | |
offset = -anchoredPositionX; | |
m_TextComponent.rectTransform.anchoredPosition += new Vector2(offset, 0); | |
AssignPositioningIfNeeded(); | |
} | |
m_isLastKeyBackspace = false; | |
} | |
m_forceRectTransformAdjustment = false; | |
} | |
/// <summary> | |
/// Validate the specified input. | |
/// </summary> | |
protected char Validate(string text, int pos, char ch) | |
{ | |
// Validation is disabled | |
if (characterValidation == CharacterValidation.None || !enabled) | |
return ch; | |
if (characterValidation == CharacterValidation.Integer || characterValidation == CharacterValidation.Decimal) | |
{ | |
// Integer and decimal | |
bool cursorBeforeDash = (pos == 0 && text.Length > 0 && text[0] == '-'); | |
bool selectionAtStart = stringPositionInternal == 0 || stringSelectPositionInternal == 0; | |
if (!cursorBeforeDash) | |
{ | |
if (ch >= '0' && ch <= '9') return ch; | |
if (ch == '-' && (pos == 0 || selectionAtStart)) return ch; | |
var separator = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator; | |
if (ch == Convert.ToChar(separator) && characterValidation == CharacterValidation.Decimal && !text.Contains(separator)) return ch; | |
} | |
} | |
else if (characterValidation == CharacterValidation.Digit) | |
{ | |
if (ch >= '0' && ch <= '9') return ch; | |
} | |
else if (characterValidation == CharacterValidation.Alphanumeric) | |
{ | |
// All alphanumeric characters | |
if (ch >= 'A' && ch <= 'Z') return ch; | |
if (ch >= 'a' && ch <= 'z') return ch; | |
if (ch >= '0' && ch <= '9') return ch; | |
} | |
else if (characterValidation == CharacterValidation.Name) | |
{ | |
char prevChar = (text.Length > 0) ? text[Mathf.Clamp(pos - 1, 0, text.Length - 1)] : ' '; | |
char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' '; | |
char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n'; | |
if (char.IsLetter(ch)) | |
{ | |
// First letter is always capitalized | |
if (char.IsLower(ch) && pos == 0) | |
return char.ToUpper(ch); | |
// Letter following a space or hyphen is always capitalized | |
if (char.IsLower(ch) && (prevChar == ' ' || prevChar == '-')) | |
return char.ToUpper(ch); | |
// Uppercase letters are only allowed after spaces, apostrophes, hyphens or lowercase letter | |
if (char.IsUpper(ch) && pos > 0 && prevChar != ' ' && prevChar != '\'' && prevChar != '-' && !char.IsLower(prevChar)) | |
return char.ToLower(ch); | |
// Do not allow uppercase characters to be inserted before another uppercase character | |
if (char.IsUpper(ch) && char.IsUpper(lastChar)) | |
return (char)0; | |
// If character was already in correct case, return it as-is. | |
// Also, letters that are neither upper nor lower case are always allowed. | |
return ch; | |
} | |
else if (ch == '\'') | |
{ | |
// Don't allow more than one apostrophe | |
if (lastChar != ' ' && lastChar != '\'' && nextChar != '\'' && !text.Contains("'")) | |
return ch; | |
} | |
// Allow inserting a hyphen after a character | |
if (char.IsLetter(prevChar) && ch == '-' && lastChar != '-') | |
{ | |
return ch; | |
} | |
if ((ch == ' ' || ch == '-') && pos != 0) | |
{ | |
// Don't allow more than one space in a row | |
if (prevChar != ' ' && prevChar != '\'' && prevChar != '-' && | |
lastChar != ' ' && lastChar != '\'' && lastChar != '-' && | |
nextChar != ' ' && nextChar != '\'' && nextChar != '-') | |
return ch; | |
} | |
} | |
else if (characterValidation == CharacterValidation.EmailAddress) | |
{ | |
// From StackOverflow about allowed characters in email addresses: | |
// Uppercase and lowercase English letters (a-z, A-Z) | |
// Digits 0 to 9 | |
// Characters ! # $ % & ' * + - / = ? ^ _ ` { | } ~ | |
// Character . (dot, period, full stop) provided that it is not the first or last character, | |
// and provided also that it does not appear two or more times consecutively. | |
if (ch >= 'A' && ch <= 'Z') return ch; | |
if (ch >= 'a' && ch <= 'z') return ch; | |
if (ch >= '0' && ch <= '9') return ch; | |
if (ch == '@' && text.IndexOf('@') == -1) return ch; | |
if (kEmailSpecialCharacters.IndexOf(ch) != -1) return ch; | |
if (ch == '.') | |
{ | |
char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' '; | |
char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n'; | |
if (lastChar != '.' && nextChar != '.') | |
return ch; | |
} | |
} | |
else if (characterValidation == CharacterValidation.Regex) | |
{ | |
// Regex expression | |
if (Regex.IsMatch(ch.ToString(), m_RegexValue)) | |
{ | |
return ch; | |
} | |
} | |
else if (characterValidation == CharacterValidation.CustomValidator) | |
{ | |
if (m_InputValidator != null) | |
{ | |
char c = m_InputValidator.Validate(ref text, ref pos, ch); | |
m_Text = text; | |
stringSelectPositionInternal = stringPositionInternal = pos; | |
return c; | |
} | |
} | |
return (char)0; | |
} | |
public void ActivateInputField() | |
{ | |
if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable()) | |
return; | |
if (isFocused) | |
{ | |
if (m_SoftKeyboard != null && !m_SoftKeyboard.active) | |
{ | |
m_SoftKeyboard.active = true; | |
m_SoftKeyboard.text = m_Text; | |
} | |
} | |
m_ShouldActivateNextUpdate = true; | |
} | |
private void ActivateInputFieldInternal() | |
{ | |
if (EventSystem.current == null) | |
return; | |
if (EventSystem.current.currentSelectedGameObject != gameObject) | |
EventSystem.current.SetSelectedGameObject(gameObject); | |
if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard == false) | |
{ | |
if (inputSystem != null && inputSystem.touchSupported) | |
{ | |
TouchScreenKeyboard.hideInput = shouldHideMobileInput; | |
} | |
if (shouldHideSoftKeyboard == false && m_ReadOnly == false) | |
{ | |
m_SoftKeyboard = (inputType == InputType.Password) ? | |
TouchScreenKeyboard.Open(m_Text, keyboardType, false, multiLine, true, false, "", characterLimit) : | |
TouchScreenKeyboard.Open(m_Text, keyboardType, inputType == InputType.AutoCorrect, multiLine, false, false, "", characterLimit); | |
OnFocus(); | |
// Opening the soft keyboard sets its selection to the end of the text. | |
// As such, we set the selection to match the Input Field's internal selection. | |
if (m_SoftKeyboard != null) | |
{ | |
int length = stringPositionInternal < stringSelectPositionInternal ? stringSelectPositionInternal - stringPositionInternal : stringPositionInternal - stringSelectPositionInternal; | |
m_SoftKeyboard.selection = new RangeInt(stringPositionInternal < stringSelectPositionInternal ? stringPositionInternal : stringSelectPositionInternal, length); | |
} | |
//} | |
} | |
// Cache the value of isInPlaceEditingAllowed, because on UWP this involves calling into native code | |
// The value only needs to be updated once when the TouchKeyboard is opened. | |
#if UNITY_2019_1_OR_NEWER | |
m_TouchKeyboardAllowsInPlaceEditing = TouchScreenKeyboard.isInPlaceEditingAllowed; | |
#endif | |
} | |
else | |
{ | |
if (!TouchScreenKeyboard.isSupported && m_ReadOnly == false && inputSystem != null) | |
inputSystem.imeCompositionMode = IMECompositionMode.On; | |
OnFocus(); | |
} | |
m_AllowInput = true; | |
m_OriginalText = text; | |
m_WasCanceled = false; | |
SetCaretVisible(); | |
UpdateLabel(); | |
} | |
public override void OnSelect(BaseEventData eventData) | |
{ | |
//Debug.Log("OnSelect()"); | |
base.OnSelect(eventData); | |
SendOnFocus(); | |
if (shouldActivateOnSelect) | |
{ | |
ActivateInputField(); | |
} | |
} | |
public virtual void OnPointerClick(PointerEventData eventData) | |
{ | |
//Debug.Log("Pointer Click Event..."); | |
if (eventData.button != PointerEventData.InputButton.Left) | |
return; | |
ActivateInputField(); | |
} | |
public void OnControlClick() | |
{ | |
//Debug.Log("Input Field control click..."); | |
} | |
public void ReleaseSelection() | |
{ | |
m_SelectionStillActive = false; | |
m_ReleaseSelection = false; | |
m_PreviouslySelectedObject = null; | |
MarkGeometryAsDirty(); | |
SendOnEndEdit(); | |
SendOnEndTextSelection(); | |
} | |
public void DeactivateInputField(bool clearSelection = false) | |
{ | |
//Debug.Log("Deactivate Input Field..."); | |
// Not activated do nothing. | |
if (!m_AllowInput) | |
return; | |
m_HasDoneFocusTransition = false; | |
m_AllowInput = false; | |
if (m_Placeholder != null) | |
m_Placeholder.enabled = string.IsNullOrEmpty(m_Text); | |
if (m_TextComponent != null && IsInteractable()) | |
{ | |
if (m_WasCanceled && m_RestoreOriginalTextOnEscape) | |
text = m_OriginalText; | |
if (m_SoftKeyboard != null) | |
{ | |
m_SoftKeyboard.active = false; | |
m_SoftKeyboard = null; | |
} | |
m_SelectionStillActive = true; | |
if (m_ResetOnDeActivation || m_ReleaseSelection) | |
{ | |
//m_StringPosition = m_StringSelectPosition = 0; | |
//m_CaretPosition = m_CaretSelectPosition = 0; | |
//m_TextComponent.rectTransform.localPosition = m_DefaultTransformPosition; | |
if (m_VerticalScrollbar == null) | |
ReleaseSelection(); | |
} | |
if (inputSystem != null) | |
inputSystem.imeCompositionMode = IMECompositionMode.Auto; | |
} | |
MarkGeometryAsDirty(); | |
} | |
public override void OnDeselect(BaseEventData eventData) | |
{ | |
DeactivateInputField(); | |
base.OnDeselect(eventData); | |
SendOnFocusLost(); | |
} | |
public virtual void OnSubmit(BaseEventData eventData) | |
{ | |
//Debug.Log("OnSubmit()"); | |
if (!IsActive() || !IsInteractable()) | |
return; | |
if (!isFocused) | |
m_ShouldActivateNextUpdate = true; | |
SendOnSubmit(); | |
} | |
//public virtual void OnLostFocus(BaseEventData eventData) | |
//{ | |
// if (!IsActive() || !IsInteractable()) | |
// return; | |
//} | |
private void EnforceContentType() | |
{ | |
switch (contentType) | |
{ | |
case ContentType.Standard: | |
{ | |
// Don't enforce line type for this content type. | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.Default; | |
m_CharacterValidation = CharacterValidation.None; | |
break; | |
} | |
case ContentType.Autocorrected: | |
{ | |
// Don't enforce line type for this content type. | |
m_InputType = InputType.AutoCorrect; | |
m_KeyboardType = TouchScreenKeyboardType.Default; | |
m_CharacterValidation = CharacterValidation.None; | |
break; | |
} | |
case ContentType.IntegerNumber: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.NumberPad; | |
m_CharacterValidation = CharacterValidation.Integer; | |
break; | |
} | |
case ContentType.DecimalNumber: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.NumbersAndPunctuation; | |
m_CharacterValidation = CharacterValidation.Decimal; | |
break; | |
} | |
case ContentType.Alphanumeric: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.ASCIICapable; | |
m_CharacterValidation = CharacterValidation.Alphanumeric; | |
break; | |
} | |
case ContentType.Name: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.Default; | |
m_CharacterValidation = CharacterValidation.Name; | |
break; | |
} | |
case ContentType.EmailAddress: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Standard; | |
m_KeyboardType = TouchScreenKeyboardType.EmailAddress; | |
m_CharacterValidation = CharacterValidation.EmailAddress; | |
break; | |
} | |
case ContentType.Password: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Password; | |
m_KeyboardType = TouchScreenKeyboardType.Default; | |
m_CharacterValidation = CharacterValidation.None; | |
break; | |
} | |
case ContentType.Pin: | |
{ | |
m_LineType = LineType.SingleLine; | |
m_InputType = InputType.Password; | |
m_KeyboardType = TouchScreenKeyboardType.NumberPad; | |
m_CharacterValidation = CharacterValidation.Digit; | |
break; | |
} | |
default: | |
{ | |
// Includes Custom type. Nothing should be enforced. | |
break; | |
} | |
} | |
SetTextComponentWrapMode(); | |
} | |
void SetTextComponentWrapMode() | |
{ | |
if (m_TextComponent == null) | |
return; | |
if (multiLine) | |
m_TextComponent.enableWordWrapping = true; | |
else | |
m_TextComponent.enableWordWrapping = false; | |
} | |
// Control Rich Text option on the text component. | |
void SetTextComponentRichTextMode() | |
{ | |
if (m_TextComponent == null) | |
return; | |
m_TextComponent.richText = m_RichText; | |
} | |
void SetToCustomIfContentTypeIsNot(params ContentType[] allowedContentTypes) | |
{ | |
if (contentType == ContentType.Custom) | |
return; | |
for (int i = 0; i < allowedContentTypes.Length; i++) | |
if (contentType == allowedContentTypes[i]) | |
return; | |
contentType = ContentType.Custom; | |
} | |
void SetToCustom() | |
{ | |
if (contentType == ContentType.Custom) | |
return; | |
contentType = ContentType.Custom; | |
} | |
void SetToCustom(CharacterValidation characterValidation) | |
{ | |
if (contentType == ContentType.Custom) | |
{ | |
characterValidation = CharacterValidation.CustomValidator; | |
return; | |
} | |
contentType = ContentType.Custom; | |
characterValidation = CharacterValidation.CustomValidator; | |
} | |
protected override void DoStateTransition(SelectionState state, bool instant) | |
{ | |
if (m_HasDoneFocusTransition) | |
state = SelectionState.Selected; | |
else if (state == SelectionState.Pressed) | |
m_HasDoneFocusTransition = true; | |
base.DoStateTransition(state, instant); | |
} | |
/// <summary> | |
/// See ILayoutElement.CalculateLayoutInputHorizontal. | |
/// </summary> | |
public virtual void CalculateLayoutInputHorizontal() | |
{ } | |
/// <summary> | |
/// See ILayoutElement.CalculateLayoutInputVertical. | |
/// </summary> | |
public virtual void CalculateLayoutInputVertical() | |
{ } | |
/// <summary> | |
/// See ILayoutElement.minWidth. | |
/// </summary> | |
public virtual float minWidth { get { return 0; } } | |
/// <summary> | |
/// Get the displayed with of all input characters. | |
/// </summary> | |
public virtual float preferredWidth | |
{ | |
get | |
{ | |
if (textComponent == null) | |
return 0; | |
float horizontalPadding = 0; | |
if (m_LayoutGroup != null) | |
horizontalPadding = m_LayoutGroup.padding.horizontal; | |
if (m_TextViewport != null) | |
horizontalPadding += m_TextViewport.offsetMin.x - m_TextViewport.offsetMax.x; | |
return m_TextComponent.preferredWidth + horizontalPadding; // Should add some extra padding for caret | |
} | |
} | |
/// <summary> | |
/// See ILayoutElement.flexibleWidth. | |
/// </summary> | |
public virtual float flexibleWidth { get { return -1; } } | |
/// <summary> | |
/// See ILayoutElement.minHeight. | |
/// </summary> | |
public virtual float minHeight { get { return 0; } } | |
/// <summary> | |
/// Get the height of all the text if constrained to the height of the RectTransform. | |
/// </summary> | |
public virtual float preferredHeight | |
{ | |
get | |
{ | |
if (textComponent == null) | |
return 0; | |
float verticalPadding = 0; | |
if (m_LayoutGroup != null) | |
verticalPadding = m_LayoutGroup.padding.vertical; | |
if (m_TextViewport != null) | |
verticalPadding += m_TextViewport.offsetMin.y - m_TextViewport.offsetMax.y; | |
return m_TextComponent.preferredHeight + verticalPadding; | |
} | |
} | |
/// <summary> | |
/// See ILayoutElement.flexibleHeight. | |
/// </summary> | |
public virtual float flexibleHeight { get { return -1; } } | |
/// <summary> | |
/// See ILayoutElement.layoutPriority. | |
/// </summary> | |
public virtual int layoutPriority { get { return 1; } } | |
/// <summary> | |
/// Function to conveniently set the point size of both Placeholder and Input Field text object. | |
/// </summary> | |
/// <param name="pointSize"></param> | |
public void SetGlobalPointSize(float pointSize) | |
{ | |
TMP_Text placeholderTextComponent = m_Placeholder as TMP_Text; | |
if (placeholderTextComponent != null) placeholderTextComponent.fontSize = pointSize; | |
textComponent.fontSize = pointSize; | |
} | |
/// <summary> | |
/// Function to conveniently set the Font Asset of both Placeholder and Input Field text object. | |
/// </summary> | |
/// <param name="fontAsset"></param> | |
public void SetGlobalFontAsset(TMP_FontAsset fontAsset) | |
{ | |
TMP_Text placeholderTextComponent = m_Placeholder as TMP_Text; | |
if (placeholderTextComponent != null) placeholderTextComponent.font = fontAsset; | |
textComponent.font = fontAsset; | |
} | |
} | |
static class SetPropertyUtility | |
{ | |
public static bool SetColor(ref Color currentValue, Color newValue) | |
{ | |
if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a) | |
return false; | |
currentValue = newValue; | |
return true; | |
} | |
public static bool SetEquatableStruct<T>(ref T currentValue, T newValue) where T : IEquatable<T> | |
{ | |
if (currentValue.Equals(newValue)) | |
return false; | |
currentValue = newValue; | |
return true; | |
} | |
public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct | |
{ | |
if (currentValue.Equals(newValue)) | |
return false; | |
currentValue = newValue; | |
return true; | |
} | |
public static bool SetClass<T>(ref T currentValue, T newValue) where T : class | |
{ | |
if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue))) | |
return false; | |
currentValue = newValue; | |
return true; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEngine.UI; | |
using UnityEditor; | |
using UnityEditor.UI; | |
using UnityEditor.AnimatedValues; | |
namespace TMPro.EditorUtilities | |
{ | |
[CanEditMultipleObjects] | |
[CustomEditor(typeof(TMP_InputField), true)] | |
public class TMP_InputFieldEditor : SelectableEditor | |
{ | |
private struct m_foldout | |
{ // Track Inspector foldout panel states, globally. | |
public static bool textInput = true; | |
public static bool fontSettings = true; | |
public static bool extraSettings = true; | |
//public static bool shadowSetting = false; | |
//public static bool materialEditor = true; | |
} | |
SerializedProperty m_TextViewport; | |
SerializedProperty m_TextComponent; | |
SerializedProperty m_Text; | |
SerializedProperty m_ContentType; | |
SerializedProperty m_LineType; | |
SerializedProperty m_LineLimit; | |
SerializedProperty m_InputType; | |
SerializedProperty m_CharacterValidation; | |
SerializedProperty m_InputValidator; | |
SerializedProperty m_RegexValue; | |
SerializedProperty m_KeyboardType; | |
SerializedProperty m_CharacterLimit; | |
SerializedProperty m_CaretBlinkRate; | |
SerializedProperty m_CaretWidth; | |
SerializedProperty m_CaretColor; | |
SerializedProperty m_CustomCaretColor; | |
SerializedProperty m_SelectionColor; | |
SerializedProperty m_HideMobileKeyboard; | |
SerializedProperty m_HideMobileInput; | |
SerializedProperty m_Placeholder; | |
SerializedProperty m_VerticalScrollbar; | |
SerializedProperty m_ScrollbarScrollSensitivity; | |
SerializedProperty m_OnValueChanged; | |
SerializedProperty m_OnEndEdit; | |
SerializedProperty m_OnSelect; | |
SerializedProperty m_OnFocus; | |
SerializedProperty m_OnDeselect; | |
SerializedProperty m_ReadOnly; | |
SerializedProperty m_RichText; | |
SerializedProperty m_RichTextEditingAllowed; | |
SerializedProperty m_ResetOnDeActivation; | |
SerializedProperty m_RestoreOriginalTextOnEscape; | |
SerializedProperty m_ShouldActivateOnSelect; | |
SerializedProperty m_OnFocusSelectAll; | |
SerializedProperty m_GlobalPointSize; | |
SerializedProperty m_GlobalFontAsset; | |
AnimBool m_CustomColor; | |
//TMP_InputValidator m_ValidationScript; | |
protected override void OnEnable() | |
{ | |
base.OnEnable(); | |
m_TextViewport = serializedObject.FindProperty("m_TextViewport"); | |
m_TextComponent = serializedObject.FindProperty("m_TextComponent"); | |
m_Text = serializedObject.FindProperty("m_Text"); | |
m_ContentType = serializedObject.FindProperty("m_ContentType"); | |
m_LineType = serializedObject.FindProperty("m_LineType"); | |
m_LineLimit = serializedObject.FindProperty("m_LineLimit"); | |
m_InputType = serializedObject.FindProperty("m_InputType"); | |
m_CharacterValidation = serializedObject.FindProperty("m_CharacterValidation"); | |
m_InputValidator = serializedObject.FindProperty("m_InputValidator"); | |
m_RegexValue = serializedObject.FindProperty("m_RegexValue"); | |
m_KeyboardType = serializedObject.FindProperty("m_KeyboardType"); | |
m_CharacterLimit = serializedObject.FindProperty("m_CharacterLimit"); | |
m_CaretBlinkRate = serializedObject.FindProperty("m_CaretBlinkRate"); | |
m_CaretWidth = serializedObject.FindProperty("m_CaretWidth"); | |
m_CaretColor = serializedObject.FindProperty("m_CaretColor"); | |
m_CustomCaretColor = serializedObject.FindProperty("m_CustomCaretColor"); | |
m_SelectionColor = serializedObject.FindProperty("m_SelectionColor"); | |
m_HideMobileKeyboard = serializedObject.FindProperty("m_HideSoftKeyboard"); | |
m_HideMobileInput = serializedObject.FindProperty("m_HideMobileInput"); | |
m_Placeholder = serializedObject.FindProperty("m_Placeholder"); | |
m_VerticalScrollbar = serializedObject.FindProperty("m_VerticalScrollbar"); | |
m_ScrollbarScrollSensitivity = serializedObject.FindProperty("m_ScrollSensitivity"); | |
m_OnValueChanged = serializedObject.FindProperty("m_OnValueChanged"); | |
m_OnEndEdit = serializedObject.FindProperty("m_OnEndEdit"); | |
m_OnSelect = serializedObject.FindProperty("m_OnSelect"); | |
m_OnFocus = serializedObject.FindProperty("m_OnFocus"); | |
m_OnDeselect = serializedObject.FindProperty("m_OnDeselect"); | |
m_ReadOnly = serializedObject.FindProperty("m_ReadOnly"); | |
m_RichText = serializedObject.FindProperty("m_RichText"); | |
m_RichTextEditingAllowed = serializedObject.FindProperty("m_isRichTextEditingAllowed"); | |
m_ResetOnDeActivation = serializedObject.FindProperty("m_ResetOnDeActivation"); | |
m_RestoreOriginalTextOnEscape = serializedObject.FindProperty("m_RestoreOriginalTextOnEscape"); | |
m_ShouldActivateOnSelect = serializedObject.FindProperty("m_ShouldActivateOnSelect"); | |
m_OnFocusSelectAll = serializedObject.FindProperty("m_OnFocusSelectAll"); | |
m_GlobalPointSize = serializedObject.FindProperty("m_GlobalPointSize"); | |
m_GlobalFontAsset = serializedObject.FindProperty("m_GlobalFontAsset"); | |
m_CustomColor = new AnimBool(m_CustomCaretColor.boolValue); | |
m_CustomColor.valueChanged.AddListener(Repaint); | |
} | |
protected override void OnDisable() | |
{ | |
base.OnDisable(); | |
m_CustomColor.valueChanged.RemoveListener(Repaint); | |
} | |
public override void OnInspectorGUI() | |
{ | |
serializedObject.Update(); | |
base.OnInspectorGUI(); | |
EditorGUILayout.Space(); | |
EditorGUILayout.PropertyField(m_TextViewport); | |
EditorGUILayout.PropertyField(m_TextComponent); | |
TextMeshProUGUI text = null; | |
if (m_TextComponent != null && m_TextComponent.objectReferenceValue != null) | |
{ | |
text = m_TextComponent.objectReferenceValue as TextMeshProUGUI; | |
//if (text.supportRichText) | |
//{ | |
// EditorGUILayout.HelpBox("Using Rich Text with input is unsupported.", MessageType.Warning); | |
//} | |
} | |
EditorGUI.BeginDisabledGroup(m_TextComponent == null || m_TextComponent.objectReferenceValue == null); | |
// TEXT INPUT BOX | |
EditorGUILayout.PropertyField(m_Text); | |
// INPUT FIELD SETTINGS | |
#region INPUT FIELD SETTINGS | |
m_foldout.fontSettings = EditorGUILayout.Foldout(m_foldout.fontSettings, "Input Field Settings", true, TMP_UIStyleManager.boldFoldout); | |
if (m_foldout.fontSettings) | |
{ | |
EditorGUI.indentLevel++; | |
EditorGUI.BeginChangeCheck(); | |
EditorGUILayout.PropertyField(m_GlobalFontAsset, new GUIContent("Font Asset", "Set the Font Asset for both Placeholder and Input Field text object.")); | |
if (EditorGUI.EndChangeCheck()) | |
{ | |
TMP_InputField inputField = target as TMP_InputField; | |
inputField.SetGlobalFontAsset(m_GlobalFontAsset.objectReferenceValue as TMP_FontAsset); | |
} | |
EditorGUI.BeginChangeCheck(); | |
EditorGUILayout.PropertyField(m_GlobalPointSize, new GUIContent("Point Size", "Set the point size of both Placeholder and Input Field text object.")); | |
if (EditorGUI.EndChangeCheck()) | |
{ | |
TMP_InputField inputField = target as TMP_InputField; | |
inputField.SetGlobalPointSize(m_GlobalPointSize.floatValue); | |
} | |
EditorGUI.BeginChangeCheck(); | |
EditorGUILayout.PropertyField(m_CharacterLimit); | |
EditorGUILayout.Space(); | |
EditorGUILayout.PropertyField(m_ContentType); | |
if (!m_ContentType.hasMultipleDifferentValues) | |
{ | |
EditorGUI.indentLevel++; | |
if (m_ContentType.enumValueIndex == (int)TMP_InputField.ContentType.Standard || | |
m_ContentType.enumValueIndex == (int)TMP_InputField.ContentType.Autocorrected || | |
m_ContentType.enumValueIndex == (int)TMP_InputField.ContentType.Custom) | |
{ | |
EditorGUI.BeginChangeCheck(); | |
EditorGUILayout.PropertyField(m_LineType); | |
if (EditorGUI.EndChangeCheck()) | |
{ | |
if (text != null) | |
{ | |
if (m_LineType.enumValueIndex == (int)TMP_InputField.LineType.SingleLine) | |
text.enableWordWrapping = false; | |
else | |
{ | |
text.enableWordWrapping = true; | |
} | |
} | |
} | |
if (m_LineType.enumValueIndex != (int)TMP_InputField.LineType.SingleLine) | |
{ | |
EditorGUILayout.PropertyField(m_LineLimit); | |
} | |
} | |
if (m_ContentType.enumValueIndex == (int)TMP_InputField.ContentType.Custom) | |
{ | |
EditorGUILayout.PropertyField(m_InputType); | |
EditorGUILayout.PropertyField(m_KeyboardType); | |
EditorGUILayout.PropertyField(m_CharacterValidation); | |
if (m_CharacterValidation.enumValueIndex == (int)TMP_InputField.CharacterValidation.Regex) | |
{ | |
EditorGUILayout.PropertyField(m_RegexValue); | |
} | |
else if (m_CharacterValidation.enumValueIndex == (int)TMP_InputField.CharacterValidation.CustomValidator) | |
{ | |
EditorGUILayout.PropertyField(m_InputValidator); | |
} | |
} | |
EditorGUI.indentLevel--; | |
} | |
EditorGUILayout.Space(); | |
EditorGUILayout.PropertyField(m_Placeholder); | |
EditorGUILayout.PropertyField(m_VerticalScrollbar); | |
if (m_VerticalScrollbar.objectReferenceValue != null) | |
EditorGUILayout.PropertyField(m_ScrollbarScrollSensitivity); | |
EditorGUILayout.PropertyField(m_CaretBlinkRate); | |
EditorGUILayout.PropertyField(m_CaretWidth); | |
EditorGUILayout.PropertyField(m_CustomCaretColor); | |
m_CustomColor.target = m_CustomCaretColor.boolValue; | |
if (EditorGUILayout.BeginFadeGroup(m_CustomColor.faded)) | |
{ | |
EditorGUILayout.PropertyField(m_CaretColor); | |
} | |
EditorGUILayout.EndFadeGroup(); | |
EditorGUILayout.PropertyField(m_SelectionColor); | |
EditorGUI.indentLevel--; | |
} | |
#endregion | |
// CONTROL SETTINGS | |
#region CONTROL SETTINGS | |
m_foldout.extraSettings = EditorGUILayout.Foldout(m_foldout.extraSettings, "Control Settings", true, TMP_UIStyleManager.boldFoldout); | |
if (m_foldout.extraSettings) | |
{ | |
EditorGUI.indentLevel++; | |
EditorGUILayout.PropertyField(m_ShouldActivateOnSelect, new GUIContent("Should Activate OnSelect")); | |
EditorGUILayout.PropertyField(m_OnFocusSelectAll, new GUIContent("OnFocus - Select All", "Should all the text be selected when the Input Field is selected.")); | |
EditorGUILayout.PropertyField(m_ResetOnDeActivation, new GUIContent("Reset On DeActivation", "Should the Text and Caret position be reset when Input Field is DeActivated.")); | |
EditorGUILayout.PropertyField(m_RestoreOriginalTextOnEscape, new GUIContent("Restore On ESC Key", "Should the original text be restored when pressing ESC.")); | |
EditorGUILayout.PropertyField(m_HideMobileKeyboard, new GUIContent("Hide Soft Keyboard", "Controls the visibility of the mobile virtual keyboard.")); | |
EditorGUILayout.PropertyField(m_HideMobileInput, new GUIContent("Hide Mobile Input", "Controls the visibility of the editable text field above the mobile virtual keyboard.")); | |
EditorGUILayout.PropertyField(m_ReadOnly); | |
EditorGUILayout.PropertyField(m_RichText); | |
EditorGUILayout.PropertyField(m_RichTextEditingAllowed, new GUIContent("Allow Rich Text Editing")); | |
EditorGUI.indentLevel--; | |
} | |
#endregion | |
EditorGUILayout.Space(); | |
EditorGUILayout.PropertyField(m_OnValueChanged); | |
EditorGUILayout.PropertyField(m_OnEndEdit); | |
EditorGUILayout.PropertyField(m_OnSelect); | |
EditorGUILayout.PropertyField(m_OnDeselect); | |
EditorGUILayout.PropertyField(m_OnFocus); | |
EditorGUI.EndDisabledGroup(); | |
serializedObject.ApplyModifiedProperties(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment