advancedDynamicTexture.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. /// <reference path="../../dist/preview release/babylon.d.ts"/>
  2. module BABYLON.GUI {
  3. export interface IFocusableControl {
  4. onFocus(): void;
  5. onBlur(): void;
  6. processKeyboard(evt: KeyboardEvent): void;
  7. }
  8. export class AdvancedDynamicTexture extends DynamicTexture {
  9. private _isDirty = false;
  10. private _renderObserver: Nullable<Observer<Camera>>;
  11. private _resizeObserver: Nullable<Observer<Engine>>;
  12. private _preKeyboardObserver: Nullable<Observer<KeyboardInfoPre>>;
  13. private _pointerMoveObserver: Nullable<Observer<PointerInfoPre>>;
  14. private _pointerObserver: Nullable<Observer<PointerInfo>>;
  15. private _canvasPointerOutObserver: Nullable<Observer<PointerEvent>>;
  16. private _background: string;
  17. public _rootContainer = new Container("root");
  18. public _lastPickedControl: Control;
  19. public _lastControlOver: {[pointerId:number]:Control} = {};
  20. public _lastControlDown: {[pointerId:number]:Control} = {};
  21. public _capturingControl: {[pointerId:number]:Control} = {};
  22. public _shouldBlockPointer: boolean;
  23. public _layerToDispose: Nullable<Layer>;
  24. public _linkedControls = new Array<Control>();
  25. private _isFullscreen = false;
  26. private _fullscreenViewport = new Viewport(0, 0, 1, 1);
  27. private _idealWidth = 0;
  28. private _idealHeight = 0;
  29. private _useSmallestIdeal: boolean = false;
  30. private _renderAtIdealSize = false;
  31. private _focusedControl: Nullable<IFocusableControl>;
  32. private _blockNextFocusCheck = false;
  33. private _renderScale = 1;
  34. public get renderScale(): number {
  35. return this._renderScale;
  36. }
  37. public set renderScale(value: number) {
  38. if (value === this._renderScale) {
  39. return;
  40. }
  41. this._renderScale = value;
  42. this._onResize();
  43. }
  44. public get background(): string {
  45. return this._background;
  46. }
  47. public set background(value: string) {
  48. if (this._background === value) {
  49. return;
  50. }
  51. this._background = value;
  52. this.markAsDirty();
  53. }
  54. public get idealWidth(): number {
  55. return this._idealWidth;
  56. }
  57. public set idealWidth(value: number) {
  58. if (this._idealWidth === value) {
  59. return;
  60. }
  61. this._idealWidth = value;
  62. this.markAsDirty();
  63. this._rootContainer._markAllAsDirty();
  64. }
  65. public get idealHeight(): number {
  66. return this._idealHeight;
  67. }
  68. public set idealHeight(value: number) {
  69. if (this._idealHeight === value) {
  70. return;
  71. }
  72. this._idealHeight = value;
  73. this.markAsDirty();
  74. this._rootContainer._markAllAsDirty();
  75. }
  76. public get useSmallestIdeal(): boolean {
  77. return this._useSmallestIdeal;
  78. }
  79. public set useSmallestIdeal(value: boolean) {
  80. if (this._useSmallestIdeal === value) {
  81. return;
  82. }
  83. this._useSmallestIdeal = value;
  84. this.markAsDirty();
  85. this._rootContainer._markAllAsDirty();
  86. }
  87. public get renderAtIdealSize(): boolean {
  88. return this._renderAtIdealSize;
  89. }
  90. public set renderAtIdealSize(value: boolean) {
  91. if (this._renderAtIdealSize === value) {
  92. return;
  93. }
  94. this._renderAtIdealSize = value;
  95. this._onResize();
  96. }
  97. public get layer(): Nullable<Layer> {
  98. return this._layerToDispose;
  99. }
  100. public get rootContainer(): Container {
  101. return this._rootContainer;
  102. }
  103. public get focusedControl(): Nullable<IFocusableControl> {
  104. return this._focusedControl;
  105. }
  106. public set focusedControl(control: Nullable<IFocusableControl>) {
  107. if (this._focusedControl == control) {
  108. return;
  109. }
  110. if (this._focusedControl) {
  111. this._focusedControl.onBlur();
  112. }
  113. if (control) {
  114. control.onFocus();
  115. }
  116. this._focusedControl = control;
  117. }
  118. public get isForeground(): boolean {
  119. if (!this.layer) {
  120. return true;
  121. }
  122. return (!this.layer.isBackground);
  123. }
  124. public set isForeground(value: boolean) {
  125. if (!this.layer) {
  126. return;
  127. }
  128. if (this.layer.isBackground === !value) {
  129. return;
  130. }
  131. this.layer.isBackground = !value;
  132. }
  133. constructor(name: string, width = 0, height = 0, scene: Nullable<Scene>, generateMipMaps = false, samplingMode = Texture.NEAREST_SAMPLINGMODE) {
  134. super(name, { width: width, height: height }, scene, generateMipMaps, samplingMode, Engine.TEXTUREFORMAT_RGBA);
  135. scene = this.getScene();
  136. if (!scene || !this._texture) {
  137. return;
  138. }
  139. this._renderObserver = scene.onBeforeCameraRenderObservable.add((camera: Camera) => this._checkUpdate(camera));
  140. this._preKeyboardObserver = scene.onPreKeyboardObservable.add(info => {
  141. if (!this._focusedControl) {
  142. return;
  143. }
  144. if (info.type === KeyboardEventTypes.KEYDOWN) {
  145. this._focusedControl.processKeyboard(info.event);
  146. }
  147. info.skipOnPointerObservable = true;
  148. });
  149. this._rootContainer._link(null, this);
  150. this.hasAlpha = true;
  151. if (!width || !height) {
  152. this._resizeObserver = scene.getEngine().onResizeObservable.add(() => this._onResize());
  153. this._onResize();
  154. }
  155. this._texture.isReady = true;
  156. }
  157. public executeOnAllControls(func: (control: Control) => void, container?: Container) {
  158. if (!container) {
  159. container = this._rootContainer;
  160. }
  161. for (var child of container.children) {
  162. if ((<any>child).children) {
  163. this.executeOnAllControls(func, (<Container>child));
  164. continue;
  165. }
  166. func(child);
  167. }
  168. }
  169. public markAsDirty() {
  170. this._isDirty = true;
  171. this.executeOnAllControls((control) => {
  172. if (control._isFontSizeInPercentage) {
  173. control._resetFontCache();
  174. }
  175. });
  176. }
  177. public addControl(control: Control): AdvancedDynamicTexture {
  178. this._rootContainer.addControl(control);
  179. return this;
  180. }
  181. public removeControl(control: Control): AdvancedDynamicTexture {
  182. this._rootContainer.removeControl(control);
  183. return this;
  184. }
  185. public dispose(): void {
  186. let scene = this.getScene();
  187. if (!scene) {
  188. return;
  189. }
  190. scene.onBeforeCameraRenderObservable.remove(this._renderObserver);
  191. if (this._resizeObserver) {
  192. scene.getEngine().onResizeObservable.remove(this._resizeObserver);
  193. }
  194. if (this._pointerMoveObserver) {
  195. scene.onPrePointerObservable.remove(this._pointerMoveObserver);
  196. }
  197. if (this._pointerObserver) {
  198. scene.onPointerObservable.remove(this._pointerObserver);
  199. }
  200. if (this._preKeyboardObserver) {
  201. scene.onPreKeyboardObservable.remove(this._preKeyboardObserver);
  202. }
  203. if (this._canvasPointerOutObserver) {
  204. scene.getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver);
  205. }
  206. if (this._layerToDispose) {
  207. this._layerToDispose.texture = null;
  208. this._layerToDispose.dispose();
  209. this._layerToDispose = null;
  210. }
  211. this._rootContainer.dispose();
  212. super.dispose();
  213. }
  214. private _onResize(): void {
  215. let scene = this.getScene();
  216. if (!scene) {
  217. return;
  218. }
  219. // Check size
  220. var engine = scene.getEngine();
  221. var textureSize = this.getSize();
  222. var renderWidth = engine.getRenderWidth() * this._renderScale;
  223. var renderHeight = engine.getRenderHeight() * this._renderScale;
  224. if (this._renderAtIdealSize) {
  225. if (this._idealWidth) {
  226. renderHeight = (renderHeight * this._idealWidth) / renderWidth;
  227. renderWidth = this._idealWidth;
  228. } else if (this._idealHeight) {
  229. renderWidth = (renderWidth * this._idealHeight) / renderHeight;
  230. renderHeight = this._idealHeight;
  231. }
  232. }
  233. if (textureSize.width !== renderWidth || textureSize.height !== renderHeight) {
  234. this.scaleTo(renderWidth, renderHeight);
  235. this.markAsDirty();
  236. if (this._idealWidth || this._idealHeight) {
  237. this._rootContainer._markAllAsDirty();
  238. }
  239. }
  240. }
  241. public _getGlobalViewport(scene: Scene): Viewport {
  242. var engine = scene.getEngine();
  243. return this._fullscreenViewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight());
  244. }
  245. public getProjectedPosition(position: Vector3, worldMatrix: Matrix): Vector2 {
  246. var scene = this.getScene();
  247. if (!scene) {
  248. return Vector2.Zero();
  249. }
  250. var globalViewport = this._getGlobalViewport(scene);
  251. var projectedPosition = Vector3.Project(position, worldMatrix, scene.getTransformMatrix(), globalViewport);
  252. projectedPosition.scaleInPlace(this.renderScale);
  253. return new Vector2(projectedPosition.x, projectedPosition.y);
  254. }
  255. private _checkUpdate(camera: Camera): void {
  256. if (this._layerToDispose) {
  257. if ((camera.layerMask & this._layerToDispose.layerMask) === 0) {
  258. return;
  259. }
  260. }
  261. if (this._isFullscreen && this._linkedControls.length) {
  262. var scene = this.getScene();
  263. if (!scene) {
  264. return;
  265. }
  266. var globalViewport = this._getGlobalViewport(scene);
  267. for (var control of this._linkedControls) {
  268. if (!control.isVisible) {
  269. continue;
  270. }
  271. var mesh = control._linkedMesh;
  272. if (!mesh || mesh.isDisposed()) {
  273. Tools.SetImmediate(() => {
  274. control.linkWithMesh(null);
  275. });
  276. continue;
  277. }
  278. var position = mesh.getBoundingInfo().boundingSphere.center;
  279. var projectedPosition = Vector3.Project(position, mesh.getWorldMatrix(), scene.getTransformMatrix(), globalViewport);
  280. if (projectedPosition.z < 0 || projectedPosition.z > 1) {
  281. control.notRenderable = true;
  282. continue;
  283. }
  284. control.notRenderable = false;
  285. // Account for RenderScale.
  286. projectedPosition.scaleInPlace(this.renderScale);
  287. control._moveToProjectedPosition(projectedPosition);
  288. }
  289. }
  290. if (!this._isDirty && !this._rootContainer.isDirty) {
  291. return;
  292. }
  293. this._isDirty = false;
  294. this._render();
  295. this.update();
  296. }
  297. private _render(): void {
  298. var textureSize = this.getSize();
  299. var renderWidth = textureSize.width;
  300. var renderHeight = textureSize.height;
  301. // Clear
  302. var context = this.getContext();
  303. context.clearRect(0, 0, renderWidth, renderHeight);
  304. if (this._background) {
  305. context.save();
  306. context.fillStyle = this._background;
  307. context.fillRect(0, 0, renderWidth, renderHeight);
  308. context.restore();
  309. }
  310. // Render
  311. context.font = "18px Arial";
  312. context.strokeStyle = "white";
  313. var measure = new Measure(0, 0, renderWidth, renderHeight);
  314. this._rootContainer._draw(measure, context);
  315. }
  316. private _doPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): void {
  317. var scene = this.getScene();
  318. if (!scene) {
  319. return;
  320. }
  321. var engine = scene.getEngine();
  322. var textureSize = this.getSize();
  323. if (this._isFullscreen) {
  324. x = x * (textureSize.width / engine.getRenderWidth());
  325. y = y * (textureSize.height / engine.getRenderHeight());
  326. }
  327. if (this._capturingControl[pointerId]) {
  328. this._capturingControl[pointerId]._processObservables(type, x, y, pointerId, buttonIndex);
  329. return;
  330. }
  331. if (!this._rootContainer._processPicking(x, y, type, pointerId, buttonIndex)) {
  332. if (type === BABYLON.PointerEventTypes.POINTERMOVE) {
  333. if (this._lastControlOver[pointerId]) {
  334. this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId]);
  335. }
  336. delete this._lastControlOver[pointerId];
  337. }
  338. }
  339. this._manageFocus();
  340. }
  341. public _cleanControlAfterRemovalFromList(list: {[pointerId:number]:Control}, control:Control) {
  342. for (var pointerId in list) {
  343. if (!list.hasOwnProperty(pointerId)) {
  344. continue;
  345. }
  346. var lastControlOver = list[pointerId];
  347. if (lastControlOver === control) {
  348. delete list[pointerId];
  349. }
  350. }
  351. }
  352. public _cleanControlAfterRemoval(control: Control) {
  353. this._cleanControlAfterRemovalFromList(this._lastControlDown, control);
  354. this._cleanControlAfterRemovalFromList(this._lastControlOver, control);
  355. }
  356. public attach(): void {
  357. var scene = this.getScene();
  358. if (!scene) {
  359. return;
  360. }
  361. this._pointerMoveObserver = scene.onPrePointerObservable.add((pi, state) => {
  362. if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE
  363. && pi.type !== BABYLON.PointerEventTypes.POINTERUP
  364. && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) {
  365. return;
  366. }
  367. if (!scene) {
  368. return;
  369. }
  370. let camera = scene.cameraToUseForPointers || scene.activeCamera;
  371. if (!camera) {
  372. return;
  373. }
  374. let engine = scene.getEngine();
  375. let viewport = camera.viewport;
  376. let x = (scene.pointerX / engine.getHardwareScalingLevel() - viewport.x * engine.getRenderWidth()) / viewport.width;
  377. let y = (scene.pointerY / engine.getHardwareScalingLevel() - viewport.y * engine.getRenderHeight()) / viewport.height;
  378. this._shouldBlockPointer = false;
  379. this._doPicking(x, y, pi.type, (pi.event as PointerEvent).pointerId || 0, pi.event.button);
  380. pi.skipOnPointerObservable = this._shouldBlockPointer;
  381. });
  382. this._attachToOnPointerOut(scene);
  383. }
  384. public attachToMesh(mesh: AbstractMesh, supportPointerMove = true): void {
  385. var scene = this.getScene();
  386. if (!scene) {
  387. return;
  388. }
  389. this._pointerObserver = scene.onPointerObservable.add((pi, state) => {
  390. if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE
  391. && pi.type !== BABYLON.PointerEventTypes.POINTERUP
  392. && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) {
  393. return;
  394. }
  395. var pointerId = (pi.event as PointerEvent).pointerId || 0;
  396. if (pi.pickInfo && pi.pickInfo.hit && pi.pickInfo.pickedMesh === mesh) {
  397. var uv = pi.pickInfo.getTextureCoordinates();
  398. if (uv) {
  399. let size = this.getSize();
  400. this._doPicking(uv.x * size.width, (1.0 - uv.y) * size.height, pi.type, pointerId, pi.event.button);
  401. }
  402. } else if (pi.type === BABYLON.PointerEventTypes.POINTERUP) {
  403. if (this._lastControlDown[pointerId]) {
  404. this._lastControlDown[pointerId].forcePointerUp(pointerId);
  405. }
  406. delete this._lastControlDown[pointerId];
  407. this.focusedControl = null;
  408. } else if (pi.type === BABYLON.PointerEventTypes.POINTERMOVE) {
  409. if (this._lastControlOver[pointerId]) {
  410. this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId]);
  411. }
  412. delete this._lastControlOver[pointerId];
  413. }
  414. });
  415. mesh.enablePointerMoveEvents = supportPointerMove;
  416. this._attachToOnPointerOut(scene);
  417. }
  418. public moveFocusToControl(control: IFocusableControl): void {
  419. this.focusedControl = control;
  420. this._lastPickedControl = <any>control;
  421. this._blockNextFocusCheck = true;
  422. }
  423. private _manageFocus(): void {
  424. if (this._blockNextFocusCheck) {
  425. this._blockNextFocusCheck = false;
  426. this._lastPickedControl = <any>this._focusedControl;
  427. return;
  428. }
  429. // Focus management
  430. if (this._focusedControl) {
  431. if (this._focusedControl !== (<any>this._lastPickedControl)) {
  432. if (this._lastPickedControl.isFocusInvisible) {
  433. return;
  434. }
  435. this.focusedControl = null;
  436. }
  437. }
  438. }
  439. private _attachToOnPointerOut(scene: Scene): void {
  440. this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add((pointerEvent) => {
  441. if (this._lastControlOver[pointerEvent.pointerId]) {
  442. this._lastControlOver[pointerEvent.pointerId]._onPointerOut(this._lastControlOver[pointerEvent.pointerId]);
  443. }
  444. delete this._lastControlOver[pointerEvent.pointerId];
  445. if (this._lastControlDown[pointerEvent.pointerId]) {
  446. this._lastControlDown[pointerEvent.pointerId].forcePointerUp();
  447. }
  448. delete this._lastControlDown[pointerEvent.pointerId];
  449. });
  450. }
  451. // Statics
  452. public static CreateForMesh(mesh: AbstractMesh, width = 1024, height = 1024, supportPointerMove = true): AdvancedDynamicTexture {
  453. var result = new AdvancedDynamicTexture(mesh.name + " AdvancedDynamicTexture", width, height, mesh.getScene(), true, Texture.TRILINEAR_SAMPLINGMODE);
  454. var material = new BABYLON.StandardMaterial("AdvancedDynamicTextureMaterial", mesh.getScene());
  455. material.backFaceCulling = false;
  456. material.diffuseColor = BABYLON.Color3.Black();
  457. material.specularColor = BABYLON.Color3.Black();
  458. material.emissiveTexture = result;
  459. material.opacityTexture = result;
  460. mesh.material = material;
  461. result.attachToMesh(mesh, supportPointerMove);
  462. return result;
  463. }
  464. /**
  465. * FullScreenUI is created in a layer. This allows it to be treated like any other layer.
  466. * As such, if you have a multi camera setup, you can set the layerMask on the GUI as well.
  467. * When the GUI is not Created as FullscreenUI it does not respect the layerMask.
  468. * layerMask is set through advancedTexture.layer.layerMask
  469. * @param name name for the Texture
  470. * @param foreground render in foreground (default is true)
  471. * @param scene scene to be rendered in
  472. * @param sampling method for scaling to fit screen
  473. * @returns AdvancedDynamicTexture
  474. */
  475. public static CreateFullscreenUI(name: string, foreground: boolean = true, scene: Nullable<Scene> = null, sampling = Texture.BILINEAR_SAMPLINGMODE): AdvancedDynamicTexture {
  476. var result = new AdvancedDynamicTexture(name, 0, 0, scene, false, sampling);
  477. // Display
  478. var layer = new BABYLON.Layer(name + "_layer", null, scene, !foreground);
  479. layer.texture = result;
  480. result._layerToDispose = layer;
  481. result._isFullscreen = true;
  482. // Attach
  483. result.attach();
  484. return result;
  485. }
  486. }
  487. }