Browse Source

Merge branch 'master' of https://github.com/Nockawa/Babylon.js.git

nockawa 8 years ago
parent
commit
dc4f374be8

+ 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"

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

@@ -1763,7 +1763,7 @@
         public static paddingProperty: Prim2DPropInfo;
 
         /**
-         * Metadata of the hAlignment property
+         * Metadata of the marginAlignment property
          */
         public static marginAlignmentProperty: Prim2DPropInfo;
 

+ 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 {