advancedDynamicTexture.ts 36 KB

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