WebGLInput.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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. using UnityEngine.EventSystems;
  12. namespace WebGLSupport
  13. {
  14. internal class WebGLInputPlugin
  15. {
  16. #if UNITY_WEBGL && !UNITY_EDITOR
  17. [DllImport("__Internal")]
  18. public static extern void WebGLInputInit();
  19. [DllImport("__Internal")]
  20. public static extern int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, string placeholder, bool isMultiLine, bool isPassword, bool isHidden, bool isMobile);
  21. [DllImport("__Internal")]
  22. public static extern void WebGLInputEnterSubmit(int id, bool flag);
  23. [DllImport("__Internal")]
  24. public static extern void WebGLInputTab(int id, Action<int, int> cb);
  25. [DllImport("__Internal")]
  26. public static extern void WebGLInputFocus(int id);
  27. [DllImport("__Internal")]
  28. public static extern void WebGLInputOnFocus(int id, Action<int> cb);
  29. [DllImport("__Internal")]
  30. public static extern void WebGLInputOnBlur(int id, Action<int> cb);
  31. [DllImport("__Internal")]
  32. public static extern void WebGLInputOnValueChange(int id, Action<int, string> cb);
  33. [DllImport("__Internal")]
  34. public static extern void WebGLInputOnEditEnd(int id, Action<int, string> cb);
  35. [DllImport("__Internal")]
  36. public static extern void WebGLInputOnKeyboardEvent(int id, Action<int, int, string, int, int, int, int> cb);
  37. [DllImport("__Internal")]
  38. public static extern int WebGLInputSelectionStart(int id);
  39. [DllImport("__Internal")]
  40. public static extern int WebGLInputSelectionEnd(int id);
  41. [DllImport("__Internal")]
  42. public static extern int WebGLInputSelectionDirection(int id);
  43. [DllImport("__Internal")]
  44. public static extern void WebGLInputSetSelectionRange(int id, int start, int end);
  45. [DllImport("__Internal")]
  46. public static extern void WebGLInputMaxLength(int id, int maxlength);
  47. [DllImport("__Internal")]
  48. public static extern void WebGLInputText(int id, string text);
  49. [DllImport("__Internal")]
  50. public static extern bool WebGLInputIsFocus(int id);
  51. [DllImport("__Internal")]
  52. public static extern void WebGLInputDelete(int id);
  53. [DllImport("__Internal")]
  54. public static extern void WebGLInputForceBlur(int id);
  55. #if WEBGLINPUT_TAB
  56. [DllImport("__Internal")]
  57. public static extern void WebGLInputEnableTabText(int id, bool enable);
  58. #endif
  59. #else
  60. public static void WebGLInputInit() { }
  61. public static int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, string placeholder, bool isMultiLine, bool isPassword, bool isHidden, bool isMobile) { return 0; }
  62. public static void WebGLInputEnterSubmit(int id, bool flag) { }
  63. public static void WebGLInputTab(int id, Action<int, int> cb) { }
  64. public static void WebGLInputFocus(int id) { }
  65. public static void WebGLInputOnFocus(int id, Action<int> cb) { }
  66. public static void WebGLInputOnBlur(int id, Action<int> cb) { }
  67. public static void WebGLInputOnValueChange(int id, Action<int, string> cb) { }
  68. public static void WebGLInputOnEditEnd(int id, Action<int, string> cb) { }
  69. public static void WebGLInputOnKeyboardEvent(int id, Action<int, int, string, int, int, int, int> cb) { }
  70. public static int WebGLInputSelectionStart(int id) { return 0; }
  71. public static int WebGLInputSelectionEnd(int id) { return 0; }
  72. public static int WebGLInputSelectionDirection(int id) { return 0; }
  73. public static void WebGLInputSetSelectionRange(int id, int start, int end) { }
  74. public static void WebGLInputMaxLength(int id, int maxlength) { }
  75. public static void WebGLInputText(int id, string text) { }
  76. public static bool WebGLInputIsFocus(int id) { return false; }
  77. public static void WebGLInputDelete(int id) { }
  78. public static void WebGLInputForceBlur(int id) { }
  79. #if WEBGLINPUT_TAB
  80. public static void WebGLInputEnableTabText(int id, bool enable) { }
  81. #endif
  82. #endif
  83. }
  84. public class WebGLInput : MonoBehaviour, IComparable<WebGLInput>
  85. {
  86. public static event KeyboardEventHandler OnKeyboardDown;
  87. public static event KeyboardEventHandler OnKeyboardUp;
  88. static Dictionary<int, WebGLInput> instances = new Dictionary<int, WebGLInput>();
  89. public static string CanvasId { get; set; }
  90. #if WEBGLINPUT_TAB
  91. public bool enableTabText = false;
  92. #endif
  93. static WebGLInput()
  94. {
  95. CanvasId = WebGLWindow.GetCanvasName();
  96. WebGLInputPlugin.WebGLInputInit();
  97. }
  98. public int Id { get { return id; } }
  99. internal int id = -1;
  100. public IInputField input { get; private set; }
  101. bool blurBlock = false;
  102. [TooltipAttribute("show input element on canvas. this will make you select text by drag.")]
  103. public bool showHtmlElement = false;
  104. private IInputField Setup()
  105. {
  106. if (GetComponent<InputField>()) return new WrappedInputField(GetComponent<InputField>());
  107. if (GetComponent<WebGLUIToolkitTextField>()) return new WrappedUIToolkit(GetComponent<WebGLUIToolkitTextField>());
  108. #if TMP_WEBGL_SUPPORT
  109. if (GetComponent<TMPro.TMP_InputField>()) return new WrappedTMPInputField(GetComponent<TMPro.TMP_InputField>());
  110. #endif // TMP_WEBGL_SUPPORT
  111. throw new Exception("Can not Setup WebGLInput!!");
  112. }
  113. private void Awake()
  114. {
  115. input = Setup();
  116. #if !(UNITY_WEBGL && !UNITY_EDITOR)
  117. // WebGL 以外、更新メソッドは動作しないようにします
  118. enabled = false;
  119. #endif
  120. // for mobile platform
  121. if (Application.isMobilePlatform)
  122. {
  123. if (input.EnableMobileSupport)
  124. {
  125. gameObject.AddComponent<WebGLInputMobile>();
  126. }
  127. else
  128. {
  129. // when disable mobile input. disable self!
  130. enabled = false;
  131. }
  132. }
  133. }
  134. /// <summary>
  135. /// Get the element rect of input
  136. /// </summary>
  137. /// <returns></returns>
  138. RectInt GetElemetRect()
  139. {
  140. var rect = input.GetScreenCoordinates();
  141. // モバイルの場合、強制表示する
  142. if (showHtmlElement || Application.isMobilePlatform)
  143. {
  144. var x = (int)(rect.x);
  145. var y = (int)(Screen.height - (rect.y + rect.height));
  146. return new RectInt(x, y, (int)rect.width, (int)rect.height);
  147. }
  148. else
  149. {
  150. var x = (int)(rect.x);
  151. var y = (int)(Screen.height - (rect.y));
  152. return new RectInt(x, y, (int)rect.width, (int)1);
  153. }
  154. }
  155. /// <summary>
  156. /// 対象が選択されたとき
  157. /// </summary>
  158. /// <param name="eventData"></param>
  159. public void OnSelect()
  160. {
  161. if (id != -1) throw new Exception("OnSelect : id != -1");
  162. var rect = GetElemetRect();
  163. bool isPassword = input.contentType == ContentType.Password;
  164. var fontSize = Mathf.Max(14, input.fontSize); // limit font size : 14 !!
  165. // モバイルの場合、強制表示する
  166. var isHidden = !(showHtmlElement || Application.isMobilePlatform);
  167. id = WebGLInputPlugin.WebGLInputCreate(WebGLInput.CanvasId, rect.x, rect.y, rect.width, rect.height, fontSize, input.text, input.placeholder, input.lineType != LineType.SingleLine, isPassword, isHidden, Application.isMobilePlatform);
  168. instances[id] = this;
  169. WebGLInputPlugin.WebGLInputEnterSubmit(id, input.lineType != LineType.MultiLineNewline);
  170. WebGLInputPlugin.WebGLInputOnFocus(id, OnFocus);
  171. WebGLInputPlugin.WebGLInputOnBlur(id, OnBlur);
  172. WebGLInputPlugin.WebGLInputOnValueChange(id, OnValueChange);
  173. WebGLInputPlugin.WebGLInputOnEditEnd(id, OnEditEnd);
  174. WebGLInputPlugin.WebGLInputOnKeyboardEvent(id, OnKeyboardEvent);
  175. WebGLInputPlugin.WebGLInputTab(id, OnTab);
  176. // default value : https://www.w3schools.com/tags/att_input_maxlength.asp
  177. WebGLInputPlugin.WebGLInputMaxLength(id, (input.characterLimit > 0) ? input.characterLimit : 524288);
  178. WebGLInputPlugin.WebGLInputFocus(id);
  179. #if WEBGLINPUT_TAB
  180. WebGLInputPlugin.WebGLInputEnableTabText(id, enableTabText);
  181. #endif
  182. if (input.OnFocusSelectAll)
  183. {
  184. WebGLInputPlugin.WebGLInputSetSelectionRange(id, 0, input.text.Length);
  185. }
  186. else
  187. {
  188. WebGLInputPlugin.WebGLInputSetSelectionRange(id, input.caretPosition, input.caretPosition);
  189. }
  190. WebGLWindow.OnBlurEvent += OnWindowBlur;
  191. }
  192. /// <summary>
  193. /// sync text from inputfield
  194. /// </summary>
  195. /// <param name="cursorIndex"></param>
  196. public void SyncText(int? cursorIndex = null)
  197. {
  198. if (!instances.ContainsKey(id)) return;
  199. var instance = instances[id];
  200. WebGLInputPlugin.WebGLInputText(id, instance.input.text);
  201. if (cursorIndex.HasValue)
  202. {
  203. WebGLInputPlugin.WebGLInputSetSelectionRange(id, cursorIndex.Value, cursorIndex.Value);
  204. }
  205. }
  206. private void OnWindowBlur()
  207. {
  208. blurBlock = true;
  209. }
  210. internal void DeactivateInputField()
  211. {
  212. if (!instances.ContainsKey(id)) return;
  213. WebGLInputPlugin.WebGLInputDelete(id);
  214. input.DeactivateInputField();
  215. instances.Remove(id);
  216. id = -1; // reset id to -1;
  217. WebGLWindow.OnBlurEvent -= OnWindowBlur;
  218. }
  219. [MonoPInvokeCallback(typeof(Action<int>))]
  220. static void OnFocus(int id)
  221. {
  222. #if UNITY_WEBGL && !UNITY_EDITOR
  223. Input.ResetInputAxes(); // Inputの状態リセット
  224. UnityEngine.WebGLInput.captureAllKeyboardInput = false;
  225. #endif
  226. }
  227. [MonoPInvokeCallback(typeof(Action<int>))]
  228. static void OnBlur(int id)
  229. {
  230. #if UNITY_WEBGL && !UNITY_EDITOR
  231. UnityEngine.WebGLInput.captureAllKeyboardInput = true;
  232. Input.ResetInputAxes(); // Inputの状態リセット
  233. #endif
  234. instances[id].StartCoroutine(Blur(id));
  235. }
  236. static IEnumerator Blur(int id)
  237. {
  238. yield return null;
  239. if (!instances.ContainsKey(id)) yield break;
  240. var block = instances[id].blurBlock; // get blur block state
  241. instances[id].blurBlock = false; // reset instalce block state
  242. if (block) yield break; // if block. break it!!
  243. instances[id].DeactivateInputField();
  244. }
  245. [MonoPInvokeCallback(typeof(Action<int, string>))]
  246. static void OnValueChange(int id, string value)
  247. {
  248. if (!instances.ContainsKey(id)) return;
  249. var instance = instances[id];
  250. if (!instance.input.ReadOnly)
  251. {
  252. instance.input.text = value;
  253. }
  254. // InputField.ContentType.Name が Name の場合、先頭文字が強制的大文字になるため小文字にして比べる
  255. if (instance.input.contentType == ContentType.Name)
  256. {
  257. if (string.Compare(instance.input.text, value, true) == 0)
  258. {
  259. value = instance.input.text;
  260. }
  261. }
  262. // InputField の ContentType による整形したテキストを HTML の input に再設定します
  263. if (value != instance.input.text)
  264. {
  265. var start = WebGLInputPlugin.WebGLInputSelectionStart(id);
  266. var end = WebGLInputPlugin.WebGLInputSelectionEnd(id);
  267. // take the offset.when char remove from input.
  268. var offset = instance.input.text.Length - value.Length;
  269. WebGLInputPlugin.WebGLInputText(id, instance.input.text);
  270. // reset the input element selection range!!
  271. WebGLInputPlugin.WebGLInputSetSelectionRange(id, start + offset, end + offset);
  272. }
  273. }
  274. [MonoPInvokeCallback(typeof(Action<int, string>))]
  275. static void OnEditEnd(int id, string value)
  276. {
  277. if (!instances[id].input.ReadOnly)
  278. {
  279. instances[id].input.text = value;
  280. }
  281. }
  282. [MonoPInvokeCallback(typeof(Action<int, int>))]
  283. static void OnTab(int id, int value)
  284. {
  285. WebGLInputTabFocus.OnTab(instances[id], value);
  286. }
  287. [MonoPInvokeCallback(typeof(Action<int, int, string, int, int, int, int>))]
  288. static void OnKeyboardEvent(int id, int mode, string key, int code, int shiftKey, int ctrlKey, int altKey)
  289. {
  290. if (!instances.ContainsKey(id)) return;
  291. var instance = instances[id];
  292. // mode : keydown(1) keyup(3)
  293. var cb = mode switch
  294. {
  295. 1 => OnKeyboardDown,
  296. 2 => OnKeyboardUp,
  297. _ => default
  298. };
  299. if (cb != null)
  300. {
  301. cb(instance, new KeyboardEvent(key, code, shiftKey != 0, ctrlKey != 0, altKey != 0));
  302. }
  303. }
  304. void Update()
  305. {
  306. if (input == null || !input.isFocused)
  307. {
  308. CheckOutFocus();
  309. return;
  310. }
  311. // 未登録の場合、選択する
  312. if (!instances.ContainsKey(id))
  313. {
  314. if (Application.isMobilePlatform)
  315. {
  316. return;
  317. }
  318. else
  319. {
  320. OnSelect();
  321. }
  322. }
  323. else if (!WebGLInputPlugin.WebGLInputIsFocus(id))
  324. {
  325. if (Application.isMobilePlatform)
  326. {
  327. //input.DeactivateInputField();
  328. return;
  329. }
  330. else
  331. {
  332. // focus this id
  333. WebGLInputPlugin.WebGLInputFocus(id);
  334. }
  335. }
  336. var start = WebGLInputPlugin.WebGLInputSelectionStart(id);
  337. var end = WebGLInputPlugin.WebGLInputSelectionEnd(id);
  338. // 選択方向によって設定します
  339. if (WebGLInputPlugin.WebGLInputSelectionDirection(id) == -1)
  340. {
  341. input.selectionFocusPosition = start;
  342. input.selectionAnchorPosition = end;
  343. }
  344. else
  345. {
  346. input.selectionFocusPosition = end;
  347. input.selectionAnchorPosition = start;
  348. }
  349. input.Rebuild();
  350. }
  351. private void OnDestroy()
  352. {
  353. if (!instances.ContainsKey(id)) return;
  354. #if UNITY_WEBGL && !UNITY_EDITOR
  355. UnityEngine.WebGLInput.captureAllKeyboardInput = true;
  356. Input.ResetInputAxes(); // Inputの状態リセット
  357. #endif
  358. DeactivateInputField();
  359. }
  360. private void OnEnable()
  361. {
  362. WebGLInputTabFocus.Add(this);
  363. }
  364. private void OnDisable()
  365. {
  366. WebGLInputTabFocus.Remove(this);
  367. }
  368. public int CompareTo(WebGLInput other)
  369. {
  370. var a = input.GetScreenCoordinates();
  371. var b = other.input.GetScreenCoordinates();
  372. var res = b.y.CompareTo(a.y);
  373. if (res == 0) res = a.x.CompareTo(b.x);
  374. return res;
  375. }
  376. private void CheckOutFocus()
  377. {
  378. if (!Application.isMobilePlatform) return;
  379. if (!instances.ContainsKey(id)) return;
  380. var current = EventSystem.current.currentSelectedGameObject;
  381. if (current != null) return;
  382. WebGLInputPlugin.WebGLInputForceBlur(id); // Input ではないし、キーボードを閉じる
  383. }
  384. /// <summary>
  385. /// to manage tab focus
  386. /// base on scene position
  387. /// </summary>
  388. static class WebGLInputTabFocus
  389. {
  390. static List<WebGLInput> inputs = new List<WebGLInput>();
  391. public static void Add(WebGLInput input)
  392. {
  393. inputs.Add(input);
  394. inputs.Sort();
  395. }
  396. public static void Remove(WebGLInput input)
  397. {
  398. inputs.Remove(input);
  399. }
  400. public static void OnTab(WebGLInput input, int value)
  401. {
  402. if (inputs.Count <= 1) return;
  403. var index = inputs.IndexOf(input);
  404. index += value;
  405. if (index < 0) index = inputs.Count - 1;
  406. else if (index >= inputs.Count) index = 0;
  407. inputs[index].input.ActivateInputField();
  408. }
  409. }
  410. }
  411. }