virtualKeyboard.ts 11 KB

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