using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace SC.XR.Unity
{
///
/// Editable text input field.
///
[AddComponentMenu("UI/SC Input Field", 31)]
public class SCInputField
: Selectable,
IUpdateSelectedHandler,
IBeginDragHandler,
IDragHandler,
IEndDragHandler,
IPointerClickHandler,
ISubmitHandler,
ICanvasElement,
ILayoutElement
{
// 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,
Integer,
Decimal,
Alphanumeric,
Name,
EmailAddress
}
public enum LineType
{
SingleLine,
MultiLineSubmit,
MultiLineNewline
}
public delegate char OnValidateInput(string text, int charIndex, char addedChar);
[Serializable]
public class SubmitEvent : UnityEvent { }
[Serializable]
public class OnChangeEvent : UnityEvent { }
protected SCKeyboardBase m_Keyboard;
static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' };
///
/// Text Text used to display the input's value.
///
[SerializeField]
[FormerlySerializedAs("text")]
protected Text m_TextComponent;
[SerializeField]
protected Graphic m_Placeholder;
[SerializeField]
private ContentType m_ContentType = ContentType.Standard;
///
/// Type of data expected by the input field.
///
[FormerlySerializedAs("inputType")]
[SerializeField]
private InputType m_InputType = InputType.Standard;
///
/// The character used to hide text in password field.
///
[FormerlySerializedAs("asteriskChar")]
[SerializeField]
private char m_AsteriskChar = '*';
///
/// Keyboard type applies to mobile keyboards that get shown.
///
[FormerlySerializedAs("keyboardType")]
[SerializeField]
private TouchScreenKeyboardType m_KeyboardType = TouchScreenKeyboardType.Default;
[SerializeField]
private LineType m_LineType = LineType.SingleLine;
///
/// Should hide mobile input.
///
[FormerlySerializedAs("hideMobileInput")]
[SerializeField]
private bool m_HideMobileInput = false;
///
/// What kind of validation to use with the input field's data.
///
[FormerlySerializedAs("validation")]
[SerializeField]
private CharacterValidation m_CharacterValidation = CharacterValidation.None;
///
/// Maximum number of characters allowed before input no longer works.
///
[FormerlySerializedAs("characterLimit")]
[SerializeField]
private int m_CharacterLimit = 0;
///
/// Event delegates triggered when the input field submits its data.
///
[FormerlySerializedAs("onSubmit")]
[FormerlySerializedAs("m_OnSubmit")]
[FormerlySerializedAs("m_EndEdit")]
[SerializeField]
private SubmitEvent m_OnEndEdit = new SubmitEvent();
///
/// Event delegates triggered when the input field changes its data.
///
[FormerlySerializedAs("onValueChange")]
[FormerlySerializedAs("m_OnValueChange")]
[SerializeField]
private OnChangeEvent m_OnValueChanged = new OnChangeEvent();
///
/// Custom validation callback.
///
[FormerlySerializedAs("onValidateInput")]
[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;
[FormerlySerializedAs("selectionColor")]
[SerializeField]
private Color m_SelectionColor = new Color(168f / 255f, 206f / 255f, 255f / 255f, 192f / 255f);
///
/// Input field's value.
///
[SerializeField]
[FormerlySerializedAs("mValue")]
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 SCKeyboardEnum m_SCKeyboardEnum = SCKeyboardEnum.SCKeyboard3D;
[SerializeField]
private bool m_UseCustomTransform;
[SerializeField]
private Vector3 m_CustomPosition;
[SerializeField]
private Vector3 m_CustomRotation;
[SerializeField]
private Vector3 m_CustomLocalScale;
protected int m_CaretPosition = 0;
protected int m_CaretSelectPosition = 0;
private RectTransform caretRectTrans = null;
protected UIVertex[] m_CursorVerts = null;
private TextGenerator m_InputTextCache;
private CanvasRenderer m_CachedInputRenderer;
private bool m_PreventFontCallback = false;
[NonSerialized] protected Mesh m_Mesh;
private bool m_AllowInput = 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;
protected int m_DrawStart = 0;
protected int m_DrawEnd = 0;
private Coroutine m_DragCoroutine = null;
private string m_OriginalText = "";
private bool m_WasCanceled = false;
private bool m_HasDoneFocusTransition = false;
private BaseInput input
{
get
{
if (EventSystem.current && EventSystem.current.currentInputModule)
return EventSystem.current.currentInputModule.input;
return null;
}
}
private string compositionString
{
get { return input != null ? input.compositionString : Input.compositionString; }
}
// Doesn't include dot and @ on purpose! See usage for details.
const string kEmailSpecialCharacters = "!#$%&'*+-/=?^_`{|}~";
protected SCInputField()
{
EnforceTextHOverflow();
}
protected Mesh mesh
{
get
{
if (m_Mesh == null)
m_Mesh = new Mesh();
return m_Mesh;
}
}
protected TextGenerator cachedInputTextGenerator
{
get
{
if (m_InputTextCache == null)
m_InputTextCache = new TextGenerator();
return m_InputTextCache;
}
}
///
/// Should the mobile keyboard input be hidden.
///
public bool shouldHideMobileInput
{
set
{
SCSetPropertyUtility.SetStruct(ref m_HideMobileInput, value);
}
get
{
switch (Application.platform)
{
case RuntimePlatform.Android:
case RuntimePlatform.IPhonePlayer:
case RuntimePlatform.TizenPlayer:
case RuntimePlatform.tvOS:
return m_HideMobileInput;
}
return true;
}
}
bool shouldActivateOnSelect
{
get
{
return Application.platform != RuntimePlatform.tvOS;
}
}
///
/// Input field's current text value.
///
public string text
{
get
{
return m_Text;
}
set
{
UnityEngine.Debug.Log("Set text value " + value);
if (this.text == value)
return;
if (value == null)
value = "";
value = value.Replace("\0", string.Empty); // remove embedded nulls
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_Keyboard != null)
m_Keyboard.Text = m_Text;
if (m_CaretPosition > m_Text.Length)
m_CaretPosition = m_CaretSelectPosition = m_Text.Length;
else if (m_CaretSelectPosition > m_Text.Length)
m_CaretSelectPosition = m_Text.Length;
SendOnValueChangedAndUpdateLabel();
}
}
public bool isFocused
{
get { return m_AllowInput; }
}
public float caretBlinkRate
{
get { return m_CaretBlinkRate; }
set
{
if (SCSetPropertyUtility.SetStruct(ref m_CaretBlinkRate, value))
{
if (m_AllowInput)
SetCaretActive();
}
}
}
public int caretWidth { get { return m_CaretWidth; } set { if (SCSetPropertyUtility.SetStruct(ref m_CaretWidth, value)) MarkGeometryAsDirty(); } }
public Text textComponent
{
get { return m_TextComponent; }
set
{
if (m_TextComponent != null)
{
m_TextComponent.UnregisterDirtyVerticesCallback(MarkGeometryAsDirty);
m_TextComponent.UnregisterDirtyVerticesCallback(UpdateLabel);
m_TextComponent.UnregisterDirtyMaterialCallback(UpdateCaretMaterial);
}
if (SCSetPropertyUtility.SetClass(ref m_TextComponent, value))
{
EnforceTextHOverflow();
if (m_TextComponent != null)
{
m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial);
}
}
}
}
public Graphic placeholder { get { return m_Placeholder; } set { SCSetPropertyUtility.SetClass(ref m_Placeholder, value); } }
public Color caretColor { get { return customCaretColor ? m_CaretColor : textComponent.color; } set { if (SCSetPropertyUtility.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 (SCSetPropertyUtility.SetColor(ref m_SelectionColor, value)) MarkGeometryAsDirty(); } }
public SubmitEvent onEndEdit { get { return m_OnEndEdit; } set { SCSetPropertyUtility.SetClass(ref m_OnEndEdit, value); } }
[Obsolete("onValueChange has been renamed to onValueChanged")]
public OnChangeEvent onValueChange { get { return onValueChanged; } set { onValueChanged = value; } }
public OnChangeEvent onValueChanged { get { return m_OnValueChanged; } set { SCSetPropertyUtility.SetClass(ref m_OnValueChanged, value); } }
public OnValidateInput onValidateInput { get { return m_OnValidateInput; } set { SCSetPropertyUtility.SetClass(ref m_OnValidateInput, value); } }
public int characterLimit { get { return m_CharacterLimit; } set { if (SCSetPropertyUtility.SetStruct(ref m_CharacterLimit, Math.Max(0, value))) UpdateLabel(); } }
// Content Type related
public ContentType contentType { get { return m_ContentType; } set { if (SCSetPropertyUtility.SetStruct(ref m_ContentType, value)) EnforceContentType(); } }
public LineType lineType
{
get { return m_LineType; }
set
{
if (SCSetPropertyUtility.SetStruct(ref m_LineType, value))
{
SetToCustomIfContentTypeIsNot(ContentType.Standard, ContentType.Autocorrected);
EnforceTextHOverflow();
}
}
}
public InputType inputType { get { return m_InputType; } set { if (SCSetPropertyUtility.SetStruct(ref m_InputType, value)) SetToCustom(); } }
public TouchScreenKeyboardType keyboardType
{
get { return m_KeyboardType; }
set
{
#if UNITY_EDITOR
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.WiiU)
{
if (value == TouchScreenKeyboardType.NintendoNetworkAccount) ;
}
#elif !UNITY_WIIU
if (value == TouchScreenKeyboardType.NintendoNetworkAccount)
Debug.LogWarning("Invalid InputField.keyboardType value set. TouchScreenKeyboardType.NintendoNetworkAccount only applies to the Wii U. InputField.keyboardType will default to TouchScreenKeyboardType.Default .");
#endif
if (SCSetPropertyUtility.SetStruct(ref m_KeyboardType, value))
SetToCustom();
}
}
public CharacterValidation characterValidation { get { return m_CharacterValidation; } set { if (SCSetPropertyUtility.SetStruct(ref m_CharacterValidation, value)) SetToCustom(); } }
public bool readOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } }
// 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 (SCSetPropertyUtility.SetStruct(ref m_AsteriskChar, value)) UpdateLabel(); } }
public bool wasCanceled { get { return m_WasCanceled; } }
protected void ClampPos(ref int pos)
{
if (pos < 0) pos = 0;
else if (pos > text.Length) pos = text.Length;
}
///
/// Current position of the cursor.
/// Getters are public Setters are protected
///
protected int caretPositionInternal { get { return m_CaretPosition + compositionString.Length; } set { m_CaretPosition = value; ClampPos(ref m_CaretPosition); } }
protected int caretSelectPositionInternal { get { return m_CaretSelectPosition + compositionString.Length; } set { m_CaretSelectPosition = value; ClampPos(ref m_CaretSelectPosition); } }
private bool hasSelection { get { return caretPositionInternal != caretSelectPositionInternal; } }
#if UNITY_EDITOR
[Obsolete("caretSelectPosition has been deprecated. Use selectionFocusPosition instead (UnityUpgradable) -> selectionFocusPosition", true)]
public int caretSelectPosition { get { return selectionFocusPosition; } protected set { selectionFocusPosition = value; } }
#endif
///
/// 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
///
public int caretPosition
{
get { return m_CaretSelectPosition + compositionString.Length; }
set { selectionAnchorPosition = value; selectionFocusPosition = value; }
}
///
/// Get: Returns the fixed position of selection
/// Set: If Input.compositionString is 0 set the fixed position
///
public int selectionAnchorPosition
{
get { return m_CaretPosition + compositionString.Length; }
set
{
if (compositionString.Length != 0)
return;
m_CaretPosition = value;
ClampPos(ref m_CaretPosition);
}
}
///
/// Get: Returns the variable position of selection
/// Set: If Input.compositionString is 0 set the variable position
///
public int selectionFocusPosition
{
get { return m_CaretSelectPosition + compositionString.Length; }
set
{
if (compositionString.Length != 0)
return;
m_CaretSelectPosition = value;
ClampPos(ref m_CaretSelectPosition);
}
}
#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();
EnforceTextHOverflow();
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;
UpdateLabel();
if (m_AllowInput)
SetCaretActive();
}
#endif // if UNITY_EDITOR
protected override void OnEnable()
{
base.OnEnable();
if (m_Text == null)
m_Text = string.Empty;
m_DrawStart = 0;
m_DrawEnd = m_Text.Length;
// If we have a cached renderer then we had OnDisable called so just restore the material.
if (m_CachedInputRenderer != null)
m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);
if (m_TextComponent != null)
{
m_TextComponent.RegisterDirtyVerticesCallback(MarkGeometryAsDirty);
m_TextComponent.RegisterDirtyVerticesCallback(UpdateLabel);
m_TextComponent.RegisterDirtyMaterialCallback(UpdateCaretMaterial);
UpdateLabel();
}
}
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);
m_TextComponent.UnregisterDirtyMaterialCallback(UpdateCaretMaterial);
}
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;
base.OnDisable();
}
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_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;
}
}
private void UpdateCaretMaterial()
{
if (m_TextComponent != null && m_CachedInputRenderer != null)
m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(Graphic.defaultGraphicMaterial), Texture2D.whiteTexture);
}
protected void OnFocus()
{
SelectAll();
}
protected void SelectAll()
{
caretPositionInternal = text.Length;
caretSelectPositionInternal = 0;
}
public void MoveTextEnd(bool shift)
{
int position = text.Length;
if (shift)
{
caretSelectPositionInternal = position;
}
else
{
caretPositionInternal = position;
caretSelectPositionInternal = caretPositionInternal;
}
UpdateLabel();
}
public void MoveTextStart(bool shift)
{
int position = 0;
if (shift)
{
caretSelectPositionInternal = position;
}
else
{
caretPositionInternal = position;
caretSelectPositionInternal = caretPositionInternal;
}
UpdateLabel();
}
static string clipboard
{
get
{
return GUIUtility.systemCopyBuffer;
}
set
{
GUIUtility.systemCopyBuffer = value;
}
}
private bool InPlaceEditing()
{
return false;//!TouchScreenKeyboard.isSupported;
}
void UpdateCaretFromKeyboard()
{
//Do nothing
//var selectionRange = m_Keyboard.selection;
//var selectionStart = selectionRange.start;
//var selectionEnd = selectionRange.end;
//var caretChanged = false;
//if (caretPositionInternal != selectionStart)
//{
// caretChanged = true;
// caretPositionInternal = selectionStart;
//}
//if (caretSelectPositionInternal != selectionEnd)
//{
// caretSelectPositionInternal = selectionEnd;
// caretChanged = true;
//}
//if (caretChanged)
//{
// m_BlinkStartTime = Time.unscaledTime;
// UpdateLabel();
//}
}
///
/// Update the text based on input.
///
// 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;
}
if (InPlaceEditing() || !isFocused)
{
return;
}
AssignPositioningIfNeeded();
if (m_Keyboard == null || m_Keyboard.Done)
{
if (m_Keyboard != null)
{
if (!m_ReadOnly)
{
UnityEngine.Debug.Log("Set text Value");
text = m_Keyboard.Text;
}
if (m_Keyboard.WasCanceled)
m_WasCanceled = true;
}
OnDeselect(null);
return;
}
string val = m_Keyboard.Text;
if (m_Text != val)
{
UnityEngine.Debug.Log("m_Text != val " + m_Text + " " + val);
if (m_ReadOnly)
{
m_Keyboard.Text = m_Text;
}
else
{
m_Text = "";
for (int i = 0; i < val.Length; ++i)
{
char c = val[i];
if (c == '\r' || (int)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_Keyboard.Text = m_Text;
OnDeselect(null);
return;
}
if (c != 0)
m_Text += c;
}
if (characterLimit > 0 && m_Text.Length > characterLimit)
m_Text = m_Text.Substring(0, characterLimit);
if (false)//(m_Keyboard.canGetSelection)
{
UpdateCaretFromKeyboard();
}
else
{
caretPositionInternal = caretSelectPositionInternal = m_Text.Length;
}
// 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_Keyboard.Text = m_Text;
SendOnValueChangedAndUpdateLabel();
}
}
else if (false)//(m_Keyboard.canGetSelection)
{
UpdateCaretFromKeyboard();
}
if (m_Keyboard.Done)
{
UnityEngine.Debug.Log("KeyboardDone");
if (m_Keyboard.WasCanceled)
m_WasCanceled = true;
OnDeselect(null);
}
}
[Obsolete("This function is no longer used. Please use RectTransformUtility.ScreenPointToLocalPointInRectangle() instead.")]
public Vector2 ScreenToLocal(Vector2 screen)
{
var theCanvas = m_TextComponent.canvas;
if (theCanvas == null)
return screen;
Vector3 pos = Vector3.zero;
if (theCanvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
pos = m_TextComponent.transform.InverseTransformPoint(screen);
}
else if (theCanvas.worldCamera != null)
{
Ray mouseRay = theCanvas.worldCamera.ScreenPointToRay(screen);
float dist;
Plane plane = new Plane(m_TextComponent.transform.forward, m_TextComponent.transform.position);
plane.Raycast(mouseRay, out dist);
pos = m_TextComponent.transform.InverseTransformPoint(mouseRay.GetPoint(dist));
}
return new Vector2(pos.x, pos.y);
}
private int GetUnclampedCharacterLineFromPosition(Vector2 pos, TextGenerator generator)
{
if (!multiLine)
return 0;
// transform y to local scale
float y = pos.y * m_TextComponent.pixelsPerUnit;
float lastBottomY = 0.0f;
for (int i = 0; i < generator.lineCount; ++i)
{
float topY = generator.lines[i].topY;
float bottomY = topY - generator.lines[i].height;
// pos is somewhere in the leading above this line
if (y > topY)
{
// determine which line we're closer to
float leading = topY - lastBottomY;
if (y > topY - 0.5f * leading)
return i - 1;
else
return i;
}
if (y > bottomY)
return i;
lastBottomY = bottomY;
}
// Position is after last line.
return generator.lineCount;
}
///
/// Given an input position in local space on the Text return the index for the selection cursor at this position.
///
protected int GetCharacterIndexFromPosition(Vector2 pos)
{
TextGenerator gen = m_TextComponent.cachedTextGenerator;
if (gen.lineCount == 0)
return 0;
int line = GetUnclampedCharacterLineFromPosition(pos, gen);
if (line < 0)
return 0;
if (line >= gen.lineCount)
return gen.characterCountVisible;
int startCharIndex = gen.lines[line].startCharIdx;
int endCharIndex = GetLineEndPosition(gen, line);
for (int i = startCharIndex; i < endCharIndex; i++)
{
if (i >= gen.characterCountVisible)
break;
UICharInfo charInfo = gen.characters[i];
Vector2 charPos = charInfo.cursorPos / m_TextComponent.pixelsPerUnit;
float distToCharStart = pos.x - charPos.x;
float distToCharEnd = charPos.x + (charInfo.charWidth / m_TextComponent.pixelsPerUnit) - pos.x;
if (distToCharStart < distToCharEnd)
return i;
}
return endCharIndex;
}
private bool MayDrag(PointerEventData eventData)
{
return IsActive() &&
IsInteractable() &&
eventData.button == PointerEventData.InputButton.Left &&
m_TextComponent != null &&
m_Keyboard == null;
}
public virtual void OnBeginDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
m_UpdateDrag = true;
}
public virtual void OnDrag(PointerEventData eventData)
{
if (!MayDrag(eventData))
return;
Vector2 localMousePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos);
caretSelectPositionInternal = GetCharacterIndexFromPosition(localMousePos) + m_DrawStart;
MarkGeometryAsDirty();
m_DragPositionOutOfBounds = !RectTransformUtility.RectangleContainsScreenPoint(textComponent.rectTransform, eventData.position, eventData.pressEventCamera);
if (m_DragPositionOutOfBounds && m_DragCoroutine == null)
m_DragCoroutine = StartCoroutine(MouseDragOutsideRect(eventData));
eventData.Use();
}
IEnumerator MouseDragOutsideRect(PointerEventData eventData)
{
while (m_UpdateDrag && m_DragPositionOutOfBounds)
{
Vector2 localMousePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos);
Rect rect = textComponent.rectTransform.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;
yield return new WaitForSecondsRealtime(delay);
}
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())
{
if (m_Keyboard == null || !m_Keyboard.Active)
{
OnSelect(eventData);
return;
}
}
// Only set caret position if we didn't just get focus now.
// Otherwise it will overwrite the select all on focus.
if (hadFocusBefore)
{
Vector2 localMousePos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(textComponent.rectTransform, eventData.position, eventData.pressEventCamera, out localMousePos);
caretSelectPositionInternal = caretPositionInternal = GetCharacterIndexFromPosition(localMousePos) + m_DrawStart;
}
UpdateLabel();
eventData.Use();
}
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:
{
ForwardSpace();
return EditState.Continue;
}
case KeyCode.Home:
{
MoveTextStart(shift);
return EditState.Continue;
}
case KeyCode.End:
{
MoveTextEnd(shift);
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();
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;
}
// Submit
case KeyCode.Return:
case KeyCode.KeypadEnter:
{
if (lineType != LineType.MultiLineNewline)
{
return EditState.Finish;
}
break;
}
case KeyCode.Escape:
{
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';
if (IsValidChar(c))
{
Append(c);
}
if (c == 0)
{
if (compositionString.Length > 0)
{
UpdateLabel();
}
}
return EditState.Continue;
}
private bool IsValidChar(char c)
{
// Delete key on mac
if ((int)c == 127)
return false;
// Accept newline and tab
if (c == '\t' || c == '\n')
return true;
return m_TextComponent.font.HasCharacter(c);
}
///
/// Handle the specified event.
///
private Event m_ProcessingEvent = new Event();
public void ProcessEvent(Event e)
{
KeyPressed(e);
}
public virtual void OnUpdateSelected(BaseEventData eventData)
{
if (!isFocused)
return;
bool consumedEvent = false;
while (Event.PopEvent(m_ProcessingEvent))
{
if (m_ProcessingEvent.rawType == EventType.KeyDown)
{
consumedEvent = true;
var shouldContinue = KeyPressed(m_ProcessingEvent);
if (shouldContinue == EditState.Finish)
{
DeactivateInputField();
break;
}
}
switch (m_ProcessingEvent.type)
{
case EventType.ValidateCommand:
case EventType.ExecuteCommand:
switch (m_ProcessingEvent.commandName)
{
case "SelectAll":
SelectAll();
consumedEvent = true;
break;
}
break;
}
}
if (consumedEvent)
UpdateLabel();
eventData.Use();
}
private string GetSelectedString()
{
if (!hasSelection)
return "";
int startPos = caretPositionInternal;
int endPos = caretSelectPositionInternal;
// Ensure startPos is always less then endPos to make the code simpler
if (startPos > endPos)
{
int temp = startPos;
startPos = endPos;
endPos = temp;
}
return text.Substring(startPos, endPos - startPos);
}
private int FindtNextWordBegin()
{
if (caretSelectPositionInternal + 1 >= text.Length)
return text.Length;
int spaceLoc = text.IndexOfAny(kSeparators, caretSelectPositionInternal + 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.
caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal);
return;
}
int position;
if (ctrl)
position = FindtNextWordBegin();
else
position = caretSelectPositionInternal + 1;
if (shift)
caretSelectPositionInternal = position;
else
caretSelectPositionInternal = caretPositionInternal = position;
}
private int FindtPrevWordBegin()
{
if (caretSelectPositionInternal - 2 < 0)
return 0;
int spaceLoc = text.LastIndexOfAny(kSeparators, caretSelectPositionInternal - 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.
caretPositionInternal = caretSelectPositionInternal = Mathf.Min(caretPositionInternal, caretSelectPositionInternal);
return;
}
int position;
if (ctrl)
position = FindtPrevWordBegin();
else
position = caretSelectPositionInternal - 1;
if (shift)
caretSelectPositionInternal = position;
else
caretSelectPositionInternal = caretPositionInternal = position;
}
private int DetermineCharacterLine(int charPos, TextGenerator generator)
{
for (int i = 0; i < generator.lineCount - 1; ++i)
{
if (generator.lines[i + 1].startCharIdx > charPos)
return i;
}
return generator.lineCount - 1;
}
///
/// Use cachedInputTextGenerator as the y component for the UICharInfo is not required
///
private int LineUpCharacterPosition(int originalPos, bool goToFirstChar)
{
if (originalPos >= cachedInputTextGenerator.characters.Count)
return 0;
UICharInfo originChar = cachedInputTextGenerator.characters[originalPos];
int originLine = DetermineCharacterLine(originalPos, cachedInputTextGenerator);
// We are on the first line return first character
if (originLine <= 0)
return goToFirstChar ? 0 : originalPos;
int endCharIdx = cachedInputTextGenerator.lines[originLine].startCharIdx - 1;
for (int i = cachedInputTextGenerator.lines[originLine - 1].startCharIdx; i < endCharIdx; ++i)
{
if (cachedInputTextGenerator.characters[i].cursorPos.x >= originChar.cursorPos.x)
return i;
}
return endCharIdx;
}
///
/// Use cachedInputTextGenerator as the y component for the UICharInfo is not required
///
private int LineDownCharacterPosition(int originalPos, bool goToLastChar)
{
if (originalPos >= cachedInputTextGenerator.characterCountVisible)
return text.Length;
UICharInfo originChar = cachedInputTextGenerator.characters[originalPos];
int originLine = DetermineCharacterLine(originalPos, cachedInputTextGenerator);
// We are on the last line return last character
if (originLine + 1 >= cachedInputTextGenerator.lineCount)
return goToLastChar ? text.Length : originalPos;
// Need to determine end line for next line.
int endCharIdx = GetLineEndPosition(cachedInputTextGenerator, originLine + 1);
for (int i = cachedInputTextGenerator.lines[originLine + 1].startCharIdx; i < endCharIdx; ++i)
{
if (cachedInputTextGenerator.characters[i].cursorPos.x >= originChar.cursorPos.x)
return i;
}
return endCharIdx;
}
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 position to end of selection before we move it down.
caretPositionInternal = caretSelectPositionInternal = Mathf.Max(caretPositionInternal, caretSelectPositionInternal);
}
int position = multiLine ? LineDownCharacterPosition(caretSelectPositionInternal, goToLastChar) : text.Length;
if (shift)
caretSelectPositionInternal = position;
else
caretPositionInternal = caretSelectPositionInternal = position;
}
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;
else
caretSelectPositionInternal = caretPositionInternal = position;
}
private void Delete()
{
if (m_ReadOnly)
return;
if (caretPositionInternal == caretSelectPositionInternal)
return;
if (caretPositionInternal < caretSelectPositionInternal)
{
m_Text = text.Substring(0, caretPositionInternal) + text.Substring(caretSelectPositionInternal, text.Length - caretSelectPositionInternal);
caretSelectPositionInternal = caretPositionInternal;
}
else
{
m_Text = text.Substring(0, caretSelectPositionInternal) + text.Substring(caretPositionInternal, text.Length - caretPositionInternal);
caretPositionInternal = caretSelectPositionInternal;
}
}
private void ForwardSpace()
{
if (m_ReadOnly)
return;
if (hasSelection)
{
Delete();
SendOnValueChangedAndUpdateLabel();
}
else
{
if (caretPositionInternal < text.Length)
{
m_Text = text.Remove(caretPositionInternal, 1);
SendOnValueChangedAndUpdateLabel();
}
}
}
private void Backspace()
{
if (m_ReadOnly)
return;
if (hasSelection)
{
Delete();
SendOnValueChangedAndUpdateLabel();
}
else
{
if (caretPositionInternal > 0)
{
m_Text = text.Remove(caretPositionInternal - 1, 1);
caretSelectPositionInternal = caretPositionInternal = caretPositionInternal - 1;
SendOnValueChangedAndUpdateLabel();
}
}
}
// Insert the character and update the label.
private void Insert(char c)
{
if (m_ReadOnly)
return;
string replaceString = c.ToString();
Delete();
// Can't go past the character limit
if (characterLimit > 0 && text.Length >= characterLimit)
return;
m_Text = text.Insert(m_CaretPosition, replaceString);
caretSelectPositionInternal = caretPositionInternal += replaceString.Length;
SendOnValueChanged();
}
private void SendOnValueChangedAndUpdateLabel()
{
SendOnValueChanged();
UpdateLabel();
}
private void SendOnValueChanged()
{
UISystemProfilerApi.AddMarker("InputField.value", this);
if (onValueChanged != null)
onValueChanged.Invoke(text);
}
///
/// Submit the input field's text.
///
protected void SendOnSubmit()
{
UISystemProfilerApi.AddMarker("InputField.onSubmit", this);
if (onEndEdit != null)
onEndEdit.Invoke(m_Text);
}
///
/// Append the specified text to the end of the current.
///
protected virtual void Append(string input)
{
if (m_ReadOnly)
return;
if (!InPlaceEditing())
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())
return;
// If we have an input validator, validate the input first
int insertionPoint = Math.Min(selectionFocusPosition, selectionAnchorPosition);
if (onValidateInput != null)
input = onValidateInput(text, insertionPoint, input);
else if (characterValidation != CharacterValidation.None)
input = Validate(text, insertionPoint, input);
// If the input is invalid, skip it
if (input == 0)
return;
// Append the character and update the label
Insert(input);
}
///
/// Update the visual text Text.
///
protected void UpdateLabel()
{
if (m_TextComponent != null && m_TextComponent.font != null && !m_PreventFontCallback)
{
// TextGenerator.Populate invokes a callback that's called for anything
// that needs to be updated when the data for that font has changed.
// This makes all Text components that use that font update their vertices.
// In turn, this makes the InputField that's associated with that Text component
// update its label by calling this UpdateLabel method.
// This is a recursive call we want to prevent, since it makes the InputField
// update based on font data that didn't yet finish executing, or alternatively
// hang on infinite recursion, depending on whether the cached value is cached
// before or after the calculation.
//
// This callback also occurs when assigning text to our Text component, i.e.,
// m_TextComponent.text = processed;
m_PreventFontCallback = true;
string fullText;
if (compositionString.Length > 0)
fullText = text.Substring(0, m_CaretPosition) + compositionString + text.Substring(m_CaretPosition);
else
fullText = text;
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 not currently editing the text, set the visible range to the whole text.
// The UpdateLabel method will then truncate it to the part that fits inside the Text area.
// We can't do this when text is being edited since it would discard the current scroll,
// which is defined by means of the m_DrawStart and m_DrawEnd indices.
if (!m_AllowInput)
{
m_DrawStart = 0;
m_DrawEnd = m_Text.Length;
}
if (!isEmpty)
{
// Determine what will actually fit into the given line
Vector2 extents = m_TextComponent.rectTransform.rect.size;
var settings = m_TextComponent.GetGenerationSettings(extents);
settings.generateOutOfBounds = true;
cachedInputTextGenerator.PopulateWithErrors(processed, settings, gameObject);
SetDrawRangeToContainCaretPosition(caretSelectPositionInternal);
processed = processed.Substring(m_DrawStart, Mathf.Min(m_DrawEnd, processed.Length) - m_DrawStart);
SetCaretVisible();
}
m_TextComponent.text = processed;
MarkGeometryAsDirty();
m_PreventFontCallback = false;
}
}
private bool IsSelectionVisible()
{
if (m_DrawStart > caretPositionInternal || m_DrawStart > caretSelectPositionInternal)
return false;
if (m_DrawEnd < caretPositionInternal || m_DrawEnd < caretSelectPositionInternal)
return false;
return true;
}
private static int GetLineStartPosition(TextGenerator gen, int line)
{
line = Mathf.Clamp(line, 0, gen.lines.Count - 1);
return gen.lines[line].startCharIdx;
}
private static int GetLineEndPosition(TextGenerator gen, int line)
{
line = Mathf.Max(line, 0);
if (line + 1 < gen.lines.Count)
return gen.lines[line + 1].startCharIdx - 1;
return gen.characterCountVisible;
}
private void SetDrawRangeToContainCaretPosition(int caretPos)
{
// We don't have any generated lines generation is not valid.
if (cachedInputTextGenerator.lineCount <= 0)
return;
// the extents gets modified by the pixel density, so we need to use the generated extents since that will be in the same 'space' as
// the values returned by the TextGenerator.lines[x].height for instance.
Vector2 extents = cachedInputTextGenerator.rectExtents.size;
if (multiLine)
{
var lines = cachedInputTextGenerator.lines;
int caretLine = DetermineCharacterLine(caretPos, cachedInputTextGenerator);
if (caretPos > m_DrawEnd)
{
// Caret comes after drawEnd, so we need to move drawEnd to the end of the line with the caret
m_DrawEnd = GetLineEndPosition(cachedInputTextGenerator, caretLine);
float bottomY = lines[caretLine].topY - lines[caretLine].height;
if (caretLine == lines.Count - 1)
{
// Remove interline spacing on last line.
bottomY += lines[caretLine].leading;
}
int startLine = caretLine;
while (startLine > 0)
{
float topY = lines[startLine - 1].topY;
if (topY - bottomY > extents.y)
break;
startLine--;
}
m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, startLine);
}
else
{
if (caretPos < m_DrawStart)
{
// Caret comes before drawStart, so we need to move drawStart to an earlier line start that comes before caret.
m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, caretLine);
}
int startLine = DetermineCharacterLine(m_DrawStart, cachedInputTextGenerator);
int endLine = startLine;
float topY = lines[startLine].topY;
float bottomY = lines[endLine].topY - lines[endLine].height;
if (endLine == lines.Count - 1)
{
// Remove interline spacing on last line.
bottomY += lines[endLine].leading;
}
while (endLine < lines.Count - 1)
{
bottomY = lines[endLine + 1].topY - lines[endLine + 1].height;
if (endLine + 1 == lines.Count - 1)
{
// Remove interline spacing on last line.
bottomY += lines[endLine + 1].leading;
}
if (topY - bottomY > extents.y)
break;
++endLine;
}
m_DrawEnd = GetLineEndPosition(cachedInputTextGenerator, endLine);
while (startLine > 0)
{
topY = lines[startLine - 1].topY;
if (topY - bottomY > extents.y)
break;
startLine--;
}
m_DrawStart = GetLineStartPosition(cachedInputTextGenerator, startLine);
}
}
else
{
var characters = cachedInputTextGenerator.characters;
if (m_DrawEnd > cachedInputTextGenerator.characterCountVisible)
m_DrawEnd = cachedInputTextGenerator.characterCountVisible;
float width = 0.0f;
if (caretPos > m_DrawEnd || (caretPos == m_DrawEnd && m_DrawStart > 0))
{
// fit characters from the caretPos leftward
m_DrawEnd = caretPos;
for (m_DrawStart = m_DrawEnd - 1; m_DrawStart >= 0; --m_DrawStart)
{
if (width + characters[m_DrawStart].charWidth > extents.x)
break;
width += characters[m_DrawStart].charWidth;
}
++m_DrawStart; // move right one to the last character we could fit on the left
}
else
{
if (caretPos < m_DrawStart)
m_DrawStart = caretPos;
m_DrawEnd = m_DrawStart;
}
// fit characters rightward
for (; m_DrawEnd < cachedInputTextGenerator.characterCountVisible; ++m_DrawEnd)
{
width += characters[m_DrawEnd].charWidth;
if (width > extents.x)
break;
}
}
}
public void ForceLabelUpdate()
{
UpdateLabel();
}
private void MarkGeometryAsDirty()
{
#if UNITY_EDITOR
if (!Application.isPlaying || UnityEditor.PrefabUtility.GetPrefabObject(gameObject) != null)
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 (!shouldHideMobileInput)
return;
if (m_CachedInputRenderer == null && m_TextComponent != null)
{
GameObject go = new GameObject(transform.name + " Input Caret", typeof(RectTransform), typeof(CanvasRenderer));
go.hideFlags = HideFlags.DontSave;
go.transform.SetParent(m_TextComponent.transform.parent);
go.transform.SetAsFirstSibling();
go.layer = gameObject.layer;
caretRectTrans = go.GetComponent();
m_CachedInputRenderer = go.GetComponent();
m_CachedInputRenderer.SetMaterial(m_TextComponent.GetModifiedMaterial(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().ignoreLayout = true;
AssignPositioningIfNeeded();
}
if (m_CachedInputRenderer == null)
return;
OnFillVBO(mesh);
m_CachedInputRenderer.SetMesh(mesh);
}
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)
{
helper.FillMesh(vbo);
return;
}
Vector2 roundingOffset = m_TextComponent.PixelAdjustPoint(Vector2.zero);
if (!hasSelection)
GenerateCaret(helper, roundingOffset);
else
GenerateHightlight(helper, roundingOffset);
helper.FillMesh(vbo);
}
}
private void GenerateCaret(VertexHelper vbo, Vector2 roundingOffset)
{
if (!m_CaretVisible)
return;
if (m_CursorVerts == null)
{
CreateCursorVerts();
}
float width = m_CaretWidth;
int adjustedPos = Mathf.Max(0, caretPositionInternal - m_DrawStart);
TextGenerator gen = m_TextComponent.cachedTextGenerator;
if (gen == null)
return;
if (gen.lineCount == 0)
return;
Vector2 startPosition = Vector2.zero;
// Calculate startPosition
if (adjustedPos < gen.characters.Count)
{
UICharInfo cursorChar = gen.characters[adjustedPos];
startPosition.x = cursorChar.cursorPos.x;
}
startPosition.x /= m_TextComponent.pixelsPerUnit;
// TODO: Only clamp when Text uses horizontal word wrap.
if (startPosition.x > m_TextComponent.rectTransform.rect.xMax)
startPosition.x = m_TextComponent.rectTransform.rect.xMax;
int characterLine = DetermineCharacterLine(adjustedPos, gen);
startPosition.y = gen.lines[characterLine].topY / m_TextComponent.pixelsPerUnit;
float height = gen.lines[characterLine].height / m_TextComponent.pixelsPerUnit;
for (int i = 0; i < m_CursorVerts.Length; i++)
m_CursorVerts[i].color = caretColor;
m_CursorVerts[0].position = new Vector3(startPosition.x, startPosition.y - height, 0.0f);
m_CursorVerts[1].position = new Vector3(startPosition.x + width, startPosition.y - height, 0.0f);
m_CursorVerts[2].position = new Vector3(startPosition.x + width, startPosition.y, 0.0f);
m_CursorVerts[3].position = new Vector3(startPosition.x, startPosition.y, 0.0f);
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < m_CursorVerts.Length; i++)
{
UIVertex uiv = m_CursorVerts[i];
uiv.position.x += roundingOffset.x;
uiv.position.y += roundingOffset.y;
}
}
vbo.AddUIVertexQuad(m_CursorVerts);
int screenHeight = Screen.height;
// Multiple display support only when not the main display. For display 0 the reported
// resolution is always the desktops resolution since its part of the display API,
// so we use the standard none multiple display method. (case 741751)
int displayIndex = m_TextComponent.canvas.targetDisplay;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
screenHeight = Display.displays[displayIndex].renderingHeight;
startPosition.y = screenHeight - startPosition.y;
input.compositionCursorPos = startPosition;
}
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)
{
int startChar = Mathf.Max(0, caretPositionInternal - m_DrawStart);
int endChar = Mathf.Max(0, caretSelectPositionInternal - m_DrawStart);
// Ensure pos is always less then selPos to make the code simpler
if (startChar > endChar)
{
int temp = startChar;
startChar = endChar;
endChar = temp;
}
endChar -= 1;
TextGenerator gen = m_TextComponent.cachedTextGenerator;
if (gen.lineCount <= 0)
return;
int currentLineIndex = DetermineCharacterLine(startChar, gen);
int lastCharInLineIndex = GetLineEndPosition(gen, currentLineIndex);
UIVertex vert = UIVertex.simpleVert;
vert.uv0 = Vector2.zero;
vert.color = selectionColor;
int currentChar = startChar;
while (currentChar <= endChar && currentChar < gen.characterCount)
{
if (currentChar == lastCharInLineIndex || currentChar == endChar)
{
UICharInfo startCharInfo = gen.characters[startChar];
UICharInfo endCharInfo = gen.characters[currentChar];
Vector2 startPosition = new Vector2(startCharInfo.cursorPos.x / m_TextComponent.pixelsPerUnit, gen.lines[currentLineIndex].topY / m_TextComponent.pixelsPerUnit);
Vector2 endPosition = new Vector2((endCharInfo.cursorPos.x + endCharInfo.charWidth) / m_TextComponent.pixelsPerUnit, startPosition.y - gen.lines[currentLineIndex].height / m_TextComponent.pixelsPerUnit);
// Checking xMin as well due to text generator not setting position if char is not rendered.
if (endPosition.x > m_TextComponent.rectTransform.rect.xMax || endPosition.x < m_TextComponent.rectTransform.rect.xMin)
endPosition.x = m_TextComponent.rectTransform.rect.xMax;
var startIndex = vbo.currentVertCount;
vert.position = new Vector3(startPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.AddVert(vert);
vert.position = new Vector3(endPosition.x, endPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.AddVert(vert);
vert.position = new Vector3(endPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.AddVert(vert);
vert.position = new Vector3(startPosition.x, startPosition.y, 0.0f) + (Vector3)roundingOffset;
vbo.AddVert(vert);
vbo.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
vbo.AddTriangle(startIndex + 2, startIndex + 3, startIndex + 0);
startChar = currentChar + 1;
currentLineIndex++;
lastCharInLineIndex = GetLineEndPosition(gen, currentLineIndex);
}
currentChar++;
}
}
///
/// Validate the specified input.
///
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 dashInSelection = text.Length > 0 && text[0] == '-' && ((caretPositionInternal == 0 && caretSelectPositionInternal > 0) || (caretSelectPositionInternal == 0 && caretPositionInternal > 0));
bool selectionAtStart = caretPositionInternal == 0 || caretSelectPositionInternal == 0;
if (!cursorBeforeDash || dashInSelection)
{
if (ch >= '0' && ch <= '9') return ch;
if (ch == '-' && (pos == 0 || selectionAtStart)) return ch;
if (ch == '.' && characterValidation == CharacterValidation.Decimal && !text.Contains(".")) 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)
{
// FIXME: some actions still lead to invalid input:
// - Hitting delete in front of an uppercase letter
// - Selecting an uppercase letter and deleting it
// - Typing some text, hitting Home and typing more text (we then have an uppercase letter in the middle of a word)
// - Typing some text, hitting Home and typing a space (we then have a leading space)
// - Erasing a space between two words (we then have an uppercase letter in the middle of a word)
// - We accept a trailing space
// - We accept the insertion of a space between two lowercase letters.
// - Typing text in front of an existing uppercase letter
// - ... and certainly more
//
// The rule we try to implement are too complex for this kind of verification.
if (char.IsLetter(ch))
{
// Character following a space should be in uppercase.
if (char.IsLower(ch) && ((pos == 0) || (text[pos - 1] == ' ')))
{
return char.ToUpper(ch);
}
// Character not following a space or an apostrophe should be in lowercase.
if (char.IsUpper(ch) && (pos > 0) && (text[pos - 1] != ' ') && (text[pos - 1] != '\''))
{
return char.ToLower(ch);
}
return ch;
}
if (ch == '\'')
{
// Don't allow more than one apostrophe
if (!text.Contains("'"))
// Don't allow consecutive spaces and apostrophes.
if (!(((pos > 0) && ((text[pos - 1] == ' ') || (text[pos - 1] == '\''))) ||
((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\'')))))
return ch;
}
if (ch == ' ')
{
// Don't allow consecutive spaces and apostrophes.
if (!(((pos > 0) && ((text[pos - 1] == ' ') || (text[pos - 1] == '\''))) ||
((pos < text.Length) && ((text[pos] == ' ') || (text[pos] == '\'')))))
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;
}
}
return (char)0;
}
public void ActivateInputField()
{
if (m_TextComponent == null || m_TextComponent.font == null || !IsActive() || !IsInteractable())
return;
if (isFocused)
{
if (m_Keyboard != null && !m_Keyboard.Active)
{
m_Keyboard.Active = true;
m_Keyboard.Text = m_Text;
}
}
m_ShouldActivateNextUpdate = true;
}
private void ActivateInputFieldInternal()
{
if (EventSystem.current == null)
return;
if (EventSystem.current.currentSelectedGameObject != gameObject)
EventSystem.current.SetSelectedGameObject(gameObject);
if (true) //(TouchScreenKeyboard.isSupported)
{
if (input.touchSupported)
{
TouchScreenKeyboard.hideInput = shouldHideMobileInput;
}
//TODO
Vector3 worldPosition = Vector3.zero;
Quaternion worldRotation = Quaternion.identity;
Vector3 localScale = Vector3.one;
GetKeyboardTransform(ref worldPosition, ref worldRotation, ref localScale);
m_Keyboard = SCKeyboardBase.Open(m_SCKeyboardEnum, m_Text, keyboardType, this.transform, worldPosition, worldRotation, localScale);
m_Keyboard.keyboardMono.stringBuilder.Clear();
m_Keyboard.keyboardMono.stringBuilder.Append(text);
m_Keyboard.keyboardMono.SetTextString();
/*(inputType == InputType.Password) ?
TouchScreenKeyboard.Open(m_Text, keyboardType, false, multiLine, true) :
TouchScreenKeyboard.Open(m_Text, keyboardType, inputType == InputType.AutoCorrect, multiLine);*/
// Mimics OnFocus but as mobile doesn't properly support select all
// just set it to the end of the text (where it would move when typing starts)
MoveTextEnd(false);
}
else
{
input.imeCompositionMode = IMECompositionMode.On;
OnFocus();
}
m_AllowInput = true;
m_OriginalText = text;
m_WasCanceled = false;
SetCaretVisible();
UpdateLabel();
}
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
if (shouldActivateOnSelect)
ActivateInputField();
}
public virtual void OnPointerClick(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left)
return;
ActivateInputField();
}
public void DeactivateInputField()
{
// 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)
text = m_OriginalText;
if (m_Keyboard != null)
{
m_Keyboard.Active = false;
m_Keyboard = null;
}
m_CaretPosition = m_CaretSelectPosition = 0;
SendOnSubmit();
input.imeCompositionMode = IMECompositionMode.Auto;
}
MarkGeometryAsDirty();
}
public override void OnDeselect(BaseEventData eventData)
{
Debug.Log("Deselect");
DeactivateInputField();
base.OnDeselect(eventData);
}
public virtual void OnSubmit(BaseEventData eventData)
{
if (!IsActive() || !IsInteractable())
return;
if (!isFocused)
m_ShouldActivateNextUpdate = true;
}
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.NamePhonePad;
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.Integer;
break;
}
default:
{
// Includes Custom type. Nothing should be enforced.
break;
}
}
EnforceTextHOverflow();
}
void EnforceTextHOverflow()
{
if (m_TextComponent != null)
if (multiLine)
m_TextComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
else
m_TextComponent.horizontalOverflow = HorizontalWrapMode.Overflow;
}
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;
}
protected override void DoStateTransition(SelectionState state, bool instant)
{
if (m_HasDoneFocusTransition)
state = SelectionState.Highlighted;
else if (state == SelectionState.Pressed)
m_HasDoneFocusTransition = true;
base.DoStateTransition(state, instant);
}
public virtual void CalculateLayoutInputHorizontal() { }
public virtual void CalculateLayoutInputVertical() { }
public virtual float minWidth { get { return 0; } }
public virtual float preferredWidth
{
get
{
if (textComponent == null)
return 0;
var settings = textComponent.GetGenerationSettings(Vector2.zero);
return textComponent.cachedTextGeneratorForLayout.GetPreferredWidth(m_Text, settings) / textComponent.pixelsPerUnit;
}
}
public virtual float flexibleWidth { get { return -1; } }
public virtual float minHeight { get { return 0; } }
public virtual float preferredHeight
{
get
{
if (textComponent == null)
return 0;
var settings = textComponent.GetGenerationSettings(new Vector2(textComponent.rectTransform.rect.size.x, 0.0f));
return textComponent.cachedTextGeneratorForLayout.GetPreferredHeight(m_Text, settings) / textComponent.pixelsPerUnit;
}
}
public virtual float flexibleHeight { get { return -1; } }
public virtual int layoutPriority { get { return 1; } }
public void SetKeyboardTransform(Vector3 localPosition, Quaternion localRotation, Vector3 localScale)
{
m_UseCustomTransform = true;
m_CustomPosition = localPosition;
m_CustomRotation = localRotation.eulerAngles;
m_CustomLocalScale = localScale;
Vector3 worldPosition = Vector3.zero;
Quaternion worldRotation = Quaternion.identity;
Vector3 scale = Vector3.one;
GetKeyboardTransform(ref worldPosition, ref worldRotation, ref scale);
ResetKeyboardTransform(worldPosition, worldRotation, scale);
}
public void UseDefaultKeyboardTransform()
{
m_UseCustomTransform = false;
Vector3 worldPosition = Vector3.zero;
Quaternion worldRotation = Quaternion.identity;
Vector3 localScale = Vector3.one;
GetKeyboardTransform(ref worldPosition, ref worldRotation, ref localScale);
ResetKeyboardTransform(worldPosition, worldRotation, localScale);
}
private void ResetKeyboardTransform(Vector3 position, Quaternion rotation, Vector3 localScale)
{
if (m_Keyboard != null && m_Keyboard.Active)
{
m_Keyboard.SetKeyboardTransform(position, rotation, localScale);
}
}
private void GetKeyboardTransform(ref Vector3 worldPosition, ref Quaternion worldRotation, ref Vector3 localScale)
{
Transform head = API_GSXR_Slam.SlamManager.head.transform;
//float inputFieldDistance = Vector3.Distance(this.transform.position, head.transform.position) * 0.5f;
worldPosition = m_UseCustomTransform ? m_CustomPosition : head.transform.position + new Vector3(head.forward.normalized.x, (head.forward.normalized.y - 0.25f), head.forward.normalized.z) * 0.5f;//new Vector3(0f, 0f, -9f);
worldRotation = m_UseCustomTransform ? Quaternion.Euler(m_CustomRotation) : Quaternion.Euler(20f, head.rotation.eulerAngles.y, 0f);
localScale = m_UseCustomTransform ? m_CustomLocalScale : Vector3.one * 0.3f;
}
}
}