advancedDynamicTexture.ts 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. import { Nullable } from "babylonjs/types";
  2. import { Observable, Observer } from "babylonjs/Misc/observable";
  3. import { Vector2, Vector3, Matrix } from "babylonjs/Maths/math.vector";
  4. import { Tools } from "babylonjs/Misc/tools";
  5. import { PointerInfoPre, PointerInfo, PointerEventTypes, PointerInfoBase } from 'babylonjs/Events/pointerEvents';
  6. import { ClipboardEventTypes, ClipboardInfo } from "babylonjs/Events/clipboardEvents";
  7. import { KeyboardInfoPre, KeyboardEventTypes } from "babylonjs/Events/keyboardEvents";
  8. import { Camera } from "babylonjs/Cameras/camera";
  9. import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
  10. import { Texture } from "babylonjs/Materials/Textures/texture";
  11. import { DynamicTexture } from "babylonjs/Materials/Textures/dynamicTexture";
  12. import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
  13. import { Layer } from "babylonjs/Layers/layer";
  14. import { Engine } from "babylonjs/Engines/engine";
  15. import { Scene } from "babylonjs/scene";
  16. import { Container } from "./controls/container";
  17. import { Control } from "./controls/control";
  18. import { IFocusableControl } from './controls/focusableControl';
  19. import { Style } from "./style";
  20. import { Measure } from "./measure";
  21. import { Constants } from 'babylonjs/Engines/constants';
  22. import { Viewport } from 'babylonjs/Maths/math.viewport';
  23. import { Color3 } from 'babylonjs/Maths/math.color';
  24. import { WebRequest } from "babylonjs/Misc/webRequest";
  25. /**
  26. * Class used to create texture to support 2D GUI elements
  27. * @see https://doc.babylonjs.com/how_to/gui
  28. */
  29. export class AdvancedDynamicTexture extends DynamicTexture {
  30. /** Define the Uurl to load snippets */
  31. public static SnippetUrl = "https://snippet.babylonjs.com";
  32. /** Snippet ID if the content was created from the snippet server */
  33. public snippetId: string;
  34. private _isDirty = false;
  35. private _renderObserver: Nullable<Observer<Camera>>;
  36. private _resizeObserver: Nullable<Observer<Engine>>;
  37. private _preKeyboardObserver: Nullable<Observer<KeyboardInfoPre>>;
  38. private _pointerMoveObserver: Nullable<Observer<PointerInfoPre>>;
  39. private _pointerObserver: Nullable<Observer<PointerInfo>>;
  40. private _canvasPointerOutObserver: Nullable<Observer<PointerEvent>>;
  41. private _canvasBlurObserver: Nullable<Observer<Engine>>;
  42. private _background: string;
  43. /** @hidden */
  44. public _rootContainer = new Container("root");
  45. /** @hidden */
  46. public _lastPickedControl: Control;
  47. /** @hidden */
  48. public _lastControlOver: { [pointerId: number]: Control } = {};
  49. /** @hidden */
  50. public _lastControlDown: { [pointerId: number]: Control } = {};
  51. /** @hidden */
  52. public _capturingControl: { [pointerId: number]: Control } = {};
  53. /** @hidden */
  54. public _shouldBlockPointer: boolean;
  55. /** @hidden */
  56. public _layerToDispose: Nullable<Layer>;
  57. /** @hidden */
  58. public _linkedControls = new Array<Control>();
  59. private _isFullscreen = false;
  60. private _fullscreenViewport = new Viewport(0, 0, 1, 1);
  61. private _idealWidth = 0;
  62. private _idealHeight = 0;
  63. private _useSmallestIdeal: boolean = false;
  64. private _renderAtIdealSize = false;
  65. private _focusedControl: Nullable<IFocusableControl>;
  66. private _blockNextFocusCheck = false;
  67. private _renderScale = 1;
  68. private _rootElement: Nullable<HTMLElement>;
  69. private _cursorChanged = false;
  70. private _defaultMousePointerId = 0;
  71. /** @hidden */
  72. public _numLayoutCalls = 0;
  73. /** Gets the number of layout calls made the last time the ADT has been rendered */
  74. public get numLayoutCalls(): number {
  75. return this._numLayoutCalls;
  76. }
  77. /** @hidden */
  78. public _numRenderCalls = 0;
  79. /** Gets the number of render calls made the last time the ADT has been rendered */
  80. public get numRenderCalls(): number {
  81. return this._numRenderCalls;
  82. }
  83. /**
  84. * Define type to string to ensure compatibility across browsers
  85. * Safari doesn't support DataTransfer constructor
  86. */
  87. private _clipboardData: string = "";
  88. /**
  89. * Observable event triggered each time an clipboard event is received from the rendering canvas
  90. */
  91. public onClipboardObservable = new Observable<ClipboardInfo>();
  92. /**
  93. * Observable event triggered each time a pointer down is intercepted by a control
  94. */
  95. public onControlPickedObservable = new Observable<Control>();
  96. /**
  97. * Observable event triggered before layout is evaluated
  98. */
  99. public onBeginLayoutObservable = new Observable<AdvancedDynamicTexture>();
  100. /**
  101. * Observable event triggered after the layout was evaluated
  102. */
  103. public onEndLayoutObservable = new Observable<AdvancedDynamicTexture>();
  104. /**
  105. * Observable event triggered before the texture is rendered
  106. */
  107. public onBeginRenderObservable = new Observable<AdvancedDynamicTexture>();
  108. /**
  109. * Observable event triggered after the texture was rendered
  110. */
  111. public onEndRenderObservable = new Observable<AdvancedDynamicTexture>();
  112. /**
  113. * Gets or sets a boolean defining if alpha is stored as premultiplied
  114. */
  115. public premulAlpha = false;
  116. /**
  117. * Gets or sets a boolean indicating that the canvas must be reverted on Y when updating the texture
  118. */
  119. public applyYInversionOnUpdate = true;
  120. /**
  121. * Gets or sets a number used to scale rendering size (2 means that the texture will be twice bigger).
  122. * Useful when you want more antialiasing
  123. */
  124. public get renderScale(): number {
  125. return this._renderScale;
  126. }
  127. public set renderScale(value: number) {
  128. if (value === this._renderScale) {
  129. return;
  130. }
  131. this._renderScale = value;
  132. this._onResize();
  133. }
  134. /** Gets or sets the background color */
  135. public get background(): string {
  136. return this._background;
  137. }
  138. public set background(value: string) {
  139. if (this._background === value) {
  140. return;
  141. }
  142. this._background = value;
  143. this.markAsDirty();
  144. }
  145. /**
  146. * Gets or sets the ideal width used to design controls.
  147. * The GUI will then rescale everything accordingly
  148. * @see https://doc.babylonjs.com/how_to/gui#adaptive-scaling
  149. */
  150. public get idealWidth(): number {
  151. return this._idealWidth;
  152. }
  153. public set idealWidth(value: number) {
  154. if (this._idealWidth === value) {
  155. return;
  156. }
  157. this._idealWidth = value;
  158. this.markAsDirty();
  159. this._rootContainer._markAllAsDirty();
  160. }
  161. /**
  162. * Gets or sets the ideal height used to design controls.
  163. * The GUI will then rescale everything accordingly
  164. * @see https://doc.babylonjs.com/how_to/gui#adaptive-scaling
  165. */
  166. public get idealHeight(): number {
  167. return this._idealHeight;
  168. }
  169. public set idealHeight(value: number) {
  170. if (this._idealHeight === value) {
  171. return;
  172. }
  173. this._idealHeight = value;
  174. this.markAsDirty();
  175. this._rootContainer._markAllAsDirty();
  176. }
  177. /**
  178. * Gets or sets a boolean indicating if the smallest ideal value must be used if idealWidth and idealHeight are both set
  179. * @see https://doc.babylonjs.com/how_to/gui#adaptive-scaling
  180. */
  181. public get useSmallestIdeal(): boolean {
  182. return this._useSmallestIdeal;
  183. }
  184. public set useSmallestIdeal(value: boolean) {
  185. if (this._useSmallestIdeal === value) {
  186. return;
  187. }
  188. this._useSmallestIdeal = value;
  189. this.markAsDirty();
  190. this._rootContainer._markAllAsDirty();
  191. }
  192. /**
  193. * Gets or sets a boolean indicating if adaptive scaling must be used
  194. * @see https://doc.babylonjs.com/how_to/gui#adaptive-scaling
  195. */
  196. public get renderAtIdealSize(): boolean {
  197. return this._renderAtIdealSize;
  198. }
  199. public set renderAtIdealSize(value: boolean) {
  200. if (this._renderAtIdealSize === value) {
  201. return;
  202. }
  203. this._renderAtIdealSize = value;
  204. this._onResize();
  205. }
  206. /**
  207. * Gets the ratio used when in "ideal mode"
  208. * @see https://doc.babylonjs.com/how_to/gui#adaptive-scaling
  209. * */
  210. public get idealRatio(): number {
  211. var rwidth: number = 0;
  212. var rheight: number = 0;
  213. if (this._idealWidth) {
  214. rwidth = (this.getSize().width) / this._idealWidth;
  215. }
  216. if (this._idealHeight) {
  217. rheight = (this.getSize().height) / this._idealHeight;
  218. }
  219. if (this._useSmallestIdeal && this._idealWidth && this._idealHeight) {
  220. return window.innerWidth < window.innerHeight ? rwidth : rheight;
  221. }
  222. if (this._idealWidth) { // horizontal
  223. return rwidth;
  224. }
  225. if (this._idealHeight) { // vertical
  226. return rheight;
  227. }
  228. return 1;
  229. }
  230. /**
  231. * Gets the underlying layer used to render the texture when in fullscreen mode
  232. */
  233. public get layer(): Nullable<Layer> {
  234. return this._layerToDispose;
  235. }
  236. /**
  237. * Gets the root container control
  238. */
  239. public get rootContainer(): Container {
  240. return this._rootContainer;
  241. }
  242. /**
  243. * Returns an array containing the root container.
  244. * This is mostly used to let the Inspector introspects the ADT
  245. * @returns an array containing the rootContainer
  246. */
  247. public getChildren(): Array<Container> {
  248. return [this._rootContainer];
  249. }
  250. /**
  251. * Will return all controls that are inside this texture
  252. * @param directDescendantsOnly defines if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered
  253. * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
  254. * @return all child controls
  255. */
  256. public getDescendants(directDescendantsOnly?: boolean, predicate?: (control: Control) => boolean): Control[] {
  257. return this._rootContainer.getDescendants(directDescendantsOnly, predicate);
  258. }
  259. /**
  260. * Gets or sets the current focused control
  261. */
  262. public get focusedControl(): Nullable<IFocusableControl> {
  263. return this._focusedControl;
  264. }
  265. public set focusedControl(control: Nullable<IFocusableControl>) {
  266. if (this._focusedControl == control) {
  267. return;
  268. }
  269. if (this._focusedControl) {
  270. this._focusedControl.onBlur();
  271. }
  272. if (control) {
  273. control.onFocus();
  274. }
  275. this._focusedControl = control;
  276. }
  277. /**
  278. * Gets or sets a boolean indicating if the texture must be rendered in background or foreground when in fullscreen mode
  279. */
  280. public get isForeground(): boolean {
  281. if (!this.layer) {
  282. return true;
  283. }
  284. return (!this.layer.isBackground);
  285. }
  286. public set isForeground(value: boolean) {
  287. if (!this.layer) {
  288. return;
  289. }
  290. if (this.layer.isBackground === !value) {
  291. return;
  292. }
  293. this.layer.isBackground = !value;
  294. }
  295. /**
  296. * Gets or set information about clipboardData
  297. */
  298. public get clipboardData(): string {
  299. return this._clipboardData;
  300. }
  301. public set clipboardData(value: string) {
  302. this._clipboardData = value;
  303. }
  304. /**
  305. * Creates a new AdvancedDynamicTexture
  306. * @param name defines the name of the texture
  307. * @param width defines the width of the texture
  308. * @param height defines the height of the texture
  309. * @param scene defines the hosting scene
  310. * @param generateMipMaps defines a boolean indicating if mipmaps must be generated (false by default)
  311. * @param samplingMode defines the texture sampling mode (Texture.NEAREST_SAMPLINGMODE by default)
  312. * @param invertY defines if the texture needs to be inverted on the y axis during loading (true by default)
  313. */
  314. constructor(name: string, width = 0, height = 0, scene: Nullable<Scene>, generateMipMaps = false, samplingMode = Texture.NEAREST_SAMPLINGMODE, invertY?: boolean) {
  315. super(name, { width: width, height: height }, scene, generateMipMaps, samplingMode, Constants.TEXTUREFORMAT_RGBA, invertY);
  316. scene = this.getScene();
  317. if (!scene || !this._texture) {
  318. return;
  319. }
  320. this._rootElement = scene.getEngine()!.getInputElement()!;
  321. this._renderObserver = scene.onBeforeCameraRenderObservable.add((camera: Camera) => this._checkUpdate(camera));
  322. this._preKeyboardObserver = scene.onPreKeyboardObservable.add((info) => {
  323. if (!this._focusedControl) {
  324. return;
  325. }
  326. if (info.type === KeyboardEventTypes.KEYDOWN) {
  327. this._focusedControl.processKeyboard(info.event);
  328. }
  329. info.skipOnPointerObservable = true;
  330. });
  331. this._rootContainer._link(this);
  332. this.hasAlpha = true;
  333. if (!width || !height) {
  334. this._resizeObserver = scene.getEngine().onResizeObservable.add(() => this._onResize());
  335. this._onResize();
  336. }
  337. this._texture.isReady = true;
  338. }
  339. /**
  340. * Get the current class name of the texture useful for serialization or dynamic coding.
  341. * @returns "AdvancedDynamicTexture"
  342. */
  343. public getClassName(): string {
  344. return "AdvancedDynamicTexture";
  345. }
  346. /**
  347. * Function used to execute a function on all controls
  348. * @param func defines the function to execute
  349. * @param container defines the container where controls belong. If null the root container will be used
  350. */
  351. public executeOnAllControls(func: (control: Control) => void, container?: Container) {
  352. if (!container) {
  353. container = this._rootContainer;
  354. }
  355. func(container);
  356. for (var child of container.children) {
  357. if ((<any>child).children) {
  358. this.executeOnAllControls(func, (<Container>child));
  359. continue;
  360. }
  361. func(child);
  362. }
  363. }
  364. private _useInvalidateRectOptimization = true;
  365. /**
  366. * Gets or sets a boolean indicating if the InvalidateRect optimization should be turned on
  367. */
  368. public get useInvalidateRectOptimization(): boolean {
  369. return this._useInvalidateRectOptimization;
  370. }
  371. public set useInvalidateRectOptimization(value: boolean) {
  372. this._useInvalidateRectOptimization = value;
  373. }
  374. // Invalidated rectangle which is the combination of all invalidated controls after they have been rotated into absolute position
  375. private _invalidatedRectangle: Nullable<Measure> = null;
  376. /**
  377. * Invalidates a rectangle area on the gui texture
  378. * @param invalidMinX left most position of the rectangle to invalidate in the texture
  379. * @param invalidMinY top most position of the rectangle to invalidate in the texture
  380. * @param invalidMaxX right most position of the rectangle to invalidate in the texture
  381. * @param invalidMaxY bottom most position of the rectangle to invalidate in the texture
  382. */
  383. public invalidateRect(invalidMinX: number, invalidMinY: number, invalidMaxX: number, invalidMaxY: number) {
  384. if (!this._useInvalidateRectOptimization) {
  385. return;
  386. }
  387. if (!this._invalidatedRectangle) {
  388. this._invalidatedRectangle = new Measure(invalidMinX, invalidMinY, invalidMaxX - invalidMinX + 1, invalidMaxY - invalidMinY + 1);
  389. } else {
  390. // Compute intersection
  391. var maxX = Math.ceil(Math.max(this._invalidatedRectangle.left + this._invalidatedRectangle.width - 1, invalidMaxX));
  392. var maxY = Math.ceil(Math.max(this._invalidatedRectangle.top + this._invalidatedRectangle.height - 1, invalidMaxY));
  393. this._invalidatedRectangle.left = Math.floor(Math.min(this._invalidatedRectangle.left, invalidMinX));
  394. this._invalidatedRectangle.top = Math.floor(Math.min(this._invalidatedRectangle.top, invalidMinY));
  395. this._invalidatedRectangle.width = maxX - this._invalidatedRectangle.left + 1;
  396. this._invalidatedRectangle.height = maxY - this._invalidatedRectangle.top + 1;
  397. }
  398. }
  399. /**
  400. * Marks the texture as dirty forcing a complete update
  401. */
  402. public markAsDirty() {
  403. this._isDirty = true;
  404. }
  405. /**
  406. * Helper function used to create a new style
  407. * @returns a new style
  408. * @see https://doc.babylonjs.com/how_to/gui#styles
  409. */
  410. public createStyle(): Style {
  411. return new Style(this);
  412. }
  413. /**
  414. * Adds a new control to the root container
  415. * @param control defines the control to add
  416. * @returns the current texture
  417. */
  418. public addControl(control: Control): AdvancedDynamicTexture {
  419. this._rootContainer.addControl(control);
  420. return this;
  421. }
  422. /**
  423. * Removes a control from the root container
  424. * @param control defines the control to remove
  425. * @returns the current texture
  426. */
  427. public removeControl(control: Control): AdvancedDynamicTexture {
  428. this._rootContainer.removeControl(control);
  429. return this;
  430. }
  431. /**
  432. * Release all resources
  433. */
  434. public dispose(): void {
  435. let scene = this.getScene();
  436. if (!scene) {
  437. return;
  438. }
  439. this._rootElement = null;
  440. scene.onBeforeCameraRenderObservable.remove(this._renderObserver);
  441. if (this._resizeObserver) {
  442. scene.getEngine().onResizeObservable.remove(this._resizeObserver);
  443. }
  444. if (this._pointerMoveObserver) {
  445. scene.onPrePointerObservable.remove(this._pointerMoveObserver);
  446. }
  447. if (this._pointerObserver) {
  448. scene.onPointerObservable.remove(this._pointerObserver);
  449. }
  450. if (this._preKeyboardObserver) {
  451. scene.onPreKeyboardObservable.remove(this._preKeyboardObserver);
  452. }
  453. if (this._canvasPointerOutObserver) {
  454. scene.getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver);
  455. }
  456. if (this._canvasBlurObserver) {
  457. scene.getEngine().onCanvasBlurObservable.remove(this._canvasBlurObserver);
  458. }
  459. if (this._layerToDispose) {
  460. this._layerToDispose.texture = null;
  461. this._layerToDispose.dispose();
  462. this._layerToDispose = null;
  463. }
  464. this._rootContainer.dispose();
  465. this.onClipboardObservable.clear();
  466. this.onControlPickedObservable.clear();
  467. this.onBeginRenderObservable.clear();
  468. this.onEndRenderObservable.clear();
  469. this.onBeginLayoutObservable.clear();
  470. this.onEndLayoutObservable.clear();
  471. super.dispose();
  472. }
  473. private _onResize(): void {
  474. let scene = this.getScene();
  475. if (!scene) {
  476. return;
  477. }
  478. // Check size
  479. var engine = scene.getEngine();
  480. var textureSize = this.getSize();
  481. var renderWidth = engine.getRenderWidth() * this._renderScale;
  482. var renderHeight = engine.getRenderHeight() * this._renderScale;
  483. if (this._renderAtIdealSize) {
  484. if (this._idealWidth) {
  485. renderHeight = (renderHeight * this._idealWidth) / renderWidth;
  486. renderWidth = this._idealWidth;
  487. } else if (this._idealHeight) {
  488. renderWidth = (renderWidth * this._idealHeight) / renderHeight;
  489. renderHeight = this._idealHeight;
  490. }
  491. }
  492. if (textureSize.width !== renderWidth || textureSize.height !== renderHeight) {
  493. this.scaleTo(renderWidth, renderHeight);
  494. this.markAsDirty();
  495. if (this._idealWidth || this._idealHeight) {
  496. this._rootContainer._markAllAsDirty();
  497. }
  498. }
  499. this.invalidateRect(0, 0, textureSize.width - 1, textureSize.height - 1);
  500. }
  501. /** @hidden */
  502. public _getGlobalViewport(scene: Scene): Viewport {
  503. var engine = scene.getEngine();
  504. return this._fullscreenViewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight());
  505. }
  506. /**
  507. * Get screen coordinates for a vector3
  508. * @param position defines the position to project
  509. * @param worldMatrix defines the world matrix to use
  510. * @returns the projected position
  511. */
  512. public getProjectedPosition(position: Vector3, worldMatrix: Matrix): Vector2 {
  513. var scene = this.getScene();
  514. if (!scene) {
  515. return Vector2.Zero();
  516. }
  517. var globalViewport = this._getGlobalViewport(scene);
  518. var projectedPosition = Vector3.Project(position, worldMatrix, scene.getTransformMatrix(), globalViewport);
  519. projectedPosition.scaleInPlace(this.renderScale);
  520. return new Vector2(projectedPosition.x, projectedPosition.y);
  521. }
  522. /**
  523. * Get screen coordinates for a vector3
  524. * @param position defines the position to project
  525. * @param worldMatrix defines the world matrix to use
  526. * @returns the projected position with Z
  527. */
  528. public getProjectedPositionWithZ(position: Vector3, worldMatrix: Matrix): Vector3 {
  529. var scene = this.getScene();
  530. if (!scene) {
  531. return Vector3.Zero();
  532. }
  533. var globalViewport = this._getGlobalViewport(scene);
  534. var projectedPosition = Vector3.Project(position, worldMatrix, scene.getTransformMatrix(), globalViewport);
  535. projectedPosition.scaleInPlace(this.renderScale);
  536. return new Vector3(projectedPosition.x, projectedPosition.y, projectedPosition.z);
  537. }
  538. private _checkUpdate(camera: Camera): void {
  539. if (this._layerToDispose) {
  540. if ((camera.layerMask & this._layerToDispose.layerMask) === 0) {
  541. return;
  542. }
  543. }
  544. if (this._isFullscreen && this._linkedControls.length) {
  545. var scene = this.getScene();
  546. if (!scene) {
  547. return;
  548. }
  549. var globalViewport = this._getGlobalViewport(scene);
  550. for (let control of this._linkedControls) {
  551. if (!control.isVisible) {
  552. continue;
  553. }
  554. let mesh = control._linkedMesh as AbstractMesh;
  555. if (!mesh || mesh.isDisposed()) {
  556. Tools.SetImmediate(() => {
  557. control.linkWithMesh(null);
  558. });
  559. continue;
  560. }
  561. let position = mesh.getBoundingInfo ? mesh.getBoundingInfo().boundingSphere.center : (Vector3.ZeroReadOnly as Vector3);
  562. let projectedPosition = Vector3.Project(position, mesh.getWorldMatrix(), scene.getTransformMatrix(), globalViewport);
  563. if (projectedPosition.z < 0 || projectedPosition.z > 1) {
  564. control.notRenderable = true;
  565. continue;
  566. }
  567. control.notRenderable = false;
  568. // Account for RenderScale.
  569. projectedPosition.scaleInPlace(this.renderScale);
  570. control._moveToProjectedPosition(projectedPosition);
  571. }
  572. }
  573. if (!this._isDirty && !this._rootContainer.isDirty) {
  574. return;
  575. }
  576. this._isDirty = false;
  577. this._render();
  578. this.update(this.applyYInversionOnUpdate, this.premulAlpha);
  579. }
  580. private _clearMeasure = new Measure(0, 0, 0, 0);
  581. private _render(): void {
  582. var textureSize = this.getSize();
  583. var renderWidth = textureSize.width;
  584. var renderHeight = textureSize.height;
  585. var context = this.getContext();
  586. context.font = "18px Arial";
  587. context.strokeStyle = "white";
  588. // Layout
  589. this.onBeginLayoutObservable.notifyObservers(this);
  590. var measure = new Measure(0, 0, renderWidth, renderHeight);
  591. this._numLayoutCalls = 0;
  592. this._rootContainer._layout(measure, context);
  593. this.onEndLayoutObservable.notifyObservers(this);
  594. this._isDirty = false; // Restoring the dirty state that could have been set by controls during layout processing
  595. // Clear
  596. if (this._invalidatedRectangle) {
  597. this._clearMeasure.copyFrom(this._invalidatedRectangle);
  598. } else {
  599. this._clearMeasure.copyFromFloats(0, 0, renderWidth, renderHeight);
  600. }
  601. context.clearRect(this._clearMeasure.left, this._clearMeasure.top, this._clearMeasure.width, this._clearMeasure.height);
  602. if (this._background) {
  603. context.save();
  604. context.fillStyle = this._background;
  605. context.fillRect(this._clearMeasure.left, this._clearMeasure.top, this._clearMeasure.width, this._clearMeasure.height);
  606. context.restore();
  607. }
  608. // Render
  609. this.onBeginRenderObservable.notifyObservers(this);
  610. this._numRenderCalls = 0;
  611. this._rootContainer._render(context, this._invalidatedRectangle);
  612. this.onEndRenderObservable.notifyObservers(this);
  613. this._invalidatedRectangle = null;
  614. }
  615. /** @hidden */
  616. public _changeCursor(cursor: string) {
  617. if (this._rootElement) {
  618. this._rootElement.style.cursor = cursor;
  619. this._cursorChanged = true;
  620. }
  621. }
  622. /** @hidden */
  623. public _registerLastControlDown(control: Control, pointerId: number) {
  624. this._lastControlDown[pointerId] = control;
  625. this.onControlPickedObservable.notifyObservers(control);
  626. }
  627. private _doPicking(x: number, y: number, pi: PointerInfoBase, type: number, pointerId: number, buttonIndex: number, deltaX?: number, deltaY?: number): void {
  628. var scene = this.getScene();
  629. if (!scene) {
  630. return;
  631. }
  632. var engine = scene.getEngine();
  633. var textureSize = this.getSize();
  634. if (this._isFullscreen) {
  635. let camera = scene.cameraToUseForPointers || scene.activeCamera;
  636. let viewport = camera!.viewport;
  637. x = x * (textureSize.width / (engine.getRenderWidth() * viewport.width));
  638. y = y * (textureSize.height / (engine.getRenderHeight() * viewport.height));
  639. }
  640. if (this._capturingControl[pointerId]) {
  641. this._capturingControl[pointerId]._processObservables(type, x, y, pi, pointerId, buttonIndex);
  642. return;
  643. }
  644. this._cursorChanged = false;
  645. if (!this._rootContainer._processPicking(x, y, pi, type, pointerId, buttonIndex, deltaX, deltaY)) {
  646. this._changeCursor("");
  647. if (type === PointerEventTypes.POINTERMOVE) {
  648. if (this._lastControlOver[pointerId]) {
  649. this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId], pi);
  650. delete this._lastControlOver[pointerId];
  651. }
  652. }
  653. }
  654. if (!this._cursorChanged) {
  655. this._changeCursor("");
  656. }
  657. this._manageFocus();
  658. }
  659. /** @hidden */
  660. public _cleanControlAfterRemovalFromList(list: { [pointerId: number]: Control }, control: Control) {
  661. for (var pointerId in list) {
  662. if (!list.hasOwnProperty(pointerId)) {
  663. continue;
  664. }
  665. var lastControlOver = list[pointerId];
  666. if (lastControlOver === control) {
  667. delete list[pointerId];
  668. }
  669. }
  670. }
  671. /** @hidden */
  672. public _cleanControlAfterRemoval(control: Control) {
  673. this._cleanControlAfterRemovalFromList(this._lastControlDown, control);
  674. this._cleanControlAfterRemovalFromList(this._lastControlOver, control);
  675. }
  676. /** Attach to all scene events required to support pointer events */
  677. public attach(): void {
  678. var scene = this.getScene();
  679. if (!scene) {
  680. return;
  681. }
  682. let tempViewport = new Viewport(0, 0, 0, 0);
  683. this._pointerMoveObserver = scene.onPrePointerObservable.add((pi, state) => {
  684. if (scene!.isPointerCaptured((<PointerEvent>(pi.event)).pointerId)) {
  685. return;
  686. }
  687. if (pi.type !== PointerEventTypes.POINTERMOVE
  688. && pi.type !== PointerEventTypes.POINTERUP
  689. && pi.type !== PointerEventTypes.POINTERDOWN
  690. && pi.type !== PointerEventTypes.POINTERWHEEL) {
  691. return;
  692. }
  693. if (!scene) {
  694. return;
  695. }
  696. if (pi.type === PointerEventTypes.POINTERMOVE && (pi.event as PointerEvent).pointerId) {
  697. this._defaultMousePointerId = (pi.event as PointerEvent).pointerId; // This is required to make sure we have the correct pointer ID for wheel
  698. }
  699. let camera = scene.cameraToUseForPointers || scene.activeCamera;
  700. let engine = scene.getEngine();
  701. if (!camera) {
  702. tempViewport.x = 0;
  703. tempViewport.y = 0;
  704. tempViewport.width = engine.getRenderWidth();
  705. tempViewport.height = engine.getRenderHeight();
  706. } else {
  707. camera.viewport.toGlobalToRef(engine.getRenderWidth(), engine.getRenderHeight(), tempViewport);
  708. }
  709. let x = scene.pointerX / engine.getHardwareScalingLevel() - tempViewport.x;
  710. let y = scene.pointerY / engine.getHardwareScalingLevel() - (engine.getRenderHeight() - tempViewport.y - tempViewport.height);
  711. this._shouldBlockPointer = false;
  712. // Do picking modifies _shouldBlockPointer
  713. let pointerId = (pi.event as PointerEvent).pointerId || this._defaultMousePointerId;
  714. this._doPicking(x, y, pi, pi.type, pointerId, pi.event.button, (<MouseWheelEvent>pi.event).deltaX, (<MouseWheelEvent>pi.event).deltaY);
  715. // Avoid overwriting a true skipOnPointerObservable to false
  716. if (this._shouldBlockPointer) {
  717. pi.skipOnPointerObservable = this._shouldBlockPointer;
  718. }
  719. });
  720. this._attachToOnPointerOut(scene);
  721. this._attachToOnBlur(scene);
  722. }
  723. /** @hidden */
  724. private onClipboardCopy = (rawEvt: Event) => {
  725. const evt = rawEvt as ClipboardEvent;
  726. let ev = new ClipboardInfo(ClipboardEventTypes.COPY, evt);
  727. this.onClipboardObservable.notifyObservers(ev);
  728. evt.preventDefault();
  729. }
  730. /** @hidden */
  731. private onClipboardCut = (rawEvt: Event) => {
  732. const evt = rawEvt as ClipboardEvent;
  733. let ev = new ClipboardInfo(ClipboardEventTypes.CUT, evt);
  734. this.onClipboardObservable.notifyObservers(ev);
  735. evt.preventDefault();
  736. }
  737. /** @hidden */
  738. private onClipboardPaste = (rawEvt: Event) => {
  739. const evt = rawEvt as ClipboardEvent;
  740. let ev = new ClipboardInfo(ClipboardEventTypes.PASTE, evt);
  741. this.onClipboardObservable.notifyObservers(ev);
  742. evt.preventDefault();
  743. }
  744. /**
  745. * Register the clipboard Events onto the canvas
  746. */
  747. public registerClipboardEvents(): void {
  748. self.addEventListener("copy", this.onClipboardCopy, false);
  749. self.addEventListener("cut", this.onClipboardCut, false);
  750. self.addEventListener("paste", this.onClipboardPaste, false);
  751. }
  752. /**
  753. * Unregister the clipboard Events from the canvas
  754. */
  755. public unRegisterClipboardEvents(): void {
  756. self.removeEventListener("copy", this.onClipboardCopy);
  757. self.removeEventListener("cut", this.onClipboardCut);
  758. self.removeEventListener("paste", this.onClipboardPaste);
  759. }
  760. /**
  761. * Connect the texture to a hosting mesh to enable interactions
  762. * @param mesh defines the mesh to attach to
  763. * @param supportPointerMove defines a boolean indicating if pointer move events must be catched as well
  764. */
  765. public attachToMesh(mesh: AbstractMesh, supportPointerMove = true): void {
  766. var scene = this.getScene();
  767. if (!scene) {
  768. return;
  769. }
  770. this._pointerObserver = scene.onPointerObservable.add((pi, state) => {
  771. if (pi.type !== PointerEventTypes.POINTERMOVE
  772. && pi.type !== PointerEventTypes.POINTERUP
  773. && pi.type !== PointerEventTypes.POINTERDOWN) {
  774. return;
  775. }
  776. var pointerId = (pi.event as PointerEvent).pointerId || this._defaultMousePointerId;
  777. if (pi.pickInfo && pi.pickInfo.hit && pi.pickInfo.pickedMesh === mesh) {
  778. var uv = pi.pickInfo.getTextureCoordinates();
  779. if (uv) {
  780. let size = this.getSize();
  781. this._doPicking(uv.x * size.width, (this.applyYInversionOnUpdate ? (1.0 - uv.y) : uv.y) * size.height, pi, pi.type, pointerId, pi.event.button);
  782. }
  783. } else if (pi.type === PointerEventTypes.POINTERUP) {
  784. if (this._lastControlDown[pointerId]) {
  785. this._lastControlDown[pointerId]._forcePointerUp(pointerId);
  786. }
  787. delete this._lastControlDown[pointerId];
  788. if (this.focusedControl) {
  789. const friendlyControls = this.focusedControl.keepsFocusWith();
  790. let canMoveFocus = true;
  791. if (friendlyControls) {
  792. for (var control of friendlyControls) {
  793. // Same host, no need to keep the focus
  794. if (this === control._host) {
  795. continue;
  796. }
  797. // Different hosts
  798. const otherHost = control._host;
  799. if (otherHost._lastControlOver[pointerId] && otherHost._lastControlOver[pointerId].isAscendant(control)) {
  800. canMoveFocus = false;
  801. break;
  802. }
  803. }
  804. }
  805. if (canMoveFocus) {
  806. this.focusedControl = null;
  807. }
  808. }
  809. } else if (pi.type === PointerEventTypes.POINTERMOVE) {
  810. if (this._lastControlOver[pointerId]) {
  811. this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId], pi, true);
  812. }
  813. delete this._lastControlOver[pointerId];
  814. }
  815. });
  816. mesh.enablePointerMoveEvents = supportPointerMove;
  817. this._attachToOnPointerOut(scene);
  818. this._attachToOnBlur(scene);
  819. }
  820. /**
  821. * Move the focus to a specific control
  822. * @param control defines the control which will receive the focus
  823. */
  824. public moveFocusToControl(control: IFocusableControl): void {
  825. this.focusedControl = control;
  826. this._lastPickedControl = <any>control;
  827. this._blockNextFocusCheck = true;
  828. }
  829. private _manageFocus(): void {
  830. if (this._blockNextFocusCheck) {
  831. this._blockNextFocusCheck = false;
  832. this._lastPickedControl = <any>this._focusedControl;
  833. return;
  834. }
  835. // Focus management
  836. if (this._focusedControl) {
  837. if (this._focusedControl !== (<any>this._lastPickedControl)) {
  838. if (this._lastPickedControl.isFocusInvisible) {
  839. return;
  840. }
  841. this.focusedControl = null;
  842. }
  843. }
  844. }
  845. private _attachToOnPointerOut(scene: Scene): void {
  846. this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add((pointerEvent) => {
  847. if (this._lastControlOver[pointerEvent.pointerId]) {
  848. this._lastControlOver[pointerEvent.pointerId]._onPointerOut(this._lastControlOver[pointerEvent.pointerId], null);
  849. }
  850. delete this._lastControlOver[pointerEvent.pointerId];
  851. if (this._lastControlDown[pointerEvent.pointerId] && this._lastControlDown[pointerEvent.pointerId] !== this._capturingControl[pointerEvent.pointerId]) {
  852. this._lastControlDown[pointerEvent.pointerId]._forcePointerUp();
  853. delete this._lastControlDown[pointerEvent.pointerId];
  854. }
  855. });
  856. }
  857. private _attachToOnBlur(scene: Scene): void {
  858. this._canvasBlurObserver = scene.getEngine().onCanvasBlurObservable.add((pointerEvent) => {
  859. Object.entries(this._lastControlDown).forEach(([key, value]) => {
  860. value._onCanvasBlur();
  861. });
  862. this._lastControlDown = {};
  863. });
  864. }
  865. /**
  866. * Serializes the entire GUI system
  867. * @returns an object with the JSON serialized data
  868. */
  869. public serializeContent(): any {
  870. let serializationObject = {
  871. root: {}
  872. };
  873. this._rootContainer.serialize(serializationObject.root);
  874. return serializationObject;
  875. }
  876. /**
  877. * Recreate the content of the ADT from a JSON object
  878. * @param serializedObject define the JSON serialized object to restore from
  879. */
  880. public parseContent(serializedObject: any) {
  881. this._rootContainer = Control.Parse(serializedObject.root, this) as Container;
  882. }
  883. /**
  884. * Recreate the content of the ADT from a snippet saved by the GUI editor
  885. * @param snippetId defines the snippet to load
  886. * @returns a promise that will resolve on success
  887. */
  888. public parseFromSnippetAsync(snippetId: string): Promise<void> {
  889. if (snippetId === "_BLANK") {
  890. return Promise.resolve();
  891. }
  892. return new Promise((resolve, reject) => {
  893. var request = new WebRequest();
  894. request.addEventListener("readystatechange", () => {
  895. if (request.readyState == 4) {
  896. if (request.status == 200) {
  897. var snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
  898. let serializationObject = JSON.parse(snippet.gui);
  899. this.parseContent(serializationObject);
  900. this.snippetId = snippetId;
  901. resolve();
  902. } else {
  903. reject("Unable to load the snippet " + snippetId);
  904. }
  905. }
  906. });
  907. request.open("GET", AdvancedDynamicTexture.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
  908. request.send();
  909. });
  910. }
  911. // Statics
  912. /**
  913. * Creates a new AdvancedDynamicTexture in projected mode (ie. attached to a mesh)
  914. * @param mesh defines the mesh which will receive the texture
  915. * @param width defines the texture width (1024 by default)
  916. * @param height defines the texture height (1024 by default)
  917. * @param supportPointerMove defines a boolean indicating if the texture must capture move events (true by default)
  918. * @param onlyAlphaTesting defines a boolean indicating that alpha blending will not be used (only alpha testing) (false by default)
  919. * @param invertY defines if the texture needs to be inverted on the y axis during loading (true by default)
  920. * @returns a new AdvancedDynamicTexture
  921. */
  922. public static CreateForMesh(mesh: AbstractMesh, width = 1024, height = 1024, supportPointerMove = true, onlyAlphaTesting = false, invertY?: boolean): AdvancedDynamicTexture {
  923. var result = new AdvancedDynamicTexture(mesh.name + " AdvancedDynamicTexture", width, height, mesh.getScene(), true, Texture.TRILINEAR_SAMPLINGMODE, invertY);
  924. var material = new StandardMaterial("AdvancedDynamicTextureMaterial", mesh.getScene());
  925. material.backFaceCulling = false;
  926. material.diffuseColor = Color3.Black();
  927. material.specularColor = Color3.Black();
  928. if (onlyAlphaTesting) {
  929. material.diffuseTexture = result;
  930. material.emissiveTexture = result;
  931. result.hasAlpha = true;
  932. } else {
  933. material.emissiveTexture = result;
  934. material.opacityTexture = result;
  935. }
  936. mesh.material = material;
  937. result.attachToMesh(mesh, supportPointerMove);
  938. return result;
  939. }
  940. /**
  941. * Creates a new AdvancedDynamicTexture in projected mode (ie. attached to a mesh) BUT do not create a new material for the mesh. You will be responsible for connecting the texture
  942. * @param mesh defines the mesh which will receive the texture
  943. * @param width defines the texture width (1024 by default)
  944. * @param height defines the texture height (1024 by default)
  945. * @param supportPointerMove defines a boolean indicating if the texture must capture move events (true by default)
  946. * @param invertY defines if the texture needs to be inverted on the y axis during loading (true by default)
  947. * @returns a new AdvancedDynamicTexture
  948. */
  949. public static CreateForMeshTexture(mesh: AbstractMesh, width = 1024, height = 1024, supportPointerMove = true, invertY?: boolean): AdvancedDynamicTexture {
  950. var result = new AdvancedDynamicTexture(mesh.name + " AdvancedDynamicTexture", width, height, mesh.getScene(), true, Texture.TRILINEAR_SAMPLINGMODE, invertY);
  951. result.attachToMesh(mesh, supportPointerMove);
  952. return result;
  953. }
  954. /**
  955. * Creates a new AdvancedDynamicTexture in fullscreen mode.
  956. * In this mode the texture will rely on a layer for its rendering.
  957. * This allows it to be treated like any other layer.
  958. * As such, if you have a multi camera setup, you can set the layerMask on the GUI as well.
  959. * LayerMask is set through advancedTexture.layer.layerMask
  960. * @param name defines name for the texture
  961. * @param foreground defines a boolean indicating if the texture must be rendered in foreground (default is true)
  962. * @param scene defines the hsoting scene
  963. * @param sampling defines the texture sampling mode (Texture.BILINEAR_SAMPLINGMODE by default)
  964. * @returns a new AdvancedDynamicTexture
  965. */
  966. public static CreateFullscreenUI(name: string, foreground: boolean = true, scene: Nullable<Scene> = null, sampling = Texture.BILINEAR_SAMPLINGMODE): AdvancedDynamicTexture {
  967. var result = new AdvancedDynamicTexture(name, 0, 0, scene, false, sampling);
  968. // Display
  969. var layer = new Layer(name + "_layer", null, scene, !foreground);
  970. layer.texture = result;
  971. result._layerToDispose = layer;
  972. result._isFullscreen = true;
  973. // Attach
  974. result.attach();
  975. return result;
  976. }
  977. }