WebGLInput.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #if UNITY_2018_2_OR_NEWER
  2. #define TMP_WEBGL_SUPPORT
  3. #endif
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. using System;
  8. using AOT;
  9. using System.Runtime.InteropServices; // for DllImport
  10. using System.Collections;
  11. namespace WebGLSupport
  12. {
  13. class WebGLInputPlugin
  14. {
  15. #if UNITY_WEBGL && !UNITY_EDITOR
  16. [DllImport("__Internal")]
  17. public static extern int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, bool isMultiLine, bool isPassword);
  18. [DllImport("__Internal")]
  19. public static extern void WebGLInputEnterSubmit(int id, bool flag);
  20. [DllImport("__Internal")]
  21. public static extern void WebGLInputTab(int id, Action<int, int> cb);
  22. [DllImport("__Internal")]
  23. public static extern void WebGLInputFocus(int id);
  24. [DllImport("__Internal")]
  25. public static extern void WebGLInputOnFocus(int id, Action<int> cb);
  26. [DllImport("__Internal")]
  27. public static extern void WebGLInputOnBlur(int id, Action<int> cb);
  28. [DllImport("__Internal")]
  29. public static extern void WebGLInputOnValueChange(int id, Action<int, string> cb);
  30. [DllImport("__Internal")]
  31. public static extern void WebGLInputOnEditEnd(int id, Action<int, string> cb);
  32. [DllImport("__Internal")]
  33. public static extern int WebGLInputSelectionStart(int id);
  34. [DllImport("__Internal")]
  35. public static extern int WebGLInputSelectionEnd(int id);
  36. [DllImport("__Internal")]
  37. public static extern int WebGLInputSelectionDirection(int id);
  38. [DllImport("__Internal")]
  39. public static extern void WebGLInputSetSelectionRange(int id, int start, int end);
  40. [DllImport("__Internal")]
  41. public static extern void WebGLInputMaxLength(int id, int maxlength);
  42. [DllImport("__Internal")]
  43. public static extern void WebGLInputText(int id, string text);
  44. [DllImport("__Internal")]
  45. public static extern bool WebGLInputIsFocus(int id);
  46. [DllImport("__Internal")]
  47. public static extern void WebGLInputDelete(int id);
  48. #else
  49. public static int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, bool isMultiLine, bool isPassword) { return 0; }
  50. public static void WebGLInputEnterSubmit(int id, bool flag) { }
  51. public static void WebGLInputTab(int id, Action<int, int> cb) { }
  52. public static void WebGLInputFocus(int id) { }
  53. public static void WebGLInputOnFocus(int id, Action<int> cb) { }
  54. public static void WebGLInputOnBlur(int id, Action<int> cb) { }
  55. public static void WebGLInputOnValueChange(int id, Action<int, string> cb) { }
  56. public static void WebGLInputOnEditEnd(int id, Action<int, string> cb) { }
  57. public static int WebGLInputSelectionStart(int id) { return 0; }
  58. public static int WebGLInputSelectionEnd(int id) { return 0; }
  59. public static int WebGLInputSelectionDirection(int id) { return 0; }
  60. public static void WebGLInputSetSelectionRange(int id, int start, int end) { }
  61. public static void WebGLInputMaxLength(int id, int maxlength) { }
  62. public static void WebGLInputText(int id, string text) { }
  63. public static bool WebGLInputIsFocus(int id) { return false; }
  64. public static void WebGLInputDelete(int id) { }
  65. #endif
  66. }
  67. public class WebGLInput : MonoBehaviour, IComparable<WebGLInput>
  68. {
  69. static Dictionary<int, WebGLInput> instances = new Dictionary<int, WebGLInput>();
  70. public static string CanvasId { get; set; }
  71. static WebGLInput()
  72. {
  73. #if UNITY_2019_1_OR_NEWER
  74. WebGLInput.CanvasId = "unityContainer";
  75. #else
  76. WebGLInput.CanvasId = "gameContainer";
  77. #endif
  78. }
  79. int id = -1;
  80. IInputField input;
  81. bool blueBlock = false;
  82. private IInputField Setup()
  83. {
  84. if (GetComponent<InputField>()) return new WrappedInputField(GetComponent<InputField>());
  85. #if TMP_WEBGL_SUPPORT
  86. if (GetComponent<TMPro.TMP_InputField>()) return new WrappedTMPInputField(GetComponent<TMPro.TMP_InputField>());
  87. #endif // TMP_WEBGL_SUPPORT
  88. throw new Exception("Can not Setup WebGLInput!!");
  89. }
  90. private void Awake()
  91. {
  92. input = Setup();
  93. #if !(UNITY_WEBGL && !UNITY_EDITOR)
  94. // WebGL 以外、更新メソッドは動作しないようにします
  95. enabled = false;
  96. #endif
  97. }
  98. /// <summary>
  99. /// 対象が選択されたとき
  100. /// </summary>
  101. /// <param name="eventData"></param>
  102. public void OnSelect(/*BaseEventData eventData*/)
  103. {
  104. var rect = GetScreenCoordinates(input.TextComponentRectTransform());
  105. bool isPassword = input.contentType == ContentType.Password;
  106. var x = (int)(rect.x);
  107. //var y = (int)(Screen.height - (rect.y + rect.height));
  108. //id = WebGLInputPlugin.WebGLInputCreate(x, y, (int)rect.width, (int)rect.height, input.textComponent.fontSize, input.text);
  109. var y = (int)(Screen.height - (rect.y));
  110. id = WebGLInputPlugin.WebGLInputCreate(WebGLInput.CanvasId, x, y, (int)rect.width, (int)1, input.fontSize, input.text, input.lineType != LineType.SingleLine, isPassword);
  111. instances[id] = this;
  112. WebGLInputPlugin.WebGLInputEnterSubmit(id, input.lineType != LineType.MultiLineNewline);
  113. WebGLInputPlugin.WebGLInputOnFocus(id, OnFocus);
  114. WebGLInputPlugin.WebGLInputOnBlur(id, OnBlur);
  115. WebGLInputPlugin.WebGLInputOnValueChange(id, OnValueChange);
  116. WebGLInputPlugin.WebGLInputOnEditEnd(id, OnEditEnd);
  117. WebGLInputPlugin.WebGLInputTab(id, OnTab);
  118. // default value : https://www.w3schools.com/tags/att_input_maxlength.asp
  119. WebGLInputPlugin.WebGLInputMaxLength(id, (input.characterLimit > 0) ? input.characterLimit : 524288);
  120. WebGLInputPlugin.WebGLInputFocus(id);
  121. if(input.OnFocusSelectAll)
  122. {
  123. WebGLInputPlugin.WebGLInputSetSelectionRange(id, 0, input.text.Length);
  124. }
  125. WebGLWindow.OnBlurEvent += OnWindowBlur;
  126. }
  127. void OnWindowBlur()
  128. {
  129. blueBlock = true;
  130. }
  131. /// <summary>
  132. /// 画面内の描画範囲を取得する
  133. /// </summary>
  134. /// <param name="uiElement"></param>
  135. /// <returns></returns>
  136. Rect GetScreenCoordinates(RectTransform uiElement)
  137. {
  138. var worldCorners = new Vector3[4];
  139. uiElement.GetWorldCorners(worldCorners);
  140. // try to support RenderMode:WorldSpace
  141. var canvas = uiElement.GetComponentInParent<Canvas>();
  142. var hasCamera = (canvas.renderMode != RenderMode.ScreenSpaceOverlay) && (canvas.worldCamera != null);
  143. if (canvas && hasCamera )
  144. {
  145. for (var i = 0; i < worldCorners.Length; i++)
  146. {
  147. worldCorners[i] = canvas.worldCamera.WorldToScreenPoint(worldCorners[i]);
  148. }
  149. }
  150. var min = new Vector3(float.MaxValue, float.MaxValue);
  151. var max = new Vector3(float.MinValue, float.MinValue);
  152. for (var i = 0; i < worldCorners.Length; i++)
  153. {
  154. min.x = Mathf.Min(min.x, worldCorners[i].x);
  155. min.y = Mathf.Min(min.y, worldCorners[i].y);
  156. max.x = Mathf.Max(max.x, worldCorners[i].x);
  157. max.y = Mathf.Max(max.y, worldCorners[i].y);
  158. }
  159. return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
  160. }
  161. void DeactivateInputField()
  162. {
  163. WebGLInputPlugin.WebGLInputDelete(id);
  164. input.DeactivateInputField();
  165. instances.Remove(id);
  166. id = -1; // reset id to -1;
  167. WebGLWindow.OnBlurEvent -= OnWindowBlur;
  168. }
  169. [MonoPInvokeCallback(typeof(Action<int>))]
  170. static void OnFocus(int id)
  171. {
  172. #if UNITY_WEBGL && !UNITY_EDITOR
  173. UnityEngine.WebGLInput.captureAllKeyboardInput = false;
  174. #endif
  175. }
  176. [MonoPInvokeCallback(typeof(Action<int>))]
  177. static void OnBlur(int id)
  178. {
  179. #if UNITY_WEBGL && !UNITY_EDITOR
  180. UnityEngine.WebGLInput.captureAllKeyboardInput = true;
  181. #endif
  182. instances[id].StartCoroutine(Blue(id));
  183. }
  184. static IEnumerator Blue(int id)
  185. {
  186. yield return null;
  187. if (!instances.ContainsKey(id)) yield break;
  188. var block = instances[id].blueBlock; // get blue block state
  189. instances[id].blueBlock = false; // reset instalce block state
  190. if (block) yield break; // if block. break it!!
  191. instances[id].DeactivateInputField();
  192. }
  193. [MonoPInvokeCallback(typeof(Action<int, string>))]
  194. static void OnValueChange(int id, string value)
  195. {
  196. if (!instances.ContainsKey(id)) return;
  197. var instance = instances[id];
  198. var index = instance.input.caretPosition;
  199. if(!instance.input.ReadOnly)
  200. {
  201. instance.input.text = value;
  202. }
  203. // InputField.ContentType.Name が Name の場合、先頭文字が強制的大文字になるため小文字にして比べる
  204. if (instance.input.contentType == ContentType.Name)
  205. {
  206. if (string.Compare(instance.input.text, value, true) == 0)
  207. {
  208. value = instance.input.text;
  209. }
  210. }
  211. // InputField の ContentType による整形したテキストを HTML の input に再設定します
  212. if (value != instance.input.text)
  213. {
  214. WebGLInputPlugin.WebGLInputText(id, instance.input.text);
  215. WebGLInputPlugin.WebGLInputSetSelectionRange(id, index, index);
  216. }
  217. }
  218. [MonoPInvokeCallback(typeof(Action<int, string>))]
  219. static void OnEditEnd(int id, string value)
  220. {
  221. if (!instances[id].input.ReadOnly)
  222. {
  223. instances[id].input.text = value;
  224. }
  225. }
  226. [MonoPInvokeCallback(typeof(Action<int, int>))]
  227. static void OnTab(int id, int value)
  228. {
  229. WebGLInputTabFocus.OnTab(instances[id], value);
  230. }
  231. void Update()
  232. {
  233. if (input == null || !input.isFocused) return;
  234. // 未登録の場合、選択する
  235. if (!instances.ContainsKey(id))
  236. {
  237. OnSelect();
  238. } else if(!WebGLInputPlugin.WebGLInputIsFocus(id))
  239. {
  240. // focus this id
  241. WebGLInputPlugin.WebGLInputFocus(id);
  242. }
  243. var start = WebGLInputPlugin.WebGLInputSelectionStart(id);
  244. var end = WebGLInputPlugin.WebGLInputSelectionEnd(id);
  245. // 選択方向によって設定します
  246. if (WebGLInputPlugin.WebGLInputSelectionDirection(id) == -1)
  247. {
  248. input.selectionFocusPosition = start;
  249. input.selectionAnchorPosition = end;
  250. }
  251. else
  252. {
  253. input.selectionFocusPosition = end;
  254. input.selectionAnchorPosition = start;
  255. }
  256. input.Rebuild(CanvasUpdate.LatePreRender);
  257. input.SetAllDirty();
  258. }
  259. private void OnEnable()
  260. {
  261. WebGLInputTabFocus.Add(this);
  262. }
  263. private void OnDisable()
  264. {
  265. WebGLInputTabFocus.Remove(this);
  266. }
  267. public int CompareTo(WebGLInput other)
  268. {
  269. var a = GetScreenCoordinates(input.TextComponentRectTransform());
  270. var b = GetScreenCoordinates(other.input.TextComponentRectTransform());
  271. var res = b.y.CompareTo(a.y);
  272. if (res == 0) res = a.x.CompareTo(b.x);
  273. return res;
  274. }
  275. /// <summary>
  276. /// to manage tab focus
  277. /// base on scene position
  278. /// </summary>
  279. static class WebGLInputTabFocus
  280. {
  281. static List<WebGLInput> inputs = new List<WebGLInput>();
  282. public static void Add(WebGLInput input)
  283. {
  284. inputs.Add(input);
  285. inputs.Sort();
  286. }
  287. public static void Remove(WebGLInput input)
  288. {
  289. inputs.Remove(input);
  290. }
  291. public static void OnTab(WebGLInput input, int value)
  292. {
  293. if (inputs.Count <= 1) return;
  294. var index = inputs.IndexOf(input);
  295. index += value;
  296. if (index < 0) index = inputs.Count - 1;
  297. else if (index >= inputs.Count) index = 0;
  298. inputs[index].input.ActivateInputField();
  299. }
  300. }
  301. }
  302. }