/// module BABYLON.GUI { export interface IFocusableControl { onFocus(): void; onBlur(): void; processKeyboard(evt: KeyboardEvent): void; } export class AdvancedDynamicTexture extends DynamicTexture { private _isDirty = false; private _renderObserver: Nullable>; private _resizeObserver: Nullable>; private _preKeyboardObserver: Nullable>; private _pointerMoveObserver: Nullable>; private _pointerObserver: Nullable>; private _canvasPointerOutObserver: Nullable>; private _background: string; public _rootContainer = new Container("root"); public _lastPickedControl: Control; public _lastControlOver: Nullable; public _lastControlDown: Nullable; public _capturingControl: Nullable; public _shouldBlockPointer: boolean; public _layerToDispose: Nullable; public _linkedControls = new Array(); private _isFullscreen = false; private _fullscreenViewport = new Viewport(0, 0, 1, 1); private _idealWidth = 0; private _idealHeight = 0; private _renderAtIdealSize = false; private _focusedControl: Nullable; private _blockNextFocusCheck = false; public get background(): string { return this._background; } public set background(value: string) { if (this._background === value) { return; } this._background = value; this.markAsDirty(); } public get idealWidth(): number { return this._idealWidth; } public set idealWidth(value: number) { if (this._idealWidth === value) { return; } this._idealWidth = value; this.markAsDirty(); this._rootContainer._markAllAsDirty(); } public get idealHeight(): number { return this._idealHeight; } public set idealHeight(value: number) { if (this._idealHeight === value) { return; } this._idealHeight = value; this.markAsDirty(); this._rootContainer._markAllAsDirty(); } public get renderAtIdealSize(): boolean { return this._renderAtIdealSize; } public set renderAtIdealSize(value: boolean) { if (this._renderAtIdealSize === value) { return; } this._renderAtIdealSize = value; this._onResize(); } public get layer(): Nullable { return this._layerToDispose; } public get rootContainer(): Container { return this._rootContainer; } public get focusedControl(): Nullable { return this._focusedControl; } public set focusedControl(control: Nullable) { if (this._focusedControl == control) { return; } if (this._focusedControl) { this._focusedControl.onBlur(); } if (control) { control.onFocus(); } this._focusedControl = control; } public get isForeground(): boolean { return (!this.layer.isBackground); } public set isForeground(value: boolean) { if (this.layer.isBackground === !value) { return; } this.layer.isBackground = !value; } constructor(name: string, width = 0, height = 0, scene: Nullable, generateMipMaps = false, samplingMode = Texture.NEAREST_SAMPLINGMODE) { super(name, {width: width, height: height}, scene, generateMipMaps, samplingMode, Engine.TEXTUREFORMAT_RGBA); scene = this.getScene(); if (!scene || !this._texture) { return; } this._renderObserver = scene.onBeforeCameraRenderObservable.add((camera: Camera) => this._checkUpdate(camera)); this._preKeyboardObserver = scene.onPreKeyboardObservable.add(info => { if (!this._focusedControl) { return; } if (info.type === KeyboardEventTypes.KEYDOWN) { this._focusedControl.processKeyboard(info.event); } info.skipOnPointerObservable = true; }); this._rootContainer._link(null, this); this.hasAlpha = true; if (!width || !height) { this._resizeObserver = scene.getEngine().onResizeObservable.add(() => this._onResize()); this._onResize(); } this._texture.isReady = true; } public executeOnAllControls(func: (control: Control) => void, container?: Container) { if (!container) { container = this._rootContainer; } for (var child of container.children) { if ((child).children) { this.executeOnAllControls(func, (child)); continue; } func(child); } } public markAsDirty() { this._isDirty = true; } public addControl(control: Control): AdvancedDynamicTexture { this._rootContainer.addControl(control); return this; } public removeControl(control: Control): AdvancedDynamicTexture { this._rootContainer.removeControl(control); return this; } public dispose(): void { let scene = this.getScene(); if (!scene) { return; } scene.onBeforeCameraRenderObservable.remove(this._renderObserver); if (this._resizeObserver) { scene.getEngine().onResizeObservable.remove(this._resizeObserver); } if (this._pointerMoveObserver) { scene.onPrePointerObservable.remove(this._pointerMoveObserver); } if (this._pointerObserver) { scene.onPointerObservable.remove(this._pointerObserver); } if (this._canvasPointerOutObserver) { scene.getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver); } if (this._layerToDispose) { this._layerToDispose.texture = null; this._layerToDispose.dispose(); this._layerToDispose = null; } this._rootContainer.dispose(); super.dispose(); } private _onResize(): void { let scene = this.getScene(); if (!scene) { return; } // Check size var engine = scene.getEngine(); var textureSize = this.getSize(); var renderWidth = engine.getRenderWidth(); var renderHeight = engine.getRenderHeight(); if (this._renderAtIdealSize) { if (this._idealWidth) { renderHeight = (renderHeight * this._idealWidth) / renderWidth; renderWidth = this._idealWidth; } else if (this._idealHeight) { renderWidth = (renderWidth * this._idealHeight) / renderHeight; renderHeight = this._idealHeight; } } if (textureSize.width !== renderWidth || textureSize.height !== renderHeight) { this.scaleTo(renderWidth, renderHeight); this.markAsDirty(); if (this._idealWidth || this._idealHeight) { this._rootContainer._markAllAsDirty(); } } } public _getGlobalViewport(scene: Scene): Viewport { var engine = scene.getEngine(); return this._fullscreenViewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight()); } private _checkUpdate(camera: Camera): void { if (this._layerToDispose) { if ((camera.layerMask & this._layerToDispose.layerMask) === 0) { return; } } if (this._isFullscreen && this._linkedControls.length) { var scene = this.getScene(); if (!scene) { return; } var globalViewport = this._getGlobalViewport(scene); for (var control of this._linkedControls) { if (!control.isVisible) { continue; } var mesh = control._linkedMesh; if (!mesh || mesh.isDisposed()) { Tools.SetImmediate(()=>{ control.linkWithMesh(null); }); continue; } var position = (mesh.getBoundingInfo()).boundingSphere.center; var projectedPosition = Vector3.Project(position, mesh.getWorldMatrix(), scene.getTransformMatrix(), globalViewport); if (projectedPosition.z < 0 || projectedPosition.z > 1) { control.notRenderable = true; continue; } control.notRenderable = false; control._moveToProjectedPosition(projectedPosition); } } if (!this._isDirty && !this._rootContainer.isDirty) { return; } this._isDirty = false; this._render(); this.update(); } private _render(): void { var textureSize = this.getSize(); var renderWidth = textureSize.width; var renderHeight = textureSize.height; // Clear var context = this.getContext(); context.clearRect(0, 0, renderWidth, renderHeight); if (this._background) { context.save(); context.fillStyle = this._background; context.fillRect(0, 0, renderWidth, renderHeight); context.restore(); } // Render context.font = "18px Arial"; context.strokeStyle = "white"; var measure = new Measure(0, 0, renderWidth, renderHeight); this._rootContainer._draw(measure, context); } private _doPicking(x: number, y: number, type: number, buttonIndex: number): void { var scene = this.getScene(); if (!scene) { return; } var engine = scene.getEngine(); var textureSize = this.getSize(); if (this._isFullscreen) { x = x * (textureSize.width / engine.getRenderWidth()); y = y * (textureSize.height / engine.getRenderHeight()); } if (this._capturingControl) { this._capturingControl._processObservables(type, x, y, buttonIndex); return; } if (!this._rootContainer._processPicking(x, y, type, buttonIndex)) { if (type === BABYLON.PointerEventTypes.POINTERMOVE) { if (this._lastControlOver) { this._lastControlOver._onPointerOut(this._lastControlOver); } this._lastControlOver = null; } } this._manageFocus(); } public attach(): void { var scene = this.getScene(); if (!scene) { return; } this._pointerMoveObserver = scene.onPrePointerObservable.add((pi, state) => { if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE && pi.type !== BABYLON.PointerEventTypes.POINTERUP && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) { return; } if (!scene) { return; } let camera = scene.cameraToUseForPointers || scene.activeCamera; if (!camera) { return; } let engine = scene.getEngine(); let viewport = camera.viewport; let x = (scene.pointerX / engine.getHardwareScalingLevel() - viewport.x * engine.getRenderWidth()) / viewport.width; let y = (scene.pointerY / engine.getHardwareScalingLevel() - viewport.y * engine.getRenderHeight()) / viewport.height; this._shouldBlockPointer = false; this._doPicking(x, y, pi.type, pi.event.button); pi.skipOnPointerObservable = this._shouldBlockPointer; }); this._attachToOnPointerOut(scene); } public attachToMesh(mesh: AbstractMesh, supportPointerMove = true): void { var scene = this.getScene(); if (!scene) { return; } this._pointerObserver = scene.onPointerObservable.add((pi, state) => { if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE && pi.type !== BABYLON.PointerEventTypes.POINTERUP && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) { return; } if (pi.pickInfo && pi.pickInfo.hit && pi.pickInfo.pickedMesh === mesh) { var uv = pi.pickInfo.getTextureCoordinates(); if (uv) { let size = this.getSize(); this._doPicking(uv.x * size.width, (1.0 - uv.y) * size.height, pi.type, pi.event.button); } } else if (pi.type === BABYLON.PointerEventTypes.POINTERUP) { if (this._lastControlDown) { this._lastControlDown.forcePointerUp(); } this._lastControlDown = null; this.focusedControl = null; } else if (pi.type === BABYLON.PointerEventTypes.POINTERMOVE) { if (this._lastControlOver) { this._lastControlOver._onPointerOut(this._lastControlOver); } this._lastControlOver = null; } }); mesh.enablePointerMoveEvents = supportPointerMove; this._attachToOnPointerOut(scene); } public moveFocusToControl(control: IFocusableControl): void { this.focusedControl = control; this._lastPickedControl = control; this._blockNextFocusCheck = true; } private _manageFocus(): void { if (this._blockNextFocusCheck) { this._blockNextFocusCheck = false; this._lastPickedControl = this._focusedControl; return; } // Focus management if (this._focusedControl) { if (this._focusedControl !== (this._lastPickedControl)) { if (this._lastPickedControl.isFocusInvisible) { return; } this.focusedControl = null; } } } private _attachToOnPointerOut(scene: Scene): void { this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add(() => { if (this._lastControlOver) { this._lastControlOver._onPointerOut(this._lastControlOver); } this._lastControlOver = null; if (this._lastControlDown) { this._lastControlDown.forcePointerUp(); } this._lastControlDown = null; }); } // Statics public static CreateForMesh(mesh: AbstractMesh, width = 1024, height = 1024, supportPointerMove = true): AdvancedDynamicTexture { var result = new AdvancedDynamicTexture(mesh.name + " AdvancedDynamicTexture", width, height, mesh.getScene(), true, Texture.TRILINEAR_SAMPLINGMODE); var material = new BABYLON.StandardMaterial("AdvancedDynamicTextureMaterial", mesh.getScene()); material.backFaceCulling = false; material.diffuseColor = BABYLON.Color3.Black(); material.specularColor = BABYLON.Color3.Black(); material.emissiveTexture = result; material.opacityTexture = result; mesh.material = material; result.attachToMesh(mesh, supportPointerMove); return result; } public static CreateFullscreenUI(name: string, foreground: boolean = true, scene: Nullable = null): AdvancedDynamicTexture { var result = new AdvancedDynamicTexture(name, 0, 0, scene); // Display var layer = new BABYLON.Layer(name + "_layer", null, scene, !foreground); layer.texture = result; result._layerToDispose = layer; result._isFullscreen = true; // Attach result.attach(); return result; } } }