using System; using System.Collections; using UnityEngine; namespace NovaSamples.UIControls { /// /// Provides text input on mobile using the apis. /// public class TextFieldMobileInput : TextFieldInputProvider { [SerializeField] [Tooltip("The type of virtual keyboard to bring up when the reference Text Field is focused.")] private TouchScreenKeyboardType keyboardType = TouchScreenKeyboardType.Default; [SerializeField] [Tooltip("Enable the system keyboard's auto correct functionality?")] private bool autoCorrect = true; [SerializeField] [Tooltip("Should the text be masked (e.g. for passwords)?")] private bool secure = false; /// /// The touch screen keyboard controller shared by all text fields. /// private static readonly TouchScreenKeyboardController touchScreenKeyboardController = new TextFieldMobileInput.TouchScreenKeyboardController(); /// /// The coroutine tracking tracking the keyboard visibility and listening for input events. /// private Coroutine inputRoutine = null; /// /// Tracks if we are currently updating the from the touch screen keyboard. /// Used for ignoring the event. /// private bool updatingText = false; protected override void OnEnable() { if (!TouchScreenKeyboard.isSupported) { // If touchscreen keyboard not supported on this device, do nothing return; } base.OnEnable(); // Subscribe to selection changed so we can update the touch screen // keyboard whenever the input field selection changes inputField.OnTextSelectionChanged += UpdateTouchScreenKeyboardSelection; } protected override void OnDisable() { base.OnDisable(); inputField.OnTextSelectionChanged -= UpdateTouchScreenKeyboardSelection; } /// /// Kickoff the input update loop. /// protected override void HandleFocused() { inputRoutine = StartCoroutine(InputLoop()); } /// /// Stop the input update loop and hide the touch screen /// keyboard. /// protected override void HandleFocusLost() { if (inputRoutine != null) { StopCoroutine(inputRoutine); inputRoutine = null; } touchScreenKeyboardController.RequestHide(this); } /// /// Updates the touch screen keyboard's selection from the text input field. /// private void UpdateTouchScreenKeyboardSelection() { if (touchScreenKeyboardController.Status != TouchScreenKeyboard.Status.Visible || !touchScreenKeyboardController.IsCurrentController(this)) { return; } if (updatingText) { // We are in the process of updating the input field, // so ignore this event return; } // Get the display string selection positions inputField.GetSelectionLeftRight(out TextPosition left, out TextPosition right); // Convert the display string positions to raw string positions int start = inputField.StringIndexFromTextPosition(left); int count = inputField.StringIndexFromTextPosition(right) - start; // Update the keyboard selection touchScreenKeyboardController.Selection = new RangeInt(start, count); } /// /// Coroutine to listen for keyboard visibility and input events. /// /// private IEnumerator InputLoop() { // Open the touch screen keyboard touchScreenKeyboardController.RequestShow(this, inputField.Text, new TouchScreenConfig() { KeyboardType = keyboardType, Autocorrection = autoCorrect, Multiline = allowNewlines, Secure = secure, Alert = false }); // Update the touch screen keyboard selection UpdateTouchScreenKeyboardSelection(); // Loop indefinitely, the loop will stop when the coroutine is stopped. while (true) { if (touchScreenKeyboardController.Status != TouchScreenKeyboard.Status.Visible) { // The touch screen keyboard was hidden (for example, if the user clicks somewhere else // on the screen), so unfocus the text input field (our unfocus event handler will // stop this coroutine and do other cleanup). selector.RemoveFocus(); yield break; } if (touchScreenKeyboardController.Text != inputField.Text) { // The keyboard text and input field text don't match, so update the input field updatingText = true; inputField.Text = touchScreenKeyboardController.Text; updatingText = false; } // Convert the touch screen keyboard positions to display string positions TextPosition left = inputField.TextPositionFromStringIndex(touchScreenKeyboardController.Selection.start); TextPosition right = inputField.TextPositionFromStringIndex(touchScreenKeyboardController.Selection.end); if (left.IsValid && right.IsValid) { // Update the input field cursor positions inputField.SetCursorPosition(right, left); } yield return null; } } /// /// Configuration describing the touch screen keyboard. /// private struct TouchScreenConfig : IEquatable { public TouchScreenKeyboardType KeyboardType; public bool Autocorrection; public bool Multiline; public bool Secure; public bool Alert; public bool Equals(TouchScreenConfig other) { return this.KeyboardType == other.KeyboardType && this.Autocorrection == other.Autocorrection && this.Multiline == other.Multiline && this.Secure == other.Secure && this.Alert == other.Alert; } } /// /// Handles control of the touch screen keyboard. Since transferring control from /// one text field to another shouldn't hide the keyboard, we need a single controller that handles /// accessing "Show" and "Hide" requests /// private class TouchScreenKeyboardController { /// /// The active touchscreen keyboard /// private TouchScreenKeyboard keyboard = null; /// /// The current controller of the virtual keyboard /// private TextFieldMobileInput currentKeyboardController = null; /// /// The current configuration of the keyboard /// private TouchScreenConfig currentConfig; public TouchScreenKeyboard.Status Status => keyboard != null ? keyboard.status : TouchScreenKeyboard.Status.LostFocus; public string Text => keyboard != null ? keyboard.text : null; public bool IsCurrentController(TextFieldMobileInput toCheck) => currentKeyboardController == toCheck; public RangeInt Selection { get => keyboard != null ? keyboard.selection : default; set { if (keyboard != null) { keyboard.selection = value; } } } public void RequestShow(TextFieldMobileInput requestor, string text, TouchScreenConfig config) { if (currentKeyboardController == requestor) { Debug.LogWarning("TextFieldMobileInput requested to show virtual keyboard when already controller", requestor); return; } currentKeyboardController = requestor; if (keyboard != null && currentConfig.Equals(config)) { // The config is the same, so just update the text keyboard.text = text; } else { // It's a different type of keyboard, so open a new one currentConfig = config; // Different keyboard type, so re-open keyboard = TouchScreenKeyboard.Open(text, config.KeyboardType, config.Autocorrection, config.Multiline, config.Secure, config.Alert); } } public void RequestHide(TextFieldMobileInput requestor) { if (requestor != currentKeyboardController) { // The requestor is not the current controller, nothing to do return; } currentKeyboardController = null; keyboard.active = false; keyboard = null; currentConfig = default; } } } }