Browse Source

Merge pull request #1460 from nockawa/master

Canvas2D Bug fixes + GUI Work in Progress
Loïc Baumann 8 năm trước cách đây
mục cha
commit
11415f63e2

+ 2 - 0
canvas2D/config.json

@@ -33,7 +33,9 @@
       "src/Engine/babylon.canvas2d.js",
       "src/Engine/babylon.worldSpaceCanvas2dNode.js",
       "src/GUI/babylon.gui.UIElement.js",
+      "src/GUI/Layouts/babylon.gui.stackPanel",
       "src/GUI/babylon.gui.control.js",
+      "src/GUI/babylon.gui.contentControl.js",
       "src/GUI/babylon.gui.window.js",
       "src/GUI/babylon.gui.label.js",
       "src/GUI/babylon.gui.button.js"

+ 12 - 6
canvas2D/src/Engine/babylon.canvas2d.ts

@@ -63,7 +63,6 @@
          */
         public static RENDEROBSERVABLE_POST = 2;
 
-
         private static _INSTANCES : Array<Canvas2D> = [];
 
         constructor(scene: Scene, settings?: {
@@ -196,7 +195,7 @@
                         throw Error("You have to specify a valid camera and renderingGroup");
                     }
                     this._renderingGroupObserver = this._scene.onRenderingGroupObservable.add((e, s) => {
-                        if (this._scene.activeCamera === settings.renderingPhase.camera) {
+                        if ((this._scene.activeCamera === settings.renderingPhase.camera) && (e.renderStage===RenderingGroupInfo.STAGE_POSTTRANSPARENT)) {
                             this._engine.clear(null, false, true, true);
                             this._render();
                         }
@@ -1118,6 +1117,11 @@
             return canvas;
         }
 
+        /**
+         * Instanced Array will be create if there's at least this number of parts/prim that can fit into it
+         */
+        public minPartCountToUseInstancedArray = 5;
+
         private checkBackgroundAvailability() {
             if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
                 throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
@@ -1469,8 +1473,6 @@
             }
         }
 
-        private static _unS = new Vector2(1, 1);
-
         /**
          * Internal method that allocate a cache for the given group.
          * Caching is made using a collection of MapTexture where many groups have their bitmap cache stored inside.
@@ -1494,6 +1496,7 @@
             // Determine size
             let size = group.actualSize;
             size = new Size(Math.ceil(size.width * scale.x), Math.ceil(size.height * scale.y));
+            let originalSize = size.clone();
             if (minSize) {
                 size.width = Math.max(minSize.width, size.width);
                 size.height = Math.max(minSize.height, size.height);
@@ -1542,15 +1545,18 @@
 
                 let sprite: Sprite2D;
                 if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_CANVAS) {
+                    if (this._cachedCanvasGroup) {
+                        this._cachedCanvasGroup.dispose();
+                    }
                     this._cachedCanvasGroup = Group2D._createCachedCanvasGroup(this);
-                    sprite = new Sprite2D(map, { parent: this._cachedCanvasGroup, id: "__cachedCanvasSprite__", spriteSize: node.contentSize, spriteLocation: node.pos });
+                    sprite = new Sprite2D(map, { parent: this._cachedCanvasGroup, id: "__cachedCanvasSprite__", spriteSize: originalSize, spriteLocation: node.pos });
                     sprite.zOrder = 1;
                     sprite.origin = Vector2.Zero();
                 }
 
                 // Create a Sprite that will be used to render this cache, the "__cachedSpriteOfGroup__" starting id is a hack to bypass exception throwing in case of the Canvas doesn't normally allows direct primitives
                 else {
-                    sprite = new Sprite2D(map, { parent: parent, id: `__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x, y: group.actualPosition.y, spriteSize: node.contentSize, spriteLocation: node.pos, dontInheritParentScale: true });
+                    sprite = new Sprite2D(map, { parent: parent, id: `__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x * scale.x, y: group.actualPosition.y * scale.y, spriteSize: originalSize, spriteLocation: node.pos, dontInheritParentScale: true });
                     sprite.origin = group.origin.clone();
                     sprite.addExternalData("__cachedGroup__", group);
                     sprite.pointerEventObservable.add((e, s) => {

+ 2 - 2
canvas2D/src/Engine/babylon.canvas2dLayoutEngine.ts

@@ -62,12 +62,12 @@
         private _doUpdate(prim: Prim2DBase) {
             // Canvas ?
             if (prim instanceof Canvas2D) {
-                prim.layoutArea = prim.actualSize;
+                prim.layoutArea = prim.actualSize.multiplyByFloats(prim.scaleX, prim.scaleY);
             }
 
             // Direct child of Canvas ?
             else if (prim.parent instanceof Canvas2D) {
-                prim.layoutArea = prim.owner.actualSize;
+                prim.layoutArea = prim.owner.actualSize.multiplyByFloats(prim.owner.scaleX, prim.owner.scaleY);
             }
 
             // Indirect child of Canvas

+ 65 - 27
canvas2D/src/Engine/babylon.group2d.ts

@@ -655,9 +655,9 @@
                 let engine = this.owner.engine;
                 let count = ts.endDataIndex - ts.startDataIndex;
 
-                // Use Instanced Array if it's supported and if there's at least 5 prims to draw.
-                // We don't want to create an Instanced Buffer for less that 5 prims
-                if (useInstanced && count >= 5) {
+                // Use Instanced Array if it's supported and if there's at least minPartCountToUseInstancedArray prims to draw.
+                // We don't want to create an Instanced Buffer for less that minPartCountToUseInstancedArray prims
+                if (useInstanced && count >= this.owner.minPartCountToUseInstancedArray) {
 
                     if (!ts.partBuffers) {
                         let buffers = new Array<WebGLBuffer>();
@@ -829,8 +829,13 @@
                 scale = this.actualScale;
             }
 
-            Group2D._s.width  = Math.ceil(this.actualSize.width * scale.x * rs);
-            Group2D._s.height = Math.ceil(this.actualSize.height * scale.y * rs);
+            if (isCanvas && this.owner.cachingStrategy===Canvas2D.CACHESTRATEGY_CANVAS) {
+                Group2D._s.width = this.owner.engine.getRenderWidth();
+                Group2D._s.height = this.owner.engine.getRenderHeight();
+            } else {
+                Group2D._s.width = Math.ceil(this.actualSize.width * scale.x * rs);
+                Group2D._s.height = Math.ceil(this.actualSize.height * scale.y * rs);
+            }
 
             let sizeChanged = !Group2D._s.equals(rd._cacheSize);
 
@@ -855,6 +860,9 @@
                 var res = this.owner._allocateGroupCache(this, this.parent && this.parent.renderGroup, curWidth ? new Size(curWidth, curHeight) : null, rd._useMipMap, rd._anisotropicLevel);
                 rd._cacheNode = res.node;
                 rd._cacheTexture = res.texture;
+                if (rd._cacheRenderSprite) {
+                    rd._cacheRenderSprite.dispose();
+                }
                 rd._cacheRenderSprite = res.sprite;
                 sizeChanged = true;
             }
@@ -879,6 +887,16 @@
             }
         }
 
+        protected _spreadActualScaleDirty() {
+            if (this._renderableData && this._renderableData._cacheRenderSprite) {
+                this.handleGroupChanged(Prim2DBase.actualScaleProperty);
+            }
+
+            super._spreadActualScaleDirty();
+        }
+
+        protected static _unS = new Vector2(1, 1);
+
         protected handleGroupChanged(prop: Prim2DPropInfo) {
             // This method is only for cachedGroup
             let rd = this._renderableData;
@@ -893,20 +911,36 @@
 
             // For now we only support these property changes
             // TODO: add more! :)
-            if (prop.id === Prim2DBase.actualPositionProperty.id) {
-                cachedSprite.actualPosition = this.actualPosition.clone();
-                if (cachedSprite.position != null) {
-                    cachedSprite.position = cachedSprite.actualPosition.clone();
-                }
-            } else if (prop.id === Prim2DBase.rotationProperty.id) {
-                cachedSprite.rotation = this.rotation;
-            } else if (prop.id === Prim2DBase.scaleProperty.id) {
-                cachedSprite.scale = this.scale;
-            } else if (prop.id === Prim2DBase.originProperty.id) {
-                cachedSprite.origin = this.origin.clone();
-            } else if (prop.id === Group2D.actualSizeProperty.id) {
-                cachedSprite.size = this.actualSize.clone();
-                //console.log(`[${this._globalTransformProcessStep}] Sync Sprite ${this.id}, width: ${this.actualSize.width}, height: ${this.actualSize.height}`);
+            switch (prop.id) {
+                case Prim2DBase.actualScaleProperty.id:
+                case Prim2DBase.actualPositionProperty.id:
+                    let noResizeScale = rd._noResizeOnScale;
+                    let isCanvas = parent == null;
+                    let scale: Vector2;
+                    if (noResizeScale) {
+                        scale = isCanvas ? Group2D._unS : this.parent.actualScale;
+                    } else {
+                        scale = this.actualScale;
+                    }
+
+                    cachedSprite.actualPosition = this.actualPosition.multiply(scale);
+                    if (cachedSprite.position != null) {
+                        cachedSprite.position = cachedSprite.actualPosition.clone();
+                    }
+                    break;
+
+                case Prim2DBase.rotationProperty.id:
+                    cachedSprite.rotation = this.rotation;
+                    break;
+                case Prim2DBase.scaleProperty.id:
+                    cachedSprite.scale = this.scale;
+                    break;
+                case Prim2DBase.originProperty.id:
+                    cachedSprite.origin = this.origin.clone();
+                    break;
+                case Group2D.actualSizeProperty.id:
+                    cachedSprite.size = this.actualSize.clone();
+                    break;
             }
         }
 
@@ -949,19 +983,23 @@
 
             // All Group cached mode, all groups are renderable/cached, including the Canvas, groups with the behavior DONTCACHE are renderable/not cached, groups with CACHEINPARENT are logical ones
             else if (canvasStrat === Canvas2D.CACHESTRATEGY_ALLGROUPS) {
-                var gcb = this.cacheBehavior & Group2D.GROUPCACHEBEHAVIOR_OPTIONMASK;
-                if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
-                    this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
+                if (isCanvas) {
+                    this._isRenderableGroup = true;
                     this._isCachedGroup = false;
-                }
+                } else {
+                    var gcb = this.cacheBehavior & Group2D.GROUPCACHEBEHAVIOR_OPTIONMASK;
+                    if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
+                        this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
+                        this._isCachedGroup = false;
+                    }
 
-                if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
-                    this._isRenderableGroup = true;
-                    this._isCachedGroup = true;
+                    if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
+                        this._isRenderableGroup = true;
+                        this._isCachedGroup = true;
+                    }
                 }
             }
 
-
             if (this._isRenderableGroup) {
                 // Yes, we do need that check, trust me, unfortunately we can call _detectGroupStates many time on the same object...
                 if (!this._renderableData) {

+ 9 - 3
canvas2D/src/Engine/babylon.prim2dBase.ts

@@ -1353,7 +1353,7 @@
      * Base class for a Primitive of the Canvas2D feature
      */
     export class Prim2DBase extends SmartPropertyPrim {
-        static PRIM2DBASE_PROPCOUNT: number = 24;
+        static PRIM2DBASE_PROPCOUNT: number = 25;
 
         public  static _bigInt = Math.pow(2, 30);
 
@@ -1771,7 +1771,7 @@
         public static paddingProperty: Prim2DPropInfo;
 
         /**
-         * Metadata of the hAlignment property
+         * Metadata of the marginAlignment property
          */
         public static marginAlignmentProperty: Prim2DPropInfo;
 
@@ -1791,6 +1791,11 @@
          */
         public static scaleYProperty: Prim2DPropInfo;
 
+        /**
+         * Metadata of the actualScale property
+         */
+        public static actualScaleProperty: Prim2DPropInfo;
+
         @instanceLevelProperty(1, pi => Prim2DBase.actualPositionProperty = pi, false, false, true)
         /**
          * Return the position where the primitive is rendered in the Canvas, this position may be different than the one returned by the position property due to layout/alignment/margin/padding computing.
@@ -2328,7 +2333,7 @@
             return this._scale.y;
         }
 
-        private _spreadActualScaleDirty() {
+        protected _spreadActualScaleDirty() {
             for (let child of this._children) {
                 child._setFlags(SmartPropertyPrim.flagActualScaleDirty);
                 child._spreadActualScaleDirty();
@@ -2338,6 +2343,7 @@
         /**
          * Returns the actual scale of this Primitive, the value is computed from the scale property of this primitive, multiplied by the actualScale of its parent one (if any). The Vector2 object returned contains the scale for both X and Y axis
          */
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 24, pi => Prim2DBase.actualScaleProperty = pi, false, true)
         public get actualScale(): Vector2 {
             if (this._isFlagSet(SmartPropertyPrim.flagActualScaleDirty)) {
                 let cur = this._isFlagSet(SmartPropertyPrim.flagDontInheritParentScale) ? null : this.parent;

+ 1 - 1
canvas2D/src/Engine/babylon.smartPropertyPrim.ts

@@ -1127,7 +1127,7 @@
             }
 
             // If the property belong to a group, check if it's a cached one, and dirty its render sprite accordingly
-            if (this instanceof Group2D) {
+            if (this instanceof Group2D && (<Group2D><any>this)._renderableData) {
                 (<SmartPropertyPrim>this).handleGroupChanged(propInfo);
             }
 

+ 7 - 11
canvas2D/src/Engine/babylon.sprite2d.ts

@@ -381,9 +381,9 @@
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this.size = settings.spriteSize;
-            this.spriteLocation = settings.spriteLocation || new Vector2(0, 0);
-            this.spriteScaleFactor = settings.spriteScaleFactor || new Vector2(1, 1);
+            this.size = (settings.spriteSize!=null) ? settings.spriteSize.clone() : null;
+            this.spriteLocation = (settings.spriteLocation!=null) ? settings.spriteLocation.clone() : new Vector2(0, 0);
+            this.spriteScaleFactor = (settings.spriteScaleFactor!=null) ? settings.spriteScaleFactor : new Vector2(1, 1);
             this.spriteFrame = 0;
             this.invertY = (settings.invertY == null) ? false : settings.invertY;
             this.alignToPixel = (settings.alignToPixel == null) ? true : settings.alignToPixel;
@@ -391,11 +391,13 @@
 
             if (settings.spriteSize == null || !texture.isReady()) {
                 if (texture.isReady()) {
-                    this.size = <Size>texture.getBaseSize();
+                    let s = texture.getBaseSize();
+                    this.size = new Size(s.width, s.height);
                 } else {
                     texture.onLoadObservable.add(() => {
                         if (settings.spriteSize == null) {
-                            this.size = <Size>texture.getBaseSize();
+                            let s = texture.getBaseSize();
+                            this.size = new Size(s.width, s.height);
                         }
                         this._positioningDirty();
                         this._instanceDirtyFlags |= Prim2DBase.originProperty.flagId | Sprite2D.textureProperty.flagId;  // To make sure the sprite is issued again for render
@@ -404,12 +406,6 @@
             }
         }
 
-        static _createCachedCanvasSprite(owner: Canvas2D, texture: MapTexture, size: Size, pos: Vector2): Sprite2D {
-
-            let sprite = new Sprite2D(texture, { parent: owner, id: "__cachedCanvasSprite__", position: Vector2.Zero(), origin: Vector2.Zero(), spriteSize: size, spriteLocation: pos, alignToPixel: true });
-            return sprite;
-        }
-
         protected createModelRenderCache(modelKey: string): ModelRenderCache {
             let renderCache = new Sprite2DRenderCache(this.owner.engine, modelKey);
             return renderCache;

+ 106 - 0
canvas2D/src/GUI/Layouts/babylon.gui.stackPanel.ts

@@ -0,0 +1,106 @@
+module BABYLON {
+
+    @className("StackPanel", "BABYLON")
+    export class StackPanel extends UIElement {
+
+        static STACKPANEL_PROPCOUNT = UIElement.UIELEMENT_PROPCOUNT + 3;
+
+        static orientationHorizontalProperty: Prim2DPropInfo;
+
+        constructor(settings?: {
+
+            id                      ?: string,
+            parent                  ?: UIElement,
+            children                ?: Array<UIElement>,
+            templateName            ?: string,
+            styleName               ?: string,
+            isOrientationHorizontal ?: any,
+            marginTop               ?: number | string,
+            marginLeft              ?: number | string,
+            marginRight             ?: number | string,
+            marginBottom            ?: number | string,
+            margin                  ?: number | string,
+            marginHAlignment        ?: number,
+            marginVAlignment        ?: number,
+            marginAlignment         ?: string,
+            paddingTop              ?: number | string,
+            paddingLeft             ?: number | string,
+            paddingRight            ?: number | string,
+            paddingBottom           ?: number | string,
+            padding                 ?: string,
+            paddingHAlignment       ?: number,
+            paddingVAlignment       ?: number,
+            paddingAlignment        ?: string,
+        }) {
+            if (!settings) {
+                settings = {};
+            }
+
+            super(settings);
+
+            this.isOrientationHorizontal = (settings.isOrientationHorizontal == null) ? true : settings.isOrientationHorizontal;
+            this._children = new Array<UIElement>();
+
+            if (settings.children != null) {
+                for (let child of settings.children) {
+                    this._children.push(child);
+                }
+            }
+
+        }
+
+        @dependencyProperty(StackPanel.STACKPANEL_PROPCOUNT + 0, pi => StackPanel.orientationHorizontalProperty = pi)
+        public get isOrientationHorizontal(): boolean {
+            return this._isOrientationHorizontal;
+        }
+
+        public set isOrientationHorizontal(value: boolean) {
+            this._isOrientationHorizontal = value;
+        }
+
+        protected createVisualTree() {
+            super.createVisualTree();
+
+            // A StackPanel Control has a Group2D, child of the visualPlaceHolder, which is the Children placeholder.
+            // The Children UIElement Tree will be create inside this placeholder.
+            this._childrenPlaceholder = new Group2D({ parent: this._visualPlaceholder, id: `StackPanel Children Placeholder of ${this.id}` });
+            let p = this._childrenPlaceholder;
+
+            p.layoutEngine = this.isOrientationHorizontal ? StackPanelLayoutEngine.Horizontal : StackPanelLayoutEngine.Vertical;
+
+            // The UIElement padding properties (padding and paddingAlignment) are bound to the Group2D Children placeholder, we bound to the Margin properties as the Group2D acts as an inner element already, so margin of inner is padding.
+            p.dataSource = this;
+            p.createSimpleDataBinding(Prim2DBase.marginProperty, "padding", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "paddingAlignment", DataBinding.MODE_ONEWAY);
+
+            // The UIElement set the childrenPlaceholder with the visual returned by the renderingTemplate.
+            // But it's not the case for a StackPanel, the placeholder of UIElement Children (the content)
+            this._visualChildrenPlaceholder = this._childrenPlaceholder;
+        }
+
+        public get children(): Array<UIElement> {
+            return this._children;
+        }
+
+        protected _getChildren(): Array<UIElement> {
+            return this.children;
+        }
+
+        private _childrenPlaceholder: Group2D;
+        private _children;
+        private _isOrientationHorizontal: boolean;
+    }
+
+
+    @registerWindowRenderingTemplate("BABYLON.StackPanel", "Default", () => new DefaultStackPanelRenderingTemplate())
+    export class DefaultStackPanelRenderingTemplate extends UIElementRenderingTemplateBase {
+
+        createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase; contentPlaceholder: Prim2DBase } {
+            return { root: visualPlaceholder, contentPlaceholder: visualPlaceholder };
+        }
+
+        attach(owner: UIElement): void {
+            super.attach(owner);
+        }
+    }
+}

+ 241 - 87
canvas2D/src/GUI/babylon.gui.UIElement.ts

@@ -55,48 +55,64 @@
 
     export abstract class UIElement extends SmartPropertyBase {
 
-        static UIELEMENT_PROPCOUNT: number = 15;
-
-        static parentProperty         : Prim2DPropInfo;
-        static widthProperty          : Prim2DPropInfo;
-        static heightProperty         : Prim2DPropInfo;
-        static minWidthProperty       : Prim2DPropInfo;
-        static minHeightProperty      : Prim2DPropInfo;
-        static maxWidthProperty       : Prim2DPropInfo;
-        static maxHeightProperty      : Prim2DPropInfo;
-        static actualWidthProperty    : Prim2DPropInfo;
-        static actualHeightProperty   : Prim2DPropInfo;
-        static marginProperty         : Prim2DPropInfo;
-        static paddingProperty        : Prim2DPropInfo;
-        static marginAlignmentProperty: Prim2DPropInfo;
-        static isEnabledProperty      : Prim2DPropInfo;
-        static isFocusedProperty      : Prim2DPropInfo;
-        static isMouseOverProperty    : Prim2DPropInfo;
+        static get enabledState(): string {
+            return UIElement._enableState;
+        }
+
+        static get disabledState(): string {
+            return UIElement._disabledState;
+        }
+
+        static get mouseOverState(): string {
+            return UIElement._mouseOverState;
+        }
+
+        static UIELEMENT_PROPCOUNT: number = 16;
+
+        static parentProperty          : Prim2DPropInfo;
+        static widthProperty           : Prim2DPropInfo;
+        static heightProperty          : Prim2DPropInfo;
+        static minWidthProperty        : Prim2DPropInfo;
+        static minHeightProperty       : Prim2DPropInfo;
+        static maxWidthProperty        : Prim2DPropInfo;
+        static maxHeightProperty       : Prim2DPropInfo;
+        static actualWidthProperty     : Prim2DPropInfo;
+        static actualHeightProperty    : Prim2DPropInfo;
+        static marginProperty          : Prim2DPropInfo;
+        static paddingProperty         : Prim2DPropInfo;
+        static marginAlignmentProperty : Prim2DPropInfo;
+        static paddingAlignmentProperty: Prim2DPropInfo;
+        static isEnabledProperty       : Prim2DPropInfo;
+        static isFocusedProperty       : Prim2DPropInfo;
+        static isMouseOverProperty     : Prim2DPropInfo;
 
         constructor(settings: {
-            id              ?: string,
-            parent          ?: UIElement,
-            templateName    ?: string,
-            styleName       ?: string,
-            minWidth        ?: number,
-            minHeight       ?: number,
-            maxWidth        ?: number,
-            maxHeight       ?: number,
-            width           ?: number,
-            height          ?: number,
-            marginTop       ?: number | string,
-            marginLeft      ?: number | string,
-            marginRight     ?: number | string,
-            marginBottom    ?: number | string,
-            margin          ?: number | string,
-            marginHAlignment?: number,
-            marginVAlignment?: number,
-            marginAlignment ?: string,
-            paddingTop      ?: number | string,
-            paddingLeft     ?: number | string,
-            paddingRight    ?: number | string,
-            paddingBottom   ?: number | string,
-            padding         ?: string,
+            id               ?: string,
+            parent           ?: UIElement,
+            templateName     ?: string,
+            styleName        ?: string,
+            minWidth         ?: number,
+            minHeight        ?: number,
+            maxWidth         ?: number,
+            maxHeight        ?: number,
+            width            ?: number,
+            height           ?: number,
+            marginTop        ?: number | string,
+            marginLeft       ?: number | string,
+            marginRight      ?: number | string,
+            marginBottom     ?: number | string,
+            margin           ?: number | string,
+            marginHAlignment ?: number,
+            marginVAlignment ?: number,
+            marginAlignment  ?: string,
+            paddingTop       ?: number | string,
+            paddingLeft      ?: number | string,
+            paddingRight     ?: number | string,
+            paddingBottom    ?: number | string,
+            padding          ?: string,
+            paddingHAlignment?: number,
+            paddingVAlignment?: number,
+            paddingAlignment ?: string,
         }) {
             super();
 
@@ -111,7 +127,8 @@
             this._visualTemplateRoot        = null;
             this._visualChildrenPlaceholder = null;
             this._hierarchyDepth            = 0;
-            this._style                     = (settings.styleName!=null) ? UIElementStyleManager.getStyle(type, settings.styleName) : null;
+            this._renderingTemplateName     = (settings.templateName != null) ? settings.templateName : GUIManager.DefaultTemplateName;
+            this._style                     = (settings.styleName!=null) ? GUIManager.getStyle(type, settings.styleName) : null;
             this._flags                     = 0;
             this._id                        = (settings.id!=null) ? settings.id : null;
             this._uid                       = null;
@@ -124,9 +141,8 @@
             this._margin                    = null;
             this._padding                   = null;
             this._marginAlignment           = null;
-            this._isEnabled                 = true;
-            this._isFocused                 = false;
-            this._isMouseOver               = false;
+
+            this._setFlags(UIElement.flagIsVisible|UIElement.flagIsEnabled);
 
             // Default Margin Alignment for UIElement is stretch for horizontal/vertical and not left/bottom (which is the default for Canvas2D Primitives)
             //this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
@@ -183,7 +199,17 @@
                 this.padding.fromString(settings.padding);
             }
 
-            this._assignTemplate(settings.templateName);
+            if (settings.paddingHAlignment) {
+                this.paddingAlignment.horizontal = settings.paddingHAlignment;
+            }
+
+            if (settings.paddingVAlignment) {
+                this.paddingAlignment.vertical = settings.paddingVAlignment;
+            }
+
+            if (settings.paddingAlignment) {
+                this.paddingAlignment.fromString(settings.paddingAlignment);
+            }
 
             if (settings.parent != null) {
                 this._parent = settings.parent;
@@ -266,13 +292,27 @@
         // SizeChanged
         // ToolTipOpening/Closing
 
-        public get ownerWindows(): Window {
+        public findById(id: string): UIElement {
+            if (this._id === id) {
+                return this;
+            }
+
+            let children = this._getChildren();
+            for (let child of children) {
+                let r = child.findById(id);
+                if (r != null) {
+                    return r;
+                }
+            }
+        }
+
+        public get ownerWindow(): Window {
             return this._ownerWindow;
         }
 
         public get style(): string {
             if (!this.style) {
-                return UIElementStyleManager.DefaultStyleName;
+                return GUIManager.DefaultStyleName;
             }
             return this._style.name;
         }
@@ -284,7 +324,7 @@
 
             let newStyle: UIElementStyle = null;
             if (value) {
-                newStyle = UIElementStyleManager.getStyle(Tools.getFullClassName(this), value);
+                newStyle = GUIManager.getStyle(Tools.getFullClassName(this), value);
                 if (!newStyle) {
                     throw Error(`Couldn't find Style ${value} for UIElement ${Tools.getFullClassName(this)}`);
                 }
@@ -485,40 +525,121 @@
             return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
         }
 
-        @dynamicLevelProperty(12, pi => UIElement.isEnabledProperty = pi)
+        @dynamicLevelProperty(12, pi => UIElement.paddingAlignmentProperty = pi)
+        /**
+         * You can get/set the margin alignment through this property
+         */
+        public get paddingAlignment(): PrimitiveAlignment {
+            if (!this._paddingAlignment) {
+                this._paddingAlignment = new PrimitiveAlignment();
+            }
+            return this._paddingAlignment;
+        }
+
+        public set paddingAlignment(value: PrimitiveAlignment) {
+            this.paddingAlignment.copyFrom(value);
+        }
+
+        /**
+         * Check if there a marginAlignment specified (non null and not default)
+         */
+        public get _hasPaddingAlignment(): boolean {
+            return (this._paddingAlignment !== null && !this._paddingAlignment.isDefault);
+        }
+
+        public get isVisible(): boolean {
+            return this._isFlagSet(UIElement.flagIsVisible);
+        }
+
+        public set isVisible(value: boolean) {
+            if (this.isVisible === value) {
+                return;
+            }
+
+            this._visualPlaceholder.levelVisible = value;
+
+            this._changeFlags(UIElement.flagIsVisible, value);
+        }
+
+        @dynamicLevelProperty(13, pi => UIElement.isEnabledProperty = pi)
         /**
-         * True if the UIElement is enabled, false if it's disabled
+         * True if the UIElement is enabled, false if it's disabled.
+         * User interaction is not possible if the UIElement is not enabled
          */
         public get isEnabled(): boolean {
-            return this._isEnabled;
+            return this._isFlagSet(UIElement.flagIsEnabled);
         }
 
         public set isEnabled(value: boolean) {
-            this._isEnabled = value;
+            this._changeFlags(UIElement.flagIsEnabled, value);
         }
 
-        @dynamicLevelProperty(13, pi => UIElement.isFocusedProperty = pi)
+        @dynamicLevelProperty(14, pi => UIElement.isFocusedProperty = pi)
         /**
          * True if the UIElement has the focus, false if it doesn't
          */
         public get isFocused(): boolean {
-            return this._isFocused;
+            return this._isFlagSet(UIElement.flagIsFocus);
         }
 
         public set isFocused(value: boolean) {
-            this._isFocused = value;
+            // If the UIElement doesn't accept focus, set it on its parent
+            if (!this.isFocusable) {
+                let p = this.parent;
+                if (!p) {
+                    return;
+                }
+                p.isFocused = value;
+            }
+
+            // If the focus is being set, notify the Focus Manager
+            if (value) {
+                this.ownerWindow.focusManager.setFocusOn(this, this.getFocusScope());
+            }
+
+            this._changeFlags(UIElement.flagIsFocus, value);
         }
 
-        @dynamicLevelProperty(14, pi => UIElement.isMouseOverProperty = pi)
+        @dynamicLevelProperty(15, pi => UIElement.isMouseOverProperty = pi)
         /**
          * True if the UIElement has the mouse over it
          */
         public get isMouseOver(): boolean {
-            return this._isMouseOver;
+            return this._isFlagSet(UIElement.flagIsMouseOver);
         }
 
         public set isMouseOver(value: boolean) {
-            this._isMouseOver = value;
+            this._changeFlags(UIElement.flagIsMouseOver, value);
+        }
+
+        public get isFocusScope(): boolean {
+            return this._isFlagSet(UIElement.flagIsFocusScope);
+        }
+
+        public set isFocusScope(value: boolean) {
+            this._changeFlags(UIElement.flagIsFocusScope, value);
+        }
+
+        public get isFocusable(): boolean {
+            return this._isFlagSet(UIElement.flagIsFocusable);
+        }
+
+        public set isFocusable(value: boolean) {
+            this._changeFlags(UIElement.flagIsFocusable, value);
+        }
+
+        // Look for the nearest parent which is the focus scope. Should always return something as the Window UIElement which is the root of all UI Tree is focus scope (unless the user disable it)
+        protected getFocusScope(): UIElement {
+            if (this.isFocusScope) {
+                return this;
+            }
+
+            let p = this.parent;
+            if (!p) {
+                return null;
+            }
+
+            return p.getFocusScope();
         }
 
         /**
@@ -582,38 +703,43 @@
 
         private _assignTemplate(templateName: string) {
             if (!templateName) {
-                templateName = UIElementRenderingTemplateManager.DefaultTemplateName;
+                templateName = GUIManager.DefaultTemplateName;
             }
             let className = Tools.getFullClassName(this);
             if (!className) {
                 throw Error("Couldn't access class name of this UIElement, you have to decorate the type with the className decorator");
             }
 
-            let factory = UIElementRenderingTemplateManager.getRenderingTemplate(className, templateName);
+            let factory = GUIManager.getRenderingTemplate(className, templateName);
             if (!factory) {
                 throw Error(`Couldn't get the renderingTemplate ${templateName} of class ${className}`);
             }
 
+            this._renderingTemplateName = templateName;
             this._renderingTemplate = factory();
             this._renderingTemplate.attach(this);
         }
 
         public _createVisualTree() {
-            let parentPrim: Prim2DBase = this.ownerWindows.canvas;
+            let parentPrim: Prim2DBase = this.ownerWindow.canvas;
             if (this.parent) {
                 parentPrim = this.parent.visualChildrenPlaceholder;
             }
 
-            this._visualPlaceholder = new Group2D({ parent: parentPrim, id: `GUI Visual Placeholder of ${this.id}`});
+            if (!this._renderingTemplate) {
+                this._assignTemplate(this._renderingTemplateName);               
+            }
+
+            this._visualPlaceholder = new Group2D({ parent: parentPrim, id: `GUI ${Tools.getClassName(this)} RootGroup of ${this.id}`});
             let p = this._visualPlaceholder;
             p.addExternalData<UIElement>("_GUIOwnerElement_", this);
             p.dataSource = this;
-            p.createSimpleDataBinding(Prim2DBase.widthProperty, "width", DataBinding.MODE_ONEWAY);
-            p.createSimpleDataBinding(Prim2DBase.heightProperty, "height", DataBinding.MODE_ONEWAY);
-            p.createSimpleDataBinding(Prim2DBase.actualWidthProperty, "actualWidth", DataBinding.MODE_ONEWAYTOSOURCE);
-            p.createSimpleDataBinding(Prim2DBase.actualHeightProperty, "actualHeight", DataBinding.MODE_ONEWAYTOSOURCE);
-            p.createSimpleDataBinding(Prim2DBase.marginProperty, "margin", DataBinding.MODE_ONEWAY);
-            p.createSimpleDataBinding(Prim2DBase.paddingProperty, "padding", DataBinding.MODE_ONEWAY);
+
+            p.createSimpleDataBinding(Prim2DBase.widthProperty          , "width"          , DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.heightProperty         , "height"         , DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.actualWidthProperty    , "actualWidth"    , DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.actualHeightProperty   , "actualHeight"   , DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.marginProperty         , "margin"         , DataBinding.MODE_ONEWAY);
             p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "marginAlignment", DataBinding.MODE_ONEWAY);
             this.createVisualTree();
         }
@@ -675,12 +801,19 @@
         protected get _position(): Vector2 { return null; } // TODO use abstract keyword when TS 2.0 will be approved
         protected abstract _getChildren(): Array<UIElement>;
 
-        public static flagVisualToBuild = 0x0000001;    // set if the UIElement visual must be updated
+        public static flagVisualToBuild = 0x0000001;
+        public static flagIsVisible     = 0x0000002;
+        public static flagIsFocus       = 0x0000004;
+        public static flagIsFocusScope  = 0x0000008;
+        public static flagIsFocusable   = 0x0000010;
+        public static flagIsEnabled     = 0x0000020;
+        public static flagIsMouseOver   = 0x0000040;
 
         protected _visualPlaceholder: Group2D;
         protected _visualTemplateRoot: Prim2DBase;
         protected _visualChildrenPlaceholder: Prim2DBase;
-        private _renderingTemplate: UIElementRenderingTemplateBase;
+        private _renderingTemplateName: string;
+        protected _renderingTemplate: UIElementRenderingTemplateBase;
         private _parent: UIElement;
         private _hierarchyDepth: number;
         private _flags: number;
@@ -699,9 +832,11 @@
         private _margin: PrimitiveThickness;
         private _padding: PrimitiveThickness;
         private _marginAlignment: PrimitiveAlignment;
-        private _isEnabled: boolean;
-        private _isFocused: boolean;
-        private _isMouseOver: boolean;
+        private _paddingAlignment: PrimitiveAlignment;
+
+        private static _enableState = "Enabled";
+        private static _disabledState = "Disabled";
+        private static _mouseOverState = "MouseOver";
     }
 
     export abstract class UIElementStyle {
@@ -710,9 +845,23 @@
         get name(): string { return null; } // TODO use abstract keyword when TS 2.0 will be approved
     }
 
-    export class UIElementStyleManager {
+    export class GUIManager {
+
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
+        // DATA TEMPLATE MANAGER
+
+        static registerDataTemplate(className: string, factory: (parent: UIElement, dataObject: any) => UIElement) {
+            
+        }
+
+        // DATA TEMPLATE MANAGER
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
+
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
+        // STYLE MANAGER
+
         static getStyle(uiElType: string, styleName: string): UIElementStyle {
-            let styles = UIElementStyleManager.stylesByUIElement.get(uiElType);
+            let styles = GUIManager.stylesByUIElement.get(uiElType);
             if (!styles) {
                 throw Error(`The type ${uiElType} is unknown, no style were registered for it.`);
             }
@@ -724,7 +873,7 @@
         }
 
         static registerStyle(uiElType: string, templateName: string, style: UIElementStyle) {
-            let templates = UIElementStyleManager.stylesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<UIElementStyle>());
+            let templates = GUIManager.stylesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<UIElementStyle>());
             if (templates.contains(templateName)) {
                 templates[templateName] = style;
             } else {
@@ -735,19 +884,20 @@
         static stylesByUIElement: StringDictionary<StringDictionary<UIElementStyle>> = new StringDictionary<StringDictionary<UIElementStyle>>();
 
         public static get DefaultStyleName(): string {
-            return UIElementStyleManager._defaultStyleName;
+            return GUIManager._defaultStyleName;
         }
 
         public static set DefaultStyleName(value: string) {
-            UIElementStyleManager._defaultStyleName = value;
+            GUIManager._defaultStyleName = value;
         }
 
-        private static _defaultStyleName = "Default";
-    }
+        // STYLE MANAGER
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
 
-    export class UIElementRenderingTemplateManager {
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
+        // RENDERING TEMPLATE MANAGER
         static getRenderingTemplate(uiElType: string, templateName: string): () => UIElementRenderingTemplateBase {
-            let templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.get(uiElType);
+            let templates = GUIManager.renderingTemplatesByUIElement.get(uiElType);
             if (!templates) {
                 throw Error(`The type ${uiElType} is unknown, no Rendering Template were registered for it.`);
             }
@@ -759,7 +909,7 @@
         }
 
         static registerRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase) {
-            let templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<() => UIElementRenderingTemplateBase>());
+            let templates = GUIManager.renderingTemplatesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<() => UIElementRenderingTemplateBase>());
             if (templates.contains(templateName)) {
                 templates[templateName] = factory;
             } else {
@@ -770,14 +920,18 @@
         static renderingTemplatesByUIElement: StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>> = new StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>>();
 
         public static get DefaultTemplateName(): string {
-            return UIElementRenderingTemplateManager._defaultTemplateName;
+            return GUIManager._defaultTemplateName;
         }
 
         public static set DefaultTemplateName(value: string) {
-            UIElementRenderingTemplateManager._defaultTemplateName = value;
+            GUIManager._defaultTemplateName = value;
         }
-        
+
+        // RENDERING TEMPLATE MANAGER
+        /////////////////////////////////////////////////////////////////////////////////////////////////////
+
         private static _defaultTemplateName = "Default";
+        private static _defaultStyleName = "Default";
     }
 
     export abstract class UIElementRenderingTemplateBase {
@@ -799,7 +953,7 @@
 
     export function registerWindowRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase): (target: Object) => void {
         return () => {
-            UIElementRenderingTemplateManager.registerRenderingTemplate(uiElType, templateName, factory);
+            GUIManager.registerRenderingTemplate(uiElType, templateName, factory);
         }
     }
 

+ 107 - 77
canvas2D/src/GUI/babylon.gui.button.ts

@@ -3,6 +3,10 @@
     @className("Button", "BABYLON")
     export class Button extends ContentControl {
 
+        static get pushedState() {
+            return Button._pushedState;
+        }
+
         static BUTTON_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 3;
 
         static isPushedProperty: Prim2DPropInfo;
@@ -11,25 +15,27 @@
 
         constructor(settings?: {
 
-            id              ?: string,
-            parent          ?: UIElement,
-            templateName    ?: string,
-            styleName       ?: string,
-            content         ?: any,
-            contentAlignment?: string,
-            marginTop       ?: number | string,
-            marginLeft      ?: number | string,
-            marginRight     ?: number | string,
-            marginBottom    ?: number | string,
-            margin          ?: number | string,
-            marginHAlignment?: number,
-            marginVAlignment?: number,
-            marginAlignment ?: string,
-            paddingTop      ?: number | string,
-            paddingLeft     ?: number | string,
-            paddingRight    ?: number | string,
-            paddingBottom   ?: number | string,
-            padding         ?: string,
+            id               ?: string,
+            parent           ?: UIElement,
+            templateName     ?: string,
+            styleName        ?: string,
+            content          ?: any,
+            marginTop        ?: number | string,
+            marginLeft       ?: number | string,
+            marginRight      ?: number | string,
+            marginBottom     ?: number | string,
+            margin           ?: number | string,
+            marginHAlignment ?: number,
+            marginVAlignment ?: number,
+            marginAlignment  ?: string,
+            paddingTop       ?: number | string,
+            paddingLeft      ?: number | string,
+            paddingRight     ?: number | string,
+            paddingBottom    ?: number | string,
+            padding          ?: string,
+            paddingHAlignment?: number,
+            paddingVAlignment?: number,
+            paddingAlignment ?: string,
         }) {
             if (!settings) {
                 settings = {};
@@ -37,28 +43,33 @@
 
             super(settings);
 
-            // For a button the default contentAlignemnt is center/center
-            if (settings.contentAlignment == null) {
-                this.contentAlignment.horizontal = PrimitiveAlignment.AlignCenter;
-                this.contentAlignment.vertical = PrimitiveAlignment.AlignCenter;
+            if (settings.paddingAlignment == null) {
+                this.paddingAlignment.horizontal = PrimitiveAlignment.AlignCenter;
+                this.paddingAlignment.vertical = PrimitiveAlignment.AlignCenter;
             }
-            this.normalEnabledBackground    = Canvas2D.GetSolidColorBrushFromHex("#337AB7FF");
-            this.normalDisabledBackground   = Canvas2D.GetSolidColorBrushFromHex("#7BA9D0FF");
-            this.normalMouseOverBackground  = Canvas2D.GetSolidColorBrushFromHex("#286090FF");
-            this.normalPushedBackground     = Canvas2D.GetSolidColorBrushFromHex("#1E496EFF");
-            this.normalEnabledBorder        = Canvas2D.GetSolidColorBrushFromHex("#2E6DA4FF");
-            this.normalDisabledBorder       = Canvas2D.GetSolidColorBrushFromHex("#77A0C4FF");
-            this.normalMouseOverBorder      = Canvas2D.GetSolidColorBrushFromHex("#204D74FF");
-            this.normalPushedBorder         = Canvas2D.GetSolidColorBrushFromHex("#2E5D9EFF");
-
-            this.defaultEnabledBackground   = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
-            this.defaultDisabledBackground  = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
-            this.defaultMouseOverBackground = Canvas2D.GetSolidColorBrushFromHex("#E6E6E6FF");
-            this.defaultPushedBackground    = Canvas2D.GetSolidColorBrushFromHex("#D4D4D4FF");
-            this.defaultEnabledBorder       = Canvas2D.GetSolidColorBrushFromHex("#CCCCCCFF");
-            this.defaultDisabledBorder      = Canvas2D.GetSolidColorBrushFromHex("#DEDEDEFF");
-            this.defaultMouseOverBorder     = Canvas2D.GetSolidColorBrushFromHex("#ADADADFF");
-            this.defaultPushedBorder        = Canvas2D.GetSolidColorBrushFromHex("#6C8EC5FF");
+
+            this._normalStateBackground  = new ObservableStringDictionary<IBrush2D>(false);
+            this._normalStateBorder      = new ObservableStringDictionary<IBrush2D>(false);
+            this._defaultStateBackground = new ObservableStringDictionary<IBrush2D>(false);
+            this._defaultStateBorder     = new ObservableStringDictionary<IBrush2D>(false);
+
+            this._normalStateBackground.add(UIElement.enabledState   , Canvas2D.GetSolidColorBrushFromHex("#337AB7FF"));
+            this._normalStateBackground.add(UIElement.disabledState  , Canvas2D.GetSolidColorBrushFromHex("#7BA9D0FF"));
+            this._normalStateBackground.add(UIElement.mouseOverState , Canvas2D.GetSolidColorBrushFromHex("#286090FF"));
+            this._normalStateBackground.add(Button.pushedState       , Canvas2D.GetSolidColorBrushFromHex("#1E496EFF"));
+            this._normalStateBorder.add(UIElement.enabledState       , Canvas2D.GetSolidColorBrushFromHex("#2E6DA4FF"));
+            this._normalStateBorder.add(UIElement.disabledState      , Canvas2D.GetSolidColorBrushFromHex("#77A0C4FF"));
+            this._normalStateBorder.add(UIElement.mouseOverState     , Canvas2D.GetSolidColorBrushFromHex("#204D74FF"));
+            this._normalStateBorder.add(Button.pushedState           , Canvas2D.GetSolidColorBrushFromHex("#2E5D9EFF"));
+
+            this._defaultStateBackground.add(UIElement.enabledState   , Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"));
+            this._defaultStateBackground.add(UIElement.disabledState  , Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"));
+            this._defaultStateBackground.add(UIElement.mouseOverState , Canvas2D.GetSolidColorBrushFromHex("#E6E6E6FF"));
+            this._defaultStateBackground.add(Button.pushedState       , Canvas2D.GetSolidColorBrushFromHex("#D4D4D4FF"));
+            this._defaultStateBorder.add(UIElement.enabledState       , Canvas2D.GetSolidColorBrushFromHex("#CCCCCCFF"));
+            this._defaultStateBorder.add(UIElement.disabledState      , Canvas2D.GetSolidColorBrushFromHex("#DEDEDEFF"));
+            this._defaultStateBorder.add(UIElement.mouseOverState     , Canvas2D.GetSolidColorBrushFromHex("#ADADADFF"));
+            this._defaultStateBorder.add(Button.pushedState           , Canvas2D.GetSolidColorBrushFromHex("#6C8EC5FF"));
         }
 
         @dependencyProperty(ContentControl.CONTROL_PROPCOUNT + 0, pi => Button.isPushedProperty = pi)
@@ -96,13 +107,20 @@
         }
 
         public _raiseClick() {
-            console.log("click");
+            if (this._clickObservable && this._clickObservable.hasObservers()) {
+                this._clickObservable.notifyObservers(this);
+            }
         }
 
         protected createVisualTree() {
             super.createVisualTree();
             let p = this._visualPlaceholder;
             p.pointerEventObservable.add((e, s) => {
+                // check if input must be discarded
+                if (!this.isVisible || !this.isEnabled) {
+                    return;
+                }
+
                 // We reject an event coming from the placeholder because it means it's on an empty spot, so it's not valid.
                 if (e.relatedTarget === this._visualPlaceholder) {
                     return;
@@ -113,37 +131,38 @@
                     this.isPushed = false;
                 } else if (s.mask === PrimitivePointerInfo.PointerDown) {
                     this.isPushed = true;
+                    this.isFocused = true;
                 }
 
             }, PrimitivePointerInfo.PointerUp|PrimitivePointerInfo.PointerDown);
         }
 
+        public get normalStateBackground(): ObservableStringDictionary<IBrush2D> {
+            return this._normalStateBackground;
+        }
+
+        public get defaultStateBackground(): ObservableStringDictionary<IBrush2D> {
+            return this._defaultStateBackground;
+        }
+
+        public get normalStateBorder(): ObservableStringDictionary<IBrush2D> {
+            return this._normalStateBorder;
+        }
+
+        public get defaultStateBorder(): ObservableStringDictionary<IBrush2D> {
+            return this._defaultStateBorder;
+        }
+
+        private _normalStateBackground: ObservableStringDictionary<IBrush2D>;
+        private _normalStateBorder: ObservableStringDictionary<IBrush2D>;
+        private _defaultStateBackground: ObservableStringDictionary<IBrush2D>;
+        private _defaultStateBorder: ObservableStringDictionary<IBrush2D>;
         private _isPushed: boolean;
         private _isDefault: boolean;
         private _isOutline: boolean;
         private _clickObservable: Observable<Button>;
 
-        protected get _position(): Vector2 {
-            return Vector2.Zero();
-        }
-
-        public normalEnabledBackground  : IBrush2D;
-        public normalDisabledBackground : IBrush2D;
-        public normalMouseOverBackground: IBrush2D;
-        public normalPushedBackground   : IBrush2D;
-        public normalEnabledBorder      : IBrush2D;
-        public normalDisabledBorder     : IBrush2D;
-        public normalMouseOverBorder    : IBrush2D;
-        public normalPushedBorder       : IBrush2D;
-
-        public defaultEnabledBackground  : IBrush2D;
-        public defaultDisabledBackground : IBrush2D;
-        public defaultMouseOverBackground: IBrush2D;
-        public defaultPushedBackground   : IBrush2D;
-        public defaultEnabledBorder      : IBrush2D;
-        public defaultDisabledBorder     : IBrush2D;
-        public defaultMouseOverBorder    : IBrush2D;
-        public defaultPushedBorder       : IBrush2D;
+        private static _pushedState = "Pushed";
     }
 
     @registerWindowRenderingTemplate("BABYLON.Button", "Default", () => new DefaultButtonRenderingTemplate())
@@ -166,37 +185,48 @@
                 Button.isDefaultProperty.flagId      |
                 Button.isOutlineProperty.flagId      |
                 Button.isPushedProperty.flagId);
+
+            // Register for brush change and update the Visual
+            let button = <Button>owner;
+            button.normalStateBackground.dictionaryChanged.add ((e, c) => this.stateChange());
+            button.normalStateBorder.dictionaryChanged.add     ((e, c) => this.stateChange());
+            button.defaultStateBackground.dictionaryChanged.add((e, c) => this.stateChange());
+            button.defaultStateBorder.dictionaryChanged.add    ((e, c) => this.stateChange());
         }
 
         stateChange(): void {
+            //console.log("state changed");
             let b = <Button>this.owner;
-            let bg = b.isDefault ? b.defaultEnabledBackground : b.normalEnabledBackground;
-            let bd = b.isDefault ? b.defaultEnabledBorder : b.normalEnabledBorder;
+            let state = UIElement.enabledState;
+            let bg = b.isDefault ? b.defaultStateBackground.get(state) : b.normalStateBackground.get(state);
+            let bd = b.isDefault ? b.defaultStateBorder.get(state) : b.normalStateBorder.get(state);
 
             if (b.isPushed) {
+                state = Button.pushedState;
                 if (b.isDefault) {
-                    bg = b.defaultPushedBackground;
-                    bd = b.defaultPushedBorder;
+                    bg = b.defaultStateBackground.get(state);
+                    bd = b.defaultStateBorder.get(state);
                 } else {
-                    bg = b.normalPushedBackground;
-                    bd = b.normalPushedBorder;
+                    bg = b.normalStateBackground.get(state);
+                    bd = b.normalStateBorder.get(state);
                 }
             } else if (b.isMouseOver) {
-                console.log("MouseOver Style");
+                state = UIElement.mouseOverState;
                 if (b.isDefault) {
-                    bg = b.defaultMouseOverBackground;
-                    bd = b.defaultMouseOverBorder;
+                    bg = b.defaultStateBackground.get(state);
+                    bd = b.defaultStateBorder.get(state);
                 } else {
-                    bg = b.normalMouseOverBackground;
-                    bd = b.normalMouseOverBorder;
+                    bg = b.normalStateBackground.get(state);
+                    bd = b.normalStateBorder.get(state);
                 }
             } else if (!b.isEnabled) {
+                state = UIElement.disabledState;
                 if (b.isDefault) {
-                    bg = b.defaultDisabledBackground;
-                    bd = b.defaultDisabledBorder;
+                    bg = b.defaultStateBackground.get(state);
+                    bd = b.defaultStateBorder.get(state);
                 } else {
-                    bg = b.normalDisabledBackground;
-                    bd = b.normalDisabledBorder;
+                    bg = b.normalStateBackground.get(state);
+                    bd = b.normalStateBorder.get(state);
                 }
             }
 

+ 128 - 0
canvas2D/src/GUI/babylon.gui.contentControl.ts

@@ -0,0 +1,128 @@
+module BABYLON {
+
+
+    @className("ContentControl", "BABYLON")
+    export abstract class ContentControl extends Control {
+        static CONTENTCONTROL_PROPCOUNT = Control.CONTROL_PROPCOUNT + 2;
+
+        static contentProperty: Prim2DPropInfo;
+
+        constructor(settings?: {
+            id              ?: string,
+            templateName    ?: string,
+            styleName       ?: string,
+            content         ?: any,
+        }) {
+            if (!settings) {
+                settings = {};
+            }
+
+            super(settings);
+
+            if (settings.content!=null) {
+                this._content = settings.content;
+            }
+        }
+
+        dispose(): boolean {
+            if (this.isDisposed) {
+                return false;
+            }
+
+            if (this.content && this.content.dispose) {
+                this.content.dispose();
+                this.content = null;
+            }
+
+            if (this.__contentUIElement) {
+                this.__contentUIElement.dispose();
+                this.__contentUIElement = null;
+            }
+
+            super.dispose();
+
+            return true;
+        }
+
+        @dependencyProperty(Control.CONTROL_PROPCOUNT + 0, pi => ContentControl.contentProperty = pi)
+        public get content(): any {
+            return this._content;
+        }
+
+        public set content(value: any) {
+            this._content = value;
+        }
+
+        protected get _contentUIElement(): UIElement {
+            if (!this.__contentUIElement) {
+                this._buildContentUIElement();
+            }
+
+            return this.__contentUIElement;
+        }
+
+        public _createVisualTree() {
+            // Base implementation will create the Group2D for the Visual Placeholder and its Visual Tree
+            super._createVisualTree();
+
+            // A Content Control has a Group2D, child of the visualPlaceHolder, which is the Content placeholder.
+            // The Content UIElement Tree will be create inside this placeholder.
+            this._contentPlaceholder = new Group2D({ parent: this._visualPlaceholder, id: `ContentControl Content Placeholder of ${this.id}` });
+            let p = this._contentPlaceholder;
+
+            // The UIElement padding properties (padding and paddingAlignment) are bound to the Group2D Content placeholder, we bound to the Margin properties as the Group2D acts as an inner element already, so margin of inner is padding.
+            p.dataSource = this;
+            p.createSimpleDataBinding(Prim2DBase.marginProperty, "padding", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "paddingAlignment", DataBinding.MODE_ONEWAY);
+
+            // The UIElement set the childrenPlaceholder with the visual returned by the renderingTemplate.
+            // But it's not the case for a ContentControl, the placeholder of UIElement Children (the content)
+            this._visualChildrenPlaceholder = this._contentPlaceholder;
+        }
+
+        private _buildContentUIElement() {
+            let c = this._content;
+            this.__contentUIElement = null;
+
+            // Already a UIElement
+            if (c instanceof UIElement) {
+                this.__contentUIElement = c;
+            }
+
+            // Test primary types
+            else if ((typeof c === "string") || (typeof c === "boolean") || (typeof c === "number")) {
+                let l = new Label({ parent: this, id: "Content of " + this.id });
+                let binding = new DataBinding();
+                binding.propertyPathName = "content";
+                binding.stringFormat = v => `${v}`;
+                binding.dataSource = this;
+                l.createDataBinding(Label.textProperty, binding);
+
+                this.__contentUIElement = l;
+            }
+
+            // Data Template!
+            else {
+                // TODO: DataTemplate lookup and create instance
+            }
+
+            if (this.__contentUIElement) {
+                this.__contentUIElement._patchUIElement(this.ownerWindow, this);               
+            }
+        }
+
+        private _contentPlaceholder: Group2D;
+        private _content: any;
+        private __contentUIElement: UIElement;
+
+        protected _getChildren(): Array<UIElement> {
+            let children = new Array<UIElement>();
+
+            if (this.content) {
+                children.push(this._contentUIElement);
+            }
+
+            return children;
+        }
+    }
+}

+ 0 - 137
canvas2D/src/GUI/babylon.gui.control.ts

@@ -72,141 +72,4 @@
         private _fontName: string;
         private _foreground: IBrush2D;
     }
-
-
-    @className("ContentControl", "BABYLON")
-    export abstract class ContentControl extends Control {
-        static CONTENTCONTROL_PROPCOUNT = Control.CONTROL_PROPCOUNT + 2;
-
-        static contentProperty: Prim2DPropInfo;
-        static contentAlignmentProperty: Prim2DPropInfo;
-
-        constructor(settings?: {
-            id              ?: string,
-            templateName    ?: string,
-            styleName       ?: string,
-            content         ?: any,
-            contentAlignment?: string,
-        }) {
-            if (!settings) {
-                settings = {};
-            }
-
-            super(settings);
-
-            if (settings.content!=null) {
-                this._content = settings.content;
-            }
-
-            if (settings.contentAlignment != null) {
-                this.contentAlignment.fromString(settings.contentAlignment);
-            }
-        }
-
-        dispose(): boolean {
-            if (this.isDisposed) {
-                return false;
-            }
-
-            if (this.content && this.content.dispose) {
-                this.content.dispose();
-                this.content = null;
-            }
-
-            if (this.__contentUIElement) {
-                this.__contentUIElement.dispose();
-                this.__contentUIElement = null;
-            }
-
-            super.dispose();
-
-            return true;
-        }
-
-        @dependencyProperty(Control.CONTROL_PROPCOUNT + 0, pi => ContentControl.contentProperty = pi)
-        public get content(): any {
-            return this._content;
-        }
-
-        public set content(value: any) {
-            this._content = value;
-        }
-
-        @dependencyProperty(Control.CONTROL_PROPCOUNT + 1, pi => ContentControl.contentAlignmentProperty = pi)
-        public get contentAlignment(): PrimitiveAlignment {
-            if (!this._contentAlignment) {
-                this._contentAlignment = new PrimitiveAlignment();
-            }
-            return this._contentAlignment;
-        }
-
-        public set contentAlignment(value: PrimitiveAlignment) {
-            this.contentAlignment.copyFrom(value);
-        }
-
-        /**
-         * Check if there a contentAlignment specified (non null and not default)
-         */
-        public get _hasContentAlignment(): boolean {
-            return (this._contentAlignment !== null && !this._contentAlignment.isDefault);
-        }
-
-        protected get _contentUIElement(): UIElement {
-            if (!this.__contentUIElement) {
-                this._buildContentUIElement();
-            }
-
-            return this.__contentUIElement;
-        }
-
-        private _buildContentUIElement() {
-            let c = this._content;
-            this.__contentUIElement = null;
-
-            // Already a UIElement
-            if (c instanceof UIElement) {
-                this.__contentUIElement = c;
-            }
-
-            // Test primary types
-            else if ((typeof c === "string") || (typeof c === "boolean") || (typeof c === "number")) {
-                let l = new Label({ parent: this, id: "Content of " + this.id });
-                let binding = new DataBinding();
-                binding.propertyPathName = "content";
-                binding.stringFormat = v => `${v}`;
-                binding.dataSource = this;
-                l.createDataBinding(Label.textProperty, binding);
-
-                binding = new DataBinding();
-                binding.propertyPathName = "contentAlignment";
-                binding.dataSource = this;
-                l.createDataBinding(Label.marginAlignmentProperty, binding);
-
-                this.__contentUIElement = l;
-            }
-
-            // Data Template!
-            else {
-                // TODO: DataTemplate lookup and create instance
-            }
-
-            if (this.__contentUIElement) {
-                this.__contentUIElement._patchUIElement(this.ownerWindows, this);               
-            }
-        }
-
-        private _content: any;
-        private _contentAlignment: PrimitiveAlignment;
-        private __contentUIElement: UIElement;
-
-        protected _getChildren(): Array<UIElement> {
-            let children = new Array<UIElement>();
-
-            if (this.content) {
-                children.push(this._contentUIElement);
-            }
-
-            return children;
-        }
-    }
 }

+ 110 - 41
canvas2D/src/GUI/babylon.gui.window.ts

@@ -1,42 +1,98 @@
 module BABYLON {
+
+    class FocusScopeData {
+        constructor(focusScope: UIElement) {
+            this.focusScope = focusScope;
+            this.focusedElement = null;
+        }
+
+        focusScope: UIElement;
+        focusedElement: UIElement;
+    }
+
+    export class FocusManager {
+        constructor() {
+            this._focusScopes = new StringDictionary<FocusScopeData>();
+            this._rootScope = new FocusScopeData(null);
+            this._activeScope = null;
+        }
+
+        public setFocusOn(el: UIElement, focusScope: UIElement) {
+            let fsd = (focusScope != null) ? this._focusScopes.getOrAddWithFactory(focusScope.uid, k => new FocusScopeData(focusScope)) : this._rootScope;
+
+            if (fsd.focusedElement !== el) {
+                // Remove focus from current
+                if (fsd.focusedElement) {
+                    fsd.focusedElement.isFocused = false;
+                }
+
+                fsd.focusedElement = el;
+            }
+
+            if (this._activeScope !== fsd) {
+                this._activeScope = fsd;
+            }
+
+        }
+
+        private _rootScope: FocusScopeData;
+        private _focusScopes: StringDictionary<FocusScopeData>;
+        private _activeScope: FocusScopeData;
+    }
+
+    class GUISceneData {
+        constructor(scene: Scene) {
+            this.scene = scene;
+            this.screenSpaceCanvas = new ScreenSpaceCanvas2D(scene, { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE });
+            this.focusManager = new FocusManager();
+        }
+
+        screenSpaceCanvas: ScreenSpaceCanvas2D;
+        scene: Scene;
+        focusManager: FocusManager;
+    }
+
     @className("Window", "BABYLON")
     export class Window extends ContentControl {
-        static WINDOW_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 2;
+        static WINDOW_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 4;
 
         static leftProperty: Prim2DPropInfo;
         static bottomProperty: Prim2DPropInfo;
         static positionProperty: Prim2DPropInfo;
+        static isActiveProperty: Prim2DPropInfo;
 
         constructor(scene: Scene, settings?: {
 
-            id              ?: string,
-            templateName    ?: string,
-            styleName       ?: string,
-            content         ?: any,
-            contentAlignment?: string,
-            left            ?: number,
-            bottom          ?: number,
-            minWidth        ?: number,
-            minHeight       ?: number,
-            maxWidth        ?: number,
-            maxHeight       ?: number,
-            width           ?: number,
-            height          ?: number,
-            worldPosition   ?: Vector3,
-            worldRotation   ?: Quaternion,
-            marginTop       ?: number | string,
-            marginLeft      ?: number | string,
-            marginRight     ?: number | string,
-            marginBottom    ?: number | string,
-            margin          ?: number | string,
-            marginHAlignment?: number,
-            marginVAlignment?: number,
-            marginAlignment ?: string,
-            paddingTop      ?: number | string,
-            paddingLeft     ?: number | string,
-            paddingRight    ?: number | string,
-            paddingBottom   ?: number | string,
-            padding         ?: string,
+            id               ?: string,
+            templateName     ?: string,
+            styleName        ?: string,
+            content          ?: any,
+            left             ?: number,
+            bottom           ?: number,
+            minWidth         ?: number,
+            minHeight        ?: number,
+            maxWidth         ?: number,
+            maxHeight        ?: number,
+            width            ?: number,
+            height           ?: number,
+            worldPosition    ?: Vector3,
+            worldRotation    ?: Quaternion,
+            marginTop        ?: number | string,
+            marginLeft       ?: number | string,
+            marginRight      ?: number | string,
+            marginBottom     ?: number | string,
+            margin           ?: number | string,
+            marginHAlignment ?: number,
+            marginVAlignment ?: number,
+            marginAlignment  ?: string,
+            paddingTop       ?: number | string,
+            paddingLeft      ?: number | string,
+            paddingRight     ?: number | string,
+            paddingBottom    ?: number | string,
+            padding          ?: string,
+            paddingHAlignment?: number,
+            paddingVAlignment?: number,
+            paddingAlignment ?: string,
         }) {
 
             if (!settings) {
@@ -45,6 +101,11 @@
 
             super(settings);
 
+            // Per default a Window is focus scope
+            this.isFocusScope = true;
+
+            this.isActive = false;
+
             if (!this._UIElementVisualToBuildList) {
                 this._UIElementVisualToBuildList = new Array<UIElement>();
             }
@@ -54,7 +115,8 @@
 
             // Screen Space UI
             if (!settings.worldPosition && !settings.worldRotation) {
-                this._canvas = Window.getScreenCanvas(scene);
+                this._sceneData = Window.getSceneData(scene);
+                this._canvas = this._sceneData.screenSpaceCanvas;
                 this._isWorldSpaceCanvas = false;
                 this._left = (settings.left != null) ? settings.left : 0;
                 this._bottom = (settings.bottom != null) ? settings.bottom : 0;
@@ -116,6 +178,19 @@
             this._bottom = value.y;
         }
 
+        @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 3, pi => Window.isActiveProperty = pi)
+        public get isActive(): boolean {
+            return this._isActive;
+        }
+
+        public set isActive(value: boolean) {
+            this._isActive = value;
+        }
+
+        public get focusManager(): FocusManager {
+            return this._sceneData.focusManager;
+        }
+
         protected get _position(): Vector2 {
             return new Vector2(this.left, this.bottom);
         }
@@ -190,28 +265,22 @@
             this._canvas.renderObservable.remove(this._renderObserver);
         }
 
+        private _sceneData: GUISceneData;
         private _canvas: Canvas2D;
         private _left: number;
         private _bottom: number;
+        private _isActive: boolean;
         private _isWorldSpaceCanvas: boolean;
         private _renderObserver: Observer<Canvas2D>;
         private _disposeObserver: Observer<SmartPropertyBase>;
         private _UIElementVisualToBuildList: Array<UIElement>;
         private _mouseOverUIElement: UIElement;
 
-        private static getScreenCanvas(scene: Scene): ScreenSpaceCanvas2D {
-            let canvas = Tools.first(Window._screenCanvasList, c => c.scene === scene);
-            if (canvas) {
-                return canvas;
-            }
-
-            canvas = new ScreenSpaceCanvas2D(scene, { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE });
-            Window._screenCanvasList.push(canvas);
-
-            return canvas;
+        private static getSceneData(scene: Scene): GUISceneData {
+            return Window._sceneData.getOrAddWithFactory(scene.uid, k => new GUISceneData(scene));
         }
 
-        private static _screenCanvasList: Array<ScreenSpaceCanvas2D> = new Array<ScreenSpaceCanvas2D>();
+        private static _sceneData: StringDictionary<GUISceneData> = new StringDictionary<GUISceneData>();
     }
 
     @registerWindowRenderingTemplate("BABYLON.Window", "Default", () => new DefaultWindowRenderingTemplate ())

+ 3 - 1
canvas2D/src/Tools/babylon.observableStringDictionary.ts

@@ -162,7 +162,9 @@
 
         private _removeWatchedElement(key: string, el: T) {
             let observer = this._watchedObjectList.getAndRemove(key);
-            (<IPropertyChanged><any>el).propertyChanged.remove(observer);
+            if (el["propertyChanged"]) {
+                (<IPropertyChanged><any>el).propertyChanged.remove(observer);
+            }
         }
 
         public set(key: string, value: T): boolean {

+ 17 - 6
src/Tools/babylon.rectPackingMap.ts

@@ -6,10 +6,15 @@
   */
     export class PackedRect {
         constructor(root: PackedRect, parent: PackedRect, pos: Vector2, size: Size) {
-            this._pos = pos;
-            this._size = size;
-            this._root = root;
-            this._parent = parent;
+            this._pos         = pos;
+            this._size        = size;
+            this._root        = root;
+            this._parent      = parent;
+            this._contentSize = null;
+            this._bottomNode  = null;
+            this._leftNode    = null;
+            this._initialSize = null;
+            this._rightNode   = null;
         }
 
         /**
@@ -104,8 +109,13 @@
             }
 
             // The node is free, but was previously allocated (_initialSize is set), rely on initialSize to make the test as it's the space we have
-            else if (this._initialSize && (size.width <= this._initialSize.width) && (size.height <= this._initialSize.height)) {
-                resNode = this;
+            else if (this._initialSize) {
+                if ((size.width <= this._initialSize.width) && (size.height <= this._initialSize.height))
+                {
+                    resNode = this;
+                } else {
+                    return null;
+                }
             }
 
             // The node is free and empty, rely on its size for the test
@@ -118,6 +128,7 @@
         private splitNode(contentSize: Size): PackedRect {
             // If there's no contentSize but an initialSize it means this node were previously allocated, but freed, we need to create a _leftNode as subNode and use to allocate the space we need (and this node will have a right/bottom subNode for the space left as this._initialSize may be greater than contentSize)
             if (!this._contentSize && this._initialSize) {
+                this._contentSize = contentSize.clone();
                 this._leftNode = new PackedRect(this._root, this, new Vector2(this._pos.x, this._pos.y), new Size(this._initialSize.width, this._initialSize.height));
                 return this._leftNode.splitNode(contentSize);
             } else {