advancedDynamicTexture.ts 40 KB

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