PageRenderTime 69ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/Assets/NGUI/Scripts/UI/UIInput.cs

https://bitbucket.org/Kiyaku/ludum-dare-31
C# | 1491 lines | 1053 code | 215 blank | 223 comment | 402 complexity | 9983cd2fbbf7d3ec24a6afa0d07b5751 MD5 | raw file
  1. //----------------------------------------------
  2. // NGUI: Next-Gen UI kit
  3. // Copyright Š 2011-2014 Tasharen Entertainment
  4. //----------------------------------------------
  5. #if !UNITY_EDITOR && (UNITY_IPHONE || UNITY_ANDROID || UNITY_WP8 || UNITY_BLACKBERRY)
  6. #define MOBILE
  7. #endif
  8. using UnityEngine;
  9. using System.Collections.Generic;
  10. using System.Text;
  11. /// <summary>
  12. /// Input field makes it possible to enter custom information within the UI.
  13. /// </summary>
  14. [AddComponentMenu("NGUI/UI/Input Field")]
  15. public class UIInput : MonoBehaviour
  16. {
  17. public enum InputType
  18. {
  19. Standard,
  20. AutoCorrect,
  21. Password,
  22. }
  23. public enum Validation
  24. {
  25. None,
  26. Integer,
  27. Float,
  28. Alphanumeric,
  29. Username,
  30. Name,
  31. }
  32. public enum KeyboardType
  33. {
  34. Default = 0,
  35. ASCIICapable = 1,
  36. NumbersAndPunctuation = 2,
  37. URL = 3,
  38. NumberPad = 4,
  39. PhonePad = 5,
  40. NamePhonePad = 6,
  41. EmailAddress = 7,
  42. }
  43. public enum OnReturnKey
  44. {
  45. Default,
  46. Submit,
  47. NewLine,
  48. }
  49. public delegate char OnValidate (string text, int charIndex, char addedChar);
  50. /// <summary>
  51. /// Currently active input field. Only valid during callbacks.
  52. /// </summary>
  53. static public UIInput current;
  54. /// <summary>
  55. /// Currently selected input field, if any.
  56. /// </summary>
  57. static public UIInput selection;
  58. /// <summary>
  59. /// Text label used to display the input's value.
  60. /// </summary>
  61. public UILabel label;
  62. /// <summary>
  63. /// Type of data expected by the input field.
  64. /// </summary>
  65. public InputType inputType = InputType.Standard;
  66. /// <summary>
  67. /// What to do when the Return key is pressed on the keyboard.
  68. /// </summary>
  69. public OnReturnKey onReturnKey = OnReturnKey.Default;
  70. /// <summary>
  71. /// Keyboard type applies to mobile keyboards that get shown.
  72. /// </summary>
  73. public KeyboardType keyboardType = KeyboardType.Default;
  74. /// <summary>
  75. /// Whether the input will be hidden on mobile platforms.
  76. /// </summary>
  77. public bool hideInput = false;
  78. /// <summary>
  79. /// Whether all text will be selected when the input field gains focus.
  80. /// </summary>
  81. [System.NonSerialized]
  82. public bool selectAllTextOnFocus = true;
  83. /// <summary>
  84. /// What kind of validation to use with the input field's data.
  85. /// </summary>
  86. public Validation validation = Validation.None;
  87. /// <summary>
  88. /// Maximum number of characters allowed before input no longer works.
  89. /// </summary>
  90. public int characterLimit = 0;
  91. /// <summary>
  92. /// Field in player prefs used to automatically save the value.
  93. /// </summary>
  94. public string savedAs;
  95. /// <summary>
  96. /// Don't use this anymore. Attach UIKeyNavigation instead.
  97. /// </summary>
  98. [HideInInspector][SerializeField] GameObject selectOnTab;
  99. /// <summary>
  100. /// Color of the label when the input field has focus.
  101. /// </summary>
  102. public Color activeTextColor = Color.white;
  103. /// <summary>
  104. /// Color used by the caret symbol.
  105. /// </summary>
  106. public Color caretColor = new Color(1f, 1f, 1f, 0.8f);
  107. /// <summary>
  108. /// Color used by the selection rectangle.
  109. /// </summary>
  110. public Color selectionColor = new Color(1f, 223f / 255f, 141f / 255f, 0.5f);
  111. /// <summary>
  112. /// Event delegates triggered when the input field submits its data.
  113. /// </summary>
  114. public List<EventDelegate> onSubmit = new List<EventDelegate>();
  115. /// <summary>
  116. /// Event delegates triggered when the input field's text changes for any reason.
  117. /// </summary>
  118. public List<EventDelegate> onChange = new List<EventDelegate>();
  119. /// <summary>
  120. /// Custom validation callback.
  121. /// </summary>
  122. public OnValidate onValidate;
  123. /// <summary>
  124. /// Input field's value.
  125. /// </summary>
  126. [SerializeField][HideInInspector] protected string mValue;
  127. [System.NonSerialized] protected string mDefaultText = "";
  128. [System.NonSerialized] protected Color mDefaultColor = Color.white;
  129. [System.NonSerialized] protected float mPosition = 0f;
  130. [System.NonSerialized] protected bool mDoInit = true;
  131. [System.NonSerialized] protected UIWidget.Pivot mPivot = UIWidget.Pivot.TopLeft;
  132. [System.NonSerialized] protected bool mLoadSavedValue = true;
  133. static protected int mDrawStart = 0;
  134. static protected string mLastIME = "";
  135. #if MOBILE
  136. // Unity fails to compile if the touch screen keyboard is used on a non-mobile device
  137. static protected TouchScreenKeyboard mKeyboard;
  138. static bool mWaitForKeyboard = false;
  139. #endif
  140. [System.NonSerialized] protected int mSelectionStart = 0;
  141. [System.NonSerialized] protected int mSelectionEnd = 0;
  142. [System.NonSerialized] protected UITexture mHighlight = null;
  143. [System.NonSerialized] protected UITexture mCaret = null;
  144. [System.NonSerialized] protected Texture2D mBlankTex = null;
  145. [System.NonSerialized] protected float mNextBlink = 0f;
  146. [System.NonSerialized] protected float mLastAlpha = 0f;
  147. [System.NonSerialized] protected string mCached = "";
  148. [System.NonSerialized] protected int mSelectMe = -1;
  149. /// <summary>
  150. /// Default text used by the input's label.
  151. /// </summary>
  152. public string defaultText
  153. {
  154. get
  155. {
  156. if (mDoInit) Init();
  157. return mDefaultText;
  158. }
  159. set
  160. {
  161. if (mDoInit) Init();
  162. mDefaultText = value;
  163. UpdateLabel();
  164. }
  165. }
  166. /// <summary>
  167. /// Should the input be hidden?
  168. /// </summary>
  169. public bool inputShouldBeHidden
  170. {
  171. get
  172. {
  173. #if UNITY_METRO
  174. return true;
  175. #else
  176. return hideInput && label != null && !label.multiLine && inputType != InputType.Password;
  177. #endif
  178. }
  179. }
  180. [System.Obsolete("Use UIInput.value instead")]
  181. public string text { get { return this.value; } set { this.value = value; } }
  182. /// <summary>
  183. /// Input field's current text value.
  184. /// </summary>
  185. public string value
  186. {
  187. get
  188. {
  189. #if UNITY_EDITOR
  190. if (!Application.isPlaying) return "";
  191. #endif
  192. if (mDoInit) Init();
  193. return mValue;
  194. }
  195. set
  196. {
  197. #if UNITY_EDITOR
  198. if (!Application.isPlaying) return;
  199. #endif
  200. if (mDoInit) Init();
  201. mDrawStart = 0;
  202. // BB10's implementation has a bug in Unity
  203. #if UNITY_4_3
  204. if (Application.platform == RuntimePlatform.BB10Player)
  205. #else
  206. if (Application.platform == RuntimePlatform.BlackBerryPlayer)
  207. #endif
  208. value = value.Replace("\\b", "\b");
  209. // Validate all input
  210. value = Validate(value);
  211. #if MOBILE
  212. if (isSelected && mKeyboard != null && mCached != value)
  213. {
  214. mKeyboard.text = value;
  215. mCached = value;
  216. }
  217. #endif
  218. if (mValue != value)
  219. {
  220. mValue = value;
  221. mLoadSavedValue = false;
  222. if (isSelected)
  223. {
  224. if (string.IsNullOrEmpty(value))
  225. {
  226. mSelectionStart = 0;
  227. mSelectionEnd = 0;
  228. }
  229. else
  230. {
  231. mSelectionStart = value.Length;
  232. mSelectionEnd = mSelectionStart;
  233. }
  234. }
  235. else SaveToPlayerPrefs(value);
  236. UpdateLabel();
  237. ExecuteOnChange();
  238. }
  239. }
  240. }
  241. [System.Obsolete("Use UIInput.isSelected instead")]
  242. public bool selected { get { return isSelected; } set { isSelected = value; } }
  243. /// <summary>
  244. /// Whether the input is currently selected.
  245. /// </summary>
  246. public bool isSelected
  247. {
  248. get
  249. {
  250. return selection == this;
  251. }
  252. set
  253. {
  254. if (!value) { if (isSelected) UICamera.selectedObject = null; }
  255. else UICamera.selectedObject = gameObject;
  256. }
  257. }
  258. /// <summary>
  259. /// Current position of the cursor.
  260. /// </summary>
  261. public int cursorPosition
  262. {
  263. get
  264. {
  265. #if MOBILE
  266. if (mKeyboard != null && !inputShouldBeHidden) return value.Length;
  267. #endif
  268. return isSelected ? mSelectionEnd : value.Length;
  269. }
  270. set
  271. {
  272. if (isSelected)
  273. {
  274. #if MOBILE
  275. if (mKeyboard != null && !inputShouldBeHidden) return;
  276. #endif
  277. mSelectionEnd = value;
  278. UpdateLabel();
  279. }
  280. }
  281. }
  282. /// <summary>
  283. /// Index of the character where selection begins.
  284. /// </summary>
  285. public int selectionStart
  286. {
  287. get
  288. {
  289. #if MOBILE
  290. if (mKeyboard != null && !inputShouldBeHidden) return 0;
  291. #endif
  292. return isSelected ? mSelectionStart : value.Length;
  293. }
  294. set
  295. {
  296. if (isSelected)
  297. {
  298. #if MOBILE
  299. if (mKeyboard != null && !inputShouldBeHidden) return;
  300. #endif
  301. mSelectionStart = value;
  302. UpdateLabel();
  303. }
  304. }
  305. }
  306. /// <summary>
  307. /// Index of the character where selection ends.
  308. /// </summary>
  309. public int selectionEnd
  310. {
  311. get
  312. {
  313. #if MOBILE
  314. if (mKeyboard != null && !inputShouldBeHidden) return value.Length;
  315. #endif
  316. return isSelected ? mSelectionEnd : value.Length;
  317. }
  318. set
  319. {
  320. if (isSelected)
  321. {
  322. #if MOBILE
  323. if (mKeyboard != null && !inputShouldBeHidden) return;
  324. #endif
  325. mSelectionEnd = value;
  326. UpdateLabel();
  327. }
  328. }
  329. }
  330. /// <summary>
  331. /// Caret, in case it's needed.
  332. /// </summary>
  333. public UITexture caret { get { return mCaret; } }
  334. /// <summary>
  335. /// Validate the specified text, returning the validated version.
  336. /// </summary>
  337. public string Validate (string val)
  338. {
  339. if (string.IsNullOrEmpty(val)) return "";
  340. StringBuilder sb = new StringBuilder(val.Length);
  341. for (int i = 0; i < val.Length; ++i)
  342. {
  343. char c = val[i];
  344. if (onValidate != null) c = onValidate(sb.ToString(), sb.Length, c);
  345. else if (validation != Validation.None) c = Validate(sb.ToString(), sb.Length, c);
  346. if (c != 0) sb.Append(c);
  347. }
  348. if (characterLimit > 0 && sb.Length > characterLimit)
  349. return sb.ToString(0, characterLimit);
  350. return sb.ToString();
  351. }
  352. /// <summary>
  353. /// Automatically set the value by loading it from player prefs if possible.
  354. /// </summary>
  355. void Start ()
  356. {
  357. if (selectOnTab != null)
  358. {
  359. UIKeyNavigation nav = GetComponent<UIKeyNavigation>();
  360. if (nav == null)
  361. {
  362. nav = gameObject.AddComponent<UIKeyNavigation>();
  363. nav.onDown = selectOnTab;
  364. }
  365. selectOnTab = null;
  366. NGUITools.SetDirty(this);
  367. }
  368. if (mLoadSavedValue && !string.IsNullOrEmpty(savedAs)) LoadValue();
  369. else value = mValue.Replace("\\n", "\n");
  370. }
  371. /// <summary>
  372. /// Labels used for input shouldn't support rich text.
  373. /// </summary>
  374. protected void Init ()
  375. {
  376. if (mDoInit && label != null)
  377. {
  378. mDoInit = false;
  379. mDefaultText = label.text;
  380. mDefaultColor = label.color;
  381. label.supportEncoding = false;
  382. if (label.alignment == NGUIText.Alignment.Justified)
  383. {
  384. label.alignment = NGUIText.Alignment.Left;
  385. Debug.LogWarning("Input fields using labels with justified alignment are not supported at this time", this);
  386. }
  387. mPivot = label.pivot;
  388. mPosition = label.cachedTransform.localPosition.x;
  389. UpdateLabel();
  390. }
  391. }
  392. /// <summary>
  393. /// Save the specified value to player prefs.
  394. /// </summary>
  395. protected void SaveToPlayerPrefs (string val)
  396. {
  397. if (!string.IsNullOrEmpty(savedAs))
  398. {
  399. if (string.IsNullOrEmpty(val)) PlayerPrefs.DeleteKey(savedAs);
  400. else PlayerPrefs.SetString(savedAs, val);
  401. }
  402. }
  403. #if !MOBILE
  404. [System.NonSerialized] UIInputOnGUI mOnGUI;
  405. #endif
  406. /// <summary>
  407. /// Selection event, sent by the EventSystem.
  408. /// </summary>
  409. protected virtual void OnSelect (bool isSelected)
  410. {
  411. if (isSelected)
  412. {
  413. #if !MOBILE
  414. if (mOnGUI == null)
  415. mOnGUI = gameObject.AddComponent<UIInputOnGUI>();
  416. #endif
  417. OnSelectEvent();
  418. }
  419. else
  420. {
  421. #if !MOBILE
  422. if (mOnGUI != null)
  423. {
  424. Destroy(mOnGUI);
  425. mOnGUI = null;
  426. }
  427. #endif
  428. OnDeselectEvent();
  429. }
  430. }
  431. /// <summary>
  432. /// Notification of the input field gaining selection.
  433. /// </summary>
  434. protected void OnSelectEvent ()
  435. {
  436. selection = this;
  437. if (mDoInit) Init();
  438. // Unity has issues bringing up the keyboard properly if it's in "hideInput" mode and you happen
  439. // to select one input in the same Update as de-selecting another.
  440. if (label != null && NGUITools.GetActive(this)) mSelectMe = Time.frameCount;
  441. }
  442. /// <summary>
  443. /// Notification of the input field losing selection.
  444. /// </summary>
  445. protected void OnDeselectEvent ()
  446. {
  447. if (mDoInit) Init();
  448. if (label != null && NGUITools.GetActive(this))
  449. {
  450. mValue = value;
  451. #if MOBILE
  452. if (mKeyboard != null)
  453. {
  454. mWaitForKeyboard = false;
  455. mKeyboard.active = false;
  456. mKeyboard = null;
  457. }
  458. #endif
  459. if (string.IsNullOrEmpty(mValue))
  460. {
  461. label.text = mDefaultText;
  462. label.color = mDefaultColor;
  463. }
  464. else label.text = mValue;
  465. Input.imeCompositionMode = IMECompositionMode.Auto;
  466. RestoreLabelPivot();
  467. }
  468. selection = null;
  469. UpdateLabel();
  470. }
  471. /// <summary>
  472. /// Update the text based on input.
  473. /// </summary>
  474. protected virtual void Update ()
  475. {
  476. #if UNITY_EDITOR
  477. if (!Application.isPlaying) return;
  478. #endif
  479. if (isSelected)
  480. {
  481. if (mDoInit) Init();
  482. #if MOBILE
  483. // Wait for the keyboard to open. Apparently mKeyboard.active will return 'false' for a while in some cases.
  484. if (mWaitForKeyboard)
  485. {
  486. if (mKeyboard != null && !mKeyboard.active) return;
  487. mWaitForKeyboard = false;
  488. }
  489. #endif
  490. // Unity has issues bringing up the keyboard properly if it's in "hideInput" mode and you happen
  491. // to select one input in the same Update as de-selecting another.
  492. if (mSelectMe != -1 && mSelectMe != Time.frameCount)
  493. {
  494. mSelectMe = -1;
  495. mSelectionEnd = string.IsNullOrEmpty(mValue) ? 0 : mValue.Length;
  496. mDrawStart = 0;
  497. mSelectionStart = selectAllTextOnFocus ? 0 : mSelectionEnd;
  498. label.color = activeTextColor;
  499. #if MOBILE
  500. if (Application.platform == RuntimePlatform.IPhonePlayer
  501. || Application.platform == RuntimePlatform.Android
  502. || Application.platform == RuntimePlatform.WP8Player
  503. #if UNITY_4_3
  504. || Application.platform == RuntimePlatform.BB10Player
  505. #else
  506. || Application.platform == RuntimePlatform.BlackBerryPlayer
  507. || Application.platform == RuntimePlatform.MetroPlayerARM
  508. || Application.platform == RuntimePlatform.MetroPlayerX64
  509. || Application.platform == RuntimePlatform.MetroPlayerX86
  510. #endif
  511. )
  512. {
  513. string val;
  514. TouchScreenKeyboardType kt;
  515. if (inputShouldBeHidden)
  516. {
  517. TouchScreenKeyboard.hideInput = true;
  518. kt = (TouchScreenKeyboardType)((int)keyboardType);
  519. #if UNITY_METRO
  520. val = "";
  521. #else
  522. val = "|";
  523. #endif
  524. }
  525. else if (inputType == InputType.Password)
  526. {
  527. TouchScreenKeyboard.hideInput = false;
  528. kt = TouchScreenKeyboardType.Default;
  529. val = mValue;
  530. mSelectionStart = mSelectionEnd;
  531. }
  532. else
  533. {
  534. TouchScreenKeyboard.hideInput = false;
  535. kt = (TouchScreenKeyboardType)((int)keyboardType);
  536. val = mValue;
  537. mSelectionStart = mSelectionEnd;
  538. }
  539. mWaitForKeyboard = true;
  540. mKeyboard = (inputType == InputType.Password) ?
  541. TouchScreenKeyboard.Open(val, kt, false, false, true) :
  542. TouchScreenKeyboard.Open(val, kt, !inputShouldBeHidden && inputType == InputType.AutoCorrect,
  543. label.multiLine && !hideInput, false, false, defaultText);
  544. #if UNITY_METRO
  545. mKeyboard.active = true;
  546. #endif
  547. }
  548. else
  549. #endif // MOBILE
  550. {
  551. Vector2 pos = (UICamera.current != null && UICamera.current.cachedCamera != null) ?
  552. UICamera.current.cachedCamera.WorldToScreenPoint(label.worldCorners[0]) :
  553. label.worldCorners[0];
  554. pos.y = Screen.height - pos.y;
  555. Input.imeCompositionMode = IMECompositionMode.On;
  556. Input.compositionCursorPos = pos;
  557. }
  558. UpdateLabel();
  559. if (string.IsNullOrEmpty(Input.inputString)) return;
  560. }
  561. #if MOBILE
  562. if (mKeyboard != null)
  563. {
  564. #if UNITY_METRO
  565. string text = Input.inputString;
  566. if (!string.IsNullOrEmpty(text)) Insert(text);
  567. #else
  568. string text = mKeyboard.text;
  569. if (inputShouldBeHidden)
  570. {
  571. if (text != "|")
  572. {
  573. if (!string.IsNullOrEmpty(text))
  574. {
  575. Insert(text.Substring(1));
  576. }
  577. else DoBackspace();
  578. mKeyboard.text = "|";
  579. }
  580. }
  581. else if (mCached != text)
  582. {
  583. mCached = text;
  584. value = text;
  585. }
  586. #endif // UNITY_METRO
  587. if (mKeyboard.done || !mKeyboard.active)
  588. {
  589. if (!mKeyboard.wasCanceled) Submit();
  590. mKeyboard = null;
  591. isSelected = false;
  592. mCached = "";
  593. }
  594. }
  595. else
  596. #endif // MOBILE
  597. {
  598. string ime = Input.compositionString;
  599. // There seems to be an inconsistency between IME on Windows, and IME on OSX.
  600. // On Windows, Input.inputString is always empty while IME is active. On the OSX it is not.
  601. if (string.IsNullOrEmpty(ime) && !string.IsNullOrEmpty(Input.inputString))
  602. {
  603. // Process input ignoring non-printable characters as they are not consistent.
  604. // Windows has them, OSX may not. They get handled inside OnGUI() instead.
  605. string s = Input.inputString;
  606. for (int i = 0; i < s.Length; ++i)
  607. {
  608. char ch = s[i];
  609. if (ch < ' ') continue;
  610. // OSX inserts these characters for arrow keys
  611. if (ch == '\uF700') continue;
  612. if (ch == '\uF701') continue;
  613. if (ch == '\uF702') continue;
  614. if (ch == '\uF703') continue;
  615. Insert(ch.ToString());
  616. }
  617. }
  618. // Append IME composition
  619. if (mLastIME != ime)
  620. {
  621. mSelectionEnd = string.IsNullOrEmpty(ime) ? mSelectionStart : mValue.Length + ime.Length;
  622. mLastIME = ime;
  623. UpdateLabel();
  624. ExecuteOnChange();
  625. }
  626. }
  627. // Blink the caret
  628. if (mCaret != null && mNextBlink < RealTime.time)
  629. {
  630. mNextBlink = RealTime.time + 0.5f;
  631. mCaret.enabled = !mCaret.enabled;
  632. }
  633. // If the label's final alpha changes, we need to update the drawn geometry,
  634. // or the highlight widgets (which have their geometry set manually) won't update.
  635. if (isSelected && mLastAlpha != label.finalAlpha)
  636. UpdateLabel();
  637. }
  638. }
  639. /// <summary>
  640. /// Perform a backspace operation.
  641. /// </summary>
  642. protected void DoBackspace ()
  643. {
  644. if (!string.IsNullOrEmpty(mValue))
  645. {
  646. if (mSelectionStart == mSelectionEnd)
  647. {
  648. if (mSelectionStart < 1) return;
  649. --mSelectionEnd;
  650. }
  651. Insert("");
  652. }
  653. }
  654. #if !MOBILE
  655. /// <summary>
  656. /// Handle the specified event.
  657. /// </summary>
  658. public virtual bool ProcessEvent (Event ev)
  659. {
  660. if (label == null) return false;
  661. RuntimePlatform rp = Application.platform;
  662. bool isMac = (
  663. rp == RuntimePlatform.OSXEditor ||
  664. rp == RuntimePlatform.OSXPlayer ||
  665. rp == RuntimePlatform.OSXWebPlayer);
  666. bool ctrl = isMac ?
  667. ((ev.modifiers & EventModifiers.Command) != 0) :
  668. ((ev.modifiers & EventModifiers.Control) != 0);
  669. // http://www.tasharen.com/forum/index.php?topic=10780.0
  670. if ((ev.modifiers & EventModifiers.Alt) != 0) ctrl = false;
  671. bool shift = ((ev.modifiers & EventModifiers.Shift) != 0);
  672. switch (ev.keyCode)
  673. {
  674. case KeyCode.Backspace:
  675. {
  676. ev.Use();
  677. DoBackspace();
  678. return true;
  679. }
  680. case KeyCode.Delete:
  681. {
  682. ev.Use();
  683. if (!string.IsNullOrEmpty(mValue))
  684. {
  685. if (mSelectionStart == mSelectionEnd)
  686. {
  687. if (mSelectionStart >= mValue.Length) return true;
  688. ++mSelectionEnd;
  689. }
  690. Insert("");
  691. }
  692. return true;
  693. }
  694. case KeyCode.LeftArrow:
  695. {
  696. ev.Use();
  697. if (!string.IsNullOrEmpty(mValue))
  698. {
  699. mSelectionEnd = Mathf.Max(mSelectionEnd - 1, 0);
  700. if (!shift) mSelectionStart = mSelectionEnd;
  701. UpdateLabel();
  702. }
  703. return true;
  704. }
  705. case KeyCode.RightArrow:
  706. {
  707. ev.Use();
  708. if (!string.IsNullOrEmpty(mValue))
  709. {
  710. mSelectionEnd = Mathf.Min(mSelectionEnd + 1, mValue.Length);
  711. if (!shift) mSelectionStart = mSelectionEnd;
  712. UpdateLabel();
  713. }
  714. return true;
  715. }
  716. case KeyCode.PageUp:
  717. {
  718. ev.Use();
  719. if (!string.IsNullOrEmpty(mValue))
  720. {
  721. mSelectionEnd = 0;
  722. if (!shift) mSelectionStart = mSelectionEnd;
  723. UpdateLabel();
  724. }
  725. return true;
  726. }
  727. case KeyCode.PageDown:
  728. {
  729. ev.Use();
  730. if (!string.IsNullOrEmpty(mValue))
  731. {
  732. mSelectionEnd = mValue.Length;
  733. if (!shift) mSelectionStart = mSelectionEnd;
  734. UpdateLabel();
  735. }
  736. return true;
  737. }
  738. case KeyCode.Home:
  739. {
  740. ev.Use();
  741. if (!string.IsNullOrEmpty(mValue))
  742. {
  743. if (label.multiLine)
  744. {
  745. mSelectionEnd = label.GetCharacterIndex(mSelectionEnd, KeyCode.Home);
  746. }
  747. else mSelectionEnd = 0;
  748. if (!shift) mSelectionStart = mSelectionEnd;
  749. UpdateLabel();
  750. }
  751. return true;
  752. }
  753. case KeyCode.End:
  754. {
  755. ev.Use();
  756. if (!string.IsNullOrEmpty(mValue))
  757. {
  758. if (label.multiLine)
  759. {
  760. mSelectionEnd = label.GetCharacterIndex(mSelectionEnd, KeyCode.End);
  761. }
  762. else mSelectionEnd = mValue.Length;
  763. if (!shift) mSelectionStart = mSelectionEnd;
  764. UpdateLabel();
  765. }
  766. return true;
  767. }
  768. case KeyCode.UpArrow:
  769. {
  770. ev.Use();
  771. if (!string.IsNullOrEmpty(mValue))
  772. {
  773. mSelectionEnd = label.GetCharacterIndex(mSelectionEnd, KeyCode.UpArrow);
  774. if (mSelectionEnd != 0) mSelectionEnd += mDrawStart;
  775. if (!shift) mSelectionStart = mSelectionEnd;
  776. UpdateLabel();
  777. }
  778. return true;
  779. }
  780. case KeyCode.DownArrow:
  781. {
  782. ev.Use();
  783. if (!string.IsNullOrEmpty(mValue))
  784. {
  785. mSelectionEnd = label.GetCharacterIndex(mSelectionEnd, KeyCode.DownArrow);
  786. if (mSelectionEnd != label.processedText.Length) mSelectionEnd += mDrawStart;
  787. else mSelectionEnd = mValue.Length;
  788. if (!shift) mSelectionStart = mSelectionEnd;
  789. UpdateLabel();
  790. }
  791. return true;
  792. }
  793. // Select all
  794. case KeyCode.A:
  795. {
  796. if (ctrl)
  797. {
  798. ev.Use();
  799. mSelectionStart = 0;
  800. mSelectionEnd = mValue.Length;
  801. UpdateLabel();
  802. }
  803. return true;
  804. }
  805. // Copy
  806. case KeyCode.C:
  807. {
  808. if (ctrl)
  809. {
  810. ev.Use();
  811. NGUITools.clipboard = GetSelection();
  812. }
  813. return true;
  814. }
  815. // Paste
  816. case KeyCode.V:
  817. {
  818. if (ctrl)
  819. {
  820. ev.Use();
  821. Insert(NGUITools.clipboard);
  822. }
  823. return true;
  824. }
  825. // Cut
  826. case KeyCode.X:
  827. {
  828. if (ctrl)
  829. {
  830. ev.Use();
  831. NGUITools.clipboard = GetSelection();
  832. Insert("");
  833. }
  834. return true;
  835. }
  836. // Submit
  837. case KeyCode.Return:
  838. case KeyCode.KeypadEnter:
  839. {
  840. ev.Use();
  841. bool newLine = (onReturnKey == OnReturnKey.NewLine) ||
  842. (onReturnKey == OnReturnKey.Default &&
  843. label.multiLine && !ctrl &&
  844. label.overflowMethod != UILabel.Overflow.ClampContent &&
  845. validation == Validation.None);
  846. if (newLine)
  847. {
  848. Insert("\n");
  849. }
  850. else
  851. {
  852. UICamera.currentScheme = UICamera.ControlScheme.Controller;
  853. UICamera.currentKey = ev.keyCode;
  854. Submit();
  855. UICamera.currentKey = KeyCode.None;
  856. }
  857. return true;
  858. }
  859. }
  860. return false;
  861. }
  862. #endif
  863. /// <summary>
  864. /// Insert the specified text string into the current input value, respecting selection and validation.
  865. /// </summary>
  866. protected virtual void Insert (string text)
  867. {
  868. string left = GetLeftText();
  869. string right = GetRightText();
  870. int rl = right.Length;
  871. StringBuilder sb = new StringBuilder(left.Length + right.Length + text.Length);
  872. sb.Append(left);
  873. // Append the new text
  874. for (int i = 0, imax = text.Length; i < imax; ++i)
  875. {
  876. // If we have an input validator, validate the input first
  877. char c = text[i];
  878. if (c == '\b')
  879. {
  880. DoBackspace();
  881. continue;
  882. }
  883. // Can't go past the character limit
  884. if (characterLimit > 0 && sb.Length + rl >= characterLimit) break;
  885. if (onValidate != null) c = onValidate(sb.ToString(), sb.Length, c);
  886. else if (validation != Validation.None) c = Validate(sb.ToString(), sb.Length, c);
  887. // Append the character if it hasn't been invalidated
  888. if (c != 0) sb.Append(c);
  889. }
  890. // Advance the selection
  891. mSelectionStart = sb.Length;
  892. mSelectionEnd = mSelectionStart;
  893. // Append the text that follows it, ensuring that it's also validated after the inserted value
  894. for (int i = 0, imax = right.Length; i < imax; ++i)
  895. {
  896. char c = right[i];
  897. if (onValidate != null) c = onValidate(sb.ToString(), sb.Length, c);
  898. else if (validation != Validation.None) c = Validate(sb.ToString(), sb.Length, c);
  899. if (c != 0) sb.Append(c);
  900. }
  901. mValue = sb.ToString();
  902. UpdateLabel();
  903. ExecuteOnChange();
  904. }
  905. /// <summary>
  906. /// Get the text to the left of the selection.
  907. /// </summary>
  908. protected string GetLeftText ()
  909. {
  910. int min = Mathf.Min(mSelectionStart, mSelectionEnd);
  911. return (string.IsNullOrEmpty(mValue) || min < 0) ? "" : mValue.Substring(0, min);
  912. }
  913. /// <summary>
  914. /// Get the text to the right of the selection.
  915. /// </summary>
  916. protected string GetRightText ()
  917. {
  918. int max = Mathf.Max(mSelectionStart, mSelectionEnd);
  919. return (string.IsNullOrEmpty(mValue) || max >= mValue.Length) ? "" : mValue.Substring(max);
  920. }
  921. /// <summary>
  922. /// Get currently selected text.
  923. /// </summary>
  924. protected string GetSelection ()
  925. {
  926. if (string.IsNullOrEmpty(mValue) || mSelectionStart == mSelectionEnd)
  927. {
  928. return "";
  929. }
  930. else
  931. {
  932. int min = Mathf.Min(mSelectionStart, mSelectionEnd);
  933. int max = Mathf.Max(mSelectionStart, mSelectionEnd);
  934. return mValue.Substring(min, max - min);
  935. }
  936. }
  937. /// <summary>
  938. /// Helper function that retrieves the index of the character under the mouse.
  939. /// </summary>
  940. protected int GetCharUnderMouse ()
  941. {
  942. Vector3[] corners = label.worldCorners;
  943. Ray ray = UICamera.currentRay;
  944. Plane p = new Plane(corners[0], corners[1], corners[2]);
  945. float dist;
  946. return p.Raycast(ray, out dist) ? mDrawStart + label.GetCharacterIndexAtPosition(ray.GetPoint(dist), false) : 0;
  947. }
  948. /// <summary>
  949. /// Move the caret on press.
  950. /// </summary>
  951. protected virtual void OnPress (bool isPressed)
  952. {
  953. if (isPressed && isSelected && label != null &&
  954. (UICamera.currentScheme == UICamera.ControlScheme.Mouse ||
  955. UICamera.currentScheme == UICamera.ControlScheme.Touch))
  956. {
  957. selectionEnd = GetCharUnderMouse();
  958. if (!Input.GetKey(KeyCode.LeftShift) &&
  959. !Input.GetKey(KeyCode.RightShift)) selectionStart = mSelectionEnd;
  960. }
  961. }
  962. /// <summary>
  963. /// Drag selection.
  964. /// </summary>
  965. protected virtual void OnDrag (Vector2 delta)
  966. {
  967. if (label != null &&
  968. (UICamera.currentScheme == UICamera.ControlScheme.Mouse ||
  969. UICamera.currentScheme == UICamera.ControlScheme.Touch))
  970. {
  971. selectionEnd = GetCharUnderMouse();
  972. }
  973. }
  974. /// <summary>
  975. /// Ensure we've released the dynamically created resources.
  976. /// </summary>
  977. void OnDisable () { Cleanup(); }
  978. /// <summary>
  979. /// Cleanup.
  980. /// </summary>
  981. protected virtual void Cleanup ()
  982. {
  983. if (mHighlight) mHighlight.enabled = false;
  984. if (mCaret) mCaret.enabled = false;
  985. if (mBlankTex)
  986. {
  987. NGUITools.Destroy(mBlankTex);
  988. mBlankTex = null;
  989. }
  990. }
  991. /// <summary>
  992. /// Submit the input field's text.
  993. /// </summary>
  994. public void Submit ()
  995. {
  996. if (NGUITools.GetActive(this))
  997. {
  998. mValue = value;
  999. if (current == null)
  1000. {
  1001. current = this;
  1002. EventDelegate.Execute(onSubmit);
  1003. current = null;
  1004. }
  1005. SaveToPlayerPrefs(mValue);
  1006. }
  1007. }
  1008. /// <summary>
  1009. /// Update the visual text label.
  1010. /// </summary>
  1011. public void UpdateLabel ()
  1012. {
  1013. if (label != null)
  1014. {
  1015. if (mDoInit) Init();
  1016. bool selected = isSelected;
  1017. string fullText = value;
  1018. bool isEmpty = string.IsNullOrEmpty(fullText) && string.IsNullOrEmpty(Input.compositionString);
  1019. label.color = (isEmpty && !selected) ? mDefaultColor : activeTextColor;
  1020. string processed;
  1021. if (isEmpty)
  1022. {
  1023. processed = selected ? "" : mDefaultText;
  1024. RestoreLabelPivot();
  1025. }
  1026. else
  1027. {
  1028. if (inputType == InputType.Password)
  1029. {
  1030. processed = "";
  1031. string asterisk = "*";
  1032. if (label.bitmapFont != null && label.bitmapFont.bmFont != null &&
  1033. label.bitmapFont.bmFont.GetGlyph('*') == null) asterisk = "x";
  1034. for (int i = 0, imax = fullText.Length; i < imax; ++i) processed += asterisk;
  1035. }
  1036. else processed = fullText;
  1037. // Start with text leading up to the selection
  1038. int selPos = selected ? Mathf.Min(processed.Length, cursorPosition) : 0;
  1039. string left = processed.Substring(0, selPos);
  1040. // Append the composition string and the cursor character
  1041. if (selected) left += Input.compositionString;
  1042. // Append the text from the selection onwards
  1043. processed = left + processed.Substring(selPos, processed.Length - selPos);
  1044. // Clamped content needs to be adjusted further
  1045. if (selected && label.overflowMethod == UILabel.Overflow.ClampContent && label.maxLineCount == 1)
  1046. {
  1047. // Determine what will actually fit into the given line
  1048. int offset = label.CalculateOffsetToFit(processed);
  1049. if (offset == 0)
  1050. {
  1051. mDrawStart = 0;
  1052. RestoreLabelPivot();
  1053. }
  1054. else if (selPos < mDrawStart)
  1055. {
  1056. mDrawStart = selPos;
  1057. SetPivotToLeft();
  1058. }
  1059. else if (offset < mDrawStart)
  1060. {
  1061. mDrawStart = offset;
  1062. SetPivotToLeft();
  1063. }
  1064. else
  1065. {
  1066. offset = label.CalculateOffsetToFit(processed.Substring(0, selPos));
  1067. if (offset > mDrawStart)
  1068. {
  1069. mDrawStart = offset;
  1070. SetPivotToRight();
  1071. }
  1072. }
  1073. // If necessary, trim the front
  1074. if (mDrawStart != 0)
  1075. processed = processed.Substring(mDrawStart, processed.Length - mDrawStart);
  1076. }
  1077. else
  1078. {
  1079. mDrawStart = 0;
  1080. RestoreLabelPivot();
  1081. }
  1082. }
  1083. label.text = processed;
  1084. #if MOBILE
  1085. if (selected && (mKeyboard == null || inputShouldBeHidden))
  1086. #else
  1087. if (selected)
  1088. #endif
  1089. {
  1090. int start = mSelectionStart - mDrawStart;
  1091. int end = mSelectionEnd - mDrawStart;
  1092. // Blank texture used by selection and caret
  1093. if (mBlankTex == null)
  1094. {
  1095. mBlankTex = new Texture2D(2, 2, TextureFormat.ARGB32, false);
  1096. for (int y = 0; y < 2; ++y)
  1097. for (int x = 0; x < 2; ++x)
  1098. mBlankTex.SetPixel(x, y, Color.white);
  1099. mBlankTex.Apply();
  1100. }
  1101. // Create the selection highlight
  1102. if (start != end)
  1103. {
  1104. if (mHighlight == null)
  1105. {
  1106. mHighlight = NGUITools.AddWidget<UITexture>(label.cachedGameObject);
  1107. mHighlight.name = "Input Highlight";
  1108. mHighlight.mainTexture = mBlankTex;
  1109. mHighlight.fillGeometry = false;
  1110. mHighlight.pivot = label.pivot;
  1111. mHighlight.SetAnchor(label.cachedTransform);
  1112. }
  1113. else
  1114. {
  1115. mHighlight.pivot = label.pivot;
  1116. mHighlight.mainTexture = mBlankTex;
  1117. mHighlight.MarkAsChanged();
  1118. mHighlight.enabled = true;
  1119. }
  1120. }
  1121. // Create the carter
  1122. if (mCaret == null)
  1123. {
  1124. mCaret = NGUITools.AddWidget<UITexture>(label.cachedGameObject);
  1125. mCaret.name = "Input Caret";
  1126. mCaret.mainTexture = mBlankTex;
  1127. mCaret.fillGeometry = false;
  1128. mCaret.pivot = label.pivot;
  1129. mCaret.SetAnchor(label.cachedTransform);
  1130. }
  1131. else
  1132. {
  1133. mCaret.pivot = label.pivot;
  1134. mCaret.mainTexture = mBlankTex;
  1135. mCaret.MarkAsChanged();
  1136. mCaret.enabled = true;
  1137. }
  1138. if (start != end)
  1139. {
  1140. label.PrintOverlay(start, end, mCaret.geometry, mHighlight.geometry, caretColor, selectionColor);
  1141. mHighlight.enabled = mHighlight.geometry.hasVertices;
  1142. }
  1143. else
  1144. {
  1145. label.PrintOverlay(start, end, mCaret.geometry, null, caretColor, selectionColor);
  1146. if (mHighlight != null) mHighlight.enabled = false;
  1147. }
  1148. // Reset the blinking time
  1149. mNextBlink = RealTime.time + 0.5f;
  1150. mLastAlpha = label.finalAlpha;
  1151. }
  1152. else Cleanup();
  1153. }
  1154. }
  1155. /// <summary>
  1156. /// Set the label's pivot to the left.
  1157. /// </summary>
  1158. protected void SetPivotToLeft ()
  1159. {
  1160. Vector2 po = NGUIMath.GetPivotOffset(mPivot);
  1161. po.x = 0f;
  1162. label.pivot = NGUIMath.GetPivot(po);
  1163. }
  1164. /// <summary>
  1165. /// Set the label's pivot to the right.
  1166. /// </summary>
  1167. protected void SetPivotToRight ()
  1168. {
  1169. Vector2 po = NGUIMath.GetPivotOffset(mPivot);
  1170. po.x = 1f;
  1171. label.pivot = NGUIMath.GetPivot(po);
  1172. }
  1173. /// <summary>
  1174. /// Restore the input label's pivot point.
  1175. /// </summary>
  1176. protected void RestoreLabelPivot ()
  1177. {
  1178. if (label != null && label.pivot != mPivot)
  1179. label.pivot = mPivot;
  1180. }
  1181. /// <summary>
  1182. /// Validate the specified input.
  1183. /// </summary>
  1184. protected char Validate (string text, int pos, char ch)
  1185. {
  1186. // Validation is disabled
  1187. if (validation == Validation.None || !enabled) return ch;
  1188. if (validation == Validation.Integer)
  1189. {
  1190. // Integer number validation
  1191. if (ch >= '0' && ch <= '9') return ch;
  1192. if (ch == '-' && pos == 0 && !text.Contains("-")) return ch;
  1193. }
  1194. else if (validation == Validation.Float)
  1195. {
  1196. // Floating-point number
  1197. if (ch >= '0' && ch <= '9') return ch;
  1198. if (ch == '-' && pos == 0 && !text.Contains("-")) return ch;
  1199. if (ch == '.' && !text.Contains(".")) return ch;
  1200. }
  1201. else if (validation == Validation.Alphanumeric)
  1202. {
  1203. // All alphanumeric characters
  1204. if (ch >= 'A' && ch <= 'Z') return ch;
  1205. if (ch >= 'a' && ch <= 'z') return ch;
  1206. if (ch >= '0' && ch <= '9') return ch;
  1207. }
  1208. else if (validation == Validation.Username)
  1209. {
  1210. // Lowercase and numbers
  1211. if (ch >= 'A' && ch <= 'Z') return (char)(ch - 'A' + 'a');
  1212. if (ch >= 'a' && ch <= 'z') return ch;
  1213. if (ch >= '0' && ch <= '9') return ch;
  1214. }
  1215. else if (validation == Validation.Name)
  1216. {
  1217. char lastChar = (text.Length > 0) ? text[Mathf.Clamp(pos, 0, text.Length - 1)] : ' ';
  1218. char nextChar = (text.Length > 0) ? text[Mathf.Clamp(pos + 1, 0, text.Length - 1)] : '\n';
  1219. if (ch >= 'a' && ch <= 'z')
  1220. {
  1221. // Space followed by a letter -- make sure it's capitalized
  1222. if (lastChar == ' ') return (char)(ch - 'a' + 'A');
  1223. return ch;
  1224. }
  1225. else if (ch >= 'A' && ch <= 'Z')
  1226. {
  1227. // Uppercase letters are only allowed after spaces (and apostrophes)
  1228. if (lastChar != ' ' && lastChar != '\'') return (char)(ch - 'A' + 'a');
  1229. return ch;
  1230. }
  1231. else if (ch == '\'')
  1232. {
  1233. // Don't allow more than one apostrophe
  1234. if (lastChar != ' ' && lastChar != '\'' && nextChar != '\'' && !text.Contains("'")) return ch;
  1235. }
  1236. else if (ch == ' ')
  1237. {
  1238. // Don't allow more than one space in a row
  1239. if (lastChar != ' ' && lastChar != '\'' && nextChar != ' ' && nextChar != '\'') return ch;
  1240. }
  1241. }
  1242. return (char)0;
  1243. }
  1244. /// <summary>
  1245. /// Execute the OnChange callback.
  1246. /// </summary>
  1247. protected void ExecuteOnChange ()
  1248. {
  1249. if (current == null && EventDelegate.IsValid(onChange))
  1250. {
  1251. current = this;
  1252. EventDelegate.Execute(onChange);
  1253. current = null;
  1254. }
  1255. }
  1256. /// <summary>
  1257. /// Convenience function to be used as a callback that will clear the input field's focus.
  1258. /// </summary>
  1259. public void RemoveFocus () { isSelected = false; }
  1260. /// <summary>
  1261. /// Convenience function that can be used as a callback for On Change notification.
  1262. /// </summary>
  1263. public void SaveValue () { SaveToPlayerPrefs(mValue); }
  1264. /// <summary>
  1265. /// Convenience function that can forcefully reset the input field's value to what was saved earlier.
  1266. /// </summary>
  1267. public void LoadValue ()
  1268. {
  1269. if (!string.IsNullOrEmpty(savedAs))
  1270. {
  1271. string val = mValue.Replace("\\n", "\n");
  1272. mValue = "";
  1273. value = PlayerPrefs.HasKey(savedAs) ? PlayerPrefs.GetString(savedAs) : val;
  1274. }
  1275. }
  1276. }