622 lines
20 KiB
C#
622 lines
20 KiB
C#
// ----------------------------------------------------------------------------
|
|
// The MIT License
|
|
// UnityMobileInput https://github.com/mopsicus/UnityMobileInput
|
|
// Copyright (c) 2018 Mopsicus <mail@mopsicus.ru>
|
|
// ----------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using NiceJson;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Mopsicus.Plugins {
|
|
|
|
/// <summary>
|
|
/// Wrapper for Unity InputField
|
|
/// Add this component on your InputField
|
|
/// </summary>
|
|
[RequireComponent (typeof (InputField))]
|
|
public class MobileInputField : MobileInputReceiver {
|
|
|
|
/// <summary>
|
|
/// Config structure
|
|
/// </summary>
|
|
private struct MobileInputConfig {
|
|
public bool Multiline;
|
|
public Color TextColor;
|
|
public Color BackgroundColor;
|
|
public string ContentType;
|
|
public string InputType;
|
|
public string KeyboardType;
|
|
public float FontSize;
|
|
public string Align;
|
|
public string Placeholder;
|
|
public Color PlaceholderColor;
|
|
public int CharacterLimit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Button type
|
|
/// </summary>
|
|
public enum ReturnKeyType {
|
|
Default,
|
|
Next,
|
|
Done,
|
|
Search
|
|
}
|
|
|
|
/// <summary>
|
|
/// "Done" button visible (for iOS)
|
|
/// </summary>
|
|
public bool IsWithDoneButton = true;
|
|
|
|
/// <summary>
|
|
/// "(x)" button visible (for iOS)
|
|
/// </summary>
|
|
public bool IsWithClearButton = true;
|
|
|
|
/// <summary>
|
|
/// Type for return button
|
|
/// </summary>
|
|
public ReturnKeyType ReturnKey;
|
|
|
|
/// <summary>
|
|
/// Action when Return pressed, for subscribe
|
|
/// </summary>
|
|
public Action OnReturnPressed = delegate { };
|
|
|
|
/// <summary>
|
|
/// Action when Focus changed
|
|
/// </summary>
|
|
public Action<bool> OnFocusChanged = delegate { };
|
|
|
|
/// <summary>
|
|
/// Event when Return pressed, for Unity inspector
|
|
/// </summary>
|
|
public UnityEvent OnReturnPressedEvent;
|
|
|
|
/// <summary>
|
|
/// Mobile input creation flag
|
|
/// </summary>
|
|
private bool _isMobileInputCreated = false;
|
|
|
|
/// <summary>
|
|
/// InputField object
|
|
/// </summary>
|
|
private InputField _inputObject;
|
|
|
|
/// <summary>
|
|
/// Text object from _inputObject
|
|
/// </summary>
|
|
private Text _inputObjectText;
|
|
|
|
/// <summary>
|
|
/// Set focus on create flag
|
|
/// </summary>
|
|
private bool _isFocusOnCreate;
|
|
|
|
/// <summary>
|
|
/// Set visible on create flag
|
|
/// </summary>
|
|
private bool _isVisibleOnCreate = true;
|
|
|
|
/// <summary>
|
|
/// Last field position cache
|
|
/// </summary>
|
|
private Rect _lastRect;
|
|
|
|
/// <summary>
|
|
/// Current config
|
|
/// </summary>
|
|
private MobileInputConfig _config;
|
|
|
|
/// <summary>
|
|
/// InputField create event
|
|
/// </summary>
|
|
const string CREATE = "CREATE_EDIT";
|
|
|
|
/// <summary>
|
|
/// InputField remove event
|
|
/// </summary>
|
|
const string REMOVE = "REMOVE_EDIT";
|
|
|
|
/// <summary>
|
|
/// Set text to InputField
|
|
/// </summary>
|
|
const string SET_TEXT = "SET_TEXT";
|
|
|
|
/// <summary>
|
|
/// Set new Rect, position, size
|
|
/// </summary>
|
|
const string SET_RECT = "SET_RECT";
|
|
|
|
/// <summary>
|
|
/// Set focus to InputField
|
|
/// </summary>
|
|
const string SET_FOCUS = "SET_FOCUS";
|
|
|
|
/// <summary>
|
|
/// Event when InputField is focused
|
|
/// </summary>
|
|
const string ON_FOCUS = "ON_FOCUS";
|
|
|
|
/// <summary>
|
|
/// Event when InputField is unfocused
|
|
/// </summary>
|
|
const string ON_UNFOCUS = "ON_UNFOCUS";
|
|
|
|
/// <summary>
|
|
/// Set visible to InputField
|
|
/// </summary>
|
|
const string SET_VISIBLE = "SET_VISIBLE";
|
|
|
|
/// <summary>
|
|
/// Event when text changing in InputField
|
|
/// </summary>
|
|
const string TEXT_CHANGE = "TEXT_CHANGE";
|
|
|
|
/// <summary>
|
|
/// Event when text end changing in InputField
|
|
/// </summary>
|
|
const string TEXT_END_EDIT = "TEXT_END_EDIT";
|
|
|
|
/// <summary>
|
|
/// Event for Android
|
|
/// </summary>
|
|
const string ANDROID_KEY_DOWN = "ANDROID_KEY_DOWN";
|
|
|
|
/// <summary>
|
|
/// Event when Return key pressed
|
|
/// </summary>
|
|
const string RETURN_PRESSED = "RETURN_PRESSED";
|
|
|
|
/// <summary>
|
|
/// Ready event
|
|
/// </summary>
|
|
const string READY = "READY";
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
private void Awake () {
|
|
_inputObject = this.GetComponent<InputField> ();
|
|
if ((object) _inputObject == null) {
|
|
Debug.LogError (string.Format ("No found InputField for {0} MobileInput", this.name));
|
|
throw new MissingComponentException ();
|
|
}
|
|
_inputObjectText = _inputObject.textComponent;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create mobile input on Start with coroutine
|
|
/// </summary>
|
|
protected override void Start () {
|
|
base.Start ();
|
|
StartCoroutine (InitialzieOnNextFrame ());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show native on enable
|
|
/// </summary>
|
|
private void OnEnable () {
|
|
if (_isMobileInputCreated) {
|
|
this.SetVisible (true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hide native on disable
|
|
/// </summary>
|
|
private void OnDisable () {
|
|
if (_isMobileInputCreated) {
|
|
this.SetFocus (false);
|
|
this.SetVisible (false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destructor
|
|
/// </summary>
|
|
protected override void OnDestroy () {
|
|
RemoveNative ();
|
|
base.OnDestroy ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for app focus lost
|
|
/// </summary>
|
|
private void OnApplicationFocus (bool hasFocus) {
|
|
if (!_isMobileInputCreated || !this.Visible) {
|
|
return;
|
|
}
|
|
this.SetVisible (hasFocus);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current InputField for external access
|
|
/// </summary>
|
|
public InputField InputField {
|
|
get {
|
|
return _inputObject;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// MobileInput visible
|
|
/// </summary>
|
|
public bool Visible {
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// MobileInput text
|
|
/// </summary>
|
|
public string Text {
|
|
get {
|
|
return _inputObject.text;
|
|
}
|
|
set {
|
|
_inputObject.text = value;
|
|
SetTextNative (value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialization coroutine
|
|
/// </summary>
|
|
private IEnumerator InitialzieOnNextFrame () {
|
|
yield return null;
|
|
this.PrepareNativeEdit ();
|
|
#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR
|
|
this.CreateNativeEdit ();
|
|
this.SetTextNative (this._inputObjectText.text);
|
|
_inputObject.placeholder.gameObject.SetActive (false);
|
|
_inputObject.enabled = false;
|
|
_inputObjectText.enabled = false;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check position on each frame
|
|
/// If changed - send to plugin
|
|
/// It's need when app rotate on input field chage position
|
|
/// </summary>
|
|
private void Update () {
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
this.UpdateForceKeyeventForAndroid ();
|
|
#endif
|
|
if (this._inputObject != null && _isMobileInputCreated) {
|
|
#if !UNITY_EDITOR
|
|
if (Input.touchCount > 0) {
|
|
for (int i = 0; i < Input.touches.Length; i++) {
|
|
if (!this._inputObjectText.rectTransform.rect.Contains (Input.touches[i].position)) {
|
|
Hide ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
SetRectNative (this._inputObjectText.rectTransform);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get sizes and convert to current screen size
|
|
/// </summary>
|
|
/// <param name="rect">RectTranform from Gameobject</param>
|
|
/// <returns>Rect</returns>
|
|
public static Rect GetScreenRectFromRectTransform (RectTransform rect) {
|
|
Vector3[] corners = new Vector3[4];
|
|
rect.GetWorldCorners (corners);
|
|
float xMin = float.PositiveInfinity;
|
|
float xMax = float.NegativeInfinity;
|
|
float yMin = float.PositiveInfinity;
|
|
float yMax = float.NegativeInfinity;
|
|
for (int i = 0; i < 4; i++) {
|
|
Vector3 screenCoord;
|
|
if (rect.GetComponentInParent<Canvas> ().renderMode == RenderMode.ScreenSpaceOverlay) {
|
|
screenCoord = corners[i];
|
|
} else {
|
|
screenCoord = RectTransformUtility.WorldToScreenPoint (Camera.main, corners[i]);
|
|
}
|
|
if (screenCoord.x < xMin) {
|
|
xMin = screenCoord.x;
|
|
}
|
|
if (screenCoord.x > xMax) {
|
|
xMax = screenCoord.x;
|
|
}
|
|
if (screenCoord.y < yMin) {
|
|
yMin = screenCoord.y;
|
|
}
|
|
if (screenCoord.y > yMax) {
|
|
yMax = screenCoord.y;
|
|
}
|
|
}
|
|
Rect result = new Rect (xMin, Screen.height - yMax, xMax - xMin, yMax - yMin);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare config
|
|
/// </summary>
|
|
private void PrepareNativeEdit () {
|
|
Text placeHolder = _inputObject.placeholder.GetComponent<Text> ();
|
|
_config.Placeholder = placeHolder.text;
|
|
_config.PlaceholderColor = placeHolder.color;
|
|
_config.CharacterLimit = _inputObject.characterLimit;
|
|
Rect rect = GetScreenRectFromRectTransform (this._inputObjectText.rectTransform);
|
|
float ratio = rect.height / _inputObjectText.rectTransform.rect.height;
|
|
_config.FontSize = ((float) _inputObjectText.fontSize) * ratio;
|
|
_config.TextColor = _inputObjectText.color;
|
|
_config.Align = _inputObjectText.alignment.ToString ();
|
|
_config.ContentType = _inputObject.contentType.ToString ();
|
|
_config.BackgroundColor = _inputObject.colors.normalColor;
|
|
_config.Multiline = (_inputObject.lineType == InputField.LineType.SingleLine) ? false : true;
|
|
_config.KeyboardType = _inputObject.keyboardType.ToString ();
|
|
_config.InputType = _inputObject.inputType.ToString ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text change callback
|
|
/// </summary>
|
|
/// <param name="text">new text</param>
|
|
private void OnTextChange (string text) {
|
|
if (text == this._inputObject.text) {
|
|
return;
|
|
}
|
|
this._inputObject.text = text;
|
|
if (this._inputObject.onValueChanged != null) {
|
|
this._inputObject.onValueChanged.Invoke (text);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text change end callback
|
|
/// </summary>
|
|
/// <param name="text">text</param>
|
|
private void OnTextEditEnd (string text) {
|
|
this._inputObject.text = text;
|
|
if (this._inputObject.onEndEdit != null) {
|
|
this._inputObject.onEndEdit.Invoke (text);
|
|
}
|
|
SetFocus (false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sending data to plugin
|
|
/// </summary>
|
|
/// <param name="data">JSON</param>
|
|
public override void Send (JsonObject data) {
|
|
MobileInput.Plugin.StartCoroutine (PluginsMessageRoutine (data));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove focus, keyboard when app lose focus
|
|
/// </summary>
|
|
public override void Hide () {
|
|
this.SetFocus (false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine for send, so its not freeze main thread
|
|
/// </summary>
|
|
/// <param name="data">JSON</param>
|
|
private IEnumerator PluginsMessageRoutine (JsonObject data) {
|
|
yield return null;
|
|
string msg = data["msg"];
|
|
if (msg.Equals (TEXT_CHANGE)) {
|
|
string text = data["text"];
|
|
this.OnTextChange (text);
|
|
} else if (msg.Equals (READY)) {
|
|
this.Ready ();
|
|
} else if (msg.Equals (ON_FOCUS)) {
|
|
OnFocusChanged (true);
|
|
} else if (msg.Equals (ON_UNFOCUS)) {
|
|
OnFocusChanged (false);
|
|
} else if (msg.Equals (TEXT_END_EDIT)) {
|
|
string text = data["text"];
|
|
this.OnTextEditEnd (text);
|
|
} else if (msg.Equals (RETURN_PRESSED)) {
|
|
OnReturnPressed ();
|
|
if (OnReturnPressedEvent != null) {
|
|
OnReturnPressedEvent.Invoke ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert float value to InvariantCulture string
|
|
/// </summary>
|
|
/// <param name="value">float value</param>
|
|
/// <returns></returns>
|
|
private string InvariantCultureString (float value){
|
|
return value.ToString ("G", System.Globalization.CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create native input field
|
|
/// </summary>
|
|
private void CreateNativeEdit () {
|
|
Rect rect = GetScreenRectFromRectTransform (this._inputObjectText.rectTransform);
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = CREATE;
|
|
data["x"] = InvariantCultureString(rect.x / Screen.width);
|
|
data["y"] = InvariantCultureString(rect.y / Screen.height);
|
|
data["width"] = InvariantCultureString(rect.width / Screen.width);
|
|
data["height"] = InvariantCultureString(rect.height / Screen.height);
|
|
data["character_limit"] = _config.CharacterLimit;
|
|
data["text_color_r"] = InvariantCultureString(_config.TextColor.r);
|
|
data["text_color_g"] = InvariantCultureString(_config.TextColor.g);
|
|
data["text_color_b"] = InvariantCultureString(_config.TextColor.b);
|
|
data["text_color_a"] = InvariantCultureString(_config.TextColor.a);
|
|
data["back_color_r"] = InvariantCultureString(_config.BackgroundColor.r);
|
|
data["back_color_g"] = InvariantCultureString(_config.BackgroundColor.g);
|
|
data["back_color_b"] = InvariantCultureString(_config.BackgroundColor.b);
|
|
data["back_color_a"] = InvariantCultureString(_config.BackgroundColor.a);
|
|
data["font_size"] = InvariantCultureString(_config.FontSize);
|
|
data["content_type"] = _config.ContentType;
|
|
data["align"] = _config.Align;
|
|
data["with_done_button"] = this.IsWithDoneButton;
|
|
data["with_clear_button"] = this.IsWithClearButton;
|
|
data["placeholder"] = _config.Placeholder;
|
|
data["placeholder_color_r"] = InvariantCultureString(_config.PlaceholderColor.r);
|
|
data["placeholder_color_g"] = InvariantCultureString(_config.PlaceholderColor.g);
|
|
data["placeholder_color_b"] = InvariantCultureString(_config.PlaceholderColor.b);
|
|
data["placeholder_color_a"] = InvariantCultureString(_config.PlaceholderColor.a);
|
|
data["multiline"] = _config.Multiline;
|
|
data["input_type"] = _config.InputType;
|
|
data["keyboard_type"] = _config.KeyboardType;
|
|
switch (ReturnKey) {
|
|
case ReturnKeyType.Next:
|
|
data["return_key_type"] = "Next";
|
|
break;
|
|
case ReturnKeyType.Done:
|
|
data["return_key_type"] = "Done";
|
|
break;
|
|
case ReturnKeyType.Search:
|
|
data["return_key_type"] = "Search";
|
|
break;
|
|
default:
|
|
data["return_key_type"] = "Default";
|
|
break;
|
|
}
|
|
|
|
this.Execute (data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// New field successfully added
|
|
/// </summary>
|
|
void Ready () {
|
|
_isMobileInputCreated = true;
|
|
if (!_isVisibleOnCreate) {
|
|
SetVisible (false);
|
|
}
|
|
if (_isFocusOnCreate) {
|
|
SetFocus (true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set text to field
|
|
/// </summary>
|
|
/// <param name="text">New text</param>
|
|
void SetTextNative (string text) {
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = SET_TEXT;
|
|
data["text"] = text;
|
|
this.Execute (data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove field
|
|
/// </summary>
|
|
private void RemoveNative () {
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = REMOVE;
|
|
this.Execute (data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set new size and position
|
|
/// </summary>
|
|
/// <param name="inputRect">RectTransform</param>
|
|
public void SetRectNative (RectTransform inputRect) {
|
|
Rect rect = GetScreenRectFromRectTransform (inputRect);
|
|
if (_lastRect == rect) {
|
|
return;
|
|
}
|
|
_lastRect = rect;
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = SET_RECT;
|
|
data["x"] = InvariantCultureString(rect.x / Screen.width);
|
|
data["y"] = InvariantCultureString(rect.y / Screen.height);
|
|
data["width"] = InvariantCultureString(rect.width / Screen.width);
|
|
data["height"] = InvariantCultureString(rect.height / Screen.height);
|
|
this.Execute (data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set focus on field
|
|
/// </summary>
|
|
/// <param name="isFocus">true | false</param>
|
|
public void SetFocus (bool isFocus) {
|
|
#if (UNITY_IOS || UNITY_ANDROID) && !UNITY_EDITOR
|
|
if (!_isMobileInputCreated) {
|
|
_isFocusOnCreate = isFocus;
|
|
return;
|
|
}
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = SET_FOCUS;
|
|
data["is_focus"] = isFocus;
|
|
this.Execute (data);
|
|
#else
|
|
if (gameObject.activeInHierarchy) {
|
|
if (isFocus) {
|
|
_inputObject.ActivateInputField ();
|
|
} else {
|
|
_inputObject.DeactivateInputField ();
|
|
}
|
|
} else {
|
|
_isFocusOnCreate = isFocus;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set field visible
|
|
/// </summary>
|
|
/// <param name="isVisible">true | false</param>
|
|
public void SetVisible (bool isVisible) {
|
|
if (!_isMobileInputCreated) {
|
|
_isVisibleOnCreate = isVisible;
|
|
return;
|
|
}
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = SET_VISIBLE;
|
|
data["is_visible"] = isVisible;
|
|
this.Execute (data);
|
|
this.Visible = isVisible;
|
|
}
|
|
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
|
|
/// <summary>
|
|
/// Send android button state
|
|
/// </summary>
|
|
/// <param name="key">Code</param>
|
|
private void ForceSendKeydownAndroid (string key) {
|
|
JsonObject data = new JsonObject ();
|
|
data["msg"] = ANDROID_KEY_DOWN;
|
|
data["key"] = key;
|
|
this.Execute (data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Keyboard handler
|
|
/// </summary>
|
|
private void UpdateForceKeyeventForAndroid () {
|
|
if (UnityEngine.Input.anyKeyDown) {
|
|
if (UnityEngine.Input.GetKeyDown (KeyCode.Backspace)) {
|
|
this.ForceSendKeydownAndroid ("backspace");
|
|
} else {
|
|
foreach (char c in UnityEngine.Input.inputString) {
|
|
if (c == '\n') {
|
|
this.ForceSendKeydownAndroid ("enter");
|
|
} else {
|
|
this.ForceSendKeydownAndroid (Input.inputString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|