advancedDynamicTexture.ts 39 KB

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