virtualKeyboard.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { Nullable } from "babylonjs/types";
  2. import { Observable, Observer } from "babylonjs/Misc/observable";
  3. import { StackPanel } from "./stackPanel";
  4. import { Button } from "./button";
  5. import { Container } from "./container";
  6. import { TextBlock } from "./textBlock";
  7. import { InputText } from "./inputText";
  8. import { _TypeStore } from 'babylonjs/Misc/typeStore';
  9. /**
  10. * Class used to store key control properties
  11. */
  12. export class KeyPropertySet {
  13. /** Width */
  14. width?: string;
  15. /** Height */
  16. height?: string;
  17. /** Left padding */
  18. paddingLeft?: string;
  19. /** Right padding */
  20. paddingRight?: string;
  21. /** Top padding */
  22. paddingTop?: string;
  23. /** Bottom padding */
  24. paddingBottom?: string;
  25. /** Foreground color */
  26. color?: string;
  27. /** Background color */
  28. background?: string;
  29. }
  30. type ConnectedInputText = {
  31. input: InputText,
  32. onFocusObserver: Nullable<Observer<InputText>>,
  33. onBlurObserver: Nullable<Observer<InputText>>
  34. };
  35. /**
  36. * Class used to create virtual keyboard
  37. */
  38. export class VirtualKeyboard extends StackPanel {
  39. /** Observable raised when a key is pressed */
  40. public onKeyPressObservable = new Observable<string>();
  41. /** Gets or sets default key button width */
  42. public defaultButtonWidth = "40px";
  43. /** Gets or sets default key button height */
  44. public defaultButtonHeight = "40px";
  45. /** Gets or sets default key button left padding */
  46. public defaultButtonPaddingLeft = "2px";
  47. /** Gets or sets default key button right padding */
  48. public defaultButtonPaddingRight = "2px";
  49. /** Gets or sets default key button top padding */
  50. public defaultButtonPaddingTop = "2px";
  51. /** Gets or sets default key button bottom padding */
  52. public defaultButtonPaddingBottom = "2px";
  53. /** Gets or sets default key button foreground color */
  54. public defaultButtonColor = "#DDD";
  55. /** Gets or sets default key button background color */
  56. public defaultButtonBackground = "#070707";
  57. /** Gets or sets shift button foreground color */
  58. public shiftButtonColor = "#7799FF";
  59. /** Gets or sets shift button thickness*/
  60. public selectedShiftThickness = 1;
  61. /** Gets shift key state */
  62. public shiftState = 0;
  63. protected _getTypeName(): string {
  64. return "VirtualKeyboard";
  65. }
  66. private _createKey(key: string, propertySet: Nullable<KeyPropertySet>) {
  67. var button = Button.CreateSimpleButton(key, key);
  68. button.width = propertySet && propertySet.width ? propertySet.width : this.defaultButtonWidth;
  69. button.height = propertySet && propertySet.height ? propertySet.height : this.defaultButtonHeight;
  70. button.color = propertySet && propertySet.color ? propertySet.color : this.defaultButtonColor;
  71. button.background = propertySet && propertySet.background ? propertySet.background : this.defaultButtonBackground;
  72. button.paddingLeft = propertySet && propertySet.paddingLeft ? propertySet.paddingLeft : this.defaultButtonPaddingLeft;
  73. button.paddingRight = propertySet && propertySet.paddingRight ? propertySet.paddingRight : this.defaultButtonPaddingRight;
  74. button.paddingTop = propertySet && propertySet.paddingTop ? propertySet.paddingTop : this.defaultButtonPaddingTop;
  75. button.paddingBottom = propertySet && propertySet.paddingBottom ? propertySet.paddingBottom : this.defaultButtonPaddingBottom;
  76. button.thickness = 0;
  77. button.isFocusInvisible = true;
  78. button.shadowColor = this.shadowColor;
  79. button.shadowBlur = this.shadowBlur;
  80. button.shadowOffsetX = this.shadowOffsetX;
  81. button.shadowOffsetY = this.shadowOffsetY;
  82. button.onPointerUpObservable.add(() => {
  83. this.onKeyPressObservable.notifyObservers(key);
  84. });
  85. return button;
  86. }
  87. /**
  88. * Adds a new row of keys
  89. * @param keys defines the list of keys to add
  90. * @param propertySets defines the associated property sets
  91. */
  92. public addKeysRow(keys: Array<string>, propertySets?: Array<KeyPropertySet>): void {
  93. let panel = new StackPanel();
  94. panel.isVertical = false;
  95. panel.isFocusInvisible = true;
  96. var maxKey: Nullable<Button> = null;
  97. for (var i = 0; i < keys.length; i++) {
  98. let properties = null;
  99. if (propertySets && propertySets.length === keys.length) {
  100. properties = propertySets[i];
  101. }
  102. var key = this._createKey(keys[i], properties);
  103. if (!maxKey || key.heightInPixels > maxKey.heightInPixels) {
  104. maxKey = key;
  105. }
  106. panel.addControl(key);
  107. }
  108. panel.height = maxKey ? maxKey.height : this.defaultButtonHeight;
  109. this.addControl(panel);
  110. }
  111. /**
  112. * Set the shift key to a specific state
  113. * @param shiftState defines the new shift state
  114. */
  115. public applyShiftState(shiftState: number): void {
  116. if (!this.children) {
  117. return;
  118. }
  119. for (var i = 0; i < this.children.length; i++) {
  120. let row = this.children[i];
  121. if (!row || !(<Container>row).children) {
  122. continue;
  123. }
  124. let rowContainer = <Container>row;
  125. for (var j = 0; j < rowContainer.children.length; j++) {
  126. let button = rowContainer.children[j] as Button;
  127. if (!button || !button.children[0]) {
  128. continue;
  129. }
  130. let button_tblock = button.children[0] as TextBlock;
  131. if (button_tblock.text === "\u21E7") {
  132. button.color = (shiftState ? this.shiftButtonColor : this.defaultButtonColor);
  133. button.thickness = (shiftState > 1 ? this.selectedShiftThickness : 0);
  134. }
  135. button_tblock.text = (shiftState > 0 ? button_tblock.text.toUpperCase() : button_tblock.text.toLowerCase());
  136. }
  137. }
  138. }
  139. private _currentlyConnectedInputText: Nullable<InputText> = null;
  140. private _connectedInputTexts: ConnectedInputText[] = [];
  141. private _onKeyPressObserver: Nullable<Observer<string>> = null;
  142. /** Gets the input text control currently attached to the keyboard */
  143. public get connectedInputText(): Nullable<InputText> {
  144. return this._currentlyConnectedInputText;
  145. }
  146. /**
  147. * Connects the keyboard with an input text control
  148. *
  149. * @param input defines the target control
  150. */
  151. public connect(input: InputText): void {
  152. const inputTextAlreadyConnected = this._connectedInputTexts.some((a) => a.input === input);
  153. if (inputTextAlreadyConnected) {
  154. return;
  155. }
  156. if (this._onKeyPressObserver === null) {
  157. this._onKeyPressObserver = this.onKeyPressObservable.add((key) => {
  158. if (!this._currentlyConnectedInputText) {
  159. return;
  160. }
  161. this._currentlyConnectedInputText._host.focusedControl = this._currentlyConnectedInputText;
  162. switch (key) {
  163. case "\u21E7":
  164. this.shiftState++;
  165. if (this.shiftState > 2) {
  166. this.shiftState = 0;
  167. }
  168. this.applyShiftState(this.shiftState);
  169. return;
  170. case "\u2190":
  171. this._currentlyConnectedInputText.processKey(8);
  172. return;
  173. case "\u21B5":
  174. this._currentlyConnectedInputText.processKey(13);
  175. return;
  176. }
  177. this._currentlyConnectedInputText.processKey(-1, (this.shiftState ? key.toUpperCase() : key));
  178. if (this.shiftState === 1) {
  179. this.shiftState = 0;
  180. this.applyShiftState(this.shiftState);
  181. }
  182. });
  183. }
  184. this.isVisible = false;
  185. this._currentlyConnectedInputText = input;
  186. input._connectedVirtualKeyboard = this;
  187. // Events hooking
  188. const onFocusObserver: Nullable<Observer<InputText>> = input.onFocusObservable.add(() => {
  189. this._currentlyConnectedInputText = input;
  190. input._connectedVirtualKeyboard = this;
  191. this.isVisible = true;
  192. });
  193. const onBlurObserver: Nullable<Observer<InputText>> = input.onBlurObservable.add(() => {
  194. input._connectedVirtualKeyboard = null;
  195. this._currentlyConnectedInputText = null;
  196. this.isVisible = false;
  197. });
  198. this._connectedInputTexts.push({
  199. input,
  200. onBlurObserver,
  201. onFocusObserver
  202. });
  203. }
  204. /**
  205. * Disconnects the keyboard from connected InputText controls
  206. *
  207. * @param input optionally defines a target control, otherwise all are disconnected
  208. */
  209. public disconnect(input?: InputText): void {
  210. if (input) {
  211. // .find not available on IE
  212. let filtered = this._connectedInputTexts.filter((a) => a.input === input);
  213. if (filtered.length === 1) {
  214. this._removeConnectedInputObservables(filtered[0]);
  215. this._connectedInputTexts = this._connectedInputTexts.filter((a) => a.input !== input);
  216. if (this._currentlyConnectedInputText === input) {
  217. this._currentlyConnectedInputText = null;
  218. }
  219. }
  220. } else {
  221. this._connectedInputTexts.forEach((connectedInputText: ConnectedInputText) => {
  222. this._removeConnectedInputObservables(connectedInputText);
  223. });
  224. this._connectedInputTexts = [];
  225. }
  226. if (this._connectedInputTexts.length === 0) {
  227. this._currentlyConnectedInputText = null;
  228. this.onKeyPressObservable.remove(this._onKeyPressObserver);
  229. this._onKeyPressObserver = null;
  230. }
  231. }
  232. private _removeConnectedInputObservables(connectedInputText: ConnectedInputText): void {
  233. connectedInputText.input._connectedVirtualKeyboard = null;
  234. connectedInputText.input.onFocusObservable.remove(connectedInputText.onFocusObserver);
  235. connectedInputText.input.onBlurObservable.remove(connectedInputText.onBlurObserver);
  236. }
  237. /**
  238. * Release all resources
  239. */
  240. public dispose(): void {
  241. super.dispose();
  242. this.disconnect();
  243. }
  244. // Statics
  245. /**
  246. * Creates a new keyboard using a default layout
  247. *
  248. * @param name defines control name
  249. * @returns a new VirtualKeyboard
  250. */
  251. public static CreateDefaultLayout(name?: string): VirtualKeyboard {
  252. let returnValue = new VirtualKeyboard(name);
  253. returnValue.addKeysRow(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\u2190"]);
  254. returnValue.addKeysRow(["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]);
  255. returnValue.addKeysRow(["a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\u21B5"]);
  256. returnValue.addKeysRow(["\u21E7", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/"]);
  257. returnValue.addKeysRow([" "], [{ width: "200px" }]);
  258. return returnValue;
  259. }
  260. }
  261. _TypeStore.RegisteredTypes["BABYLON.GUI.VirtualKeyboard"] = VirtualKeyboard;