ExternalKeyboard.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
  2. #define ZF_OSX
  3. #endif
  4. using System;
  5. using System.Collections;
  6. using UnityEngine;
  7. namespace ZenFulcrum.EmbeddedBrowser {
  8. /// <summary>
  9. /// Helper/worker class for displaying an external keyboard and
  10. /// sending the input to a browser.
  11. ///
  12. /// Don't put this script on your target browser directly, add a separate browser
  13. /// to the scene and attach it to that instead.
  14. ///
  15. /// </summary>
  16. [RequireComponent(typeof(Browser))]
  17. [RequireComponent(typeof(PointerUIBase))]
  18. public class ExternalKeyboard : MonoBehaviour {
  19. [Tooltip("Set to true before startup to have the keyboard automatically hook to the browser with the most recently focused text field.")]
  20. public bool automaticFocus;
  21. [Tooltip("Browser to start as the focused browser for this keyboard. Not really needed if automaticFocus is on.")]
  22. public Browser initialBrowser;
  23. [Tooltip("Set to true to have the keyboard automatically hide when we don't have a text entry box to type into.")]
  24. public bool hideWhenUnneeded = true;
  25. protected PointerUIBase activeBrowserUI;
  26. protected Browser keyboardBrowser;
  27. protected bool forcingFocus;
  28. protected Browser _activeBrowser;
  29. /// <summary>
  30. /// Browser that gets input if we press keys on the keyboard.
  31. /// </summary>
  32. protected virtual Browser ActiveBrowser {
  33. get { return _activeBrowser; }
  34. set {
  35. _SetActiveBrowser(value);
  36. DoFocus(_activeBrowser);
  37. }
  38. }
  39. protected void _SetActiveBrowser(Browser browser) {
  40. if (ActiveBrowser) {
  41. if (activeBrowserUI && forcingFocus) {
  42. activeBrowserUI.ForceKeyboardHasFocus(false);
  43. forcingFocus = false;
  44. }
  45. }
  46. _activeBrowser = browser;
  47. activeBrowserUI = ActiveBrowser.GetComponent<PointerUIBase>();
  48. if (!activeBrowserUI) {
  49. //We can't focus the browser when we type, so the typed letters won't appear as we type.
  50. Debug.LogWarning("Browser does not haver a PointerUI, external keyboard may not work properly.");
  51. }
  52. }
  53. /// <summary>
  54. /// Called when the focus of the keyboard changes.
  55. /// The browser is the browser we are focused on (may or may not be different), editable will be true if we
  56. /// are expected to type in the new focus, false if not.
  57. /// </summary>
  58. public event Action<Browser, bool> onFocusChange = (browser, editable) => {};
  59. public void Awake() {
  60. var keyboardPage = Resources.Load<TextAsset>("Browser/Keyboard").text;
  61. keyboardBrowser = GetComponent<Browser>();
  62. keyboardBrowser.onBrowserFocus += OnBrowserFocus;
  63. keyboardBrowser.LoadHTML(keyboardPage);
  64. keyboardBrowser.RegisterFunction("textTyped", TextTyped);
  65. keyboardBrowser.RegisterFunction("commandEntered", CommandEntered);
  66. if (initialBrowser) ActiveBrowser = initialBrowser;
  67. if (automaticFocus) {
  68. StartCoroutine(FindAndListenForBrowsers());
  69. }
  70. }
  71. protected IEnumerator FindAndListenForBrowsers() {
  72. yield return null;
  73. foreach (var browser in FindObjectsOfType<Browser>()) {
  74. if (browser == keyboardBrowser) continue;
  75. ObserveBrowser(browser);
  76. }
  77. Browser.onAnyBrowserCreated += ObserveBrowser;
  78. //in theory we shouldn't need to deal with browsers being destroyed since the whole callback chain should get cleaned up
  79. //(might need some more work if you repeatedly destroy and recreate keyboards, though)
  80. }
  81. protected void ObserveBrowser(Browser browser) {
  82. browser.onNodeFocus += (tagName, editable, value) => {
  83. if (!this) return;
  84. if (!browser.focusState.hasMouseFocus) return;
  85. DoFocus(browser);
  86. };
  87. var pointerUI = browser.GetComponent<PointerUIBase>();
  88. if (pointerUI) {
  89. pointerUI.onClick += () => {
  90. DoFocus(browser);
  91. };
  92. }
  93. }
  94. protected void DoFocus(Browser browser) {
  95. if (browser != ActiveBrowser) {
  96. _SetActiveBrowser(browser);
  97. }
  98. bool visible;
  99. if (browser) visible = browser.focusState.focusedNodeEditable;
  100. else visible = false;
  101. SetVisible(visible);
  102. onFocusChange(_activeBrowser, visible);
  103. }
  104. protected void SetVisible(bool visible) {
  105. var renderer = GetComponent<Renderer>();
  106. if (renderer) renderer.enabled = visible;
  107. var collider = GetComponent<Collider>();
  108. if (collider) collider.enabled = visible;
  109. }
  110. protected void OnBrowserFocus(bool mouseFocused, bool kbFocused) {
  111. //when our keyboard is focused, focus the browser we will be typing into.
  112. if (!activeBrowserUI) return;
  113. if ((mouseFocused || kbFocused) && !forcingFocus) {
  114. activeBrowserUI.ForceKeyboardHasFocus(true);
  115. forcingFocus = true;
  116. }
  117. if (!(mouseFocused || kbFocused) && forcingFocus) {
  118. activeBrowserUI.ForceKeyboardHasFocus(false);
  119. forcingFocus = false;
  120. }
  121. }
  122. protected void CommandEntered(JSONNode args) {
  123. if (!ActiveBrowser) return;
  124. string command = args[0];
  125. bool shiftPressed = args[1];
  126. if (shiftPressed) ActiveBrowser.PressKey(KeyCode.LeftShift, KeyAction.Press);
  127. #if ZF_OSX
  128. const KeyCode wordShifter = KeyCode.LeftAlt;
  129. #else
  130. const KeyCode wordShifter = KeyCode.LeftControl;
  131. #endif
  132. switch (command) {
  133. case "backspace":
  134. ActiveBrowser.PressKey(KeyCode.Backspace);
  135. break;
  136. case "copy":
  137. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Copy);
  138. break;
  139. case "cut":
  140. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Cut);
  141. break;
  142. case "delete":
  143. ActiveBrowser.PressKey(KeyCode.Delete);
  144. break;
  145. case "down":
  146. ActiveBrowser.PressKey(KeyCode.DownArrow);
  147. break;
  148. case "end":
  149. ActiveBrowser.PressKey(KeyCode.End);
  150. break;
  151. case "home":
  152. ActiveBrowser.PressKey(KeyCode.Home);
  153. break;
  154. case "insert":
  155. ActiveBrowser.PressKey(KeyCode.Insert);
  156. break;
  157. case "left":
  158. ActiveBrowser.PressKey(KeyCode.LeftArrow);
  159. break;
  160. case "pageDown":
  161. ActiveBrowser.PressKey(KeyCode.PageDown);
  162. break;
  163. case "pageUp":
  164. ActiveBrowser.PressKey(KeyCode.PageUp);
  165. break;
  166. case "paste":
  167. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Paste);
  168. break;
  169. case "redo":
  170. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
  171. break;
  172. case "right":
  173. ActiveBrowser.PressKey(KeyCode.RightArrow);
  174. break;
  175. case "selectAll":
  176. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
  177. break;
  178. case "undo":
  179. ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Undo);
  180. break;
  181. case "up":
  182. ActiveBrowser.PressKey(KeyCode.UpArrow);
  183. break;
  184. case "wordLeft":
  185. ActiveBrowser.PressKey(wordShifter, KeyAction.Press);
  186. ActiveBrowser.PressKey(KeyCode.LeftArrow);
  187. ActiveBrowser.PressKey(wordShifter, KeyAction.Release);
  188. break;
  189. case "wordRight":
  190. ActiveBrowser.PressKey(wordShifter, KeyAction.Press);
  191. ActiveBrowser.PressKey(KeyCode.RightArrow);
  192. ActiveBrowser.PressKey(wordShifter, KeyAction.Release);
  193. break;
  194. }
  195. if (shiftPressed) ActiveBrowser.PressKey(KeyCode.LeftShift, KeyAction.Release);
  196. }
  197. protected void TextTyped(JSONNode args) {
  198. if (!ActiveBrowser) return;
  199. ActiveBrowser.TypeText(args[0]);
  200. }
  201. }
  202. }