advancedDynamicTexture.ts 35 KB

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