advancedDynamicTexture.ts 18 KB

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