advancedDynamicTexture.ts 37 KB

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