advancedDynamicTexture.ts 20 KB

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