advancedDynamicTexture.ts 31 KB

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